jQuery event bubbling - javascript

I want to understand how exactly to interpret bubbling. Does it mean going up the HTML code hierarchy or something else?
Secondly, I was going through an example and I could not understand the last part where it says
The P-based click handler listens for the click event and then prevents it from being propagated (bubbling up)
What does this mean?

The concept of "bubbling up" is like if you have a child element with a click event and you don't want it to trigger the click event of the parent. You could use event.stopPropagation().
event.stopPropagation() basically says only apply this click event to THIS CHILD NODE and don't tell the parent containers anything because I don't want them to react.
Event Capturing:
| |
---------------| |-----------------
| element1 | | |
| -----------| |----------- |
| |element2 \ / | |
| ------------------------- |
| Event CAPTURING |
-----------------------------------
Event Bubbling:
/ \
---------------| |-----------------
| element1 | | |
| -----------| |----------- |
| |element2 | | | |
| ------------------------- |
| Event BUBBLING |
-----------------------------------
If you are using live() or delegate() you will need to return false;, though it may not work. Read the quote below.
Per jQuery docs:
Since the .live() method handles events once they have propagated to
the top of the document, it is not possible to stop propagation of
live events. Similarly, events handled by .delegate() will propagate
to the elements to which they are delegated; event handlers bound on
any elements below it in the DOM tree will already have been executed
by the time the delegated event handler is called. These handlers,
therefore, may prevent the delegated handler from triggering by
calling event.stopPropagation() or returning false.
In the past it was a platform issue, Internet Explorer had a bubbling model, and Netscape was more about capturing (yet supported both).
The W3C model calls for you be able to choose which one you want.
I think bubbling is more popular because, as stated there are some platforms that only support bubbling...and it sort of makes sense as a "default" mode.
Which one you choose is largely a product of what you are doing and what makes sense to you.
More info http://www.quirksmode.org/js/events_order.html
Another great resource: http://fuelyourcoding.com/jquery-events-stop-misusing-return-false/

return false;
will prevent "bubbling". It's used to stop default actions like checking a checkbox, opening a select, a click, etc.
To stop further handlers from executing after one bound using .live(),
the handler must return false. Calling .stopPropagation() will not
accomplish this.
From Caveats in jQuery .live()
Reasoning (thanks to #AlienWebguy):
The reason stopPropagation() doesn't work with live() is that live() binds the event to document so by the time it fires there's no where else for it to propagate.

What it says is that the live () method attach a handler to the document element and check the target of the event to see where it comes from. If the target match the selector, then it fires the eventHandler. All that repose on the bubbling event system.
In the example, the click handler on the p element, witch is an ancestor of the a element, cancel the bubbling by returning false. Then the document element will never receive the event, so it will not trigger the event handler.

In the below example it is attaching a click event to anchor with id "anchor". This anchor is within a div which also has a click event attached. If we click on this anchor it is as good as we are clicking on the containing div. Now if we want to do some stuff on this anchor click but do not want the div's click to be fired we can stop the event bubling as below.
<div id="div">
</div>
$("#div").click(function(e){//On anchor click this event will not be fired as we have stop the event propagation in anchor click handler.
//Do stuff here
});
$("#anchor").click(function(e){
//Do stuff here
//This line stops the event bubling and
//jquery has abstracted it in the event object to make it cross browser compatible.
e.stopPropagation();
});

Also:
event.stopPropagation()
http://api.jquery.com/event.stopPropagation/

Yes, the event goes up the tree and if any element has a handler for that event it will be called.
By adding return:false in a handler of one of the elements the event will be prevented from bubbling.

These two links provide clear and elaborate explanation on event bubbling (as well as commonly used event concepts).
http://jqfundamentals.com/chapter/events
http://www.mattlunn.me.uk/blog/2012/05/what-does-event-bubbling-mean/
From the first link
event will be triggered for the a element as well as for all of
the elements that contain the a — all the way up to the document
From the second link
<div>
<h1>
<a href="#">
<span>Hello</span>
</a>
</h1>
</div>
Lets assume we click the span, which causes a click event to be fired on the span; nothing revolutionary so far. However, the event then propagates (or bubbles) to the parent of the span (the ), and a click event is fired on that. This process repeats for the next parent (or ancestor) up to the document element.
Now let's put all this into the context of a DOM. The DOM is a... tree and each element is a node in the DOM tree. Bubbling is then merely the traversal of a node, some element to the root node, document (follow your parent until you can't anymore)

