What's the right mindset in using jQuery's event.stopPropagation() - javascript

I have two approaches in mind on how to apply jQuery's event.stopPropagation():
Optimistic approach - By default, all event handlers are without stopPropagation(). stopPropagation() would be added to a handler only if there's some unnecessary and undesired behaviour caused by event bubbling.
Pessimistic approach - By default, stopPropagation() is added to all event handlers. If there's some desired behaviour missing due to disconnected even bubbling, then the corresponding stopPropagation() would get removed.
I was wondering which one of the two, or neither, is the mindset I should have when utilizing stopPropagation() in my development. I am leaning more towards 2) because it looks like it would have better performance because unnecessary bubbling gets prevented. I would like to hear from experts on the topic.

Calling stopPropagation everywhere isn't good. It breaks event delegation*, is probably less efficient unless you literally add handlers to every single element on the page (and if you're doing that, you should be using event delegation), and you might not remember to do it.
So rule of thumb: Only call stopPropagation when you need to do so.
* That is, .live, .delegate, and the delegate version of .on.
Here's an example of event delegation, which relies on bubbling to listen for events that originate from elements added after the listener was created:
document.documentElement.addEventListener('click', function(e) {
if(e.target.nodeName === 'a') {
console.log('A link was clicked!');
}
});
Here's somebody throwing in jQuery that breaks that:
$('div').click(function(e) {
e.stopPropagation();
});
Any <a> that is a descendant of a <div> will no longer get its clicks handled! Please don't do this.

Call stopPropagation only if you realy need it. And remember, if you use jQuery live function and you stop it, live handler never calls, because live event stored in document element.

Related

jquery - catch Enter two ways

