Stop click from propagating to a parent - javascript

When a particular element is clicked, I want to call a function. When any of its children are clicked, I do not want to call the function.
I am not using jQuery.
Example:
I created a modal:
<div class="fullscreen-overlay">
<div class="card">
...
</div>
</div>
I want to call my closeModal() function when ".fullscreen-overlay" is clicked, but not when ".card" or any of its content are clicked.
jsfiddle:
https://jsfiddle.net/5kuwmf9s/
Research:
I could've sworn there was a CSS attribute for this, but after Googling and searching on SO for it, I can't find it / might've been imagining it. "pointer-events" stops it on the target element, not bubbling.
Theres another answer that suggests attaching an event handler to ALL children that catches their events and stops propagation - which seems unnecessary. My children are dynamic, and this will get complicated to keep attaching handlers.

You might have compared event's target and currentTarget
They would be equal only if the current element is the one that was the initial source of the event:
function handleClick(e){
if (e.target === e.currentTarget) {
alert('clicked!');
}
}
References:
https://developer.mozilla.org/en-US/docs/Web/API/Event/currentTarget
https://developer.mozilla.org/en-US/docs/Web/API/Event/target
JSFiddle: https://jsfiddle.net/u78k4k6t/

try use an element and style it as your '.fullscreen-overlay' and bind event to this element
as the answer link shows
use event.target or event.srcElement

Related

using event.target vs selecting the element