Related

How to ONLY trigger parent click event when a child is clicked

Both child and parent are clickable (child could be a link or div with jQuery click events). When I click on child, how do I only trigger parent click event but not the child event?
DOM Event Phases
Events have three phases:
Capture: The first phase is "capture" where event handlers are called starting with the <window> and moving down through descendants towards the target of the event.
Target: The second phase is the "target" phase when the event listeners on the target are called.
Bubbling: The third phase is "bubbling" which starts with the handlers listening on parent of the target being called first, then, progressively, the ancestors of that element.
Events also have a "default action", which happens after the bubbling phase. The default action is the browser-defined action that normally occurs for events of the specified type on the kind of element which is the target of the event (e.g. the browser navigating to the href of an <a> upon a click, whereas a click on another type of element will have a different default action).
The DOM Level 3 Events draft has a diagram that graphically shows how events propagate through the DOM:
Image Copyright © 2016 World Wide Web Consortium, (MIT, ERCIM, Keio, Beihang). http://www.w3.org/Consortium/Legal/2015/doc-license (Use permitted per the license)
For more information, on capture and bubbling, see: "What is event bubbling and capturing?"; The DOM Level 3 Events draft; or W3C DOM4: Events
Preventing the event from getting to the child
For what you want, to get the event on the parent prior to, and prevent, the event on the child, you have to receive the event in the capture phase. Once you have received it in the capture phase, you have to stop the event from propagating to any event handlers on elements lower in the DOM tree, or which have registered to listen in the bubbling phase (i.e. all listeners on elements/phases which would be visited by the event after your listener). You do this by calling event.stopPropagation().
Receiving events during the capture phase
When adding the listener with addEventListener(type, listener[, useCapture]), you can have the useCapture argument be true.
Quoting MDN:
[useCapture is] A Boolean that indicates that events of this type will be dispatched to the registered listener before being dispatched to any EventTarget beneath it in the DOM tree. Events that are bubbling upward through the tree will not trigger a listener designated to use capture. Event bubbling and capturing are two ways of propagating events that occur in an element that is nested within another element, when both elements have registered a handle for that event. The event propagation mode determines the order in which elements receive the event. See DOM Level 3 Events and JavaScript Event order for a detailed explanation. If not specified, useCapture defaults to false.
Preventing other handlers getting the event
event.preventDefault() is used to prevent the default action (e.g. prevent the browser from navigating to the href of an <a> upon a click). [This is used in the example below, but has no real effect as there is no default action for text. It's used here because most of the time when you are adding a click event handler you want to prevent the default action. Thus, it's a good idea to be in the habit of doing so, and just not doing so when you know you don't want to.]
event.stopPropagation() is used to prevent any handlers on elements later in any of the event phases from receiving the event. It does not prevent any additional handlers on the current element and phase from being called. It does not prevent the default action from occurring.
event.stopImmediatePropagation(): Handlers on the same element and phase are called in the order in which they are added. In addition to having the same effect as event.stopPropagation(), event.stopImmediatePropagation() prevents any additional handlers on the same element and event phase from receiving the event. It does not prevent the default action from occurring. Given that the requirement for this question is to prevent the event from propagating to children, we don't need to use this, but could do so instead of using event.stopPropagation(). Note, however, that listeners on the same element are called in the order they are added. Thus, event.stopImmediatePropagation() will not prevent the event from being received by those listeners on the same element and phase as your listener which were added prior to your listener.
Example
In the following example, event listeners are placed on both the parent and the child <div> elements. Only the listener placed on the parent receives the event because it receives the event during the capture phase prior to the child and it executes event.stopPropagation().
var parent=document.getElementById('parent');
var child=document.getElementById('child');
var preventChild=document.getElementById('preventChild');
parent.addEventListener('click',function(event){
if(preventChild.checked) {
event.stopPropagation();
}
event.preventDefault();
var targetText;
if(event.target === parent) {
targetText='parent';
}
if(event.target === child) {
targetText='child';
}
console.log('Click Detected in parent on ' + targetText);
},true);
child.addEventListener('click',function(event){
console.log('Click Detected in child (bubbling phase)');
});
child.addEventListener('click',function(event){
console.log('Click Detected in child (capture phase)');
},true);
<input id="preventChild" type="checkbox" checked>Prevent child from getting event</input>
<div id="parent">Parent Text<br/>
<div id="child" style="margin-left:10px;">Child Text<br/>
</div>
</div>
jQuery
jQuery does not support using capture on events. For more information as to why see: "Why does jQuery event model does not support event Capture and just supports event bubbling"
Another option for this that may be useful in certain circumstances when you know that none of the child elements are interactive is to set pointer-events: none in your css (link). I usually apply it to all child elements of the element on which I want to capture interaction. Like this:
#parentDiv * {
pointer-events: none
}
Note the *, declaring that the rule applies to all children of the parentDiv.
Prevent the children from receiving the parent's click event:
parent.addEventListener('click',function(e){
e.stopPropagation();
console.log('event on parent!')
},true);
(Note that the second parameter is true)
Prevent the parent from receiving itself or it children's click event:
parent.addEventListener('click',function(e){
e.stopPropagation();
console.log('event on parent or childs!', e.target.closest('.parent_selector'))
});
e.stopPropagation means that stop next ones in the hierarchy to receive the event.
second argument (useCapture) is a flag, and means that reverse the order of receiving events. (use capture phase instead of bubble phase.).
it means if you set it to true, parent will receive the click event, then the child. (normally the child will get the event first.)
(see the #Makyen's answer for detailed explanation.)
To make life really simple and easy here i am
Use on parent node similar to this
target_image.addEventListener('drop',dropimage,true);
This will enable the parent child ancestor relationship and the same event will be called in for the parent and child.
To make the event only be called for the parent use the following code snippet in the event handler. First line
event.stopPropagation();
event.preventDefault();
You can use $event.stopPropagation() in the html file.
(click)="openAttendeesList(event.id,event.eventDetailId,event.eventDate) ; $event.stopPropagation();"
You can use the CustomEvents property on elements.
Create an event object and let the child element dispatch the event to its parent
see demo here
document.getElementById('parent').onclick = function() {
alert("you are clicking on the parent stop it");
}
document.getElementById('child').onclick = function(e) {
alert('I am sending this event to my parent');
event = new CustomEvent('click');
document.getElementById('parent').dispatchEvent(event);
}
#parent {
display: inline-block;
width: 100px;
height: 100px;
border: solid black;
}
#child {
border: solid red;
}
<div id=parent>
<div id=child>I am a child</div>
</div>

