How to clone ui-sref - javascript

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.

Related

How to unbind an keyboard short cut events in angular js

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.

Change display text when focus is lost

I have the option to delete an entity by clicking DELETE ENTITY. When this is clicked the words DELETE ENTITY are changed to CLICK AGAIN TO CONFIRM. I want the words to change back to DELETE ENTITY if the focus is lost on the button. I tried using ng-blur="resetConfirmDelete", but that didn't work. Any ideas?
HTML
<!--Delete entity-->
<p class="action-link no-outline" ng-click="setConfirmDelete()" ng-show="!confirmDelete" ng-if="permissions.entities.collaborator">DELETE ENTITY</p>
<p id="delete-confirm" class="action-link no-outline" ng-click="delete()" ng-show="confirmDelete" ng-if="permissions.entities.collaborator">CLICK AGAIN TO CONFIRM</p>
</div>
CONTROLLER
$scope.setConfirmDelete = function () {
$scope.confirmDelete = true;
};
$scope.resetConfirmDelete = function () {
$scope.confirmDelete = false;
}
Try using ngBlur for this: https://docs.angularjs.org/api/ng/directive/ngBlur
<p id="delete-confirm" class="action-link no-outline" ng-blur=resetConfirmDelete()">..</p>
UPD
Then you may make use of some directive that detects click anywhere outside of the specific DOM element, for example like this one:
.directive('clickElsewhere', function($parse, $rootScope) {
return {
restrict: 'A',
compile: function($element, attr) {
var fn;
fn = $parse(attr['clickElsewhere']);
return function(scope, element) {
var offEvent;
offEvent = $rootScope.$on('click', function(event, target) {
if (element.find($(target)).length || element.is($(target))) {
return;
}
return scope.$apply(function() {
return fn(scope);
});
});
return scope.$on('$destroy', offEvent);
};
}
};
});
then in the template you can do:
<p id="delete-confirm" class="action-link no-outline"
click-else-where="resetConfirmDelete()" >
You might be running into a problem using ng-blur because the statements appear to be in a div, which is not a :focus-able element by default. Try using a button instead.
Weird that ng-blur="resetConfirmDelete" didnt work, it is the right solution as much as i know.
Maybe the second <p> is not being focused when it appears there for the focuse is never lost and ng-blur is never activated.
Also try adding the ng-blur to the first <p> instead of the second one, assuming it will be focused when the second one appears.
Editted for better answer..
You need to swap the hide/show between elements and focus one at same time.
<!--Delete entity-->
<div
<p class="action-link
no-outline"
ng-click="setConfirmDelete()"
ng-show="!confirmDelete"
ng-if="permissions.facilities.collaborator">DELETE FACILITY</p>
<p id="delete-confirm"
class="action-link no-outline"
ng-click="delete()"
ng-show="confirmDelete"
ng-focus="confirmDelete"
ng-blur="blurDelete()"
ng-if="permissions.facilities.collaborator">CLICK AGAIN TO CONFIRM</p>
</div>
u'll need this lines:
ng-focus="confirmDelete"
ng-blur="blurDelete()"

How To Make Link on Parent Node Active in Tree View?

