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

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>

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>

On element click Trigger event fire twice for input checkbox

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

How can I prevent parent element event execution from code inside children event

My question in the title probably looks vague. And I sketched an example for the question:
container.onclick = () => {
alert(0);
};
content.onclick = () => {
alert("how can I prevent here appearance alert(0) from parent element event?");
//how can I prevent parent event by content clicked?
};
#container{
height: 100px;
background-color: gray;
}
#content{
height: 50px;
width: 150px;
background-color: green;
}
<div id="container">
<div id="content"></div>
</div>k
This is a simple example. In a real project, I can't combine these two events into one, because the first one is programmatically assigned somewhere in the bowels of my framework and it shouldn't be removed from the EventListener after clicking on content
In General, is it possible to somehow interrupt the execution of the call chain event by clicking in the DOM layers? I tried to do this:
content.onclick = () => {
alert("how can I prevent here appearance alert(0) from parent element event?");
e.preventDefault();
return false;
//how can I prevent parent event by content clicked?
};
But this, of course, was not successful
You should pass the event by dependency injection to the specific method (content.onclick) and then stop the propagation of it.
container.onclick = () => {
alert(0);
};
content.onclick = (e) => {
e.stopPropagation();
alert("VoilĂ , this prevent that appears alert(0) from parent element event.");
};
#container{
height: 100px;
background-color: gray;
}
#content{
height: 50px;
width: 150px;
background-color: green;
}
<div id="container">
<div id="content"></div>
</div>
For this, you can use stop propogation of js like this
<div id="container">
<div id="content" onclick="event.stopPropagation();">
</div>
</div>
So when you click on content it will not trigger container event only.

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.

Chrome: Recover document focus after jquery fadeOut

I'm trying to have an overlay that I can toggle with keyboard keys.
However, once I hide the menu using fade out, the document won't receive my keydown events until I click in the window. How can I make the document receive focus so that it will listen directly after fade out has finished?
<div id="overlay" class="overlay">
<input type="text" value="test"/>
</div>
$('#overlay').on('keydown', function() {
$('#overlay').fadeOut(1000);
return false;
});
$(document).on('keydown', function() {
$('#overlay').fadeIn(1000);
return false;
});
.overlay {
position: fixed;
background: black;
color: white;
width: 50%;
height: 100px;
}
See jsFiddle also
Put cursor in input field, press one key.
It should fade out over a second. After that document is not receiving any keydown until I click in it with the mouse. How can I make document receive focus so that I could toggle the modes with just one keyboard key?
Edit: Tested the fiddle in different browsers. This problem seems to be specific for chrome.
Remove focus from your text field after fading out your overlay
Working Fiddle
$(document).ready(function () {
$('#overlay').on('keyup', function () {
$('#overlay').fadeOut(1000);
$("input[type='text']").blur();
return false;
});
$('body').on('keyup', function () {
$('#overlay').fadeIn(1000);
return false;
});
});

Categories