On element click Trigger event fire twice for input checkbox - javascript

when i click on the div element should trigger click on checkbox only once but for some reason i get event fired twice , i saw other topics with similar problem but noone helped me
$('div').click(function(e) {
$('input').trigger('click');
check();
});
function check() {
if ($('input').is(':checked')) {
console.log('input cheked')
} else {
console.log('unchecked')
}
}
.test {
height: 150px;
width: 150px;
background: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="test">
<input type="checkbox" name="">
</div>

The issue is because the click occurs on the div, which triggers a click on the child checkbox which in turn propagates up the DOM and runs the click handler on the div again.
If you are trying to create a bigger hit-area for the checkbox, just use a label element instead. Then you get this behaviour for free without needing any JS.
If you want to know the state of a checkbox when it's changed, hook a change event handler to it. Try this:
$(':checkbox').on('change', function() {
if (this.checked) {
console.log('input checked')
} else {
console.log('unchecked')
}
});
.test {
height: 150px;
width: 150px;
background: red;
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<label class="test">
<input type="checkbox" name="">
</label>

That is because, when you are trigerring click on the input the event is being bubbles to all its parents. To stop that use e.stopPropagation on the click event handler of input.
$('input').click(function(e) {
e.stopPropagation();
});
Read more about bubbling and capturing here.
Check the working code below:
$('div').click(function(e) {
$('input').trigger('click');
check();
});
$('input').click(function(e) {
e.stopPropagation();
});
function check() {
if ($('input').is(':checked')) {
console.log('input cheked')
} else {
console.log('unchecked')
}
}
.test {
height: 150px;
width: 150px;
background: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="test">
<input type="checkbox" name="">
</div>

Your checkbox is inside div and you are binding click evet to div and from that you are triggering checkbox click event which again triggers click of div. That's why it's triggering 2 times.
You can directly go for checkbox change event:
$(':checkbox').on('change', function() {
if (this.checked) {
console.log('input checked')
} else {
console.log('unchecked')
}
});
.test{
height: 150px;
width: 150px;
background: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="test">
<input type="checkbox" name="">
</div>

JQuery uses event bubbling when setting up events. This means that when you click the input the event is fired once for the input and then it 'bubbles' up the DOM tree to the parent DIV. This then notices the click event on the DIV and fires again. Therefore the event fires twice, once for the input and again for the DIV.
To stop this you will need to use the 'capture' technique instead of event bubbling. This would mean that you would use addEventListener and pass in the option as the third argument as true.
See here: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
Also see here to understand bubbling vs capturing: https://javascript.info/bubbling-and-capturing

Related

JavaScript click event targetting element only, not its parent container

I have this:
<div onclick="myFunc()" style="height:200px;width:200px;">
<button></button>
</div>
I want myFunc to execute when any place on the div is clicked EXCEPT for the button. How can I do this?
On the button's click event, you need to cancel propagation. Or stop 'bubbling up'.
See https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation
So on your button's click event - you need something like:
function button_click(event) {
event.stopPropagation();
console.log("button clicked.");
}
By default, an element's click event gets passed to its parent.
document.querySelector('#mydiv').addEventListener('click', myFunc)
function myFunc(event) {
if (event.target.tagName !== 'BUTTON') {
console.log('works')
}
}
#mydiv {
background: red;
height: 200px;
width: 200px;
}
<div id="mydiv">
<button>click me</button>
</div>
If your contents are going to be more complex than just a single button and you want to make sure you're only running when you click on the parent element, you could use: e.target === e.currentTarget to detect when the event is occurring on that element specifically. (Documentation: target, currentTarget)
This avoid having to check for every child element, or prevent every child from propagating events. (But if you only have a single child element, one of the other answers would be simpler)
document.getElementById('example').addEventListener('click', myFunc)
function myFunc(e) {
if (e.target === e.currentTarget) {
console.log('Parent div clicked')
}
}
#example {
width: 100px;
height: 100px;
}
div,
span {
border: 1px solid #555;
}
<div id="example">
<button>A</button>
<div>B</div>
<span>C</span>
</div>

How to make sure clickable objects don't propagate to the wrong element?

Languages involved: HTML, CSS, JS
Context: I'm relatively new to web development. I have two elements overlapping each other. One is a slider, one is a div. The slider is on top of the div.
Code snippets:
<div id="myDiv">
<input id="mySlider" type="range" min=1 max=100 step=1>
</div>
and
initListeners() {
document.getElementById("myDiv").addEventListener("click", divFunction);
document.getElementById("mySlider").addEventListener("input", sliderFunction);
}
I need to make it that when you click the slider, it doesn't click the div. How would I go about doing that? I've tried z-index, but that doesn't seem to change anything.
Thanks in advance!
As I'm sure you've figured out by now, events in JavaScript by default bubble up from a child to a parent. You need to stop that from happening at the child level, also known as preventing propagation.
Using the stopPropagation function, you can handle this as follows:
function sliderFunction(e) {
e.stopPropagation();
}
Simple. That event will no longer reach the parent.
EDIT
While stop propagation is the correct method to use, event listeners must also match in type. Therefore, both the slider and the parent DIV must have click event listeners (instead of input and click). stopPropagation stops propagation of a specific type of event.
function divFunction() {
console.log('DIV clicked!');
}
function sliderFunction(event) {
event.stopPropagation();
console.log('Slider clicked!');
}
function initListeners() {
document.getElementById('myDiv').addEventListener('click', divFunction);
document.getElementById('mySlider').addEventListener('click', sliderFunction);
}
initListeners();
/* unnecessary visual aides */
body *:not(label) {
padding: 2rem;
outline: 1px solid red;
position: relative;
}
label {
display: inline-block;
position: absolute;
background: #222;
color: #fff;
top: 0; left: 0;
}
<div id="myDiv">
<label>#myDiv</label>
<div id="tools">
<label>#tools</label>
<input type="range" id="mySlider">
</div>
</div>
You can also check the target once you fire that click event. I've used this approach before:
JSFiddle: http://jsfiddle.net/L4ck7ygo/1/
function divFunction(e) {
if (e.target !== this) {
return;
} else {
console.log('hit');
}
}
When the fiddle first loads, click the slider and you'll see the console log out some text. To see it work, remove the line that is being pointed to and rerun the fiddle. Now when you click the slider, you won't see anything logged in the console, but if you click on the div and not the slider, it will log to the console.
function initListeners() {
document.getElementById("myDiv").addEventListener("click", divFunction);
document.getElementById("mySlider").addEventListener("input", sliderFunction);
}
initListeners();
function divFunction(e) {
console.log('Firing...') // <-- This will log on any click
if (e.target !== this) {
return;
} else {
console.log('hit'); // <-- This will NOT log except for div click
}
}
function sliderFunction() {
console.log('Doing stuffs...');
}
<div id="myDiv">
<input id="mySlider" type="range" min=1 max=100 step=1>
</div>
UPDATE: Stupidity on my part. I had the ordering wrong for the elements which caused propagation to not act as intended.

Why are my events on button fired after events on parent elements?

Intro
I am extending photoswipe with my own button & modal dialog, similar to built in share dialog.
I already made code that worked, but then followed these modifications to photoswipe:
https://github.com/dimsemenov/PhotoSwipe/issues/1209
Now it doesn't work anymore. Issue is that photoswipe's event handlers get called before mine, so it appears as if user clicked on photoswipe controls and photoswipe hides image, controls & everything and only my modal is visible.
Diagnostics
I have modified onControlsTap and onGlobalTap and my button click to log to console and I see they are fired in this order:
onControlsTap
onGlobalTap
Settings button click
Html on the other hand looks like this:
<div id="globalTapContainer">
<div id="controlTapContainer">
<button id="myButton"></button>
</div>
</div>
And events are registered using addEventListener(..., false)
Code
This is my code which binds to click event
$("#pswp__settings__dropdown_background, .pswp__button--settings")
.click(function(ev) {
console.log('Settings button click');
ev.stopPropagation();
toggleSettings();
});
This is photoswipe code that binds events.
_controls = framework.getChildByClass(pswp.scrollWrap, 'pswp__ui');
// ...
framework.bind(_controls, 'pswpTap click', _onControlsTap);
framework.bind(pswp.scrollWrap, 'pswpTap', ui.onGlobalTap);
var framework = {
// ...
bind: function(target, type, listener, unbind) {
var methodName = (unbind ? 'remove' : 'add') + 'EventListener';
type = type.split(' ');
for(var i = 0; i < type.length; i++) {
if(type[i]) {
target[methodName]( type[i], listener, false);
}
}
}
}
My button and modal are one of child nodes of pswp__ui.
Question
How is it possible that their events are called before mine when I have registered click event to a specific button?
What to do to make photoswipe events not fire when you click on my controls?
I'm not familiar with photoswipe, but its events use a custom event called pswpTap, not click. Presumably this fires when an element is tapped or when the mouse button is pressed. click events don't fire until the mouse button is released, so that would explain why their events are firing before yours.
Example:
$('#outerdiv').on('mousedown', function() {
console.log('outer mousedown');
});
$('#innerdiv').on('click', function() {
console.log('inner click');
});
#outerdiv {
width: 100px;
height: 100px;
background-color: blue;
}
#innerdiv {
width: 40px;
height: 40px;
background-color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="outerdiv">
<div id="innerdiv"></div>
</div>
You should presumably be able to prevent this by having your element handle and cancel the mousedown event. You may also need to add an event handler for tap events if they work differently from mousedown (I'm not sure whether they are).
$('#outerdiv').on('mousedown', function() {
console.log('outer mousedown');
});
$('#innerdiv').on('mousedown', function(event) {
console.log('inner mousedown');
event.stopPropagation();
});
$('#innerdiv').on('click', function() {
console.log('inner click');
});
#outerdiv {
width: 100px;
height: 100px;
background-color: blue;
}
#innerdiv {
width: 40px;
height: 40px;
background-color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="outerdiv">
<div id="innerdiv"></div>
</div>

Click event not emitted when mousedown/mouseup do add/removeClass on element

I'm attaching mousedown, mouseup and click handlers to an element. On mousedown I add a class to the element, on mouseup I remove the class, and on click I do some work. (This is a simplification of the context. In my project the click event is handled by a 3rd party component.)
The problem I'm having is that the click event is never emitted in Safari and Firefox, but it works just fine in Chrome. (I don't know what IE does. I don't have access to it, and don't care about it.)
The code is as follows:
HTML:
<div id="clickme">
<div class="normal"></div>
<div class="highlight"></div>
</div>
<input type="text" id="textinput"/>
CSS:
#clickme:not(.active) > .highlight {
display: none;
}
#clickme.active > .normal {
display: none;
}
.normal, .highlight {
width: 100px;
height: 100px;
}
.normal {
background: blue;
}
.highlight {
background: red;
}
JS:
var clickme = $('#clickme');
var textinput = $('#textinput');
clickme.on('mousedown', function(e) {
clickme.addClass('active');
// ^-- comment this out and the click event starts working
});
clickme.on('mouseup', function(e) {
clickme.removeClass('active');
// ^-- comment this out and the click event starts working after the second click
});
clickme.on('click', function(e) {
e.preventDefault();
textinput.val(Date.now());
});
JSFiddle: https://jsfiddle.net/xLskk3po/14/
JSFiddle without JQuery: https://jsfiddle.net/xLskk3po/15/ It shows that it's not a JQuery problem.
I stumbled upon this SO question: When a mousedown and mouseup event don't equal a click and it looks like my issue is similar to that. So I did something silly: I put a transparent, absolutely positioned element on top.
HTML:
<div id="clickme">
<div class="normal"></div>
<div class="highlight"></div>
<div class="abs"></div> <!-- this is the absolute element, covering #clickme -->
</div>
<input type="text" id="textinput"/>
That fixed it.

