AngularJS custom directive for a ng-indeterminate attribute on checkboxes - javascript

Here is a directive that handles the indeterminate state on checkboxes:
.directive('ngIndeterminate', function() {
return {
restrict: 'A',
link: function(scope, element, attributes) {
attributes.$observe('ngIndeterminate', function(value) {
$(element).prop('indeterminate', value == "true");
});
}
};
})
Then, for example with these data:
$scope.data = [
{name: 'foo', displayed: 2, total: 4},
{name: 'bar', displayed: 3, total: 3}
];
You would simply need:
<ul ng-repeat="item in data">
<li>
<input type="checkbox" ng-indeterminate="{{item.displayed > 0 && item.displayed < item.total}}" ng-checked="item.displayed > 0" />
{{item.name}} ({{item.displayed}}/{{item.total}})
</li>
</ul>
Is there any way to write the ng-indeterminate expression without the double-curly notation, just as the native ng-checked one?
ng-indeterminate="item.displayed > 0 && item.displayed < item.total"
I tried:
.directive('ngIndeterminate', function($compile) {
return {
restrict: 'A',
link: function(scope, element, attributes) {
attributes.$observe('ngIndeterminate', function(value) {
$(element).prop('indeterminate', $compile(value)(scope));
});
}
};
})
But I get the following error:
Looking up elements via selectors is not supported by jqLite!
Here is a fiddle you can play with.

First off, you do not need to wrap element in jQuery if you load jQuery before angular. So you would then never need to use $(element) inside your directives and instead can use element directly as angular will automatically wrap element as a jQuery object.
For your example, you don't actually even need jQuery, so the answer provided below does not rely on jQuery at all.
As to your question, you can $watch your attribute values, angular automatically returns the compiled attribute value. So the following works as you would expect:
.directive('ngIndeterminate', function($compile) {
return {
restrict: 'A',
link: function(scope, element, attributes) {
scope.$watch(attributes['ngIndeterminate'], function (value) {
element.prop('indeterminate', !!value);
});
}
};
});
Here's a working jsfiddle: http://jsfiddle.net/d9rG7/5/

