Detect clicked element by using directive - angularjs

HTML
<div my-dir>
<tour step=" currentstep">
<span tourtip="Few more steps to go."
tourtip-next-label="Close"
tourtip-placement="bottom"
tourtip-offset="80"
tourtip-step="0">
</span>
</tour>
</div>
I have written below directive to detect the x element of tour directive.But it always shows the parent div element even though I have clicked the x.So how can I do this ? Thanks in advance.
Directive
.directive('myDir', [
'$document',
function($document) {
return {
restrict: 'A',
scope: true,
link: function(scope, element, attrs) {
element.on('click', function(e) {
scope.$apply(function() {
if (element[0].className === 'tour-close-tip') {
console.log('my task');
}
});
e.stopPropagation(); //stop event from bubbling up to document object
});
}
};
}
]);
UI
This is the generated HTML on the browser:
<div hide-element-when-clicked-out-side="" class="ng-scope">
<tour step=" currentstep" class="ng-scope">
<span tourtip="Few more steps to go.!" tourtip-next-label="Close" tourtip-placement="bottom" tourtip-offset="80" tourtip-step="0" class="ng-scope">
</span><div class="tour-tip" tour-popup="" style="display: block; top: 80px; left: 0px;">
<span class="tour-arrow tt-bottom"></span>
<div class="tour-content-wrapper">
<p ng-bind="ttContent" class="ng-binding">Few more steps to go.!</p>
<a ng-click="setCurrentStep(getCurrentStep() + 1)" ng-bind="ttNextLabel" class="small button tour-next-tip ng-binding">Close</a>
<a ng-click="closeTour()" class="tour-close-tip">×</a>
</div>
</div>
Can you tell me how to access class="tour-close-tip" element within the above directive ? For me it always shows the ng-scope as the class.