Delegated events don't work in combination with :not() selector

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

Capturing and Bubbling using jQuery

I am new to jQuery and I‘m trying to understand the concept of capturing and bubbling.
I have read a lot of articles, but most of them described event propagation for Javascript.
Lets assume we have the following HTML code:
<div id="outer">
outer
<div id="inner">
inner
</div>
</div>
Capturing is the phase where we go down the DOM elements and bubbling is when we go up.
In Javascript you can decide which way to follow (using true or false parameters):
element.addEventListener('click', doSomething, true) --> capture phase
element.addEventListener('click', doSomething, false) --> bubble phase
Is there anything similar for jQuery to denote which way to follow other than the JavaScript way?
Also does jQuery uses a default phase? For example bubble?
Because i used the following code to test this:
css
<style>
div {
border: 1px solid green;
width: 200px;
}
</style>
jQuery
<script>
$(document).ready(function(){
$('div').click(function(){
$(this).animate({'width':'+=10px'},{duration: 3000})
});
});
</script>
It appears that when I click on the outer div, only that div animates to a larger div. When I click to the inner div both divs animate to larger divs.
I don’t know if I am wrong, but this test shows that the default browser propagation method is bubble.
Please correct me if I’m wrong.
jQuery only uses event bubbling. If you want to add an event handler that uses the capturing model, you have to do it explicitly using addEventListener, with the third argument true as you show in the question.
Event bubbling which will start executing from the innermost element to the outermost element.
Event Capturing which will start executing from the outer element to the innermost element.
But jQuery will use event bubbling. We can achieve event capturing with:
$("body")[0].addEventListener('click', callback, true);
The 3rd parameter in the addEventListener which will tell the browser whether to take event bubbling or event capturing.
By default it is false.
If it is false then it will take event bubbling.
If it is true then it will take event capturing.
Question and answers live with the following misconception: that the browser does either capture or bubble.
Truth is: the browser does always both, capture and bubble, on every click, in that order.
Is there anything similar for jQuery to denote which way to follow other than the JavaScript way?
Also does jQuery uses a default phase? For example bubble?
jQuery has no event phases. The DOM has. And the DOM does always both.
But jQuery registers handlers only to the bubble phase. There is no jQuery way to register to the capture phase, so bubble registration is not a default, it is the only way (with jQuery).
I don’t know if I am wrong, but this test shows that the default browser propagation method is bubble.
You are wrong, if I’m allowed to say. When you click on the outer div, capture happens, until it reaches the outer div, then bubble... It just does not go any deeper than the actual target of the event.
If you click the inner div, capture passes the outer div, but no handler is registered there for that phase, then it reaches the target, and on the way back up (bubble) it triggers the outer div handler.—I haven’t run your code, but it would be hard to tell which one happened first (the inner is first).
(Note: once the target is reached, the phase is actually called “target phase” and handlers are called independent of which phase they registered for (in registration order, btw).)
Every Event is going first through "capturing" phase and then through "bubbling" phase.
For instance, when user clicks on <a>, all event handlers bound using "capturing" (third argument in addEventListener method set to true, not supported in jQuery) are called starting from outermost <html> all the way down to the link. Then, the "bubbling" phase starts and all event handlers using "bubbling" (supported in jQuery) are called the opposite way - from link back to the <html>.
You can try it on your own, firing this code in developer tools and clicking anywhere on your site.
document.querySelectorAll("*").forEach(it => {
it.addEventListener("click", function() {console.log("capturing: ", it)}, true);
it.addEventListener("click", function() {console.log("bubbling: ", it)}, false);
});
The event is triggered in event bubbling on the element on to which the user has clicked,and unless we call .stopPropagation() on the event object the event is then triggered all the way up the DOM.
Default is event bubbling set in Jquery in order to use Capture ypu need to set parameter as true in .addEventListner