Use scope.$eval and element.prop directly to change attribute:
.directive('ngIndeterminate', function() {
return {
restrict: 'A',
link: function(scope, element, attributes) {
attributes.$observe('ngIndeterminate', function(value) {
element.prop('indeterminate', scope.$eval(value));
});
}
};
});
FIDDLE
By using attributes.$observe you can only catch attribute changes that contains interpolation (i.e., {{}}'s). You should use scope.$watch that can observe/watch an "expression". So, #Beyers answer is more correct i think. Thx for noting #Chi_Row

Related

Can you watch the attribute which triggered the directive?

Is it possible to watch the attribute which triggered the directive?
export function minDirective(): ng.IDirective {
return {
restrict: 'A',
require: 'ngModel',
link: (scope, elem, attr, ctrl) => {
scope.$watch(<name of the attribute>, () => {
// Do something
});
}
};
}
I'd like to listen to bb-civic-registration-number-format attribute the example below, except I have no idea it's named that way by the programmer reusing my directive:
I'm trying to create a validation directive which would take an arbitrary expression and use it for validation. A typical example is ngMin and ngMax except I'd like to implement similar functionality for an arbitrary input type:
<input type="number" ng-model="someModel" />
<input type="text" myprefix-max="someModel*0.5" />
You can actually get the name of your directive in the compile function, which should give you the ability to look up the value by comparing it to the attribute collection.
compile: function (elem, attrs) {
console.log(this.name);
}
If I understand question properly you can use the attribute value and [] notation.
<element my-directive scope-var="controllerScopePropertyName"></element >
JS
export function myDirective(): ng.IDirective {
return {
restrict: 'A',
require: 'ngModel',
link: (scope, elem, attr, ctrl) => {
// using ES5 syntax
scope.$watch(function(){
return scope[attr.scopeVar];
}, function(newVal,oldVal){
// do something
});
}
};
}
Alternatively just set up isolated scope and bind directly to the controller

My directive is not firing

I want to recreate nsClick behavior with my directive ( changing priority).
So this is my code:
angular.module('MyApp').directive('nsClickHack', function () {
return {
restrict: 'E',
priority: 100,
replace: true,
scope: {
key: '=',
value: '=',
accept: "&"
},
link: function ($scope, $element, $attrs, $location) {
$scope.method();
}
}
});
and the line I'm trying to bind to:
<li ng-repeat="item in items" ns-click-hack="toggle(); item.action()">
toggle and item.action are from other directives.
Can you point me where I was making mistake?
If you are trying to re-create ng-click, then it's probably better to look at the source of the ngClick directive.
For example, it does not create an isolate scope since only one isolate scope can be created on an element and it tries to be accommodating towards other directives. The alternative is to $parse the attribute value, which is what the built-in implementation is doing.
If you are just creating a "poor's man" version of ngClick, then, sure, you could use a callback function "&" defined on the scope, and invoke it when the element is clicked:
.directive("nsClickHack", function(){
return {
restrict: "A",
scope: {
clickCb: "&nsClickHack"
},
link: function(scope, element){
element.on("click", function(e){
scope.clickCb({$event: e}); // ngClick also passes the $event var
});
}
}
});
The usage is as you seem to want it:
<li ng-repeat="item in items" ns-click-hack="toggle(); item.action()">
plunker

Angularjs directive for replacing text

I am new at angularjs and I want to create a directive to change text for human readable.
scope including records coming from database. I want to change them matching humanReadable array.
angular.module('app', [])
.directive("humanReadable", function () {
return {
restrict: "A",
replace: true
}
});
var humanReadable= [{
text: "first_name",
replace: "First Name"
},
{
text: "last_name",
replace: "Last Name"
}];
function MyCtrl($scope) {
$scope.comesFromDatabase = ["first_name", "last_name"];
}
my html is like this.
<div ng-app="app">
<div ng-controller="MyCtrl">
<ul>
<li ng-repeat="item in comesFromDatabase">{{item}} -
<span human-readable="item"></span>
</li>
</ul>
</div>
</div>
and jsfiddle is here
As Martinspire mentioned, it's better to use a filter which might look something like below -
angular.module('myapp')
.filter('humanReadable', [function () {
return function (str) {
return str.split("_").join(" ").replace(/([^ ])([^ ]*)/gi,function(v,v1,v2){ return v1.toUpperCase()+v2; });
};
}]);
If you want directive only, with a bit of modification for the above code, it looks like this -
angular.module('myapp')
.directive('humanReadable', function () {
return {
restrict: 'A',
link: function (scope, element, attrs) {
element.html(attrs.humanReadable.split("_").join(" ").replace(/([^ ])([^ ]*)/gi,function(v,v1,v2){ return v1.toUpperCase()+v2; }));
}
};
});
Edit: I have done it without using your humamReadable array to generalize it assuming that you might find it useful instead of using a separate array.
angular.module('app', [])
.directive("humanReadable", function () {
return {
restrict: "A",
scope: {
items: '=',
humanReadable: '='
},
link: function (scope, element, attrs) {
scope.items.forEach(function (item, i) {
if (item.text === scope.humanReadable) {
element.text(item.replace);
}
});
}
}
});
Demo: http://jsfiddle.net/vhbg6104/4/
A better way would be to use a custom filter. You can read all about it in the docs https://docs.angularjs.org/guide/filter or api https://docs.angularjs.org/api/ng/filter/filter
You could take some inspiration from the translate-filters too: https://github.com/angular-translate/angular-translate
In summary, you would probably write it like so: {{item | human-readable}} or with ng-bind like so: <span ng-bind="item | human-readable">
Use the tools and i'm sure you can figure something out

How to add a new directive to an element from a directive?

I have a directive that I want to add other directives in its place. I am able to add the new directive's attribute but it does not seem to compile and do anything.
hat I have so far is: plunkr
app.directive('xxx', function($compile) {
return {
priority: '3',
compile: function(e, a) {
e.removeAttr('xxx');
a.$set('yyy','');
return function(scope, el, attrs) {
el.html('step 1');
$compile(el)(scope);
};
}
};
});
app.directive('yyy', function() {
return {
priority: '1',
compile: function(e, a) {
return function(scope, el, attrs) {
el.html('step 2');
};
}
};
});
app.directive('zzz', function() {
return {
priority: '2',
compile: function(e, a) {
return function(scope, el, attrs) {
console.log('zzz');
};
}
};
});
the html is
<div xxx zzz></div>
the current output is:
<div zzz yyy>Step 2</div>
which is what I want except that directive zzz is run twice!! and that is not good.
if I do not $compile in directive xxx after I add directive yyy, yyy does not execute and the result is
<div zzz>Step 1</div>
if I do not first remove xxx before $compile then the app crashes due to what I assume is infinitely trying to compile the xxx directive.
The priorities of the directives don't seem to have any effect.
What I need is to be able to add one or more directives from inside a directive without recompiling other directives on that element.

AngularJS + ChosenJS update model

I am creating an angular directive for the ChosenJS plugin based on this tutorial here: https://www.youtube.com/watch?v=8ozyXwLzFYs
What I want to do is have the model update when a value is selected.
function Foo($scope) {
$scope.legalEntitiesList = [
{ name: 'Foo' },
{ name: 'Bar' }
];
$scope.legalEntity = { name: 'Foo' };
}
myApp.directive('chosen', ['$timeout', function($timeout) {
var linker = function(scope, element, attrs, ngModel) {
if (!ngModel) return;
element.addClass('chzn-select');
$(element).chosen()
.change(function(e) {
console.log(ngModel.$viewValue);
});
scope.$watch(attrs.chosen, function() {
$(element).trigger('liszt:updated');
});
}
return {
restrict: 'A',
scope: true,
require: '?ngModel',
link: linker
}
}]);
Here is a fiddle: http://jsfiddle.net/dkrotts/MQzXq/7/. If you select a different option, the model value is not updated.
If you modify the select to bind to legalEntity.name instead of just legalEntity your fiddle works.
<select id="legalEntityInput" chosen="legalEntitiesList" ng-model="legalEntity.name" ng-options="legalEntity.name for legalEntity in legalEntitiesList" data-placeholder="Select..."><option></option></select>
See this updated fiddle for an example.
I wanted to add this as a comment, but I'm lacking reputation points. However, please note that newer versions of Chosen use the event chosen:updated instead of liszt:updated -- Thanks for the video, Dustin!

Categories

Resources