I want to make link on parent node active in tree view. So far I do this:
<li>A - Referensi Spasial <!--this is parent node-->
<ul>
<li>Jaring Kerangka Referensi Geodesi</li>
<li>Model Geoid
<ul>
<li>AB01010010</li>
<li>AB01010020</li>
</ul>
</li>
<li>Stasiun Pasang Surut</li>
</ul>
</li>
When I click the parent node, it just expand the children nodes. What I want is when I click it, it open the link I set on <a></a>
Here is my screenshot of my tree view:
And this is the javascript code:
$.fn.extend({
treed: function (o) {
var openedClass = 'glyphicon-minus-sign';
var closedClass = 'glyphicon-plus-sign';
if (typeof o != 'undefined'){
if (typeof o.openedClass != 'undefined'){
openedClass = o.openedClass;
}
if (typeof o.closedClass != 'undefined'){
closedClass = o.closedClass;
}
};
//initialize each of the top levels
var tree = $(this);
tree.addClass("tree");
tree.find('li').has("ul").each(function () {
var branch = $(this); //li with children ul
branch.prepend("<i class='indicator glyphicon " + closedClass + "'></i>");
branch.addClass('branch');
branch.on('click', function (e) {
if (this == e.target) {
var icon = $(this).children('i:first');
icon.toggleClass(openedClass + " " + closedClass);
$(this).children().children().toggle();
}
})
branch.children().children().toggle();
});
//fire event from the dynamically added icon
tree.find('.branch .indicator').each(function(){
$(this).on('click', function () {
$(this).closest('li').click();
});
});
//fire event to open branch if the li contains an anchor instead of text
tree.find('.branch>a').each(function () {
$(this).on('click', function (e) {
$(this).closest('li').click();
e.preventDefault();
});
});
//fire event to open branch if the li contains a button instead of text
tree.find('.branch>button').each(function () {
$(this).on('click', function (e) {
$(this).closest('li').click();
e.preventDefault();
});
});
}
});
//Initialization of treeviews
$('#tree1').treed();
So, how can I do that thing? Can anyone help me? Thanks
If my understanding is correct, you are asking why your links seem to have no effect at all, and clicking on them just expands the tree as if it were normal text?
It seems to me that this is simply due to the code that attaches events on those links, i.e. the block below comment "fire event to open branch if the li contains an anchor instead of text".
The $(this).closest('li').click(); instruction generates a new click event on the parent "li" item.
The e.preventDefault(); instruction prevents the link from receiving the "click" event, therefore it does not redirect the page / scroll to anchor.
So the result is as if the "click" had "jumped" your link and be passed to the parent "li", therefore not redirecting but expanding the tree.
You could simply remove that block to restore the links normal behaviour. However, the "click" event would still bubble to the parent "li" element, and expand the tree. Not an issue if the pages is redirected, but it is noticeable if the link goes to a local anchor (same page).
To prevent this (but still let the link do its normal job), keep the block but replace the 2 inner instructions by e.stopPropagation();. On the contrary of preventDefault(), it lets the current event happening, but it stops the event bubbling (parent elements do not receive it).
Now I am not sure about the reason for that block. It seems that it was more intended for anchors (which use the same "a" tag but with "name" attribute instead of "href"). But there would be no reason to prevent the "click" event on an anchor?

X-Editable: stop propagation on "click to edit"

I have an editable element inside a div which itself is clickable. Whenever I click the x-editable anchor element, the click bubbles up the DOM and triggers a click on the parent div. How can I prevent that? I know it's possible to stop this with jQuery's stopPropagation() but where would I call this method?
Here's the JSFiddle with the problem: http://jsfiddle.net/4RZvV/ . To replicate click on the editable values and you'll see that the containing div will catch a click event. This also happens when I click anywhere on the x-editable popup and I'd like to prevent that as well.
EDIT after lightswitch05 answer
I have multiple dynamic DIVs which should be selectable so I couldn't use a global variable. I added an attribute to the .editable-click anchors which get's changed instead.
editable-active is used to know if the popup is open or not
editable-activateable is used instead to know if that .editable-click anchor should be treated like it is
$(document).on('shown', "a.editable-click[editable-activateable]", function(e, reason) {
return $(this).attr("editable-active", true);
});
$(document).on('hidden', "a.editable-click[editable-activateable]", function(e, reason) {
return $(this).removeAttr("editable-active");
});
The check is pretty much like you've described it
$(document).on("click", ".version", function() {
$this = $(this)
// Check that the xeditable popup is not open
if($this.find("a[editable-active]").length === 0) { // means that editable popup is not open so we can do the stuff
// ... do stuff ...
}
})
For the click on the links, simply catch the click event and stop it:
$("a.editable-click").click(function(e){
e.stopPropagation();
});
The clicks within X-editable are a bit trickier. One way is to save a flag on weather the X-editable window is open or not, and only take action if X-editable is closed
var editableActive = false;
$("a.editable-click").on('shown', function(e, reason) {
editableActive = true;
});
$("a.editable-click").on('hidden', function(e, reason) {
editableActive = false;
});
$("div.version").click(function(e) {
var $this;
$this = $(this);
if(editableActive === false){
if ($this.hasClass("selected")) {
$(this).removeClass("selected");
} else {
$(this).addClass("selected");
}
}
});
Fixed Fiddle
It's not pretty, but we solved this problem with something like:
$('.some-class').click(function(event) {
if(event.target.tagName === "A" || event.target.tagName === "INPUT" || event.target.tagName === "BUTTON"){
return;
}
We're still looking for a solution that doesn't require a specific list of tagNames that are okay to click on.

AngularJS Directive - How to let an event only be triggered once if otherwise it'll be triggered on multiple elements?

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.

Categories

Resources