beatgammit
beatgammit

Reputation: 20225

Why can't a mouseup event prevent a click event

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);
<div class='wrapper'>
  <button class='child'>Click me</button>
</div>

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).

Upvotes: 27

Views: 31176

Answers (4)

Chet
Chet

Reputation: 19879

I have a partial solution here to cancel the click. However, I had to switch up the heirarchy - I'm listening to mouseup on the child and canceling the click on the parent.

Since mouseup happens before onclick and bubbled up the heirarchy, you can add an event listener on mouseup to cancel the click and then remove itself.

However, this solution doesn't work with the question's exact example because the parent listener gets called after the child listener. I would recommend trying to re-structure. You can also experiment with adding event listeners to the document and checking which element is getting clicked. I'm not sure exactly what the context of your problem is, but I was able to make something work like this in my use-case.

const stopNextClick = (element) => {
const onClick = (event) => {
    event.stopPropagation()
    element.removeEventListener("click", onClick)
}
setTimeout(() => {
    // Cleanup in case the click event never happened.
    element.removeEventListener("click", onClick)
}, 0)
element.addEventListener("click", onClick)
}

function onMouseUp(e) {
stopNextClick(e.target)
}

document.querySelector(".child").addEventListener("mouseup", (e) => stopNextClick(e.target))
document.querySelector(".wrapper").addEventListener("click", () => alert("clicked"))
<div class='wrapper'>
  <button class='child'>Click me</button>
</div>

Upvotes: 0

Alexander Shostak
Alexander Shostak

Reputation: 732

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

Upvotes: 1

Fran&#231;ois MENTEC
Fran&#231;ois MENTEC

Reputation: 1304

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.

Upvotes: 0

Joseph Spens
Joseph Spens

Reputation: 664

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.

Upvotes: 0

Related Questions