I'm trying to stop a particular click event from bubbling to document-root, which in result closes one of my popup. I need to stop bubbling of the event and body
or html
are my only options to intercept and stop it.
The date-picker popup is generated on the fly so I cannot use a direct event on .ui-icon
element, so I have registered a delegate event on body
element to stop it from bubbling.
(function ($) {
$(function () {
$('body').on('click', '.ui-icon', function (e) {
e.stopPropagation();
});
});
})(jQuery);
Surprisingly enough registering a direct event to body
element and checking the event's target works just fine.
(function ($) {
$(function () {
$('body').on('click', function (e) {
if ($(e.target).is('.ui-icon')) {
e.stopPropagation();
}
});
});
})(jQuery);
I am really at a loss, why the previous one does not work where the later does, both of them are supposed to do the same. What am I missing? It might have to do with jQuery datepicker getting recomposed (its whole content block is rebuilt on navigation) before the event reaches body (but it does not make sense)?
Snippet with the issue is added below. I just want the arrows (datepicker navigation) to stop bubbling to document/root level (which closes my popup) and because datepicker gets appended to body, the only available intercept points are body/html.
$(function() {
let popup = $('#some-popup').addClass('visible');
let input = $('#some-date');
let toggler = $('#toggler');
// binding popup
toggler.on('click', function(e) {
e.stopPropagation();
popup.toggleClass('visible');
});
// initializing jQuery UI datepicker
input.datepicker();
// closing popup on document clicks other than popup itself
$(document).on('click', function(e) {
let target = $(e.target);
if (target.is('.ui-icon, .ui-datepicker-prev, .ui-datepicker-next')) {
console.warn('shouldn\'t have reached this, got: ' + target.attr('class'));
}
if (!(target.is('#some-popup'))) {
popup.removeClass('visible');
}
});
// trying to prevent click from reaching document
$('body').on('click', '.ui-icon, .ui-datepicker-prev, .ui-datepicker-next', function(e) {
e.stopPropagation();
})
});
#some-popup {
padding: 15px 25px;
background: #000;
color: #fff;
display: none;
max-width: 200px;
}
#some-popup.visible {
display: block;
}
#toggler {
margin-bottom: 10px;
}
<head>
<link href="https://code.jquery.com/ui/1.11.4/themes/black-tie/jquery-ui.css" rel="stylesheet">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>
</head>
<body>
<div id="some-popup">
This is the popup
</div>
<button type="button" id="toggler">Show/Hide Popup</button>
<form>
<label for="some-date">The Date-Picker</label>
<input id="some-date" onclick="event.stopPropagation();" />
</form>
</body>
The delegate event does not fire because when you click on the arrows, at first the arrow button's click event is fired where jquery-ui-datepicker removes the whole calendar element from body
element and generates a new calendar for previous/next month.
You can verify if an element is removed or not by checking if the element has any parent <body>
tag i.e by checking the length of closest('body')
.
$('body').on('click', function (e) {
if ($(e.target).is('.ui-icon')) {
console.log($(e.target).closest('body').length);
// Prints 0 i.e this element is not a child of <body>
}
});
To fire the delegate event, the target element must be a child element of the event bound element, otherwise jQuery does not trigger the event. The following Demo confirms it.
$(function() {
$('.a').on('click', function() {
console.log('Direct Event');
})
$('.a').on('click', '.b,.c', function(e) {
console.log('Delegate Event');
})
$('.b').on('click', function() {
console.log('datepicker arrow event');
if($('input').is(':checked')) $(this).remove();
})
});
.a {
padding: 20px;
background: #ffc55a;
text-align: center;
}
.b {
margin-top: 5px;
padding: 5px;
background: #7ddbff;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="a">
This is the parent element (Delegate Bound Element)
<div class="b">
This is the Arrow element of date-picker (Delegate Target)<br>
Click on it to see how many events fire
</div>
</div>
<input type="checkbox" checked> Remove On<br>
With Remove Off 3 events fire<br>
With Remove On 2 events fire, Delegate event does not fire<br>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With