// Example A
$("#delegate").on("click", function(event) {
// executes on click on any descendant of #delegate or self (not a delegate at all)
// 'this' is #delegate
});
// Example B
$("#delegate").on("click", "#outer", function(event) {
// executes on click on any descendant of #outer or self
// 'this' is #outer or #inner (depends on the actual click)
});
$("#delegate").on("click", "#inner", function(event) {
// executes on click on any descendant of #inner or self (nothing happens when clicking #outer)
// 'this' is #inner
});
// Example C (now it's getting weird)
$("#delegate").on("click", "div", function(event) {
// executes twice, when clicking #inner, because the event passes #outer when bubbling up
// one time 'this' is #inner, and the other time 'this' is #outer
// stopPropagation() // actually prevents the second execution
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="delegate">
<div id="outer">
outer
<div id="inner">
inner
</div>
</div>
</div>
How do you explain this behaviour logically?
There is exactly one click event, which starts on #inner, bubbles through #outer and finally reaches #delegate.
The event is catched (exactly) once by the #delegate's on-handler. The handler checks if event's history contains any div elements.
If this applies, the callback function should be called once. That's what I would expect. "Single Event, single Handler, single Condition, single Callback".
It gets more crazy if you take a look at the stopPropagation() behaviour. You can actually avoid the second execution, though the event has already reached #delegate. stopPropagation(should not work here.
What kind of "magic" is done in the implementation of the on-delegation logic? Do event-bubbling and program flow split up in any way?
Please don't post "practical advice" in the first place ("Use xyz instead!"). I'd like to understand why the code works the ways it does.
As you have bound events on all the divs in two ways:
Using id attribute of the element div.
Using the tag name of the element div.
so if you event.stopPropagation(); on the last one still the alert will come two times because you have cliked the div and also the #inner (for instance.)
Check the snippet below.
$("#delegate").on("click", function(event) {
alert(this.id);
});
/*
$("#delegate").on('click', "#outer", function(event) {
alert(this.id);
});
$("#delegate").on('click', "#inner", function(event) {
alert(this.id);
});
*/
// now it's getting weird
$("#delegate").on("click", "div", function(event) {
event.stopPropagation();
alert(this.id);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="delegate">
<div id="outer">
outer
<div id="inner">
inner
</div>
</div>
</div>
The way event delegation works is that jQuery walks up the DOM tree from the innermost target to the element that the delegation was bound to, testing each element to see if it matches the selector. If it does, it executes the handler with this bound to that element.
When event.stopPropagation is called, it sets a flag in the event object. The loop that walks the DOM tree also calls event.isPropagationStopped(). If propagation is stopped, it breaks out of the loop.
In other words, jQuery is doing its own bubbling and propagation stopping when it implements delegation, it's not making use of the browser's bubbling (except that this bubbling is necessary for the initial event to be triggered on #delegate, so that the jQuery loop will run).
Everything works as expected. Have a look at this fiddle:
$("#delegate").on("click", "div", function(event) {
// event.stopPropagation();
alert('div: ' + $(this).attr('id'));
});
Clicking #inner will fire the above event. The event will bubble up to #outer since #inner is a descendent of #outer. Since #outer also is a <div, the event will be fired on #outer, also. Logically, clicking on #inner will first alert "div: inner" and then "div: outer".
Calling event.stopPropagation() tells the event not to bubble up, so #outer stays uninvoked.
Other than in this fiddle:
<div id="delegate">
<div id="first">
first
</div>
<div id="second">
second
<div id="third">
third, inside second
</div>
</div>
</div>
Clicking on third will first alert third, then second and then stop, because #first is not a parent but a sibling.
Related
So, i wondered, why this code doesn't work properly, and what can i do, to prevent such a behaviour:
If I would need to prevent event propagation of parent, whilst particular child got clicked, i used method 1, but it seems not to be working, but method 2 is working fine though.
//method 1
$(document).on({
click: function(e) {
console.log('clicked!');
e.preventDefault();
return false;
}
}, '.hax');
//method 2
/*$('.hax').on('click', function(e){
e.preventDefault();
return false;
});*/
//uncommenting will prevent event propagation
.hax {
background-color: whitesmoke;
cursor: pointer;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class='wrapper' onclick='alert("hello")'>
<div class='hax'>hax!</div>
</div>
Method 1 Is using event delegation ,so in it event is not directly bind with the element , its bound with the parent ,So in your case the parent is document . in this the case whatever event will be fired for that particular element it will be tracked down from the DOM tree and will execute the parent call before. In your case it will first call the alert from parent .
In method 2 - event is directly bound with the element , The event of parent will still got fired unless you prevent that in the handler but since the handler is bound to the target , you will not face any other action(alert in your case)
Get better Idea of
Event Delegation
You are creating an event delegation by method 1, which can be created the following way too:
$(document).on('click', '.hax', function (e) {
console.log('clicked!');
e.preventDefault();
return false;
});
For clarifying event delegation briefly:
Understanding how events propagate is an important factor in being able to leverage Event Delegation. Any time one of our anchor tags is clicked, a click event is fired for that anchor, and then bubbles up the DOM tree(Up to DOM top), triggering each of its parent click event handlers.
It does not mean you can't achieve your goal here with this method, but in order to make it work, you can create a middle parent for div.hax which is descendant of div.wrapper. I mean:
<div class='wrapper' onclick='alert("hello")'>
<div id="stopHere">
<div class='hax'>hax!</div>
</div>
</div>
Now, we can use method 1, but we only need to stop event propagation / event delegation before it reach div.wrapper. Thus in our newly added div#stopHere:
$("div#stopHere").on('click', '.hax', function (e) {
console.log('clicked!');
e.preventDefault();
return false;
});
I want to do something on all clicks except on a certain element.
I've created a very simple example which demonstrates the issue: http://jsfiddle.net/nhe6wk77/.
My code:
$('body').on('click', ':not(a)', function () {
// do stuff
});
I'd expect all click to on <a> to be ignored, but this is not the case.
Am I doing something wrong or is this a bug on jQuery's side?
There's a lot going on in that code that's not obvious. Most importantly, the click event is actually attached to the body element. Since that element isn't an anchor, you'll always get the alert. (Event delegation works because the click event bubbles up from the a through all its ancestors, including body, until it reaches document.)
What you want to do is check the event.target. That will tell you the element that was actually clicked on, but the actual click event is still bound to the body element:
$('body').on('click', function (e) { // e = event object
if ($(e.target).is(':not(a)')) {
alert('got a click');
}
});
http://jsfiddle.net/y3kx19z7/
No this is not a bug but rather intended behaviour.
The event bubbles all the way up. By clicking the a node, you are still triggering it's parents event from the div node.
Read more about event bubbling in the W3C DOM Specification. Just search for "bubble".
You need to stop the event propagation of the a nodes. i.e.:
$('body').on('click', ':not(a)', function () {
// do something effectively
alert('you should not see me when clicking a link');
});
$("a").click(function( event ) {
// do nothing effectively, but stop event bubbling
event.stopPropagation();
});
JSFiddle: http://jsfiddle.net/nhe6wk77/6/
It's working as intended, here's why!
Use of the :not() selector is honored in delegated events, but it's an uncommon practice because of how events bubble up the DOM tree potentially triggering the handler multiple times along the way.
The jQuery API Documentation states that:
jQuery bubbles the event from the event target up to the element where the handler is attached (i.e., innermost to outermost element) and runs the handler for any elements along that path matching the selector.
Notice the phrase "and runs the handler for any elements along that path matching the selector".
In your example, jQuery is accurately not running the handler on the a element, but as the event bubbles up the tree, it runs the handler for any element that matches :not(a), which is every other element in the path.
Here is a clear example showing how this works: http://jsfiddle.net/gfullam/5mug7p2m/
$('body').on('click', ':not(a)', function (e) {
alert($(this).text());
});
<div class="outer">
<div class="inner">
Click once, trigger twice
</div>
</div>
<div class="outer">
<div class="inner">
<button type="button">Click once, trigger thrice</button>
</div>
</div>
Clicking on the link in the first block of nested divs, will start the event bubbling, but the clicked a element — a.k.a. the event target — doesn't trigger the handler because it doesn't match the :not(a) selector.
But as the event bubbles up through the DOM, each of its parents — a.k.a the event currentTarget — triggers the handler because they do match the :not(a) selector, causing the handler to run twice. Multiple triggering is something to be aware of since it may not be a desired result.
Likewise, clicking on the button in the second block of nested divs, will start the event bubbling, but this time the event target does match the :not(a) selector, so it triggers the handler immediately. Then as the event bubbles up, each of its parents matching the selector triggers the handler, too, causing the handler to run three times.
As others have suggested, you need to either bind an alternate handler that stops propagation on a click events or check the event target against the :not(a) selector inside your handler instead of the delegated selector.
$("body").click(function(e) {
if($(e.target).is('a')){
e.preventDefault();
return;
}
alert("woohoo!");
});
check the target of the click. this way you dont need to bind another event.
updated fiddle
<!DOCTYPE HTML>
<html>
<body>
<style type="text/css">
#a {background-color:blue;width:100px;height:200px;}
#b {background-color:red;margin-left:25px;width:50px;height:100px;}
</style>
<div id="a">a
<div id="b">b</div>
</div>
<script>
document.getElementById("a").onclick = function() {console.log("A is clicked");}
document.getElementById("b").onclick = function(event) {console.log("B is clicked");event.stopPropagation();}
document.onclick = function() {console.log("Document is clicked");}
</script>
</body>
</html>
Questions:
1.function(event), event is formal parameter, what is the actual parameter? how did actual parameter pass to event? Because usually we use parameters in below style:
function method(int num){ //num is the formal
/*implementation*/
}
//in main
method(42); //42 is the actual
when I clicked div a, in console, it shows:
A is clicked
Document is clicked
I thought the result should be
A is clicked
B is clicked
this is my thought: when clicked div a, document.getElementById("a").onclick got excuted, then document.getElementById("b").onclick got excuted, since we have event.stopPropagation(); it will stop in div b, and will not excute document.onclick, but it seems not work this way, can anyone help me to understand what happened here?
function(event), event is formal parameter, what is the actual parameter?
The actual parameter is an event object, created by the browser. For a click event, it will be an instance of MouseEvent.
when I clicked div a... I thought the result should be...
You clicked on the parent of b. The event propagates up the DOM tree from the target, so it will never touch b. The propagation phase will start at a, and move up through each parent until it reaches the document.
Had you clicked on b, the stopPropagation method would have prevented the event from bubbling and you would only see "B is clicked" in the output.
Event propagation means bubling up of event in to the ancestor chain till the document object.
So in your case both event attached to element a and document are fired. When you click on element b then event attached to only element b is fired since the propagation is prevented from that point onward.
The event is the browser event. You cannot really change it, but you can use closures or use data attributes to pass more data into your event handler.
You got the right result on clicking A. The event bubbles upwards to until it reaches the document's root element. If you stop propagation, you prevent it from bubbling upwards to it's parent elements.
If you click b then it would bubble upwards to a then to the body. But you stop propagation on b so the only output should be
B is clicked
The event object itself is different in every browser, although there is a recommendation for it. Some browsers don't support the stopPropagation and preventDefault methods or other properties (for example older IEs don't fully support the DOM Level 2 specs).
Suppose I have:
<div id="outer" onclick="thingsHappen()">
<div id="inner"></div>
</div>
When I click on outer or inner div, thingsHappen() is executed. That is obvious.
Now I have got a need to define a different method for the inner div.
For example
$("#inner").click(function() {
doThings();
});
When I click on inner both thingsHappen() and doThings() executes.
How do I execute doThings() when I click on inner div without executing thingsHappen()?
I tried to unbind click method from #inner, but it did not work.
PS. I cannot change the structure of HTML.
Stop the propagation of the event:
$("#inner").click(function(e) {
doThings();
e.stopPropagation();
});
Example: http://jsfiddle.net/QNt76/
JavaScript events bubble up the DOM tree unless you stop them from propagating. This is what was causing the parent event handler to get notified.
You want Event.stopPropagation():
$("#inner").click(function(e) {
doThings();
e.stopPropagation();
});
Events pertaining to a child element bubble up to parent elements in the DOM unless propagation is stopped like so:
$("#inner").click(function(event) {
doThings();
event.stopPropagation();
});
Here is a good read on capturing/bubbling and Javascript events. http://www.quirksmode.org/js/events_order.html
$("#inner").click(function(e) {
e.stopPropagation();
doThings();
});
What you are trying to do is stop the event (click) from "bubbling" up. In this case, you would want to stop the propagation of the event in the bubbling phase. If you are using jquery, you can use this function:
HTML
<div id="outer" onclick="thingsHappenOuter()">
<div id="inner">
</div>
</div>
JS
$("#inner").click(function(event) {
event.stopPropagation();
// do something
});
SEE: http://api.jquery.com/event.stopPropagation/ for more information.
You have to stop the propagation to the Document Tree:
$("#inner").click(function(event) {
doThings();
event.stopPropagation();
});
See: http://api.jquery.com/event.stopPropagation/
Prevents the event from bubbling up the DOM tree, preventing any parent handlers from being notified of the event.
What's the difference between event.stopPropagation() and event.stopImmediatePropagation()?
stopPropagation will prevent any parent handlers from being executed stopImmediatePropagation will prevent any parent handlers and also any other handlers from executing
Quick example from the jquery documentation:
$("p").click(function(event) {
event.stopImmediatePropagation();
});
$("p").click(function(event) {
// This function won't be executed
$(this).css("background-color", "#f00");
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p>example</p>
Note that the order of the event binding is important here!
$("p").click(function(event) {
// This function will now trigger
$(this).css("background-color", "#f00");
});
$("p").click(function(event) {
event.stopImmediatePropagation();
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p>example</p>
Surprisingly, all other answers only say half the truth or are actually wrong!
e.stopImmediatePropagation() stops any further handler from being called for this event, no exceptions
e.stopPropagation() is similar, but does still call all handlers for this phase on this element if not called already
What phase?
E.g. a click event will always first go all the way down the DOM (called “capture phase”), finally reach the origin of the event (“target phase”) and then bubble up again (“bubble phase”). And with addEventListener() you can register multiple handlers for both capture and bubble phase independently. (Target phase calls handlers of both types on the target without distinguishing.)
And this is what the other answers are incorrect about:
quote: “event.stopPropagation() allows other handlers on the same element to be executed”
correction: if stopped in the capture phase, bubble phase handlers will never be reached, also skipping them on the same element
quote: “event.stopPropagation() [...] is used to stop executions of its corresponding parent handler only”
correction: if propagation is stopped in the capture phase, handlers on any children, including the target aren’t called either, not only parents
...and: if propagation is stopped in the bubble phase, all capture phase handlers have already been called, including those on parents
A fiddle and mozilla.org event phase explanation with demo.
A small example to demonstrate how both these propagation stoppages work.
var state = {
stopPropagation: false,
stopImmediatePropagation: false
};
function handlePropagation(event) {
if (state.stopPropagation) {
event.stopPropagation();
}
if (state.stopImmediatePropagation) {
event.stopImmediatePropagation();
}
}
$("#child").click(function(e) {
handlePropagation(e);
console.log("First event handler on #child");
});
$("#child").click(function(e) {
handlePropagation(e);
console.log("Second event handler on #child");
});
// First this event will fire on the child element, then propogate up and
// fire for the parent element.
$("div").click(function(e) {
handlePropagation(e);
console.log("Event handler on div: #" + this.id);
});
// Enable/disable propogation
$("button").click(function() {
var objectId = this.id;
$(this).toggleClass('active');
state[objectId] = $(this).hasClass('active');
console.log('---------------------');
});
div {
padding: 1em;
}
#parent {
background-color: #CCC;
}
#child {
background-color: #000;
padding: 5em;
}
button {
padding: 1em;
font-size: 1em;
}
.active {
background-color: green;
color: white;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="parent">
<div id="child"> </div>
</div>
<button id="stopPropagation">Stop Propogation</button>
<button id="stopImmediatePropagation" ">Stop Immediate Propogation</button>
There are three event handlers bound. If we don’t stop any propagation, then there should be four alerts - three on the child div, and one on the parent div.
If we stop the event from propagating, then there will be 3 alerts (all on the inner child div). Since the event won’t propagate up the DOM hierarchy, the parent div won’t see it, and its handler won’t fire.
If we stop propagation immediately, then there will only be 1 alert. Even though there are three event handlers attached to the inner child div, only 1 is executed and any further propagation is killed immediately, even within the same element.
I am a late comer, but maybe I can say this with a specific example:
Say, if you have a <table>, with <tr>, and then <td>. Now, let's say you set 3 event handlers for the <td> element, then if you do event.stopPropagation() in the first event handler you set for <td>, then all event handlers for <td> will still run, but the event just won't propagate to <tr> or <table> (and won't go up and up to <body>, <html>, document, and window).
Now, however, if you use event.stopImmediatePropagation() in your first event handler, then, the other two event handlers for <td> WILL NOT run, and won't propagate up to <tr>, <table> (and won't go up and up to <body>, <html>, document, and window).
Note that it is not just for <td>. For other elements, it will follow the same principle.
event.stopPropagation will prevent handlers on parent elements from running.
Calling event.stopImmediatePropagation will also prevent other handlers on the same element from running.
From the jQuery API:
In addition to keeping any additional
handlers on an element from being
executed, this method also stops the
bubbling by implicitly calling
event.stopPropagation(). To simply
prevent the event from bubbling to
ancestor elements but allow other
event handlers to execute on the same
element, we can use
event.stopPropagation() instead.
Use
event.isImmediatePropagationStopped()
to know whether this method was ever
called (on that event object).
In short: event.stopPropagation() allows other handlers on the same element to be executed, while event.stopImmediatePropagation() prevents every event from running.
1)event.stopPropagation():
=>It is used to stop executions of its corresponding parent handler only.
2) event.stopImmediatePropagation():
=> It is used to stop the execution of its corresponding parent handler and also handler or function attached to itself except the current handler.
=> It also stops all the handler attached to the current element of entire DOM.
Here is the example: Jsfiddle!
Thanks,
-Sahil
Here is a demo to illustrate the difference:
document.querySelectorAll("button")[0].addEventListener('click', e=>{
e.stopPropagation();
alert(1);
});
document.querySelectorAll("button")[1].addEventListener('click', e=>{
e.stopImmediatePropagation();
alert(1);
});
document.querySelectorAll("button")[0].addEventListener('click', e=>{
alert(2);
});
document.querySelectorAll("button")[1].addEventListener('click', e=>{
alert(2);
});
<div onclick="alert(3)">
<button>1...2</button>
<button>1</button>
</div>
Notice that you can attach multiple event handlers to an event on an element.
event.stopPropagation() allows other handlers on the same element to be executed, while event.stopImmediatePropagation() prevents every event from running. For example, see below jQuery code block.
$("p").click(function(event)
{ event.stopImmediatePropagation();
});
$("p").click(function(event)
{ // This function won't be executed
$(this).css("color", "#fff7e3");
});
If event.stopPropagation was used in previous example, then the next click event on p element which changes the css will fire, but in case event.stopImmediatePropagation(), the next p click event will not fire.
Here I am adding my JSfiddle example for stopPropagation vs stopImmediatePropagation.
JSFIDDLE
let stopProp = document.getElementById('stopPropagation');
let stopImmediate = document.getElementById('stopImmediatebtn');
let defaultbtn = document.getElementById("defalut-btn");
stopProp.addEventListener("click", function(event){
event.stopPropagation();
console.log('stopPropagation..')
})
stopProp.addEventListener("click", function(event){
console.log('AnotherClick')
})
stopImmediate.addEventListener("click", function(event){
event.stopImmediatePropagation();
console.log('stopimmediate')
})
stopImmediate.addEventListener("click", function(event){
console.log('ImmediateStop Another event wont work')
})
defaultbtn.addEventListener("click", function(event){
alert("Default Clik");
})
defaultbtn.addEventListener("click", function(event){
console.log("Second event defined will also work same time...")
})
div{
margin: 10px;
}
<p>
The simple example for event.stopPropagation and stopImmediatePropagation?
Please open console to view the results and click both button.
</p>
<div >
<button id="stopPropagation">
stopPropagation-Button
</button>
</div>
<div id="grand-div">
<div class="new" id="parent-div">
<button id="stopImmediatebtn">
StopImmediate
</button>
</div>
</div>
<div>
<button id="defalut-btn">
Normat Button
</button>
</div>