Prevent custom event from executing

I want to prevent custom event on parent when child is clicked. Note that I don't have access to the code of parent event. I've tried doing e.preventDefault() on the button itself but it doesn't help.
Is there any way of ignoring all parent events when something inside of it is clicked?
$(function(){
// Note that this is just an example, I don't have access to this code
// This is some custom event inside custom plugin
$('.container').on('click', function() {
alert('This should be alerted only if you click on green box');
});
$('.btn').on('click', function() {
// Here I want to make sure that *parent* events are not triggered.
alert('Button is triggered, green box should be not triggered');
});
});
.container {
width: 300px;
height: 200px;
background: green;
padding-top: 100px;
}
.btn {
width: 100px;
height: 100px;
display: block;
margin: auto;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="container">
<button class="btn">Click Me</button>
</div>
Since you're using jQuery, you can use the event.stopPropagation() method. The event.stopPropagation() method stops the bubbling of an event to parent elements, preventing any parent event handlers from being executed. You can see it in action here
$(document).ready(function () {
$("#button").click(function (event) {
alert("This is the button.");
// Comment the following to see the difference
event.stopPropagation();
});
$("#outerdiv").click(function (event) {
alert("This is the outer div.");
});
});
In this simple example, if you click on the button, the event is handled by its own handler and it won't bubble up the DOM hierarchy. You can add a very simple handler calling event.stopPropagation() on the button and it won't bubble up. No need to mess with the parent's JS.

Categories