Event bubbling/capturing - where does it start/end?

I understand that an event has two modes -- bubbling and capturing.
When an event is set to bubble, does Javascript checks up to "document"?
When an event is set to capture, does Javascript always starts from "document"?
How does Javascript know where to stop/start?
Let's say I have the following code in my body tag.
<div id='outer'>
<div id='inner'></div>
</div>
When I set an event to #inner to bubble, does Javascript check up to document or does it stop at #outer?
From W3C Document Object Model Events
I know I'm nitpicking but it isn't javascript that handles the events you are describing, it is the DOM-engine (Document Object Model). In the browser there are bindings between the javascript and DOM engines so that events can be propagated to javascript, but it is not limited to javascript. For example MSIE has support for BASIC.
When an event is set to bubble, does Javascript checks up to "document" ?
1.2.3 "This upward propagation will continue up to and including the Document"
"Any event handler may choose to prevent further event propagation by calling the stopPropagation method of the Event interface. If any EventListener calls this method, all additional EventListeners on the current EventTarget will be triggered but bubbling will cease at that level"
When an event is set to capture, does Javascript always starts from "document"?
1.2.2 "Capture operates from the top of the tree, generally the Document,"
Event bubbling
JavaScript checks all the way up to document. If you add a listener on document and a listener on inner, both listeners fire.
Event capturing
JavaScript starts from document and goes all the way down to inner. If you add a listener on document and a listener on inner, both listeners fire.
My Findings
Turns out that the browser does some sort of smart processing so that it
a) doesn't have to loop through the entire parent hierachy
and
b) doesn't have to loop through all events.
Proof
a) It takes the browser no time to trigger both click events when the inner div is clicked:
Fiddle
b) It takes the browser no time to trigger both click events when the inner div is clicked when lots of other events exist that are attached to other DOM elements not in the parent hierachy:
Fiddle
Partial answer..
1 - When an event is set to bubble, does Javascript check up to "document" ?
Not if one of the elements in the hierarchy decides to stop the bubbling by calling stopPropagation()

