What is the correct W3C Click event specification? - javascript

Is it normal that in Firefox or a previous version of QTWebkit a click event is not fired in these cases:
mousedown on element > mouseup on child element >>> NO CLICK triggered
mousedown on child element > mouseup on parent element >>> NO CLICK triggered
http://www.w3.org/TR/DOM-Level-3-Events/#event-type-click
https://bugzilla.mozilla.org/show_bug.cgi?id=326851
Test URL: http://jsfiddle.net/3d6dzr02/
document.getElementById("test").addEventListener("click",function(){
alert("ok");
})
div {
padding:20px;
background:red;
}
<div id="test">
<div style="margin:20px; background:yellow;">
</div>
</div>
Works well on last chrome version
I don't know if chrome is OK or if it is Firefox that do correctly the spec.
So should it trigger event or not?

Indeed, Firefox behaves differently from IE and Chrome, which fire a 'click' on the common ancestor(?) when mousedown and mouseup happen on different elements. The votes from the browser vendors are:
Mozilla bug 1229143 ("Not receiving click event on the common ancestor of two elements") - no decision made
Chrome issue 484655 merged into 543776 ("Stop sending "click" on mouse drag across elements") - doesn't intend to change their behavior.
IE bug 809003 moved to Edge issue #103724 ("Unexpected Click event triggered when the elements below cursor at mousedown and mouseup events are different") - marked as fixed without any comments (I didn't test if it indeed is)
I think even the latest version of the spec (now called The UI Events) is unclear about this:
The click event type MUST be dispatched on the topmost event target indicated by the pointer, when the user presses down and releases the primary pointer button [...]
You could argue that this implies that the "topmost event target indicated by the pointer" is the same for the mousedown and the mouseup, but it's not clear.
Later in an example there's this text:
trigger a click event [..], since the user has stayed within the scope of the same element.
...which supports the hypothesis that the intention of the spec authors aligned with what was implemented in Firefox.

Related

Why do browsers inconsistently render incorrectly ordered jQuery?

Context:
I recently executed a block of jQuery code in which I incorrectly applied positioned on event handler before the event. The objective of the code was to produce an alert box on a change event.
Observation:
On my end, the alert box did not execute in the latest versions of FF, Chrome and IE. Other StackOverflow members (via live chat and another question) confirmed to me that the alert box executed on their end (across a variety of browsers) whereas others observed that the alert boxes did not.
Code:
Incorrectly ordered code:
//Event handler
$('#b').val($('#a').val()).change();
//Event
$('#b').on('change', function() {
alert("Change event fired on load.");
});
JSFiddle: http://jsfiddle.net/clarusdignus/rHYLM/
Correctly ordered code:
//Event
$('#b').on('change', function() {
alert("Change event fired on load.");
});
//Event handler
$('#b').val($('#a').val()).change();
JSFiddle: http://jsfiddle.net/clarusdignus/rHYLM/4/
My question:
Why do browsers inconsistently render incorrect jQuery with regards to incorrectly ordered event handlers and events?
According to w3 standard change event should propagate in bubble order.All do that except IE<9
Probably this is why there is inconsistency in firing of change event.

Internet Explorer leaks click event after adding an overlay in a jQuery mousedown handler

