I am having issues with my click events due to nested elements and the nature of what I am trying to accomplish.
When I click on the .main, the selected class is added. However when I click on .close, the selected class is removed and then added back again. Is there anyway to get around this?
The reason why I have it setup this way is because I want to maximize my clickable area (the entire element).
$('.main').off('click.a').on('click.a', function() {
$('.child2', $(this)).addClass('selected');
});
$('.close').off('click.B').on('click.B', function() {
$(this).parent().removeClass('selected')
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
<div class="main">
<div>some content</div>
<div class="child2">
<span class="close">X</span>
</div>
</div>
The issue is because the .close element is a child of the .main. This means that the event fires on .close, and the class is removed. The event then propagates up the DOM to the .main element where it's caught by the other event handler and the class gets added on again.
To prevent the event from propagating up the DOM, call stopPropagation() on it from within the .close click handler:
$('.main').off('click.a').on('click.a', function() {
$(this).find('.child2').addClass('selected');
});
$('.close').off('click.B').on('click.B', function(e) {
e.stopPropagation();
$(this).parent().removeClass('selected')
});
.selected { color: #C00; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
<div class="main">
<div>some content</div>
<div class="child2">
<span class="close">X</span>
</div>
</div>
As a side note, using .off('event').on('event', fn) is a code smell. You may be better off using a single delegated event handler which you can bind only once when the page loads. It would depend on your exact use case, though.
You could double check it's actually the correct item.
$('.main').off('click.a').on('click.a', function(e) {
if (!$(e.target).hasClass('.main')){ return };
$('.child2', $(this)).addClass('selected');
});
$('.close').off('click.B').on('click.B', function(e) {
if (!$(e.target).hasClass('.close')){ return };
$(this).parent().removeClass('selected')
});
I've had similar issues when I wanted an event to go off when I click the whitespace in Table Cells but not the contents itself.. if that makes any sense
Related
I have a container <div> that has dynamically appended divs. The divs are given click handles through the body element, like this: $(body).on("click",".divClass",function); (I did it this way so that the bindings on the dynamically added elements can be done all in one place in the code, and to prevent tons of bindings). I need to put a stopPropagation() on the container div, so that the click handle on its parent doesn't get triggered. The problem is, when I put in the stopPropagation on the container div, it stops the child div from triggering (since it is actually the body that has the binding, not the child div)
Here is a fiddle that demonstrates my problem:
$("#A").click(function() {
console.log('This event should not fire when clicking innermost')
});
$("#B").click(function(e) {
e.stopPropagation();
});
$("body").on("click", "#C", function() {
console.log('This event should fire when clicking innermost')
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="A">
outermost
<div id="B">
middle
<div id="C">
innermost
</div>
</div>
</div>
I want the other children to propagate as normal, only the children in
#B should not propagate.
Taking this into account, your original code wasn't too far off. Seeing you are binding directly to #B in your example, I assume #B is static.
In that case use #B as the static reference instead of body.
$("#A").click(function(e) {
console.log('This event should not fire when clicking innermost')
});
$("#B").click(function(e) {
e.stopPropagation();
});
$("#B").on("click", "#C", function(e) {
console.log('This event should fire when clicking innermost')
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="A">
outermost
<div id="B">
middle
<div id="C">
innermost
</div>
</div>
</div>
I think this whole problem would be much simpler if you bound the delegated handler to #B in the first place. Then the events don't need to bubble up to the body and stopPropagation will have the desired effect.
$("#A").click(function() {
console.log('This event should not fire when clicking innermost')
});
$("#B").on("click", "#C", function(e) {
e.stopPropagation();
console.log('This event should fire when clicking innermost')
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="A">
outermost
<div id="B">
middle
<div id="C">
innermost
</div>
</div>
</div>
I have a series of click events which are causing me some propagation issues. The data for this container is loaded in via ajax hence the body on click method. Here's the code I have:
$(function () {
$("body").on("click","#top-div",function(){
console.log("top event");
});
$("body").on("click","#middle-div",function(e){
e.stopPropagation();
});
$("body").on("click","#bottom-div",function(){
console.log("bottom event");
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="top-div">
top
<div id="middle-div">
middle
<div id="bottom-div">
bottom
</div>
</div>
</div>
The top div has an event which the middle one needs to not inherit hence the stopPropagation(). However the bottom one needs to have an event of its own but the stopPropagation() from the middle one is stopping it from executing the event (if I remove the stop propagation the bottom event triggers but coincidentally so does the top)
How can I get around this bubbling issue?
I think what you want to happen is that the propagation is only stopped if it originated from the middle div; if it originated from the bottom div you want the event to bubble all the way to the top.
You need to call stopPropagation conditionally. If the event did not originate on or inside #bottom-div, you want to call it. You can test for this using closest:
if (!$(e.target).closest('#bottom-div').length) {
e.stopPropagation();
}
It is better to use closest for this, rather than testing the element's id directly, because with closest it will continue to work as expected even if there are other elements (a or em, for example) within the various divs.
$(function () {
$("body").on("click","#top-div",function(){
console.log("top event");
});
$("body").on("click","#middle-div",function(e){
if (!$(e.target).closest('#bottom-div').length) {
e.stopPropagation();
}
});
$("body").on("click","#bottom-div",function(){
console.log("bottom event");
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="top-div">
top
<div id="middle-div">
middle
<div id="bottom-div">
bottom
</div>
</div>
</div>
Bubbling works from most nested element to tree root, I think You misunderstanding it in some way. So for example click in bottom-div will:
call event on bottom-div --> call event on middle-div --> call event on top-div.
As I understand the need You want to some how check what div was clicked, so to have control on it just create single event and check clicked target like that:
$("body").on("click","#top-div",function(e){
if ( e.target.id ==='top-div'){
//click in top div
}
if ( e.target.id ==='middle-div'){
//click in middle div
}
if ( e.target.id ==='bottom-div'){
//click in bottom div
}
});
In this proposition You know exactly which div was clicked, but it will always be the most nested one, so target will be middle-div only when will be clicked part of middle-div which is not bottom-div, it will be for example padding space, the same is with top-div it will be target only if click will be in space without child elements.
More about event bubbling You can find in http://maciejsikora.com/standard-events-vs-event-delegation/.
Instead of attaching event to every div. You can consider doing it as follows
check this snippet
$(document).ready(function() {
$("body").on("click", "div", function(event) {
console.log(event.currentTarget.id + "event");
if (event.target.id === "middle-div") {
event.stopPropagation()
}
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="top-div">
hello
<div id="middle-div">
hi
<div id="bottom-div">
bott-mid-v
</div>
</div>
</div>
Hope it helps
<div id="outerDiv" onclick="javascript:addEvent()">
<div id="inner">
<a class='editEV' href="javascript:void(0)">event1</a>//*added dynamically from addEvent method.*
</div>
</div>
My script is:
$('#inner').on('click','.editEV',function(){
editEvent();
});
When I click on anchor addEvent() called first then editEvent(), but I want that when I click on div then addEvent() should call and when I click anchor then editEvent.
I am aware about bubbling so that's why I introduce an inner static div to bind listener, but still addEvent() calls first. I am unable to figure out how can I force to call editEvent first.
You are using both - inline event set ( onclick=.. ) and Jquery on. Choose one of them to have more clear code. I would choose jquery on, and solution for this problem is to create two events - one on div, second on edit element, but in second We need to stop bubbling by using e.stopPropagation method. Here full working example:
$(function(){
$("#outerDiv").on("click",function(e){
console.log("Add click");
});
$("#outerDiv").on("click",".editEV",function(e){
console.log("Edit click");
e.stopPropagation();//very important to stop propagate event
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="outerDiv" style="padding:20px; background:grey;">
<div id="inner">
<a class='editEV' >event1</a>
</div>
</div>
Without stopPropagation our event is going up to div and runs its event, stopPropagation avoids propagate event to upper elements.
You also use this if you don't want to alter your code.
$(function(){
$("#outerDiv").on("click",function(e){
addEvent();
});
$("#outerDiv").on("click",".editEV",function(e){
editEvent();
e.stopPropagation();
});
});
e.stopPropagation();
use this.
If you want to read more on .stopPropagation(), look here.
I've got a div which has multiple elements nested. If I press on the div, the buttons appear in the same DIV. If I press on the button however, the buttons dissapear as well (this is because of a toggleClass on the div)
I've tried returning but that isn't working as well. See below for a JSFiddle
http://jsfiddle.net/p168uLv2/
$(document).on('click', "[data-link=level1]", function() {
console.log("li clicked");
$(this).find(".knoppenbalk").toggleClass("displaynone");
});
$(document).on('click', "[data-link=solo]", function() {
console.log("solo BUTTON clicked");
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div data-link="level1" class="limodeknop">
<div class="overlay"></div>
<div class="modeknop">
<div class="titel">
<lrmodenaam>Level 1</lrmodenaam>
<lrsubmode>Tutorial</lrsubmode>
</div>
<div class="knoppenbalk displaynone">
<div data-link="solo" class="solo btn">Solo</div>
</div>
</div>
</div>
This is made with jQuery, and I'm working with Framework7, but I don't think thats an issue.
You should stop the propagation of the events (calling bubbling). You can search tons of articles about it.
http://jsfiddle.net/p168uLv2/1/
$(document).on('click',"[data-link=solo]", function(e){
console.log("solo BUTTON clicked");
e.stopPropagation();
});
This only apply to the child element, to avoid the parent click propagation.
You can stop an event propagating to parent elements with use of:
event.stopPropagation();
You will want to accept event as an argument to your inline functions.
$(document).on('click',"[data-link=solo]", function(e){
e.stopImmediatePropagation();
console.log("solo BUTTON clicked");
})
;
I am using jquery to expand/hide a piece of content. I then changed it such that the link used to expand/hide the content is a button and that broke it. Here is the code working before I changed to a button:
<body>
<div class="content">
<a class="toggle" href="">Expand box 1</a>
<div class="contentHidden" style="display:none;">Hidden 1</div>
</div>
<div class="content">
<a class="toggle" href="">Expand box 2</a>
<div class="contentHidden" style="display:none;">Hidden 2</div>
</div>
</body>
<script type='text/javascript'>
$(function() {
$(".toggle").click(function(event) {
$(event.target).parent(".content").find(".contentHidden").toggle('slow');
event.preventDefault()
});
});
</script>
However if I change the function to the following it no longer works:
$(function() {
$(".toggle").button({ icons: { primary: 'ui-icon ui-icon-arrowthick-1-e'} });
$(".toggle").click(function(event) {
$(event.target).parent(".content").find(".contentHidden").toggle('slow');
event.preventDefault()
});
});
Its as if the call to button() changes the hierarchy and my search no longer returns the '.contentHidden' div
Regards
Des
Yes, JQuery UI button() creates a new hierarchy below the original link (adds a couple of spans). One of these spans is the new clickable item, so it goes wrong for this reason alone (the clicked element is one level deeper than before), so use closest instead of parent:
$(".toggle").button({
icons: {
primary: 'ui-icon ui-icon-arrowthick-1-e'
}
});
$(document).on('click', '.toggle', function (event) {
$(event.target).closest(".content").find(".contentHidden").toggle('slow');
event.preventDefault()
});
Working JSFiddle here: http://jsfiddle.net/HxKLy/
The other issue, of using click instead of say a deferred on is actually a red herring in this instance, but I will retain below for reference.
Binding to specific DOM elements with click, means that as soon as you change the DOM that click event has been detached. Generally you will want to use the deferred calling syntax of on instead of a click, but in this case it makes no odds.
e.g.
$(document).on('click', '.toggle', function(event)...
instead of
$(".toggle").click(function(event)
Just to prove it was not the actual issue, here is a variation of the fiddle with the click back:
http://jsfiddle.net/HxKLy/1/