I need to be able to chain click events, and temporarily pause event propagation between them.
So, for a given element, it runs three different click events, but the second one needs user input, so it pauses propagation while the user fills in the form and then continues.
clickAction2 = ->
#Pause event propagation
somehow.pauseEventPropegationRightHere()
#Go and handle the dialogs, user input, JS requests
goDoSomething().thenDoCallback( ->
#User is now authenticated completely.
somehow.continueEventPropegationAsIfNothingHappened()
)
In an effort to allow single responsibility, and events chained higher/lower shouldn't have knowledge that the event propagation was paused and nothing should be called twice.
No, the three click events can't be called sequentially from another function or any similar primitive solution.
This is in relation to AngularJS directives, but the solution does not need to rely on it.
There is a similar question but none of the answers are satisfactory: How to continue event propagation after cancelling?.
Edit:
What I need is a cleaner way to call e.stopImmediatePropagation(), and then continue from that point. As of right now, my best option is by manually entering the jQuery "private' data[1] and calling the functions manually.
$._data( $(element)[0], 'events' ).click[].handler()
I had a similar issue myself and I hope my solution is satisfactory because I realize it may not directly apply to your situation. However hopefully the principle I show here helps you find a solution of your own.
The problem (if I understand correctly) is that you need a way to prevent a child element from triggering an event if a parent element has triggered the same event at essentially the same time. Whereas e.stopImmediatePropagation() prevents event bubbling, effectively halting any parent elements from triggering the event. I believe the solution is using a setTimeout() with a zero millisecond delay to perform the desired function and if the triggering event ever occurs again while the setTimeout() is in use, use clearTimeout() to stop the previous event from occuring. The best way I can demonstrate this approach is by creating a parent and child element and watching the mouseleave event.
Here is the JS/jQuery:
var timerActive = false;
$('#div1, #div2').mouseleave(function(e) {
var trigger = $(this).attr('id'); // for monitoring which element triggered the event
if (timerActive) {
clearTimeout(announce); // stops the previous event from performing the function
}
window.announce = setTimeout(function() {
alert('mouse exited'+trigger); // trigger could be use in an if/else to perform unique tasks
timerActive = false;
},0);
timerActive = true;
});
Here is a JSFiddle demo: http://jsfiddle.net/XsLWE/
Since the events are triggered sequentially from child to parent, clearing each previous timeout effectively waits for the last or top-most element to trigger the event. At which point you could use an IF/ELSE statement to perform desired tasks. Or perform one function then a callback on complete.
In the demo, the effect is that on the left and bottom edges of the div elements, each div element is allowed to trigger the event individually. However on the top and right edges of div2, the only element allowed to trigger the event is div1.
Again, from your original post, I take it you are after something slightly different. But perhaps this approach will help you in some way.
Try something like
elem.on('event',function(e) {
e.stopPropagation();
goDoSomething(function(){
elem.parent().trigger(e);
});
});
goDoSomething() could do an AJAX call or something else async, and then for whatever reason call the callback to have the event propagation continue
Related
My goal is to be able to have this code run the alert() even thou the event is triggered after the listener is set up. Standard Backbone.Events.on doesn't support this.
app.trigger('alarm');
app.onRetro('alarm', function(){
alert();
});
Retroactive event listeners should fire immediately so long as the event has been already triggered at least once. They should also continue to fire for each subsequent trigger.
Something can be written to handle this by checking app._events.alarm.length, but I'm wondering if someone has already solved this problem.
Here's an example that will help you achieve what you want.
In this example if you call :
Backbone.Events.trigger('alarm', { retro: true });
an alarm event will be waiting the next call to
Backbone.Events.on('alarm', callback)
to execute the callback.
I don't know what's your need. So I set a retro event to be removed once it's used. You can modify it to your needs.
I'm in a situation where I make a lot of ajax calls to change the same portion of html. This html represent a grid. After changing the html in the ajax call, I attach a event handler to an event of the grid. When the user click on a refresh button, I execute the same ajax call that set new html code and also add again an event handler to listen of event of the grid.
I want to know if each time I refresh my grid and add a new event handler if the previous event handler is still in memory and if yes, what are the bests practices in this situation? (e.g. unbind the event handler if exist before putting new html)
Here is an example of what I do:
$.get(this.config.actionLoggingUserListUrl, viewModel, function (data) {
MyNamespace.ui.hideGridAnimation($("#LoggingActionUsersList"));
if (data.success) {
$("#validationSummary").html("");
$("#usersList").html(data.result);
$("#LoggingActionUsersList").click(function() {
console.log("Here is my event handler attached multiple times!");
});
}
else {
$("#validationSummary").html(data.result);
$("#usersList").html("");
}
});
Note that the event handler I'm talking in this case is:
$("#LoggingActionUsersList").click(function() {
console.log("Here is my event handler attached multiple times!");
});
Event handlers stack, so yeah, this is a memork leak. Probably a fairly insignificant one, but its more the principle than the effect. Unless for some reason you really do need to have dynamic event handlers (something that is pretty rarely used as there aren't very many realistic uses for it), I'd strongly suggest pulling the event handler assignment out of the ajax call.
If you do need the event handler to change, the clever way to do it would be to make your event handler smart enough to know a little bit about the object to which it is assigned. That way, instead of adding a new event each time, you can just have logic in the event handler do different things based on the current identity of the object.
why are you binding it every time you make the call?
You are adding onto the stack every time. You are not replacing it. Best solution is to use on and do it once.
Other solution is to unbind the click event, before you add click event. The problem with this solution is if anything else added the click event, you just removed it.
As you know, events usually bubble up in JavaScript, so the event handler of the element that fires the event is executed first, then the event handler of the parent element is called and so on. This behaviour causes some problems on a project I'm currently working on, I would rather have the execution order reversed.
I figured out a solution that is using timeouts:
$(element).mouseover(function(){
var that = this;
setTimeout(function() {
//actual event handler, but references to "this" are replaced with "that"
},$(this).parents().length)
});
So basically, the event handlers are executed after a short timeout, the waiting time depends on the the depth of the element in the DOM-tree:
The event handler of the the html-element is executed right away, the event handler of the body element is executed after waiting 1ms and so on. So the execution order of the events is reversed.
The results of my first tests are positive, but I'm still not sure if there are any problems or drawbacks with this solution. What do you think of this solution? Other ideas on how to solve this problems are also highly appreciated.
Reverse event bubbling is called capture phase.
See the DOM event architecture
Pass true as the 3rd argument to Event.addEventListener to have it trigger on capture phase
el.addEventListener("click", function () {
console.log("i run in capture");
}, true);
Of course it won't work in legacy platforms. You'll need a DOM3 events shim to emulate the event system. Feel free to contribute to the DOM-shim
You could try to emulate it, but probably it could make lots of troubles.
Here is very very simple example (and on jsfiddle).
The idea is to aggregate callbacks from each event, and calling them in reverse order in document event handler (we also clear our list, to ensure new queue on next click).
<div id="a">
<div id="b">
Click Me!
</div>
</div>
var cb = [];
$(document).click(function(evt){
cb.unshift(function(){
console.log('document');
});
var i;
for(i=0; i<cb.length; ++i){
cb[i].call(null, evt);
}
cb = [];
});
$("#a").click(function(){
cb.unshift(function(){
console.log('div#a');
});
});
$("#b").click(function(){
cb.unshift(function(){
console.log('div#b');
});
});
$('a').click(function(evt){
cb.unshift(function(evt){
evt.preventDefault();
console.log('a');
});
});
Maybe you need to change your design? Could you post more information, why you need event capturing?
I can see one huge drawback of your solution is fact, that for every handled event you have to traverse DOM upwards starting with this to establish number of parents.
Traversing in such manner in every event handler = low performance.
I have HTML similar to the following in my page
<div id="someDiv">
<img src="foo.gif" class="someImg" />
</div>
The wrapper div is set up such that when it is clicked, it's background-color changes using the following jQuery code.
$("div").click(function(event){
$(this).css("background-color", "blue");
});
I also have some jQuery associated with my img that will do some other function (for the sake of argument I am going to display and alert box) like so:
$("img[class=someImg]").click(function(event){
alert("Image clicked");
});
The issue I have come across is that when I click on the img, the event associated with the div is also triggered. I'm pretty sure that this is due to the way that jQuery (or indeed JavaScript) is handling the two DOM elements - clicking the img would require you to also technically click the div, thus triggering both events.
Two questions then really:
Is my understanding of the
DOM/JavaScript flawed in some way or
is this actually how things are
occurring?
Are there any jQuery methods that
would allow me to perform actions on
a child element without invoking
those associated with its parent?
That is known as event bubbling, you can prevent it with stopPropagation():
$("img[class=someImg]").click(function(event){
alert("Image clicked");
event.stopPropagation();
});
.
Is my understanding of the DOM/JavaScript flawed in some way or
is this actually how things are
occurring?
That is because of what is known event bubbling.
Are there any jQuery methods that would allow me to perform actions
on a child element without invoking
those associated with its parent?
Yes, you need stopPropagation()
No, this is by design. Events bubble up through the entire dom, if you put another handler on body, it would fire too
Yes :) JQuery normalizes the event object, so adding event.stopPropagation() in your img click handler will give you the behavior you expect on all browsers
The problem you just facing is called "event bubbling". That means, if you click on a nested
element, that click event will "bubble up" the DOM tree.
If other elements also are bound to an click event, their listeners will fire aswell.
Solution to prevent this is called:
stopPropagation()
which is used within your event handler
$("img[class=someImg]").click(function(event){
event.stopPropagation();
alert("Image clicked");
});
This is what's called event bubbling, and you can stop it to get the behavior you want with .stopPropagation() (or return false; if you want to stop the event completely, including handlers on the same level), like this:
$("img[class=someImg]").click(function(event){
alert("Image clicked");
event.stopPropagation();
});
You can view a demo here, comment it out and click run again to see the difference.
The short version is that when most event types happen, they happen on the immediate element, then bubble up the DOM, occurring on each parent as they go. This is not jQuery specific at all, native JavaScript does this. If you're more curious, I'd read the linked article, it has a great explanation of what's going on.
What's the best way to execute a function exactly once every time a button is clicked, regardless of click speed and browser?
Simply binding a "click" handler works perfectly in all browsers except IE.
In IE, when the user clicks too fast, only "dblclick" fires, so the "click" handler is never executed. Other browsers trigger both events so it's not a problem for them.
The obvious solution/hack (to me at least) is to attach a dblclick handler in IE that triggers my click handler twice. Another idea is to track clicks myself with mousedown/mouseup, which seems pretty primitive and probably belongs in a framework rather than my application.
So, what's the best/usual/right way of handling this? (pure Javascript or jQuery preferred)
Depending on your situation you can use different approaches, but I would suggest using namespaced event handlers with jQuery like this:
function eventHandler(event) {
// your handler code here
doSomeMagic();
}
var element = $('#element');
element.one('click.someNameSpace', function(event){
// first we unbind all other event handlers with this namespace
element.unbind('.someNameSpace');
// then we execute our eventHandler
eventHandler();
}).one('dblclick.someNameSpace', function(event){
// If this fires first, we also unbind all event handlers
element.unbind('.someNameSpace');
// and then execute our eventHandler
eventHandler();
});
I'm not sure this will work the way you want it, but it's a start, I guess.
Mousedown and mouseup works just like the click functions, unfortunately so much that when IE omits a click because of a doubleclick it will also omit the mousedown and mouseup. In any case, you can add both click and dblclick to the same object and feed the clicks through a function that sort out any click happening too close to the last.
<div onclick="clk()" ondblclick="clk()"></div>
lastclicktime=0
function clk(){
var time=new Date().getTime()
if(time>lastclicktime+50){
lastclicktime=time
//Handle click
}
}
I by the way just found out that, at least in Firefox the dblclick event is not given an event time, therefore I had to resolve to the Date method.