In a mousedown event-handler of a div another new div is created and appended to the body.
This new div has position:fixed (can also be position:absolute) and has 100% width and 100% height. Therefore it immediately covers the source div which triggered the mouse down event.
Now with the latest Google Chrome (v30), latest Firefox (v24), Opera v12.16 and even with a older Safari v5.1.1 (on Windows) after the mousedown event no click event gets fired on an event listener attached to the body.
Only Internet Explorer (both 9 and 10) does fire the click event on the body afterwards! Why? And how can this be prevented? Is this actually a bug in IE?
The HTML:
<div class="clickme">Click me</div>
The CSS:
.clickme {
background-color: #BBB;
}
.overlay {
position: fixed; /* or absolute */
left: 0;
top: 0;
height: 100%;
width: 100%;
background-color: #000;
}
The JavaScript:
$(document).on('click', function(event) {
console.log('body click');
});
$('.clickme').on('mousedown', function(event) {
console.log('div mousedown');
var mask = $('<div></div>');
mask.attr('class', 'overlay');
mask.appendTo('body');
});
Here is a the example with some additional comments: http://jsfiddle.net/Fh4sK/5/
After clicking the "Click me" div, only
div mousedown
should be written to the console, but in Internet Explorer it actually is
div mousedown
body click
I appreciate any help!
EDIT 1:
I found some resources describing the conditions when to trigger a click event:
http://www.quirksmode.org/dom/events/click.html:
"click - Fires when a mousedown and mouseup event occur on the same element."
http://www.w3.org/TR/DOM-Level-3-Events/#events-mouseevent-event-order
"...in general should fire click and dblclick events when the event target of the associated mousedown and mouseup events is the same element with no mouseout or mouseleave events intervening, and should fire click and dblclick events on the nearest common ancestor when the event targets of the associated mousedown and mouseup events are different."
I'm not 100% sure what the "correct" behaviour now actually should be (maybe IE is the only browser which handles it right?). From the last sentence, it seems that it is correct to fire the click event on the body, because the body is the "nearest common ancestor" of both div elements. There are some other statements on the referenced w3.org page above, which describe the behaviour if an element gets removed, but again I'm not sure if this applies here, as no element gets removed, but covered by an other element.
EDIT 2:
#Evan opened a bug report asking Microsoft to drop the described behaviour: https://connect.microsoft.com/IE/feedback/details/809003/unexpected-click-event-triggered-when-the-elements-below-cursor-at-mousedown-and-mouseup-events-are-different
EDIT 3:
In addition to Internet Explorer, Google Chrome recently started to have the same behaviour: https://code.google.com/p/chromium/issues/detail?id=484655
I bumped into this issue too. I decided I'd make a jQuery plugin to solve this issue and put it on GitHub.
It's all here, feedback is welcome : https://github.com/louisameline/XClick
#mkurz : thanks for finding that W3 directive, you saved me a lot of time.
#vitalets : I solved this issue because I use select2 like you (you led me to this topic). I'll fork the select2 repo and leave a message for the people interested in this.
I'll see if I can ask the Microsoft people to take a look at it and hopefully change that annoying click behavior.
I also struggled with such behavior. I've modified your fiddle to find out how all mouse events are triggered with dynamically created overlay:
http://jsfiddle.net/Fh4sK/9/
So, when mousedown handler of some element shows overlay on the same place where mousedown occured:
Chrome, FF:
mousedown triggered on element
mouseup triggered on overlay
click does not trigger
IE:
mousedown triggered on element
mouseup triggered on overlay
click triggered on BODY(!)
The same behavior if you hide mask in mousedown.
This issue can lead to weird things in IE with modals, masks, overlays etc..
Intresting thing: if you show overlay in mouseup instead of mousedown - everything works
The solution I found is to use mouseup instead of mousedown.
It can't be explained normally because both these events are triggered before click.
In that case all mouse events are triggered on element:
http://jsfiddle.net/Fh4sK/11/
mousedown triggered on element
mouseup triggered on element
click triggered on element
Hope this helps.
You could filter the document click by the target to exclude clicks on that div:
$(document).on('click', function(event) {
if(!$(event.target).hasClass('clickme')) {
console.log('body click');
}
});
If you want to stop bubbling of the click event, try this : (I don't have IE to test)
$(document).on('click', function(event) {
console.log('body click');
});
$('.clickme').on('mousedown', function(event) {
console.log('div mousedown');
});
$('.clickme').on('click', function(event) {
event.stopPropagation();
});
Click, mousedown and mouseup are differents events, independant.
here is a similar question

Why can't a mouseup event prevent a click event

jsfiddle
<div class='wrapper'>
<button class='child'>Click me</button>
</div>
function h(e) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
alert(e.type);
return false;
}
document.querySelector('.wrapper').addEventListener('mouseup', h, false);
document.querySelector('.child').addEventListener('click', h, false);
I expect this to prevent the 'click' event from firing, but it doesn't. However, changing mouseup to mousedown does in fact prevent the click event.
I've also tried setting the useCapture argument to true, and that also doesn't produce the desired behavior with mouseup. I've tested this on Chrome and Firefox. Before I file bugs, I figured I'd ask here.
Is this a bug in current browsers, or is it documented behavior?
I've reviewed the W3C standard (DOM level 2), and I wasn't able to find anything that could explain this behavior, but I could have missed something.
In my particular case, I'm trying to decouple two pieces of code that listen to events on the same element, and I figured using capture events on the part that has priority would be the most elegant way to solve this, but then I ran into this problem. FWIW, I only have to support officially supported versions of FF and Chrome (includes ESR for FF).
Check out this quirksmode article
The click event:
Fires when a mousedown and mouseup event occur on the same element.
So when the mouse click is released, both the mouseup and click events are fired, click doesn't wait for the mouseup callback to finish. Almost always, mouseup and click can be used synonymously.
In order to cancel the click, like you demonstrated, you can return false in the mousedown event callback which prevents the click event from ever completing.
I just want to provide my work around for this issue:
let click_works = true
this.addEventListener('mousedown', e => {
click_works = // condition why the click may work or not
})
this.addEventListener('click', e => {
if (click_works) // Do your stuff
})
Hopefully, it will help someone.
Finally found a way to prevent click event from firing. Tested on latest Chromium and Firefox. It may be some bug or implementation details.
Solution
Handle onpointerdown or onpointerup event, remove the element and insert it in the same position.
<span>
<button onpointerdown="let parent = this.parentElement; this.remove(); parent.appendChild(this);" onclick="alert();">TEST</button>
</span>
Result
onpointerdown
onmousedown
onpointerup
onmouseup
<-- no click event occures