jQuery Listener Excluding Child

I have a dl list of dd items. Each dd item has a listener attached to it (see below) so if it is clicked on I can rebuild the page and change some stuff. Each of these dd items also have a checkbox within them though which I would like to be excluded from that listener (so it can be picked up by another listener).
The problem that is occuring is whenever I click anywhere in the dd it will apply the dd listener, not the checkbox listener even if I clicked on the checkbox. Is there a way to distinguish what exactly was clicked without setting up divs inside the dd and applying listeners individually?
Example HTML Code:
<dl>
<dd class="class1 class2 class3">Some text and stuff
<input type="checkbox" class="class1 checkBox">
</dd>
</dl>
Example jQuery Code:
$("class1.checkbox").live("click", function() {
//Do some other, completely different, cool stuff
//console.log($(this).parent().attr("id"));
console.log("test");
});
$("dd.class1.class2").live("click", function () {
//Do some cool stuff
});
You need to stop the event bubbling.
$(".class1:checkbox").click(function(e) {
alert('clicked checkbox');
e.stopPropagation();
});
$("dd.class1.class2").click(function () {
alert('clicked dd');
});
http://api.jquery.com/event.stopPropagation/
The concept of "bubbling up" is like if you have a child element with a click event and you don't want it to trigger the click event of the parent. You could use event.stopPropagation().
event.stopPropagation() basically says only apply this click event to THIS CHILD NODE and don't tell the parent containers anything because I don't want them to react.
Event Capturing:
| |
---------------| |-----------------
| element1 | | |
| -----------| |----------- |
| |element2 \ / | |
| ------------------------- |
| Event CAPTURING |
-----------------------------------
Event Bubbling:
/ \
---------------| |-----------------
| element1 | | |
| -----------| |----------- |
| |element2 | | | |
| ------------------------- |
| Event BUBBLING |
-----------------------------------
If you are using live() or delegate() you will need to return false;, though it may not work. Read the quote below.
Per JQuery docs:
Since the .live() method handles events once they have propagated to
the top of the document, it is not possible to stop propagation of
live events. Similarly, events handled by .delegate() will propagate
to the elements to which they are delegated; event handlers bound on
any elements below it in the DOM tree will already have been executed
by the time the delegated event handler is called. These handlers,
therefore, may prevent the delegated handler from triggering by
calling event.stopPropagation() or returning false.
A good resource: http://fuelyourcoding.com/jquery-events-stop-misusing-return-false/
$(":checkbox").live("click", function(e) {
e.stopPropagation();
//Do some other, completely different, cool stuff
//console.log($(this).parent().attr("id"));
alert("test");
});
$("dd.class1.class2").live("click", function (e) {
e.stopPropagation();
//Do some cool stuff
alert("test2");
});
http://jsfiddle.net/PsaHQ/4/
Ok, you've got a bunch of little problems adding up to a big one here.
Your first selector has 2 issues in it. Look at the updated selector below (and notice the capitalization):
$(".class1.checkBox").live("click", function() {
//Do some other, completely different, cool stuff
console.log("test");
return false; //added.
});
If you want to stop the event from bubbling to the next jquery listener, just return false.
Just bind the click event to the dd and check the target delegate appropriately
$("dd.class1.class2").live("click", function (event) {
if($(event.target).is(":checkbox"))
{
console.log("put checkbox func here");
}else{
console.log("put div func here");
}
});
Working Example:
http://jsfiddle.net/QWLpd/1/

Categories

Resources