Trigger child element's onclick event, but not parent's - javascript

I've got some nested elements, each with an onclick event. In most cases, I want both events to fire when the user clicks the child (both parent and child events are triggered - default behavior). However, there is at least one case where I want to trigger the child's onclick event (from javascript, not a user's click), but not the parent's. How can I prevent this from triggering the parent event?
What I was is:
User clicks A: A's onclick fires.
User clicks B: B's onclick fires, A's onclick also fires
Manually trigger B's click: B fires, A does not (this one is the problem)

Use triggerHandler on the child; this will not let the event propagate through the DOM:
Events created with .triggerHandler() do not bubble up the DOM
hierarchy; if they are not handled by the target element directly,
they do nothing.

Method 1:
You can use stopPropagation() method to prevent event bubbling.
$('.element').on('click', function(e){
e.stopPropagation();
});
Check out thisDEMO
Method 2:
You can use return false to prevent event bubbling and this will also prevent the default action.
$('.element').on('click', function(e){
//--do your stuff----
return false;
});

Related

jQuery 'on()' command

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;
});

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>

Jquery off() for a specific element

I have a table where i have bound all my elements with class="shift" to a click function.
Now, because I also need to use another click event on part of the element, I would like to unbind the click event on element when the mouse enters the element and rebind when i leaves (meant for some touch events and whatnot)
Now, I bind like this
$("table").on("touchstart mousedown",".shift", function(e){ ... })
But when i try to unbind on a specific element, say it has a class="selected" added to distinguish the current element i use:
$("table").off("touchstart mousedown",".shift.selected")
which does not work....
I can remove all the handlers at once, but it would be wasteful to remove all the handlers and reinsert them as soon as the mouse leaves.
So, is there a way to remove the handler on a single element after the event is bound to all current and future elements?
Thanks in advance!
You don't need to unbind the click event on the element when the mouse enters. I know, the element click event will trigger when you click an inner element with the click event bound, right ? you can stop that:
The click handler of the inner element must look like this:
$("some inner element").click(function(event) {
//That's what are you looking for ;)
event.stopPropagation();
//You code here
});
event.stopPropagation() will prevent the event from bubbling up the DOM tree, preventing any parent handlers from being notified of the event.

How to add an eventhandler which can prevent any other click eventhandler with jQuery?

I would like to implement a confirmation logic for links or buttons, where I can annotate the related element with a special data-confirm attribute. If this attribute exists on the element, I would like to attach a click handler which has the power to prevent any other handler including default event, and other jQuery handlers added before or after my confirm handler was added.
Here is my code:
$(document).on("click", "a[data-confirm], button[data-confirm]", function(e) {
var confirmData = $(this).data("confirm");
if (confirmData === "true")
confirmData = $(this).prop("title");
if (confirmData && !confirm(confirmData)) {
e.stopImmediatePropagation();
e.stopPropagation();
e.preventDefault();
return false;
}
return true;
});
The problem is that I'm not sure about where this handler will go into the handlers list, so I guess there is a good probability that other handlers could execute before it. Also, I'm not sure if this handler will precede for example knockoutjs' click binding.
To prevent other handlers from being called use stopImmediatePropagation() and to prevent default behavior use preventDefault().
Like so:
$("body").on("click", ".elementClass[attributeName]", function(event){
event.stopImmediatePropagation();
event.preventDefault();
});
By adding the [attributeName] in the selector, the selector will only apply to elements with the attribute "attributeName"
In the example above this will be executed before your $(document) handler as the event bubbles up the DOM and gets to the body before it gets to the document. To ensure that this event is attached sooner, you can attach the handler to the element like so:
$(".elementClass[attributeName]").on("click", function(event){
event.stopImmediatePropagation();
event.preventDefault();
});
The downside to this, is that this handler must be attached after the element has the attribute.
Another technique you can use with just vanilla JavaScript, however, is event capturing: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener.
If true, useCapture indicates that the user wishes to initiate capture. After initiating capture, all events of the specified type will be dispatched to the registered listener before being dispatched to any EventTarget beneath it in the DOM tree. Events which are bubbling upward through the tree will not trigger a listener designated to use capture
You can do something like this:
document.addEventListener("click", function(event){
if(event.target.hasAttribute("attributeName")){ // jQuery: if($(event.target).attr("attributeName")){...
event.stopPropagation();
event.preventDefault();
//Do stuff
}
}, true);
By adding true at the end the event is captured, meaning this will be run before other handlers.
If an element has more than one click handlers, the execution order of those is unspecified. So you can't reliably make one always execute first.
Although all EventListeners on the EventTarget are guaranteed to be triggered by any event which is received by that EventTarget, no specification is made as to the order in which they will receive the event with regards to the other EventListeners on the EventTarget.
W3C-Events-flow-basics

How to catch event that was stopPropagation

For example we have a page with a link that has onclick event listener on it. But handler makes stopPropagation. How I can handle that click event was made, if it's not bubble to root anymore?
e.g.
document.addEventListener('click', function(e) {console.log(e);});
a.onclick = function(e) {e.stopPropagation();};
DOM event propagation works in three phases: the capture phase, the target phase and the bubble phase. Roughly, the event first works its way down to the target, reaches the target and then works its way back up.
By default, event handlers are attached in the final bubble phase. If you attach a click event listener in the first capture phase, it will run before the previous handler has had the chance to call stopPropagation.
See this question for a deeper explanation.
The simple answer is, add a third argument, true, when adding your event listener.
document.addEventListener('click', someFunction, true)
This flag (called useCapture) will call someFunction on all clicks in a document, even when the user clicked inside an element with a click handler that called event.stopPropagation.
With options
If you're already passing an object of options as the third argument, simply include capture: true in them:
document.addEventListener('click', someFunction, { capture: true, ...someMoreOptions })
Why?
Enabling the handler's useCapture mode like this means the listener listens during the earlier "capture" phase of the event (which starts at the outmost element then trickles down through children), instead of the later "bubble" phase (which starts at the innermost element and bubbles back up through ancestors, and is the one stopPropagation blocks).
Side effects
That also means that applying this setting changes the timing: your capture phase click event will occur before any click events of either type inside child or descendant elements.
For example, in the above function, if a user clicks on a button on the page, the someFunction attached to the document's capture phase will be called before any handlers attached to the button; whereas without setting use capture to true, you'd expect it to be called after.

Categories

Resources