I made some AngularJS directives in the past week and they all work but this one just won't work and I don't know what I am doing wrong..
This is the directive I'm talking about:
app.directive('idleCheck', [function () {
return {
restrict: 'I',
link: function (scope, elem, attrs) {
ifvisible.setIdleDuration(5);
ifvisible.on("idle", function () {
var div = document.getElementById('fullscreenWrap');
div.style.cursor = 'none';
stream.pause();
});
ifvisible.on("wakeup", function () {
var div = document.getElementById('fullscreenWrap');
div.style.cursor = 'auto';
stream.resume();
});
}
}
}]);
This is my HTML code where I call the directive:
<div id="fullscreenWrap" idle-check>
...
</div>
Do you see anything wrong in the code ?
Or do you know why it isn't working ?
You need to change restrict field to 'A'.
The restrict option is typically set to:
'A' - only matches attribute name 'E' - only matches element name 'C'
- only matches class name These restrictions can all be combined as needed:
'AEC' - matches either attribute or element or class name
Angular directive
The restrict options available are: 'E','A','C','M'
One of EACM restricts the directive to a specific directive declaration style.
You can even use Multiple restrictions on same directive restrict: 'AC'
If you don't restrict any, the defaults (elements and attributes) are used.
E - Element name (default): <my-directive></my-directive>
A - Attribute (default): <div my-directive="exp"></div>
C - Class: <div class="my-directive: exp;"></div>
M - coMment: <!-- directive: my-directive exp -->
For Example:
ng-if is restricted to 'A'. so it can be used only as attribute, you can't use as comment or element
Here's the angularjs code for ngIf
var ngIfDirective = ['$animate', function($animate) {
return {
transclude: 'element',
priority: 600,
terminal: true,
restrict: 'A', // --> This means restricting to Attribute
Without knowing what error it is actually giving your issue is most likely your directive declaration.
There is no restrict: I. Angular only supports three values for this:
A - only matches attribute name
E - only matches element name
C - only matches class name
You can but any combination of the three though to support multiple cases.
Docs: https://docs.angularjs.org/guide/directive#template-expanding-directive
It states the info at the bottom of the template expanding directive section.
Related
I have an angularjs dropdown directive called X that opens and shows related items when I start typing on an input field. There are two X directives in my page which have the same controller and template (ie same directive but different isolated scope).
The first directive shows list of planets and the second shows list of minerals in the planet that is being typed. When I start typing on an input, I want to open both directive showing related data dynamically. But as they share same template, I don't know how to open the dropdown for the second directive.
There is a single ng-repeat in the template but 2 lists on 2 different scopes. I just want to know the approach in understanding this. Thank you.
Here directive will listen inpVal and updates the list in directive (you need to pass inpVal to the directive).
.directive("myCustomSelect", function(){
return {
restrict: "AE",
scope: {
inpVal: "="
},
link: function(scope, ele, attrs){
var _list = [];
scope.list = _list;
var _watch = scope.$watch("inpVal", function(n){
if(n){
updateList();
}
});
scope.$on("$destroy", function(){
_watch();
});
function updateList(){
var enteredVal = scope.inpVal;
//_.filter is underscore js
scope.list = _.filter(_list, function(v){
return enteredVal == v.name?true:false;
});
}
},
template: "<select ng-options="l.name as l for l in list"></select>"
}
})
So I have a a custom directive, named e.g custom as below:
app.directive('custom', function()
{
return {
restrict: 'A',
scope: { itemSelector: '=custom', gutter: '=' },
link: function(scope, element, attrs){
console.log("IS: " + scope.itemSelector);
console.log("GUTTER: " + scope.gutter);
}
}
}
invoked via HTML like the below
<div custom="item" gutter="10"><!--content--></div>
Can anyone suggest why scope.gutter === '10' yet scope.itemSelector === undefined?
Is it possible to obtain the value of the directives defining attribute this way?
Thanks
I guess item is not defined in the parent scope of your directive. You have two solutions explained in the following post (AngularJS Directive Passing String)
Either you wanted item to be passed as a string and add single quotes around it (by default it's evaluated as an angular expression)
<div custom="'item'" gutter="10"><!--content--></div>
Or change your directive configuration so that it considers the custom attribute as a string :
scope:
{ itemSelector: '#custom', gutter: '=' }
Hope this helps
I created a directive for my AngularJS app which checks the $scope to see if a profile image is present. If it is the directive shows the image by appending a new DIV, if not it shows a default via a css class. The directive looks like so... and it works in the app.
.directive('profileImage',function() {
return {
restrict: 'A',
scope: {
profileImage: '='
},
link: function(scope, element) {
if(!scope.profileImage) {
element.addClass('icon-default-profile-image defaultProfileImage');
} else {
element.append('<img ng-src="profileImage" class="profileImage">');
}
}
};
})
;
now I wish to write a unit test for this (I know I should have done that first but I am just learning about tests). I have written the following:
it('should display a specific image', inject(function($rootScope, $compile) {
//$rootScope.profileImage = "srcOfImage";
$scope = $rootScope;
$scope.profileImage = "srcOfImage";
var element = angular.element('<div profile-image="profileImage"></div>')
element = $compile(element)($scope)
console.log(element);
var imgElem = element.find('img');
expect(imgElem.length).toBe(1);
expect(imgElem.eq(0).attr('src')).toBe('srcOfImage');
}));
Now the test fails in this scenario (other tests where no $scope.profileImage is available has passed). When I out put the element I have created I get the following:
LOG: {0: <div profile-image="profileImage" class="ng-scope ng-isolate-scope"><img ng-src="profileImage" class="profileImage"></div>, length: 1}
So everything works but the profile-image="profileImage" in the var element = angular.element('<div profile-image="profileImage"></div>') is being taken as a literal string. It is not showing the value 'srcOfImage'. What am I doing wrong?
The error looks like so:
PhantomJS 1.9.2 (Mac OS X) Profile Image Directive should display a specific image FAILED
-src="prExpected undefined to be 'srcOfImage'.>, length: 1}
Big thanks for any advice / help / explanations
This is expected behavior because:
element.append('<img ng-src="profileImage" class="profileImage">');
appends a normal DOM image without compilation, so the properties are added as they are.
Try:
element.append('<img ng-src="profileImage" class="profileImage">');
$compile(element.contents())(scope); //adding this line to compile the image.
Remember to declare the $compile service in your directive:
.directive('profileImage',function($compile) {
One more problem is you have to use ng-src with {{}} like this:
ng-src="{{profileImage}}"
Your final directive looks like this:
.directive('profileImage',function($compile) { //declare $compile service.
return {
restrict: 'A',
scope: {
profileImage: '='
},
link: function(scope, element) {
if(!scope.profileImage) {
element.addClass('icon-default-profile-image defaultProfileImage');
} else {
element.append('<img ng-src="{{profileImage}}" class="profileImage">');
$compile(element.contents())(scope); //adding this line to compile the image.
}
}
};
})
;
I am using "draggable" directive to support image dragging. However, as per the role of the user, I need to disable image dragging for certain groups of users. I have used following code.
<!--draggable attribute is used as handle to make it draggable using jquery event-->
<li ng-repeat="template in templates" draggable id="{{template._id}}" type="template" class="template-box">
<!-- Images and other fields are child of "li" tag which can be dragged.-->
</li>
The method dragSupported is in the template scope and returns true or false. I don't want to create two big duplicate <li> elements by using ng-if for each value returned by dragSupported(). In other words, I am not looking for the following approach to solve this.
<!--draggable attribute is used as handle to make it draggable using jquery event-->
<li ng-if="dragSupported() ==true" ng-repeat="template in templates" draggable id="{{template._id}}" type="template" class="template-box">
<!-- Images and other fields are child of "li" tag which can be dragged.-->
</li>
<!--remove "draggable" directive as user doesn't have permission to drag file -->
<li ng-if="dragSupported() !=true" ng-repeat="template in templates" id="{{template._id}}" type="template" class="template-box">
<!-- Images and other fields are child of "li" tag which can be dragged.-->
</li>
Is there any other approach to avoid code duplicity?
ng-attr-<attrName>
Support for conditionally declaring an HTML attribute is included with Angular as the dynamically-titled ng-attr-<attrName> directive.
Official Docs for ng-attr
Example
In your case, the code might look like this:
<li
id="{{template._id}}"
class="template-box"
type="template"
ng-repeat="template in templates"
ng-attr-draggable="dragSupported() === true"
></li>
Demo
JSFiddle
This contains examples of usage for the following values: true, false, undefined, null, 1, 0, and "". Note how typically-falsey values may yield unexpected results.
Thanks Jason for your suggestion. I took little different approach here. Since I don't want to change the "scope" variable therefore I used "attrs" to check if drag is allowed or not. Following is approach I tool which seems good so far.
Directive code:
app.directive('draggable', function () {
return {
// A = attribute, E = Element, C = Class and M = HTML Comment
restrict: 'A',
replace:true,
link: function (scope, element, attrs) {
if(attrs.allowdrag =="true")
{
element.draggable({
cursor: 'move',
helper: 'clone',
class:'drag-file'
});
}
}
}
});
HTML Code:
<ul>
<!--draggable attribute is used as handle to make it draggable using jquery event-->
<li ng-repeat="template in templates" draggable allowdrag="{{userHasPrivilege()}}" >
<!--Ohter code part of li tag-->
</li>
</ul>
Controller is having implementation of userHasPrivilege().
Not sure if this is correct way or not. Looking for thoughts.
There is no way to directly add or remove an attribute from an element. However, you could create a directive that simply adds the attribute to the element when the condition is met. I've put something together that illustrates the approach.
Demo: http://jsfiddle.net/VQfcP/31/
Directive
myApp.directive('myDirective', function () {
return {
restrict: 'A',
scope: {
canDrag: '&'
},
link: function (scope, el, attrs, controller) {
/*
$parent.$index is ugly, and it's due to the fact that the ng-repeat is being evaluated
first, and then the directive is being applied to the result of the current iteration
of the repeater. You may be able to clean this by transcluding the repeat into the
directive, but that may be an inappropriate separation of concerns.
You will need to figure out the best way to handle this, if you want to use this approach.
*/
if (scope.canDrag&& scope.canDrag({idx: scope.$parent.$index})) {
angular.element(el).attr("draggable", "draggable");
}
}
};
});
HTML
<ul>
<!-- same deal with $parent -->
<li ng-repeat="x in [1, 2, 3, 4, 5]" my-directive="true" can-drag="checkPermissions(idx)">{{$parent.x}}</li>
</ul>
Controller
function Ctl($scope) {
$scope.checkPermissions = function(idx) {
// do whatever you need to check permissions
// return true to add the attribute
}
}
I used a different approach as the previous examples didn't work for me. Perhaps it has to do with using custom directives? Perhaps someone can clear that up.
In my particular example, I'm using ui-grid, but not all ui-grids should use pagination. I pass in a "paginated" attribute and then $compile the directive based on true/false. Seems pretty brutish but hopefully it can push people in a positive direction.
HTML
<sync-grid service="demand" paginated="true"></sync-grid>
Directive
angular
.module('app.directives')
.directive('syncGrid', ['$compile', SyncGrid]);
function SyncGrid($compile){
var nonPaginatedTemplate = '' +
'<div>' +
' <div ui-grid="gridOptions" class="grid"></div>' +
'</div>';
var paginatedTemplate = '' +
'<div>' +
' <div ui-grid="gridOptions" class="grid" ui-grid-pagination></div>' +
'</div>';
return {
link: link,
restrict: 'E',
replace: true
};
function link(scope, element, attrs) {
var isPaginated = attrs['paginated'];
var template = isPaginated ? paginatedTemplate : nonPaginatedTemplate;
var linkFn = $compile(template);
var content = linkFn(scope);
element.append(content);
// Continue with ui-grid initialization code
// ...
}
}
Is there a way to write a directive that just applies to a specific element + attribute + attribute value?
My very first intention would be to have separate directives, for modularity and maintenance purposes, but I'm afraid that's not possible as I get an error from Angular telling me there are multiple directives matching the element.
So my scenario is as follows: I want to write my own input elements, e.g.
<input type="time-picker">
<input type="date-picker">
so I did
app.directive('input', function () {
return {
restrict: 'E',
templateUrl: function ($element, $attrs) {
if ($attrs.type === 'date-picker' || $attrs.type === 'time-picker') {
return $attrs.type + '.html';
}
},
controller: function ($scope, $element, $attrs) {
if ($attrs.type === 'date-picker') {
console.log('date-picker');
}
else if($attrs.type === 'time-picker') {
console.log('time-picker');
}
}
}
});
This works well as long as there are no other input elements in the page.
If I put
<input type="time-picker">
<input type="date-picker">
it works fine. Now if I add
<input type="text">
the whole page hangs.
See my fiddle here: http://jsfiddle.net/pWc3K/8/
If you change your html to this:
<input type="text" time-picker> <input type="text" date-picker>
Then you could wire up your directives based on those attributes like so:
app.directive('timePicker', function(){
return {
restrict: 'A',
...
}
});
app.directive('datePicker', function(){
return {
restrict: 'A',
...
}
});
Putting time-picker/date-picker in an input type for an element isn't really valid. If you read up on the angular docs for directives you'll find a whole list of the different things you can associate on. The four ones are:
E - Element name: <my-directive></my-directive>
A - Attribute: <div my-directive="exp"> </div>
C - Class: <div class="my-directive: exp;"></div>
M - Comment: <!-- directive: my-directive exp -->
Try and understand In AngularJs custom directives created can have following restrictions:
The restrict option is typically set to:
'A' - only matches attribute name
'E' - only matches element name
'C' - only matches class name
‘M’ – only comments
These restrictions can all be combined as needed:
for eg:
'AEC' - matches either attribute or element or class name