Is there a difference between working a property of an element through the event.target vs working it directly?
I don't understand if there's any difference.
const btn = document.querySelector('#btn');
btn.addEventListener('click', function(e) {
console.log(e.target.value);
//vs
console.log(btn.value);
}
Is there a better practice when doing this?
Yes, there is an important difference. The event.target property tells you what element was involved with the creation of the event. For a "click", it's the element that was under the cursor when the "click" happened.
Thus if your HTML looked like:
<button><span>Text</span><img src="something.jpg"></button>
a click on the button content would trigger the event from either the <span> or the <img>, and one of those elements would be the target.
There's another event property, event.currentTarget, that is always a reference to the element to which the event handler is attached. That's probably what you want. Alternatively, if you bind handlers with .addEventListener(), then the value of this when the handler is invoked will be a reference to the same thing as currentTarget.
In your case you're selecting an element by ID. Your reference is the same as the target so there is no difference. A lot of time we use delegation so you might not have reference to the clicked element. Hence we use target from the event object.
Worth also to check target vs currentTarget

trigger event only on parent click not child

Consider the following code as an example:
<div id="parent">
<div id="child">info goes here</div>
</div>
//javascript
function something{
//do something;}
//initial attempt
document.getElementById('parent').addEventListener('click',something);
//event capture
document.getElementById('parent').addEventListener('click',something,true);
When I click on the parent element I would like it to do something, and when I click the child I want it to do nothing. The problem is that when I click in the child element it is triggering 'something'.
I thought that it may be event bubbling, such that if I clicked on the child element, the event would bubble up from there to the parent. So then I was thinking about an event capture, but that also causes this problem.
Any advice or suggestions on how to accomplish this would be greatly appreciated.
Instead, check if the element originating the event Event.target - was indeed the desired element.
PS: Don't confuse with Event.currentTarget which (in contrast) is always the Element who has the Event handler attached.
function something (evt){
if (evt.target !== this) return; // Do nothing
// else...
console.log(`#${this.id} clicked`);
}
const el_parent = document.getElementById('parent');
el_parent.addEventListener('click', something);
// Example why you should not use `Event.stopPropagation()`...
document.body.addEventListener('click', () => console.log("BODY is notified!"));
<div id="parent">
PARENT ELEMENT
<div id="child">CHILD ELEMENT</div>
</div>
Don't use Event.stopPropagation()
Event.stopPropagation() would be an idea, but a bad one, since we should avoid an application to (at some layer) prevent an event to bubble - and eventually notify other elements that such an event happened. Imagine your body listens for click events to close custom select dropdowns... If you have elements wandering around your app, and that use Event.stopPropagation() - clicking on such element an opened dropdown will not close - resulting in broken UI. And this was just a simple example.
Use event.stopPropagation to stop event bubbling:
function something() {
console.log("something");
}
document.getElementById('parent').addEventListener('click', something);
document.getElementById('child').addEventListener('click', e => e.stopPropagation());
<div id="parent">
Parent info goes here!
<div id="child">Child info goes here!</div>
</div>
It is event bubbling. Just because you are handling the click event on the child, does not mean it stops bubbling to the parent.
There are two approaches to this. The first one is to stop the event from propagating like this:
document.getElementById('child').addEventListener('click',(e) => { e.stopPropagation(); something() },true);
The second is to check the event target and only run something when the deepest element that caused the click event is the child element:
document.getElementById('child').addEventListener('click',(e) => { e.target.id == "child" ? something() : nothing() },true);

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

Is binding a click event to a null selector dangerous?

Is there anything wrong that can happen if I bind a null selector using on? It would simplify my code and allow me to chain a few things if I didn't have to explicitly check if the selector is null myself.
Any performance, security, or memory-leak implications if I do this a dozen times on my page?
$(document.body).on('click', null, function () { ... }
If you plan on dynamically adding elements, there is nothing wrong by binding on a higher element using the .on() method.
Keep in mind though you have to specify a selector that will ultimately define the dynamically added elements.
The code below will fire when a label is clicked.
$(document).on('click', 'label', function(e) {
alert('dynamically added label clicked');
});
This code will fire when any element is clicked.
$(document).on('click', null, function (e) {
alert('fired regardless what element you clicked');
});
From the jQuery docs:
A selector string to filter the descendants of the selected elements
that will call the handler. If the selector is null or omitted, the
handler is always called when it reaches the selected element.
If your problem is that the elements that raise the click event are dynamically added you can still use a direct event handler on body and catch those events.
The delegated event handler gives you the opportunity to filter out some of those click events, but it seems that's not the case since you are setting the selector to null.
For example, if you have a div and add buttons inside and add an event handler to the click event on the div you'll catch all the click events from all the buttons, even the ones added dynamically.
<div>
<input type="button" value="add button" id="buttonAddMore"/>
<span id="whatClicked"></span>
</div>
$('div').on("click", function(event){
$('#whatClicked').text("the id of the clicked element inside the div is: " + event.target.id);
});
This fiddle demonstrates this.
The delegated events have other subtleties, such as, they won't respond to events raised in the elements they are registered in, in this example that would be a click on the div, and that might be important.
Either way if you look at how the events are registered, in your case you can have a look by calling in the console $._data(document.body, "events") and have a look at the click event handler with your method and registered using the shorthand version (i.e. .click or on("click", function() {...})) you'll see that they produce the same object, except for the selector being null (.on("click", null...) in one case and undefined in the other (.click)

how to select the rest of a div?

i got a problem
<div id='parent'>
<div id='child'>
</div>
</div>
what i want is when the child is clicked addClass,and when the rest of parent is clicked removeClass,so when i try to do
$('#child').click(function(){
$(this).addClass();
})
$('#parent').click(function(){
$('#child').removeClass();
})
its not working i think its because the child is actually inside the parent,so when the child is clicked the parent clicked right?
so how can i do that?
try this:
$('#child').click(function(evt){
evt.stopPropagation();
$(this).addClass("myClass");
});
You could use event.stopPropagation to prevents the event from bubbling up the DOM tree, preventing any parent handlers from being notified of the event.
$('#child').click(function(e){
e.stopPropagation();
$(this).addClass();
});
Several users have already suggested a good solution - here's an explanation of why it works:
When you click an HTML element (actually a DOM object...), the click event "bubbles" all the way up to the root element. For example, a click in #child also triggers a click in #parent, as you expected.
To stop this behavior, you need to call .stopPropagation() on the click event - that will tell the browser that you do not want the event to propagate, but keep it "local". Basically, when you've handled it here, you're done with it and don't want to see it again.
Conveniently, jQuery event handlers take the event as the first argument, so if you assign any function with the signature function (e) { ... }, you can stop event propagation by e.stopPropagation(); as others have suggested. In your case, you want
$('#child').click(function(e){
$(this).addClass();
e.stopPropagation();
});
$('#parent').click(function(){
$('#child').removeClass();
});

Categories

Resources