What is the technical difference between this:
$('#myid').keypress(function(e)
{
if(e.which == 13) {
alert('test');
}
});
and this:
$(document).on('keypress', '#myid', function(e) {
{
if(e.which == 13) {
alert('test');
}
});
I have a jQuery script which runs with the second example but not with the first
They're using two completely different events. The first one uses the keypress event:
$('#myid').keypress(function(e)
while the second one uses the click event:
$(document).on('click', '#myid', function(e) {
In addition to that, the first one is binding directly to the #myid element, while the second one is binding to the document and filtering events based on the #myid selector. The resulting observed effect of these two kinds of binding is probably the same, but they accomplish that effect in very different ways.
i have a Jquery script which runs with the second example but not with the first
That's very unlikely, since the .which property on a click event doesn't equal 13. Though it depends on what you mean by "runs". You may be observing something entirely different than what you're describing. For example, a key difference between these two types of binding is that the second one is used to capture events from dynamically-added DOM elements. So if you try to use the first example on DOM elements which are loaded via AJAX then it won't find those elements when attaching the event handler.
There are two things going on here, so I will split my answer accordingly.
1. With regards to event binding in your code:
$('#myid').keypress(function(e){//...});
Is binding the keypress event to the HTML element whose id is myid. Therefore any keypress event that occur within said element, such as focus, tab, etc; they will all be handled by the keypress function.
$(document).on('click', '#myid', function(e) { //... });
Binds the click event handler to the entire document (website), as well as the HTML element whose id is myid. This is the ability of the on function which I will further describe below.
The on event handler can be very useful if you plan on binding one or more events to one or more HTML elements. I like to use it particularly when doing Chaining, as it can be both syntactically and efficiently better than writing one event handler at a time.
2. With regards to event handlers:
The difference between on and keypress is that the keypress is a shortcut for the event handler:
on('keypress', function(e){//...});
Meaning it is the same thing when it comes down to what it does.
Whereas on is used to attach event handlers in general. Such as the click event handler:
on('click', function(e){//...});
As well as the keypress event handler:
on('keypress', function(e){//...});.
... and many others.
The documentation in jQuery establishes very clearly how these functions work, so if you have any more questions after reading through the docs linked here, feel free to ask :-)

Prevent click event on the clicked element?

I have a click event on the body of my document:
$("body").on('click.findElem', function(e) {
e.stopPropagation();
e.preventDefault();
self.hinter(e.target);
return false;
});
It basically catches the clicked target and does something to it. However, there are some targets that already have a click event on them, and they prevent my click from being executed at all. How do I overcome that issue? I tried unbinding it, but the click doesn't even work at all to actually execute the unbinding.
e.stopImmediatePropagation() does the job, but only if your handler executes before whatever other handler exists.
Unfortunately there is no way to insert your own handler in the first position - but you can use this nasty hack if the other handlers were bound using jQuery, too: How do you force your javascript event to run first, regardless of the order in which the events were added?
If you really need this you might want to bind an event handler in capture mode using the native DOM API: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget.addEventListener
Capture handlers are triggered before bubble handlers (which are used by jQuery and most other scripts) so that way you have very high chance to execute your handler first.
try this and see demo
$( "body" ).on( "click", ".clickme:not(.clicked)", function( event ) {
$(this).addClass('clicked');
alert('here');
});
i tend to not use on and stick with the bind/unbind combo.
i have some pages that reload partial content and then has to rebind the events.
i tipically do something like this
$(".theobjectsiwant").unbind("click").bind("click", function() {
alert('hi there');
});
If you want/have to stick with the on() function, you shouldn't mix on() with unbind() and try a similar approach with .off("click").on("click")
Check here for a sample http://api.jquery.com/off/

How do you change the event firing order of the same event?

Jquery bind is amazing, but I don't know in what order the binding happens. My current problem is thus:
$(document.body).delegate('form', methods.checkForm);
$('form').bind('submit', methods.submitFormIfCheckedFormIsTrue);
methods.checkForm = function (e) {
if (!$(this).isVerified()) {
return false;
}
return true;
};
methods.submitFormIfCheckedFormIsTrue = function (e) {
e.preventDefault();
$.ajax("/submitForm", {
data:$(this).serialize(),
type:"post"
});
};
This is obviously not the actual code that I'm using, but it's pretty close. What happens is, the submitFormIfCheckedFormIsTrue function fires before, or during, the checkForm function, so the checking is pretty useless. My hack for it was to add the class "checked" to the form and the check if the form has that class in the other function...but then you click submit, it checks, then you have to click it again to submit it if everything went right...which is retarded.
Another thing that's important regarding this problem is that I'm they're in completely different parts of my application, for reasons that can't change. Also, they're being loaded asynchronously.
The main thing I want to know then...is how to change the order, or set the priority of the events somehow...
If you are using 'delegate' the way you have it in your example, then the ajax submission is always going to run first, so the short answer to your question is "You Can't". Your delegate is attached to the 'body' element, so events attached to elements closer to the form in the DOM tree will fire first.
Events bubble from the form -> body, so there is no ordering when you are doing that.
One option would be to have your verification trigger a second event.
methods.checkForm = function (e) {
e.preventDefault()
if ($(this).isVerified()) {
$(this).trigger('form-verified');
}
};
Then instead of binding the other handler to 'submit', you would bind it to 'form-verified'.
$('form').bind('form-verified', methods.submitFormIfCheckedFormIsTrue);
This is also another way to accomplish ordering event if they are attached to the same element instead of using delegate.
Also, if you are using jQuery >= 1.7, then you should be using on instead of bind and delegate. http://api.jquery.com/on/
Update
If both are bound to the same element, then they will be triggered in the order that they were attached to the element. Assuming checkForm is bound before the other one, then the issue is that return false; does not stop other events from firing if they are attached to the same element. For that you also need e.stopImmediatePropagation().
methods.checkForm = function (e) {
e.preventDefault()
if (!$(this).isVerified()) {
e.stopImmediatePropagation();
}
};
There is also a useful answer over here if you ever have to tweak the ordering of events. jQuery event handlers always execute in order they were bound - any way around this?
In a general sense event handlers will be called in the order that they were bound, but only if they're bound at the same level. In your case you're binding one directly to the form while the other is a delegated handler bound at the document.body level. The directly bound one will happen first and then the event bubbles up to be handled by the other.
If you bind both handlers at the same level with .delegate() then they should be called in order:
$(document.body).delegate('form', 'submit', methods.checkForm);
$(document.body).delegate('form', 'submit',
methods.submitFormIfCheckedFormIsTrue);
Then in the first (generic) handler you should call the event.stopImmediatePropagation() method to prevent other handlers being called (noting that simply returning false prevents the default and stops the event bubbling up further, but it doesn't stop other handlers at that level from running):
methods.checkForm = function (e) {
if (!$(this).isVerified()) {
e.stopImmediatePropagation();
return false;
}
return true;
};
(By the way, the code shown in the question left out the event (second param) from the .delegate() call - I've put it in in my code.)
Or bind both handlers directly rather than using .delegate(). And speaking of using .delegate(), if you're using the latest version of jQuery you may like to switch over to using .on(), the new do-everything event binding method.
"What happens is, the submitFormIfCheckedFormIsTrue function fires before, or during, the checkForm function"
Definitely before, not during. (In pretty much all browsers) JavaScript runs on a single thread, so you will not ever have two functions running simultaneously.

When jquery.delegate call the event handler?

Recently , Ive been using .delegate and .live a lot.They have a subtle difference when it comes to event capturing I guess.
When using live for link clicks like $('a').live("click",... , the links which had an image as their html content, ended up with the click handler getting the target as the image instead of the link.
Whereas with delegation ,it seems that it the link which is passed as the target.
What is the catch here?
Also, when exactly is a click handler called for .delegate, while the capturing phase or the bubbling phase?
The main difference between .live and .delegate is, that .delegate() uses a context. In other words, .delegate() is actually a wrapper for .live(), but instead watching the document root for bubbling events, it'll just watch a given root node.
For instance
$('a').live('click', function() {
});
will create an event handler attached to your document.body. This will catch absolutly all anchors-clicks that occur on your site.
$('#someDiv').delegate('a', 'click', function() {
});
will only "watch" all anchors which are childnodes from #someDiv.
It's unlikely that a delegated event by .live() have another target. Both .live() and .delegate() pass in the event object into the handler. It should make no difference at all, the event.target should always be the node of invocation.
Ref.: .live(), .delegate()

Performing an action on Parent/Child elements independently

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.

Categories

Resources