How to use event.stopPropagation() JavaScript - javascript

I have three elements with events assigned to them. How should I use event.stopPropagation so only events for elements #1 and #2 are executed, but not for #3? I put event.stopPropagation in element #2 as it stops bubbling. In my understanding, it should stop the triggering of the event in #3 but it doesn't seem to be working, all events are still being executed. Here is JS code:
document.addEventListener("DOMContentLoaded", function() {
document.querySelector('#element1').addEventListener('click', function(e) {
console.log('Event in #element1 fired!');
});
document.querySelector('#element2').addEventListener('click', function(e) {
event.stopPropagation();
console.log('Event in #element2 fired!');
});
document.querySelector('#element3').addEventListener('click', function(e) {
console.log('Event in #element3 fired!');
});
});
<div id="element3" class="element">
Element 3
<div id="element2" class="element">
Element 2
<div id="element1" class="element">
Element 1
</div>
</div>
</div>

This line is wrong:
event.stopPropagation();
because the parameter with the event object passed to the function is named e.
You need to write this as:
e.stopPropagation();

Related

Disabling a click event from the child of a parent div

I am trying to add a on click event to a div with a class parent. Now inside that div I have a div with class child that has its own click event.
How can I manage to disable the click event of the parent function for that child element in order to execute the function of child element itself?
I have tried using pointer-event:none; but it does not seem to be working. I have wrote a jsfiddle for better understanding.
https://jsfiddle.net/arq1epbs/
$(document).on('click', '.parent', function() {
var url = $(this).attr("data-url")
document.location.href = url
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="parent" data-url="www.google.com">
Im the parent
<div class="child">
im the child and I don't want to go to Google
</div>
</div>
Thanks for all the help in advance!
You can use stopPropagation():
$(document).on('click', '.parent', function () {
var url = $(this).attr("data-url")
document.location.href = url
});
$(document).on('click', '.child', function (e) {
e.stopPropagation();
});
As it's not working in the Stack Snippet, here a Fiddle
For reference: stopPropagation()
You can simply call event.stopPropagation() inside child click event, to prevent the event from bubbling up the DOM tree, preventing any parent handlers from being notified of the child click event like:
$(document).on('click', '.parent', function() {
//var url = $(this).attr("data-url")
//document.location.href = url
console.log('Parent Clicked');
});
$(document).on('click', '.child', function(event) {
event.stopPropagation();
console.clear();
console.log('Child Clicked');
});
.parent{background:#99c0c3;width:350px;height:120px;position:relative}
.child{background:#ffde99;width:300px;height:50%;position:absolute;left:50%;top:50%;transform:translate(-50%,-50%)}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="parent" data-url="www.google.com">
Im the parent
<div class="child">
im the child and I don't want to go to Google
</div>
</div>
Just add this line:
$(document).on('click', '.parent', function (e) {
if(e.target !== this) return false; //This line added
var url = $(this).attr("data-url")
document.location.href = url
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="parent" data-url="www.google.com">
Im the parent
<div class="child">
im the child and I don't want to go to Google
</div>
</div>
You can do this in "pure" JS
document.querySelector('div.parent').onclick = evt =>
{
if (!evt.target.matches('div.parent')) return // reject sub elements click
document.location.href = evt.target.dataset.url
}
div.parent { cursor: pointer }
div.child { cursor: default }
<div class="parent" data-url="www.google.com">
Im the parent
<div class="child">
Im the child and I don't want to go to Google
</div>
</div>

Usage of :not() to filter out elements in event handler

I'm pretty confused by the :not() selector. In plain CSS is seems to be rather straightforward:
section { /* Overriden as expected */
color: red;
}
input {
color: green; /* In effect as expected */
}
:not(input) {
color: blue; /* In effect as expected */
}
<section>
<p>Lorem ipsum</p>
<input value="Lorem ipsum">
</section>
However, when applied to filter the descendants of the selected elements that trigger an event I'm unable to grasp the logic:
jQuery(function($){
$(document).on("keydown", "input", function(event){
// This fires only for <input> as expected
console.log("Event handler #1 on", event.target);
});
$(document).on("keydown", ":not(input)", function(event){
// This fires for *all* elements :-?
console.log("Event handler #2 on", event.target);
// ... even though these checks return the results that intuition suggests
console.log('Is "input"? %s; Is ":not(input)"? %s',
$(event.target).is("input"),
$(event.target).is(":not(input)")
);
});
$(document).on("keydown", "section :not(input)", function(event){
// This *never* fires :-?
console.log("Event handler #3 on", event.target);
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<section>
<p>Click and type here</p>
<input value="Click and type here">
</section>
What's the rationale behind the way :not() works here?
I'm really looking for an explanation as opposed to a fix.
The issue is that the keydown event bubbles up from the input. If you use :not(input), the handler will not fire when the event was just initialized at the input element, but it will fire when the event bubbles up to the section element. You can check this by checking this inside the function, which will refer to the element to which the event has bubbled when the handler fires. (The event.target will always be the input when you're typing in the input field)
jQuery(function($){
$(document).on("keydown", ":not(input)", function(event){
// This fires for *all* elements :-?
console.log("Event handler #2 on", this);
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<section>
<p>Click and type here</p>
<input value="Click and type here">
</section>
If you continue adding :nots, you'll see it bubble up all the way up to the HTML tag:
jQuery(function($){
$(document).on("keydown", ":not(input):not(section):not(body)", function(event){
// This fires for *all* elements :-?
console.log("Event handler #2 on", this);
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<section>
<p>Click and type here</p>
<input value="Click and type here">
</section>
I suppose you could use :not(input):not(section):not(body):not(html), but that's a bit silly and hard to manage.
Your third handler is properly excluding the input from firing the event, but only input (and similar) elements fire keydown events - it can't be fired from a <section>, for example. It might be clearer if there's a textarea child of the section as well as the input - you'll see that the textarea triggers the handler, but the input doesn't:
$(document).on("keydown", "section :not(input)", function(event) {
console.log("Event handler #3 on", event.target);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<section>
<p>Click and type here</p>
<input value="Click and type here">
<textarea></textarea>
</section>

stopPropagation & preventDefault are not working, parent click is still firing

In my code, I have added onclick on parent div and want to perform other action on inner div, but clicking on inner div also triggering parent click.
how to stop that?
$(document).on('click', '.child', function(e) {
e.preventDefault();
e.stopPropagation();
console.log('child');
});
function parentfun(sender) {
console.log('parent');
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="parent" onclick="parentfun(this)">
parent
<div class="child">child</div>
</div>
Above divs are generated on run time on some other event.
Clicking on child, also trigger parent's click. preventDefault & stopPropagation are not working.
FYI: my question is different than How do I prevent a parent's onclick event from firing when a child anchor is clicked?
What you are actually doing here is binding the click-event to the document, not the child-element. So the event has already bubbled up all the way to the document, it's too late to try to stop the bubbling with stopPropagation.
See here when I change the click-handler to the child instead:
$(".child").on('click', function(e) {
e.preventDefault();
e.stopPropagation();
console.log('child');
});
function parentfun(sender) {
console.log('parent');
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="parent" onclick="parentfun(this)">
parent
<div class="child">child</div>
</div>
Edit
As the question changed a bit, here is what you can do (for example) if the elements are created dynamically:
$(document).on('click', '.parent, .child', function(e) {
e.stopPropagation();
if ($(this).is(".child")) {
console.log('child');
} else {
console.log('parent');
}
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="parent">
parent
<div class="child">child</div>
</div>
Using plain vanilla JS it works as expected:
function logEventTarget(e) {
e.stopPropagation();
console.log(e.target.id);
}
parentDiv.addEventListener('click', logEventTarget)
childDiv.addEventListener('click', logEventTarget)
<div id="parentDiv">
parent
<div id="childDiv">child</div>
</div>
Using an inline event handler won't pass the event to the handler function:
function logEventTarget(e) {
e.stopPropagation();
console.log(e.target.id);
}
childDiv.addEventListener('click', logEventTarget)
<div id="parentDiv" onclick="logEventTarget()">
parent
<div id="childDiv">child</div>
</div>
One of the many reasons you shouldn't use inline event handlers at all. Note that e.stopPropagation() still works for the childDiv.
You can notice that when clicking the chlid element the parent triggers first (that is why parent prints first then child ) because of event capturing which precedes event bubbling. In-order to stop the event capturing phase from parent you can stop propagating that event and then only child event will trigger.
$(document).on('click', '.child', function(e) {
//e.preventDefault();
e.stopPropagation();
console.log('child');
});
$(document).on('click', '.parent', parentfun);
function parentfun(e) {
e.stopPropagation();
console.log('parent');
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="parent">
parent
<div class="child">child</div>
</div>
You can also resolve this problem by editing little bit in Your html code
<div class="parent" id="parent-div">
<!-- Just adding parent div text inside span tag -->
<span>parent</span>
<div class="child">child</div>
</div>
now go to jquery code
$('.parent span').on('click',function(){
//only print child text u can do more
alert($('.child').text());
//Change color of child
$('.child').css('color','red');
});

How do I allow only the most nested elements to be ACTIVE at one time?

I've got some nested elements which CSS controlling appearance when they are active.
.test {
background: red;
}
.test:active {
background: blue;
}
<div class="test" id="A">
This is part A.
<div class="test" id="B">
This is nested part B.
</div>
</div>
The problem is, when I mouse-down on the nested DIV (B), both A and B change color, instead of just B.
I tried capturing the mouse-down event and stopping propagation:
document.getElementById("B").addEventListener("mousedown", function(event) {
event.stopPropagation()
})
When I do this, I see that the mousedown event handlers for "A" are not being called. However, the "A" element has already changed its appearance to the active state by the time the captured event is fired!
How do I make enforce only allowing the actual most nested element being active at one time? I still need "A" to change background when it is active (mouse down on) but not when "B" is active.
You can use this selector in this case:
.test > .test:active {
background: blue;
}
This can be done with JavaScript, which will check to see if a given element has any children with the .test class. In the future this can be done with the :has() CSS selector, but does not have stylesheet support in any browser yet.
The below demo will only turn blue if it is the "most-nested" .test element in a given structure, which I believe that is what you were looking for.
Let me know if you have any questions.
document.querySelectorAll(".test").forEach(function(elem) {
elem.addEventListener("mousedown", function (event) {
event.stopPropagation();
event.preventDefault();
if (!event.target.querySelector(".test")) {
event.target.classList.add("active");
}
});
elem.addEventListener("mouseup", function (event) {
event.stopPropagation();
event.preventDefault();
document.querySelectorAll(".active").forEach(function (elem) {
elem.classList.remove("active");
});
});
});
.test {
background: red;
}
.active {
background: blue;
}
<div class="test" id="A">
This is part A.
<div class="test" id="B">
This is nested part B.
</div>
</div>
<div class="test" id="C">
This is part C.
</div>
<div class="test" id="D">
This is part D.
<div class="test" id="E">
This is nested part E.
<div class="test" id="F">
This is nested-nested part F.
</div>
</div>
</div>
----EDIT----
After clarification, it seems like you really don't care about the "most-nested" element being the only thing becoming active, what you really want is just only the single element that was moused-down on to become active. If that is the case, it is actually simpler. The code would look like so:
document.querySelectorAll(".test").forEach(function(elem) {
elem.addEventListener("mousedown", function(event) {
event.stopPropagation();
event.preventDefault();
event.target.classList.add("active");
});
elem.addEventListener("mouseup", function(event) {
event.stopPropagation();
event.preventDefault();
document.querySelectorAll(".active").forEach(function(elem) {
elem.classList.remove("active");
});
});
});

On-Click 'div' inside a 'div' executes both div's event handlers

I have a Div inside a Div, I am trying to keep different onClick handlers for both the div, but when Clicked on inner div, Both the event Handlers executes and the one of inner div first excuted and then Oter div,
I dont want to actually execute both.
Code:
<div style="width:100%;height:200px;background-color:yellow" onclick="alert('Div A');return false;">
This is Div A
<div style="width:20%;height:100px;background-color:red" onclick="alert('Div B');return false;">
This is Div B
</div>
</div>
Jsfiddle: fiddle
Just add event.stopPropagation(); inside the onclick of the inside <div>.
Like this :
<div style="width:100%;height:200px;background-color:yellow" onclick="alert('Div A');return false;">
This is Div A
<div style="width:20%;height:100px;background-color:red" onclick="event.stopPropagation(); alert('Div B');return false;">
This is Div B
</div>
</div>
<div style="width:100%;height:200px;background-color:yellow" onclick=" if (!e) var e = window.event;
e.cancelBubble = true;
if (e.stopPropagation) e.stopPropagation();alert('Div A');return false;">This is Div A
<div style="width:20%;height:100px;background-color:red" onclick=" if (!e) var e = window.event;
e.cancelBubble = true;
if (e.stopPropagation) e.stopPropagation();alert('Div B');return false;"> This is Div B</div>
</div>
add this code in your onclick function
if (!e) var e = window.event;
e.cancelBubble = true;
if (e.stopPropagation) e.stopPropagation();
fiddle
read more about event capturing and event bubbling here and here
$(document).ready(function () {
$(".yellow").click(function () {
alert("Div A");
});
$(".red").click(function (objEvent) {
objEvent.stopPropagation();
alert("Div B");
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div style="width:100%;height:200px;background-color:yellow" class="yellow">This is Div A
<div style="width:20%;height:100px;background-color:red" class="red"> This is Div B</div>
</div>
First: don't use inline click handlers. It's unsafe and inline handlers spawn a new js-interpreter on every activation;
Second: use some mechanism to identify your divs (for example data-id or just an id);
Third: using event delegation, you'll need only one handler and you don't have to worry about event capturing/bubbling.
For example:
// append handler to click for anything within the body of your document
document.body.addEventListener('click', reportFromDiv);
// one handler for divs with data-id attribute
function reportFromDiv(evt) {
var from = evt.target || evt.srcElement;
// do nothing for elements without the data-id attribute
if (!from.getAttribute('data-id')) {
return true;
}
return report(from.getAttribute('data-id'));
}
function report(str) {
document.querySelector('#report').textContent += 'clicked: ' + str + '\n';
}
<div style="width:100%;height:200px;background-color:yellow" data-id="divA">
This is Div A
<div style="width:20%;height:100px;background-color:red" data-id="divB">
This is Div B
</div>
</div>
<pre id="report"></pre>

Categories