You can either bind directly to that element or check which element has been clicked on, using the target attribute:
element.on('click', function (e) {
scope.$apply(function () {
if (angular.element(e.target).hasClass('tour-close-tip')) {

Your eventListener is not on the X but on the outer div element. One option would be to add the listener to the X element using a query selector on the element
You could try something like the following to get the X span and add the listener
element[0].querySelector('span').on...
Another probably better approach would be to use event delegation such as
element.on('click', selector, function(e){
});
Edit: I see your comment regarding not using JQuery so this may not work as Angular doesn't support event delegation with .on as far as I am aware.

you could use this:
app.directive('myDir', [
'$document',
function($document) {
return {
restrict: 'A',
scope: true,
link: function(scope, element, attrs) {
var x = angular.element(document.querySelector('.tour-close-tip'));
x.bind('click', function() {
console.log('clicked');
});
}
};
}
]);
here's a demo plnkr:
http://plnkr.co/edit/cUCJRetsqKmSbpI0iNoJ?p=preview
there's a heading with class 'tour-close-tip' there, and we attached a click event to it.
try it out, click the heading and look in your browser's console.
from this demo hopefuly you can make progress with your code.

Related

angular fall back image on background-image error

I've used this sort of directive before for a fallback image if the image does not load correctly.
app.directive('fallbackSrc', function () {
var fallbackSrc = {
link: function postLink(scope, element, attrs) {
element.bind('error', function() {
angular.element(this).css("background-image", attrs.fallbackSrc);
});
}
}
return fallbackSrc;
});
This works great when it is placed on the html like this, of course the directive would replace the src of the image instead of modifying the css:
<img fallback-src="http://google.com/favicon.ico" ng-src="{{image}}"/>
I currently have a background-image though:
<div class="issue-gallery-container" fallback-src="http://google.com/favicon.ico" style="background-image: url({{ AWS }}images/cover/{{ item.volume.number }}.{{ item.number }}.png)">
</div>
The directive right now does not pick up the error on the element since it occurs in the elements css. How would I modify the directive to listen for an error on the elements background-image?
I would setup a "dummy" img directive that changes its parent css. Or you could create a a template to simplify things even more.
Here is a working plunker http://plnkr.co/edit/334WIH2VUGReVTUYt2Tb?p=preview
Code is a bit messy but it works.
app.directive('backgroundFallbackSrc', function () {
return {
link : function(scope, element, attrs) {
element.bind('error', function() {
element.parent().css('background-image', 'url("' + attrs.backgroundFallbackSrc + '")');
});
}
}
});
html
<div class="issue-gallery-container" style="display:block; height:2000px;">
<img background-fallback-src="http://keithfimreite.com/BlogFiles/keithfimreite/SAAS.jpg"
ng-src="{{invalidImage}}" style="display:none;">
</div>

Can angularjs ng-click process events during the capturing phase?

Is it possible to have angularjs ng-click process events during the capturing phase instead of bubbling phase? I want to aggregate data from each of the parent elements in order starting from the parent and ending with the element that was clicked on.
Lets see the source code of ng-click at ngEventDirs.js#L50
As you can see the ng-click and all other event directives using .on().
So, the answer is No, it is not possible.
If you really need it, you could write a custom directive for that. For example, modify the code of ng-click a bit:
.directive('captureClick', function($parse) {
return {
restrict: 'A',
compile: function(element, attrs) {
var fn = $parse(attrs.captureClick);
return function(scope, element) {
element[0].addEventListener('click', function(event) {
scope.$apply(function() {
fn(scope, {
$event: event
});
});
}, true);
};
}
}
});
and use it like this:
<div title="A" ng-click="onBubbled($event)" capture-click="onCaptured($event)">
<div title="B" ng-click="onBubbled($event)" capture-click="onCaptured($event)">
<div title="C" ng-click="onBubbled($event)" capture-click="onCaptured($event)">
Yo!
</div>
</div>
</div>
Example Plunker: http://plnkr.co/edit/SVPv0fCNRQX4JXHeL47X?p=preview

Enable/Disable Anchor Tags using AngularJS

How do I enable/disable anchor tags using the directive approach?
Example:
while clicking on edit link, create & delete needs to be disabled or grayed out
while clicking on create link, edit & delete needs to be disabled or grayed out
JAVASCRIPT:
angular.module('ngApp', []).controller('ngCtrl',['$scope', function($scope){
$scope.create = function(){
console.log("inside create");
};
$scope.edit = function(){
console.log("inside edit");
};
$scope.delete = function(){
console.log("inside delete");
};
}]).directive('a', function() {
return {
restrict: 'E',
link: function(scope, elem, attrs) {
if(attrs.ngClick || attrs.href === '' || attrs.href === '#'){
elem.on('click', function(e){
e.preventDefault();
if(attrs.ngClick){
scope.$eval(attrs.ngClick);
}
});
}
}
};
});
LINK to CODE
Update:
Disabling the href works better in the link function return. Code below has been updated.
aDisabled naturally executes before ngClick because directives are sorted in alphabetical order. When aDisabled is renamed to tagDisabled, the directive does not work.
To "disable" the "a" tag, I'd want the following things:
href links not to be followed when clicked
ngClick events not to fire when clicked
styles changed by adding a disabled class
This directive does this by mimicking the ngDisabled directive. Based on the value of a-disabled directive, all of the above features are toggled.
myApp.directive('aDisabled', function() {
return {
compile: function(tElement, tAttrs, transclude) {
//Disable ngClick
tAttrs["ngClick"] = "!("+tAttrs["aDisabled"]+") && ("+tAttrs["ngClick"]+")";
//return a link function
return function (scope, iElement, iAttrs) {
//Toggle "disabled" to class when aDisabled becomes true
scope.$watch(iAttrs["aDisabled"], function(newValue) {
if (newValue !== undefined) {
iElement.toggleClass("disabled", newValue);
}
});
//Disable href on click
iElement.on("click", function(e) {
if (scope.$eval(iAttrs["aDisabled"])) {
e.preventDefault();
}
});
};
}
};
});
Here is a css style that might indicate a disabled tag:
a.disabled {
color: #AAAAAA;
cursor: default;
pointer-events: none;
text-decoration: none;
}
And here is the code in action, with your example
My problem was slightly different: I have anchor tags that define an href, and I want to use ng-disabled to prevent the link from going anywhere when clicked. The solution is to un-set the href when the link is disabled, like this:
<a ng-href="{{isDisabled ? '' : '#/foo'}}"
ng-disabled="isDisabled">Foo</a>
In this case, ng-disabled is only used for styling the element.
If you want to avoid using unofficial attributes, you'll need to style it yourself:
<style>
a.disabled {
color: #888;
}
</style>
<a ng-href="{{isDisabled ? '' : '#/foo'}}"
ng-class="{disabled: isDisabled}">Foo</a>
For people not wanting a complicated answer, I used Ng-If to solve this for something similar:
<div style="text-align: center;">
<a ng-if="ctrl.something != null" href="#" ng-click="ctrl.anchorClicked();">I'm An Anchor</a>
<span ng-if="ctrl.something == null">I'm just text</span>
</div>
Modifying #Nitin's answer to work with dynamic disabling:
angular.module('myApp').directive('a', function() {
return {
restrict: 'E',
link: function(scope, elem, attrs) {
elem.on('click', function(e) {
if (attrs.disabled) {
e.preventDefault(); // prevent link click
}
});
}
};
});
This checks the existence of disabled attribute and its value upon every click.
Disclaimer:
The OP has made this comment on another answer:
We can have ngDisabled for buttons or input tags; by using CSS we can
make the button to look like anchor tag but that doesn't help much! I
was more keen on looking how it can be done using directive approach
or angular way of doing it?
You can use a variable inside the scope of your controller to disable the links/buttons according to the last button/link that you've clicked on by using ng-click to set the variable at the correct value and ng-disabled to disable the button when needed according to the value in the variable.
I've updated your Plunker to give you an idea.
But basically, it's something like this:
<div>
<button ng-click="create()" ng-disabled="state === 'edit'">CREATE</button><br/>
<button ng-click="edit()" ng-disabled="state === 'create'">EDIT</button><br/>
<button href="" ng-click="delete()" ng-disabled="state === 'create' || state === 'edit'">DELETE</button>
</div>
Have you tried using lazy evaluation of expressions like disabled || someAction()?
Lets assume I defined something like so in my controller:
$scope.disabled = true;
Then I can disabling a link and apply inline styles like so:
<a data-ng-click="disabled || (GoTo('#/employer/'))" data-ng-style="disabled && { 'background-color': 'rgba(99, 99, 99, 0.5)', }">Higher Level</a>
Or better still disable a link and apply a class like so:
<a data-ng-click="disabled || (GoTo('#/employer/'))" data-ng-class="{ disabled: disabled }">Higher Level</a>
Note: that you will have a class="disabled" applied to DOM element by that statement.
At this stage you just need to handle what you action GoTo() will do. In my case its as simple as redirect to associated state:
$scope.GoTo = function (state) {
if (state != undefined && state.length > 0) {
$window.location.hash = state;
}
};
Rather than being limited by ngDisabled you are limited by what you decide to do.
With this technique I successfully applied permission level checking to enable or disable user access to certain part of my module.
Simple plunker to demonstrate the point
You can create a custom directive that is somehow similar to ng-disabled and disable a specific set of elements by:
watching the property changes of the custom directive, e.g. my-disabled.
clone the current element without the added event handlers.
add css properties to the cloned element and other attributes or event handlers that will
provide the disabled state of an element.
when changes are detected on the watched property, replace the current element with the cloned element.
HTML
<a my-disabled="disableCreate" href="#" ng-click="disableEdit = true">CREATE</a><br/>
<a my-disabled="disableEdit" href="#" ng-click="disableCreate = true">EDIT</a><br/>
<a my-disabled="disableCreate || disableEdit" href="#">DELETE</a><br/>
RESET
JAVASCRIPT
directive('myDisabled', function() {
return {
link: function(scope, elem, attr) {
var color = elem.css('color'),
textDecoration = elem.css('text-decoration'),
cursor = elem.css('cursor'),
// double negation for non-boolean attributes e.g. undefined
currentValue = !!scope.$eval(attr.myDisabled),
current = elem[0],
next = elem[0].cloneNode(true);
var nextElem = angular.element(next);
nextElem.on('click', function(e) {
e.preventDefault();
e.stopPropagation();
});
nextElem.css('color', 'gray');
nextElem.css('text-decoration', 'line-through');
nextElem.css('cursor', 'not-allowed');
nextElem.attr('tabindex', -1);
scope.$watch(attr.myDisabled, function(value) {
// double negation for non-boolean attributes e.g. undefined
value = !!value;
if(currentValue != value) {
currentValue = value;
current.parentNode.replaceChild(next, current);
var temp = current;
current = next;
next = temp;
}
})
}
}
});
Make a toggle function in the respective scope to grey out the link.
First,create the following CSS classes in your .css file.
.disabled {
pointer-events: none;
cursor: default;
}
.enabled {
pointer-events: visible;
cursor: auto;
}
Add a $scope.state and $scope.toggle variable. Edit your controller in the JS file like:
$scope.state='on';
$scope.toggle='enabled';
$scope.changeState = function () {
$scope.state = $scope.state === 'on' ? 'off' : 'on';
$scope.toggleEdit();
};
$scope.toggleEdit = function () {
if ($scope.state === 'on')
$scope.toggle = 'enabled';
else
$scope.toggle = 'disabled';
};
Now,in the HTML a tags edit as:
CREATE<br/>
EDIT<br/>
DELETE
To avoid the problem of the link disabling itself,
change the DOM CSS class at the end of the function.
document.getElementById("create").className = "enabled";
You may, redefine the a tag using angular directive:
angular.module('myApp').directive('a', function() {
return {
restrict: 'E',
link: function(scope, elem, attrs) {
if ('disabled' in attrs) {
elem.on('click', function(e) {
e.preventDefault(); // prevent link click
});
}
}
};
});
In html:
<a href="nextPage" disabled>Next</a>
I'd expect anchor tags to lead to a static page with a url. I think that a buttons suits more to your use case, and then you can use ngDisabled to disable it. From the docs: https://docs.angularjs.org/api/ng/directive/ngDisabled
ui-router v1.0.18 introduces support for ng-disabled on anchor tags
Example: <a ui-sref="go" ng-disabled="true">nogo</a>
https://github.com/angular-ui/ui-router/issues/2957
https://github.com/angular-ui/ui-router/pull/3692/commits/a59fcae300f9d8f73a5b91fa77c92b926e68281d

AngularJS directives: ng-click is not triggered after blur

DEMO
Consider the following example:
<input type="text" ng-model="client.phoneNumber" phone-number>
<button ng-click="doSomething()">Do Something</button>
.directive("phoneNumber", function($compile) {
return {
restrict: 'A',
scope: true,
link: function(scope, element, attrs) {
scope.mobileNumberIsValid = true;
var errorTemplate = "<span ng-show='!mobileNumberIsValid'>Error</span>";
element.after($compile(errorTemplate)(scope)).on('blur', function() {
scope.$apply(function() {
scope.mobileNumberIsValid = /^\d*$/.test(element.val());
});
});
}
};
});
Looking at the demo, if you add say 'a' at the end of the phone number, and click the button, doSomething() is not called. If you click the button again, then doSomething() is called.
Why doSomething() is not called for the first time? Any ideas how to fix this?
Note: It is important to keep the validation on blur.
Explain
Use click button, mousedown event is triggered on button element.
Input is on blur, blur callback triggered to validate input value.
If invalid, error span is displayed, pushing button tag down, thus cursor left button area. If user release mouse, mouseup event is not triggered. This acts like click on a button but move outside of it before releasing mouse to cancel the button click. This is the reason ng-click is not triggered. Because mouseup event is not triggered on button.
Solution
Use ng-pattern to dynamically validate the input value, and show/hide error span immediately according to ngModel.$invalid property.
Demo 1 http://jsbin.com/epEBECAy/14/edit
----- Update 1 -----
According to author's request, updated answer with another solution.
Demo 2 http://jsbin.com/epEBECAy/21/edit?html,js
HTML
<body ng-app="Demo" ng-controller="DemoCtrl as demoCtrl">
<pre>{{ client | json }}</pre>
<div id="wrapper">
<input type="text" phone-number
ng-model="client.phoneNumber"
ng-blur="demoCtrl.validateInput(client.phoneNumber)">
</div>
<button ng-mousedown="demoCtrl.pauseValidation()"
ng-mouseup="demoCtrl.resumeValidation()"
ng-click="doSomething()">Do Something</button>
</body>
Logic
I used ng-blur directive on input to trigger validation. If ng-mousedown is triggered before ng-blur, ng-blur callback will be deferred until ng-mouseup is fired. This is accomplished by utilizing $q service.
Here: http://jsbin.com/epEBECAy/25/edit
As explained by other answers, the button is moved by the appearance of the span before an onmouseup event on the button occurs, thus causing the issue you are experiencing. The easiest way to accomplish what you want is to style the form in such a way that the appearance of the span does not cause the button to move (this is a good idea in general from a UX perspective). You can do this by wrapping the input element in a div with a style of white-space:nowrap. As long as there is enough horizontal space for the span, the button will not move and the ng-click event will work as expected.
<div id="wrapper">
<div style='white-space:nowrap;'>
<input type="text" ng-model="client.phoneNumber" phone-number>
</div>
<button ng-click="doSomething()">Do Something</button>
</div>
It is because the directive is inserting the <span>Error</span> underneath where the button is currently placed, interfering with the click event location. You can see this by moving the button above the text box, and everything should work fine.
EDIT:
If you really must have the error in the same position, and solve the issue without creating your own click directive, you can use ng-mousedown instead of ng-click. This will trigger the click code before handling the blur event.
Not a direct answer, but a suggestion for writing the directive differently (the html is the same):
http://jsbin.com/OTELeFe/1/
angular.module("Demo", [])
.controller("DemoCtrl", function($scope) {
$scope.client = {
phoneNumber: '0451785986'
};
$scope.doSomething = function() {
console.log('Doing...');
};
})
.directive("phoneNumber", function($compile) {
var errorTemplate = "<span ng-show='!mobileNumberIsValid'> Error </span>";
var link = function(scope, element, attrs) {
$compile(element.find("span"))(scope);
scope.mobileNumberIsValid = true;
scope.$watch('ngModel', function(v){
scope.mobileNumberIsValid = /^\d*$/.test(v);
});
};
var compile = function(element, attrs){
var h = element[0].outerHTML;
var newHtml = [
'<div>',
h.replace('phone-number', ''),
errorTemplate,
'</div>'
].join("\n");
element.replaceWith(newHtml);
return link;
};
return {
scope: {
ngModel: '='
},
compile: compile
};
});
I would suggest using $parsers and $setValidity way while validating phone number.
app.directive('phoneNumber', function () {
return {
restrict: 'A',
require: '?ngModel',
link: function(scope, element, attrs, ctrl) {
if (!ctrl) return;
ctrl.$parsers.unshift(function(viewValue) {
var valid = /^\d*$/.test(viewValue);
ctrl.$setValidity('phoneNumber', valid);
return viewValue;
});
ctrl.$formatters.unshift(function(modelValue) {
var valid = /^\d*$/.test(modelValue);
ctrl.$setValidity('phoneNumber', valid);
return modelValue;
});
}
}
});
So, you will be able to use $valid property on a field in your view:
<form name="form" ng-submit="doSomething()" novalidate>
<input type="text" name="phone" ng-model="phoneNumber" phone-number/>
<p ng-show="form.phone.$invalid">(Show on error)Wrong phone number</p>
</form>
If you want to show errors only on blur you can use (found here: AngularJS Forms - Validate Fields After User Has Left Field):
var focusDirective = function () {
return {
restrict: 'E',
require: '?ngModel',
link: function (scope, element, attrs, ctrl) {
var elm = $(element);
if (!ctrl) return;
elm.on('focus', function () {
elm.addClass('has-focus');
ctrl.$hasFocus = true;
if(!scope.$$phase) scope.$digest();
});
elm.on('blur', function () {
elm.removeClass('has-focus');
elm.addClass('has-visited');
ctrl.$hasFocus = false;
ctrl.$hasVisited = true;
if(!scope.$$phase) scope.$digest();
});
elm.closest('form').on('submit', function () {
elm.addClass('has-visited');
ctrl.$hasFocus = false;
ctrl.$hasVisited = true;
if(!scope.$$phase) scope.$digest();
})
}
}
};
app.directive('input', focusDirective);
So, you will have hasFocus property if field is focused now and hasVisited property if that field blured one or more times:
<form name="form" ng-submit="doSomething()" novalidate>
<input type="text" name="phone" ng-model="phoneNumber" phone-number/>
<p ng-show="form.phone.$invalid">[error] Wrong phone number</p>
<p ng-show="form.phone.$invalid
&& form.phone.$hasVisited">[error && visited] Wrong phone number</p>
<p ng-show="form.phone.$invalid
&& !form.phone.$hasFocus">[error && blur] Wrong phone number</p>
<div><input type="submit" value="Submit"/></div>
</form>
Demo: http://jsfiddle.net/zVpWh/4/
I fixed it with the following.
<button class="submitButton form-control" type="submit" ng-mousedown="this.form.submit();" >

AngularJS directive only when the condition is true

I am going to have a contextmenu directive in ng-repeat items.
Based on whether a condition is true, the directive should be applied.
How do I put a condition like only when item.hasMenu == true then apply the directive ?
<ul ng-controller="ListViewCtrl" >
<li contextmenu ng-repeat="item in items">{{item.name}} </li>
</ul>
EDIT
This seems to have worked for me. First the directive.
app.directive('menu',function(){
return {
restrict : 'A',
link : function(scope,element,attrs){
if(scope.hasMenu){
element.contextmenu({
menu:[
{title:"Remove" , "cmd" : "remove"},
{title:"Add" , "cmd" : "add"},
],
select:function(event,ui){
//alert("select " + ui.cmd + " on" + ui.target.text());
if (ui.cmd ==='remove'){
alert('Remove selected on ' + scope.item);
}
if (ui.cmd ==='add'){
alert("Add selected");
}
}
});
}
}
}
}
);
Then the html
<ul ng-controller="ListViewCtrl" >
<li menu ng-repeat="item in items">{{item.name}} </li>
</ul>
Can you do something like this, using ng-if?
<ul ng-controller="ListViewCtrl" >
<li ng-repeat="item in items">
<span>{{item.name}}</span>
<div contextmenu ng-if="item.hasMenu"></div>
</li>
</ul>
Here are the docs for ng-if.
EDIT:
If you are driving the context menu off of a class, you should be able to do this:
<ul ng-controller="ListViewCtrl" >
<li ng-class="{'hasmenu': item.hasMenu}" ng-repeat="item in items">{{item.name}} </li>
</ul>
I think this is pretty tricky if you don't want to change your DOM structure. If you could just place your contextmenu directive on a sub DOM node inside the <li> things would be a lot easier.
However, let's assume you can't do that and let's also assume that you don't own the contextmenu directive so that you can't change it to your needs.
Here is a possible solution to your problem that might be a bit hackish (actually I don't know!)
'use strict';
angular.module('myApp', [])
.controller('TestController', ['$scope', function($scope) {
$scope.items = [
{name:1, hasMenu: true},
{name:2, hasMenu: false },
{name:3, hasMenu: true}
];
}])
.directive('contextmenu', function(){
return {
restrict: 'A',
link: function(scope, element){
element.css('color', 'red');
}
}
})
.directive('applyMenu', ['$compile', function($compile){
return {
restrict: 'A',
link: function(scope, element){
if (scope.item.hasMenu){
//add the contextmenu directive to the element
element.attr('contextmenu', '');
//we need to remove this attr
//otherwise we would get into an infinite loop
element.removeAttr('apply-menu');
//we also need to remove the ng-repeat to not let the ng-repeat
//directive come between us.
//However as we don't know the side effects of
//completely removing it, we add it back after
//the compile process is done.
var ngRepeat = element.attr('ng-repeat');
element.removeAttr('ng-repeat');
var enhanced = $compile(element[0])(scope);
element.html(enhanced);
element.attr('ng-repeat', ngRepeat);
}
}
}
}]);
I faked the contextmenu directive to just change the color to red just so that we can see it's taking place.
Then I created an apply-menu attribute directive. This directive than checks if the hasMenu property is true and if so hooks in and adds the contextmenu directive and does a manual $compile process.
However, what worries me a bit about this solution is that I had to temporally remove the ng-repeat directive (and also the apply-menu directive) to get the $compile process to act the way we want it to act. We then add the ng-repeat directive back once the $compile has been made. That is because we don't know the side effects of removing it entirely from the resulting html. This might be perfectly valid to do, but it feels a bit arkward to me.
Here is the plunker: http://plnkr.co/edit/KrygjX
You can do this way
angularApp.directive('element', function($compile) {
return {
restrict: 'E',
replace: true,
transclude: true,
require: '?ngModel',
scope: 'isolate',
link: function($scope, elem, attr, ctrl) {
$scope.isTrue = function() {
return attr.hasMenu;
};
if($scope.isTrue())
//some html for control
elem.html('').show();
else
//some html for control
elem.html('').show();
$compile(elem.contents())($scope);
}
};
});

Resources