I created an angular directive that binds the keyboard short cut. However, once this is bind, it keeps the binding for all other divs. But i have attached only to one div. How do i unbind it after it executes and re bind when the user clicks within that div.
ex:
angular.module('Dummy').directive('keypressEvents',
function ($document, $rootScope) {
return {
restrict: 'A',
link: function () {
$document.bind('keydown', function (e) {
if ((e.which == '115' || e.which == '83' ) && (e.ctrlKey || e.metaKey)){
$rootScope.$broadcast('Ctrl+s');
}
});
}
} });
in controller
$rootScope.$on('Ctrl+s', function (e) {
$scope.$apply(function () {
$scope.doDummyAction();
});
});
in html
<div keypress-events>this is a div that binds keyboard shortcut</div>
<div>Another div which doesn't need a short cut key</div>
Appreciate any suggestions.
Don't use $document. The link function gets a scope and the element it's applied to passed to it.
link: function(scope, iElem){
iElem.bind(...
}
When you bind to the document it is listening to events that bubble out the document (page). If you just bind to the element itself you'll only get the event handler triggered when that element has focus and the event occurs.
Related
I have a form in which I have enter-key press event and ng-blur. It's like when a user enters something and click elsewhere the form is submitted similarly if a user writes something and press enter the form is submitted and API call goes.
Problem Statement
It works fine on ng-blur only a single call goes to the API. But when I try to use key event two calls go. That's why two success messages show. I don't know why but it acting like that.
Form
input ng-model="cusBoard.boardData.musicalWorkName"
id="superTitleInput" class="title-edit-input superTitle-input" type="text"
maxlength="50"
my-key-enter="cusBoard.updateInfo()"
ng-blur="cusBoard.updateInfo()">
My-key-enter Directive
app.directive('myKeyEnter', function () {
return {
controller: 'SignInController',
link: function (scope, elements, attrs) {
elements.bind('keydown keypress', function (event) {
if (event.which === 13) {
scope.$apply(function () {
scope.$eval(attrs.myKeyEnter);
});
event.preventDefault();
}
});
}
}
controller function
function updateInfo(){
editable('superTitle',0);
var params = {
superTitle : cusBoard.boardData.musicalWorkName,
boardId : cusBoard.boardData._id
};
CastingBoard.updateSuperTitle(params).then(function (res) {
if (res.msgCode === '405') {
$mdToast.show($mdToast.simple().textContent("Board title updated successfully.").position('bottom right'));
}
});
}
You're binding to both the keydown and keypress events:
elements.bind('keydown keypress', function (event) {
Both events are fired for the Enter key, so the handler is executed twice.
Just pick one, or the other.
I'm looking to write a directive that allows clicks on an outer element to clone the ui-sref of one of its contained elements such that clicking on the outer element behaves the same as clicking on the .cloned element
<div clone-click=".cloned">
...
<a class="cloned" ui-sref="root.a" ng-if="true">example</a>
<a class="cloned" ui-sref="root.b" ng-if="false">example</a>
...
<a ui-sref="root.c">elsewhere</a>
...
</div>
I tried an attribute directive that triggers the click
app.directive('cloneClick', function() {
return {
restrict: 'A',
scope: {
selector: '#cloneClick'
},
link: function(scope, element) {
element.click(function() {
element.find(scope.selector).not(':disabled').first().click();
})
}
};
})
but this causes an infinite loop or something and doesn't work. How can I make it work? Or is there a better way to achieve this?
You aren't taking into consideration event bubbling. As it is now, any click event on the children will already bubble to the parent at which point you are telling it to click same element again ... thus infinite loop if the target you want is clicked
My suggestion would be to prevent propagation of the event on the <a> .
If the <a> itself is clicked, let browser handle the redirect and if any other part of parent is clicked use $location service to redirect using the href value that ui-sref generates.
Something like:
link: function(scope, element) {
var $link = element.find(scope.selector).not(':disabled').first();
// prevent bubbling on target link
$link.click(function(e) {
e.stopImmediatePropagation()
});
element.click(function(e) {
// make sure target link wasn't element clicked
if (e.target !== $link[0]) { // assumes no child tags in `<a>`
$location.url($link.attr('href'));
}
});
}
You may need to adjust a bit depending on whether or not you are using html5mode
EDIT: it occurs to me after writing this that you may be able to trigger the click on the <a> instead of using $location since the event propagation (bubbling) is still prevented
<ANY clone-click=".is-clone-click:not(:disabled):not(.is-disabled)">
<a class="is-clone-click" ui-sref="root.example">example</a>
</ANY>
I got it working like this.
Some pointer disabled elements were able to be clicked thru making their container the e.target so I added .is-no-clone-click on those containers to ignore them.
app.directive('cloneClick', function() {
var angular = require('angular');
var ignore = '[href], [ui-sref], [ng-click], .is-no-clone-click, label, input, textarea, button, select, option, optgroup';
return {
restrict: 'A',
scope: {
selector: '#cloneClick'
},
link: function (scope, element) {
element.click(function(e) {
if (e.isTrigger) {
return;
}
var cloned = element.find(scope.selector).first();
var target = angular.element(e.target);
if (cloned.length && !cloned.is(target) && !target.is(ignore)) {
cloned.click();
}
});
}
};
});
Cursor can also be added via mouseover and a CSS class for that like
element.mouseover(function() {
element.toggleClass('is-pointer', !!element.has(scope.selector).length);
});
But I didn't end up using this directive because I was able to create a CSS link masking solution to actually solve what I was trying to do instead.
I know this can be done with jQuery as shown here: How to have click event ONLY fire on parent DIV, not children?
$('.foobar').on('click', function(e) {
if (e.target !== this)
return;
alert( 'clicked the foobar' );
});
But in angular, this keyword is not binded to the element when I use something like:
<div ng-dblclick="ctrl.doubleClickHandler($event)">
parent, event should fire here.
<div>child, event should not fire here.</div>
</div>
and in my controller:
this.doubleClickHandler = doubleClickHandler;
function doubleClickHandler(event) {
console.log(this); // this binds to controller
console.log(event);
}
The event fires, but I need to prevent it from firing when I click on the element's children.
I don't want to hardcode a check on the event.target based on class or attribute because it might change later. Is there anyway to achieve this within the HTML tag, or in JavaScript without hardcoding the element's class or attributes (similar to the technique of binding the this keyword in jQuery)?
You can compare target and currentTarget. The former is the clicked element and the latter is the element with the handler.
function doubleClickHandler(event) {
if (event.target !== event.currentTarget) return;
// do something
}
I think you could try something like this:
<div ng-dblclick="ctrl.doubleClickHandler($event) $event.stopPropagation()">
reference: What's the best way to cancel event propagation between nested ng-click calls?
If your using jquery try this,
<div class="foobar"> .foobar (alert)
<span>child (no alert)</span>
</div>
$('.foobar').on('click', function(e) {
if (e.target !== this)
return;
$scope.myFunc(e);
});
$scope.myFunc = function(e) {
$scope.clicked = "Parent Cicked";
$scope.$apply();
};
DEMO
surely its not leading to a good programmatic skills. Hence you can try below example with angular and JS
<div class="foobar" data-parent="true" ng-click="myFunc($event)"> .foobar (alert)
<span>child (no alert)</span>
</div>
// add data attribute as data-parent="true"
$scope.myFunc = function(e) {
if (! e.target.hasAttribute('data-parent'))
return;
$scope.clicked = "Parent Cicked";
};
//if clicked element has `data-parent` property do the things.
DEMO
i update the answer by create directive with jquery in your sample.
var app = angular.module("app", []);
app.controller("controller", function ($scope) {
var ctrl = this;
ctrl.doubleClickHandler = function () {
alert("parent clicked")
}
});
app.directive("directiveName", function () {
return {
restrict: "A",
scope: {
click: "&"
},
link: function (scope, element, attr) {
element.on('click', function (e) {
if (e.target !== this) {
return;
} else {
scope.click()
}
});
}
}
})
.foobar span {
background: red;
}
<!DOCTYPE html>
<html ng-app="app" ng-controller="controller as ctrl">
<head>
<title></title>
</head>
<body>
<div click="ctrl.doubleClickHandler()" directive-name class="foobar">
parent
<span>
child
</span>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
</body>
</html>
i have a directive that has several inputs inside of it, and i have an specific input that i need to execute a function when enter is pressed.
This is the input that i need to execute a function.
<input type="text" class="form-control" ng-model="cep" id="inputcep">
And i have a jquery function that uses a mask plugin and a keypress listener that prevents the default action of the enter key and executes the function. My problem is that the keypress is not being called, but the mask is being applied :/
$(document).ready(function () {
$('#inputcep')
.keypress(function(ev){
console.log(ev);
if(ev.keyCode == 13){
ev.preventDefault();
scope.searchCep(scope.cep);
}
})
.mask('00000-000');
});
Use angular instead of jQuery :
https://docs.angularjs.org/api/ng/directive/ngKeypress
Do create you custom directive as below.
Directive
app.directive('customDir', function() {
return function(scope, element, attrs) {
element.bind("keydown keypress", function(event) {
element.keypress(function(ev) {
console.log(ev);
if (ev.keyCode == 13) {
ev.preventDefault();
scope.searchCep(scope.cep);
}
}).mask('00000-000');
});
};
});
This is what I currently have:
http://plnkr.co/edit/L9XqEyOtRSGHc9rqFPJX
I am trying to make it so that when I press the down key for the first time, the focus will move onto #div2; repeating the action should have the focus moving onto li > div:first-child.
In the demo however, upon the first time the down key is pressed, the focus will jump to li > div:first-child directly, because both the key-map on #div1 and #div2 captured the same keydown event, so the focus jumps from #div1 to #div2 then #div2 to li > div:first-child in one go.
How should I solve this issue or, better yet, is there anyway to improve how the codes are structured? I am not sure whether those event listeners are attaching to the DOM in the optimal fashion right now.
You are attaching a keydown handler to the document for each element. I don't think stopPropagation() will do any good because the two handlers are on the same element and it won't propagate up from document but both will still fire.
I suggest re-evaluating how you're approaching it. You only wish the element with the focus class to have it's options evaluated, so why not wrap all those elements in an element with your directive and have it listen only once and choose the element to act on.
(plunker)
<div key-mapped="">
<!-- children will apply key map of element with focus class -->
<div id="div1" class="focus" key-map="{
40: '#div2'
}">Hello</div>
directive:
}).directive('keyMapped', function($rootScope) {
return {
restrict: 'A',
link: function(scope, element, attr) {
angular.element(document).bind('keydown', function(event) {
var target = $(element).find('.focus');
console.log('target: ' + target);
var options = scope.$eval(target.attr('key-map'));
----EDIT----
Someone let me know if it's not a good practice, but you could always put the event handler on your directive object and ensure it is only set once and send a custom event to the element with the 'focus' class that you bind to in your link function.
(plunker)
}).directive('keyMap', function($rootScope) {
var dir = {
onKeydown: function(event) {
var element = document.querySelector('.focus');
var newEvent = new CustomEvent("keyMapKeydown", {
detail: { keyCode: event.keyCode },
bubbles: false,
cancelable: true
});
element.dispatchEvent(newEvent);
},
registered: false, // ensure we only listen to keydown once
restrict: 'A',
link: function(scope, element, attr) {
// register listener only if not already registered
if (!dir.registered) {
dir.registered = true;
angular.element(document).bind('keydown', dir.onKeydown);
}
var options = scope.$eval(attr.keyMap);
// listen for custom event which will be dispatched only to the
// element that has the 'focus' class
element.bind('keyMapKeydown', function(event) {
var keyCode = event.detail.keyCode;
if (options && (keyCode in options)) {
element.removeClass('focus');
angular.element(document.querySelector(options[keyCode])).addClass('focus');
event.stopPropagation();
}
});
}
};
You need to stop the propagation of events. Events propagate upwards. In your directive put something like the following:
$(elm).click(function (event) {
event.stopPropagation();
});
Or, you could create a stop-propagation directive itself. This SO question has a great way of doing it.