How to write an Angular directive for input and contenteditable - angularjs

my html is taking input in two form, input and contenteditable div . I want to write one directive that handles both, but I cannot find a way to figure out which tag has called the function (because Angular's JQLite doesnt provide a is() or get() function). The following code will be complete if I can figure out to evaluate IS_INPUT_TAG:
function funct() { return {
require: 'ngModel',
link: function(scope, element, attrs, ctrl) {
// view -> model
element.bind('input', function() {
scope.$apply(function() {
if(IS_INPUT_TAG)
ctrl.$setViewValue(element.val());
else
ctrl.$setViewValue(element.text());
scope.watchCallback(element.attr('data-ng-model'));
});
});
// model -> view
ctrl.$render = function() {
if(IS_INPUT_TAG)
element.val(ctrl.$viewValue);
else
element.text(ctrl.$viewValue);
};
}};
}
app.directive('input', funct);
app.directive('contenteditable', funct);

In your directive, you can make use of the element parameter of the linking function to identify the tag on which the directive is applied. You can then use that in your IF condition as follows:
ctrl.$render = function() {
var tagname = element["0"].tagName;
if(tagName === "INPUT")
element.val(ctrl.$viewValue);
else
element.text(ctrl.$viewValue);
};
After, this you can simply attach the directive to the input and the div tags as an attribute to the tags to identify the tag to which the directive is applied.

Related

addClass() doesn't work with getElementById in AngularJS

I'm trying to manipulate an element class within a directive. The directive is of a toolbar and it's supposed to add a class to 2 elements after some scroll.
The element directive itseld;
The view, to add/remove margin;
This is my html structure:
<ag-toolbar class="ag-toolbar--sec"></ag-toolbar>
<div ui-view="app" autoscroll="false" id="appView"></div>
And this is my directive:
function agToolbar($window) {
return {
restrict: 'E',
link: function(scope, element, attrs) {
var elView;
setTimeout(function(){
elView = document.getElementById("appView");
}, 400);
angular.element($window).bind("scroll", function() {
if (this.pageYOffset >= 128) {
element.addClass('scroll');
elView.addClass('agMargin');
} else {
element.removeClass('scroll');
elView.removeClass('agMargin');
};
});
}
};
}
In the console I keep getting the error:
elView.addClass is not a function
elView.removeClass is not a function
But the element.addClass is working fine. Any ideas why?
addClass belongs to jqLite (or jQuery if available), see https://docs.angularjs.org/api/ng/function/angular.element.
That is, you need to wrap the DOM element in a jqLite/jQuery element:
elView = angular.element(document.getElementById("appView"));

pass data from controller to directive's link?

In my controller :
myApp.controller('homeCtrl', function($scope, $rootScope, $state, 'red';
$rootScope.$on('new_story', function(event, data) {
$scope.cardObj = {key:'value'};
});
});
In my HTML :
<div clickmeee ></div>
<div id="feedContainer" card='{{cardObj}}'> </div>
In my directive :
myApp.directive('clickmeee', function($compile, $rootScope) {
return {
restrict: 'A',
scope: {
card: '#'
},
link: function(scope, element, attrs) {
element.bind('click', function() {   
scope.$watch('card', function(newVal, oldVal) {
alert(scope.card);
});       
});
}
};
});
How do I pass data from controller to this directive. I compile some html and prepend it to the div. All of that is sorted out but I need some data from object I am trying to pass.
Any help??
There are several problems in your code:
you define a scope attribute named 'card', but you use cardObj instead
you use a watch that is completely unnecessary. And worse: you create a new watch every time the element is clicked
you don't define any card attribute on your clickmeee element. Instead, you're placing it on another element, on which the directive is not applied
you're passing the attribute with '#'. That works, but the directive will receive a string, containing the JSONified object, rather than the object itself
you're not showming us where you emit an event that will initialize cardObj in the controller scope
Here is a plunkr showing a working version of your code.
Also, note that using bind('click') is a bad idea. You'd better have a template in your directive and use ng-click in the template, or simply not use a directive at all and just use ng-click directly on the div element.
Bad news. You are doing it wrong all the ways.
Firstly
card='{{cardObj}}' >
this one should be put in the
<div clickmeee ></div>
So you can take it as binded scope variable in your directive registration
Secondly
If you managed to use '#' syntax
card: '#'
it will turn your input to string, not a binded scope. Use '=' instead.
In the end
You dont need to use watch here:
scope.$watch('card', function(newVal, oldVal) {
alert(newVal);
});
since scope.card is binded via '=' connector. Just simple use alert(scope.card). (Need to warn you that alert an object is not a good idea)
I have tried your code here: plunker. Changed a litte bit by using cardObj as string for easier presentation. Does it match your work?
You should watch the card object:
myApp.directive('clickmeee', function() {
return {
restrict: 'A',
scope: {
card: '#'
},
link: function(scope, element, attrs) {
scope.$watch('card', function(value) {
console.log(value);
});
}
};
});
And:
<div clickmeee id="feedContainer" card='{{cardObj}}'> </div>
Whenever the controller changes the cardObj, the directive's watch on card is triggered:
$scope.$apply(function() {
$scope.cardObj = "test";
}

Toggle edit and display of the fields in a form

Above all, I have the plnkr at here.
I am trying to create a series of directive that support in-place toggle of text display and edit within a form. As I understand, there is a similar module like xeditable available, but we need to do something different down the road. So I started with an experiment to start with something similar.
First, I create a directive that allows toggling edit/display by setting an attribute editEnabled on the directive called editableForm. The following code does not do anything special other than a line of log message.
function editableForm ($log) {
var directive = {
link: link,
require: ['form'],
restrict: 'A',
scope: {
editEnabled: "&editEnabled"
}
};
return directive;
function link(scope, element, attrs, controller) {
//$log.info('editEnabled: ' + scope.editEnabled());
$log.info('editEnabled: ' + attrs.editEnabled); //this also works
}
} //editableForm
Then I wrote the following directive to override the input tag in html:
//input directive
function input($log) {
var directive = {
link: link,
priority: -1000,
require: ['^?editableForm', '?ngModel'],
restrict: 'E'
};
return directive;
function link(scope, element, attrs, ngModel) {
ngModel.$render = function() {
if (!ngModel.$viewValue || !ngModel.$viewValue) {
return;
}
element.text(ngModel.$viewValue);
};
$log.info('hello from input');
$log.info('input ngModel: ' + attrs.ngModel);
// element.val('Hello');
scope.$apply(function() {
ngModel.$setViewValue('hello');
ngModel.$render();
});
}
} //input
I was trying to show the ngModel value of the input as text in the input directive, however, it doesn't seem to do anything in my testing. Could someone spot where I am doing wrong? I wish to replace each input fields with text/html (e.g. <span>JohnDoe</span> for Username).
My first attempt on input is a proof of concept. If it works, I will keep working on other tags like button, select, etc.
Long shot here... Your requiring both editableForm and ngModel in your input directive. So the fourth parameter of your link function should be an array of controllers in the respective order of the require array, not the ngModel controller as you are expecting.
I didnt go any further in examining your code, but check it out.

Angular composite (super) directive not allowing 2 way binding on component (child) directives

I have a need to create a composite directive that incorporates separate fully functional directives. One of my component directives adds an element to the dom and that element binds to a value in the component directive's controller. When the composite directive adds the component directive in the compile function, it seems to work but the piece that has the 2 way binding in the component directive does not appear to get compiled and just renders the {{ctrl.value}} string on the screen. I realize this is a bit convoluted so I have included a plunk to help clarify the issue.
app.directive('compositeDirective', function($compile){
return {
compile: compileFunction
}
function compileFunction(element, attrs){
attrs.$set("component-directive", "");
element.removeAttr("composite-directive");
element.after("<div>Component value when added in composite directive: {{compCtrl.myValue}}</div>");
return { post: function(scope, element){
$compile(element)(scope);
}};
}
});
app.directive('componentDirective', function(){
return {
controller: "componentController as compCtrl",
link: link
};
function link(scope, element){
element.after("<div>Component value: {{compCtrl.myValue}}</div>");
}
});
app.controller('componentController', function(){
var vm = this;
vm.myValue = "Hello";
});
http://plnkr.co/edit/alO83j9Efz62VTKDOVgc
I don't think any compilation will happen as a result of changes in the link function, unless you call $compile manually, i.e.,
app.directive('componentDirective', function($compile){
return {
controller: "componentController as compCtrl",
link: link
};
function link(scope, element){
var elm = $compile("<div>Component value: {{compCtrl.myValue}}</div>")(scope);
element.append(elm);
}
});
Updated plunk: http://plnkr.co/edit/pIixQujs1y6mPMKT4zxK
You can also use a compile function instead of link: http://plnkr.co/edit/fjZMd4FIQ97oHSvetOgU
Also, make sure to use .append() instead of .after().

Correct way to insert elements with angular directive using angular.element()

What is the correct way to insert element into the DOM using angular.element() ?
app.directive('validationTest', function(){
return {
restrict: 'A',
replace: false,
link: function(scope, element, attrs){
scope.call = function(){
console.log('Someone clicked it');
};
var newElement = angular.element('<span ng-click="call()">').text('Yo Yo');
element.append(newElement);
}
};
});
In this code, I am trying to add a span element within the element on which directive has been applied. I am able to add this span element as the child of the parent div on which append mehtod is called.
However, as you can see in the code, a ng-click also has been associated with this span. I know it is not useful from any point of view, it is just for demo purpose. So, normally, clicking on this span, a line should be printed in the console. However, it doesn't happen.
What am I missing here ? Have I used wrong approach for this append or there is some error in my code ?
If the HTML that you dynamically append to the DOM has directives, then you'll want to $compile and link it before appending it to the DOM:
var newElement = angular.element('<span ng-click="call()">').text('Yo Yo');
$compile(newElement)(scope);
element.append(newElement);
An alternative approach that is less error prone is to move your DOM manipulation to the compile function. By inserting the new element during the compile phase, the new HTML will be automatically linked during the linking phase (no manual compile /link required):
app.directive('myDirective', function() {
return {
compile: function(element, attr) {
var newElement = angular.element('<span ng-click="call()">').text('Yo Yo');
element.append(newElement);
return function(scope, element, attr) {
}
}
}
});
You're missing angular compilation. The compilation traverses your DOM looking for directives and initializes them. Without calling compile, nothing will initialize your ngClick. Try this to use $compile:
app.directive('validationTest', ['$compile', function($compile){
return {
restrict: 'A',
replace: false,
link: function(scope, element, attrs){
scope.call = function(){
console.log('Someone clicked it');
};
var newElement = angular.element('<span ng-click="call()">').text('Yo Yo');
element.append(newElement);
$compile(element)(scope);
}
};
});
For further information: https://docs.angularjs.org/api/ng/service/$compile
It's better to use templateUrl or template.
View $compile docs.

Resources