Is there no Event.fromElement in a dragenter/dragover event?

I was playing with drag n drop in full forms (so no instant upload). I though small part was gonna be highlighting a certain fieldset when hovered over with a file. Enter dragover and dragenter events (and dragleave etc).
Turns out it's not such a small part. The Fiddle: http://jsfiddle.net/rudiedirkx/epp74/
Try it out: drag over a fieldset and move around a bit. The first over triggers the fieldset's dragenter event (fieldset is yellow). The moving around after that (within the same fieldset) triggers dragenters and dragleaves (fieldset no more yellow), which is bad.
Which is why I wanted to make what IE made for mouseover and mouseout a long time ago: mouseenter and mouseleave (they trigger just once). For drag events, the exact same thing applies: they should trigger only once in the exact same way. JS libraries spoof these IE events by using Event.fromElement and Event.toElement (and compare them against the event owner element). (See jQuery or Mootools source for specifics.)
To make the same for drag events, I need the same fromElement and toElement. You can see in the Fiddle, I try, but I can't find them.
Anybody know where they are? Why they're not available?
I'm using Chrome primarily, which doesn't have a fromElement in the dragenter event, but does have a toElement in the dragleave event. In Firefox it's slightly worse (but more logical): both are empty.
Any and all ideas are so very welcome.
edit
After a little more debugging I've found out that Chrome's toElement in dragleave isn't always correct. It's never 'bigger' thanthis, but sometimes it should be: when I leave the fieldset (this) to its parent form (toElement). When I do that, both this and toElement are the fieldset (which is incorrect, right?).
edit Solution:
I ended up with something like this: http://jsfiddle.net/rudiedirkx/Lwd3md71/ which ignores elements in the event, and uses the event coordinates to find the element under the mouse. To make it trigger max once per animation frame, it uses requestAnimationframe, which results into 31-59 fps.
Firefox provides the relatedTarget event property, but Chrome and Safari don't. Sadly, this issue has been open for a couple years as this Chrome bug and this Webkit bug.
Edit: The issue has been fixed in Chrome.
There is a way of faking the relatedTarget for a "dragleave" event, which is to set a variable from the accompanying "dragenter" event -- since dragleave is always preceded by dragenter, a variable set in the latter will be available to the former:
var relatedTarget = null;
document.addEventListener('dragenter', function(e)
{
relatedTarget = e.target;
}, false);
document.addEventListener('dragleave', function(e)
{
console.log('target = ' + e.target + ' relatedTarget = ' + relatedTarget);
}, false);
It won't work the other way round, but you don't really need dragenter for anything else if you use it this way -- i.e. the dragleave alone is enough to tell you when the mouse is moving into, or entirely out of, a particular element.

Webkit <button onclick> does not fire in certain situations

I noticed that in Webkit the <button> element does not fire the onclick event when the mouse is moved from/to child elements of the button during the click. With other words: when the mousedown and mouseup events do not happen on the same element - even if both are children of the button.
The same happens when clicking/releasing on/out of the pixels of the button text.
To clarify I made a testcase: http://jsfiddle.net/gx9B3/
It works fine in FireFox. Fails in Chrome 15 and QtWebkit 4.7.1
Is there a way around this? I need a solution specifically for Webkit because my project is targeted to this browser only.
Solution
I could solve this problem based on the method suggested by Jan Kuča (the solution I accepted). Some additional tweaks were necessary, especially introducing a timer to avoid double clicks. Have a look at my fully working solution at JSFiddle:
http://jsfiddle.net/mwFQq/
You could set up a mousedown listener on document.body (to fix the problem on the whole page). You would check if the mousedown event originated from an HTMLButtonElement (or from any of its child elements) and if it did, you set up a mouseup listener (on the button so it does not have to bubble too much) that will check the target property of the mouseup event. If it is contained in the button and is different from the target of the mousedown event, you fire a click event like this:
var e = document.createEvent('Events');
e.initEvent('click', true, true);
button.dispatchEvent(e);
(Do this only for WebKit-based browsers so that you don't get multiple click events in other browsers. Or you could call the preventDefault method of the mousedown event as it should also prevent firing the click event.)
You could try adding this CSS style:
button * {
pointer-events: none;
}
Child elements will then ignore mouse events and the click will come from the button element itself. Here's an example http://jsfiddle.net/Tetaxa/gx9B3/2/
You can also solve it by pure CSS trick. Just place pseudo element over <button> to cover text:
button {
position:relative;
}
button:after {
position:absolute;
top:0;
left:0;
right:0;
bottom:0;
content:'';
}

Categories