Stackers,
I've got a curious case. There is a container element, that contains some other element with the class "clickable". The event listener to clickable 'click' is attached to the container, using delegation.
<input type="text"/>
<div class="container">
<div class="clickable">Click</div>
</div>
$container.on('click', '.clickable', incrementCounter);
There is also an input element outside the container. On 'change' event the content of the container is replaced with the identical one (using $container.html()) on the next event loop turn (to be realistic :-).
$input.on('change', function() {
setTimeout(function() {
$container.html('<div class="clickable">Click</div>');
}, 0);
});
Now the case: enter something in the input field (do not click anywhere or press Enter). Then immediately click on the clickable.
What happens: 'change' event causes the re-rendering of the container on the next event loop turn and the 'click' event is never generated.
It can be solved with replacement of the 'click' event with the 'mouseup'.
Question: why such a difference?
See plunk.
You are running into a race condition between the blur
and the click
events.
A click
event is actually a combined mousedown
and mouseup
events on the same element.
If you change the input and then click on your container, the events you have are:
mousedown (on row)
blur (on input)
mouse up (on row)
Since the mouse down and mouse up event were interfered (by the blur) your don't get the click
event.
I added a console.log
for the relevant events, you can check this snippet:
$(function() {
var rowHtml = '<div class="row">Click</div>';
var $rowsContainer = $('.rows-container');
var $counter = $('.counter');
var $blinker = $('.blinker');
var $input = $('input[type=text]');
$input.on('change', blink);
$rowsContainer.on('mousedown', function(e) {
console.log('mousedown', e.target);
});
$rowsContainer.on('mouseup', function(e) {
console.log('mouseup', e.target);
});
$rowsContainer.on('click', function(e) {
console.log('click', e.target);
});
$input.on('blur', function(e) {
console.log('blur', e.target);
});
$rowsContainer.on('click', '.row', incrementCounter);
// this works as expected
//$rowsContainer.on('mouseup', '.row', incrementCounter);
function incrementCounter() {
var oldValue = parseInt($counter.text(), 10) || 0;
$counter.text(1 + oldValue);
}
function blink() {
setTimeout(function() {
$rowsContainer.html(rowHtml);
}, 0)
$blinker.html('☼');
setTimeout(function() {
$blinker.html(' ');
}, 300);
}
});
.counter,
.blinker {
display: inline-block;
width: 1.2em;
}
.rows-container {
border: 1px dotted #ccc;
margin: 1em;padding: 2em;
}
.row {
padding: 1em;
background-color: #eef;
}
p {
font-size: 12px;
line-height: 12px;
}
<script data-require="[email protected]" data-semver="2.1.4" src="https://code.jquery.com/jquery-2.1.4.js"></script>
<h1>Click vs. mouseup whith DOM manipulation</h1>
<p>On the input field change event a little sun shortly appears on the right
and the content of the "Click" parent is redrawn with the identical content.
</p>
<p>Clicking on "Click" increments the counter. It works by delegation of the
event handling to the "Click" container.
</p>
<p>Try to enter something in the text field and trigger the "change" event
by clicking on "Click". Sun appears but counter is unchanged.</p>
<p>When listening to 'mouseup' event instead of the 'click' everything works.</p>
<h2>Why?</h2>
<span class="counter">0</span>
<span class="blinker"> </span>
<input type="text" />
<div class="rows-container">
<div class="row">Click</div>
</div>
You can use this example to see how exactly the mousedown/mouseup/click events works.
mousedown
on the container and mouseup
on some other element (like body
) but no click
event.click
event.