Cancel click event in the mouseup event handler
Asked Answered
T

16

41

Writing some drag&drop code, I'd like to cancel the click events in my mouseup handler. I figured preventing default should do the trick, but the click event is still fired.

Is there a way to do this?


This doesn't work:

<div id="test">test</div>
<script>
$("#test").mouseup (function (e) {
  var a = 1;
  e.preventDefault();
});
$("#test").click (function (e) {
  var a = 2;
});
Throughway answered 27/12, 2011 at 10:42 Comment(2)
Doesn't the click event happen before the mouse up?Jazminejazz
Nope.. it happens after the mouseupThroughway
B
6

I had the same problem and didn't found a solution either. But I came up with a hack that seems to work.

Since the onMouseUp handler doesn't seem to be able to cancel the click on a link with preventDefault or stopEvent or anything, we need to make the link cancel itself. This can be done by writing an onclick attribute which returns false to the a-tag when the drag begins, and removing it when the drag ends.

And since the onDragEnd or onMouseUp handlers are run before the click is interpreted by the browser, we need to do some checking where the drag ends and which link is clicked and so on. If it ends outside the dragged link (we dragged the link so that the cursor isn't on the link anymore), we remove the onclick handler in the onDragEnd; but if it ends where the cursor is on the dragged link (a click would be initiated), we let the onclick-handler remove itself. Complicated enough, right?

NOT COMPLETE CODE, but just to show you the idea:

// event handler that runs when the drag is initiated
function onDragStart (args) {
  // get the dragged element
  // do some checking if it's a link etc
  // store it in global var or smth

  // write the onclick handler to link element
  linkElement.writeAttribute('onclick', 'removeClickHandler(this); return false;');
}

// run from the onclick handler in the a-tag when the onMouseUp occurs on the link
function removeClickHandler (element) {
  // remove click handler from self
  element.writeAttribute('onclick', null);
}

// event handler that runs when the drag ends
function onDragEnds (args) {
  // get the target element

  // check that it's not the a-tag which we're dragging,
  // since we want the onclick handler to take care of that case
  if (targetElement !== linkElement) {
    // remove the onclick handler
    linkElement.writeAttribute('onclick', null);
  }
}

I hope this gives you an idea of how this can be accomplished. As I said, this is not a complete solution, just explaining the concept.

Beutner answered 19/1, 2012 at 14:18 Comment(0)
T
47

Use the event capture phase

Put an element around the element you want to cancel the click event for, and add a capture event handler to it.

var btnElm = document.querySelector('button');

btnElm.addEventListener('mouseup', function(e){
    console.log('mouseup');
    
    window.addEventListener(
        'click',
        captureClick,
        true // <-- This registeres this listener for the capture
             //     phase instead of the bubbling phase!
    ); 
});

btnElm.addEventListener('click', function(e){
    console.log('click');
});

function captureClick(e) {
    e.stopPropagation(); // Stop the click from being propagated.
    console.log('click captured');
    window.removeEventListener('click', captureClick, true); // cleanup
}
<button>Test capture click event</button>

JSFiddle Demo

What happens:

Before the click event on the button is triggered the click event on the surrounding div gets fired because it registered itself for the capture phase instead of the bubbling phase.

The captureClick handler then stops the propagation of it's click event and prevents the click handler on the button to be called. Exactly what you wanted. It then removes itself for cleanup.

Capturing vs. Bubbling:

The capture phase is called from the DOM root up to the leafs while the bubbling phase is from the leafs up the root (see: wonderful explanation of event order).

jQuery always adds events to the bubbling phase that's why we need to use pure JS here to add our capture event specifically to the capture phase.

Keep in mind, that IE introduced the W3C's event capturing model with IE9 so this won't work with IE < 9.


With the current Event API you can't add a new event handler to a DOM Element before another one that was already added. There's no priority parameter and there's no safe cross-browser solution to modify the list of event listeners.

Trisaccharide answered 29/11, 2013 at 16:49 Comment(1)
you can use the once option of the eventListener to remove the eventListener after the clickTower
Y
31

There is a solution!

This approach works for me very well (at least in chrome):

on mousedown I add a class to the element that is currently being moved and on mouseup I remove the class.

All that class does is sets pointer-events:none

Somehow this makes it work and click event is not fired.

Yoko answered 17/6, 2014 at 21:36 Comment(1)
Looks nice but didn't work for me on Chrome 80; pointer-events: none also canceled the mouseup...Enceladus
S
10

The best solution for my situation was:

let clickTime;

el.addEventListener('mousedown', (event) => {
  clickTime = new Date();
});

el.addEventListener('click', (event) => {
  if (new Date() - clickTime < 150) {
    // click
  } else {
    // pause
  }
});

This gives the user 150ms to release, if they take longer than 150ms it's considered a pause, rather than a click

Selfrealization answered 23/6, 2018 at 22:8 Comment(0)
B
6

I had the same problem and didn't found a solution either. But I came up with a hack that seems to work.

Since the onMouseUp handler doesn't seem to be able to cancel the click on a link with preventDefault or stopEvent or anything, we need to make the link cancel itself. This can be done by writing an onclick attribute which returns false to the a-tag when the drag begins, and removing it when the drag ends.

And since the onDragEnd or onMouseUp handlers are run before the click is interpreted by the browser, we need to do some checking where the drag ends and which link is clicked and so on. If it ends outside the dragged link (we dragged the link so that the cursor isn't on the link anymore), we remove the onclick handler in the onDragEnd; but if it ends where the cursor is on the dragged link (a click would be initiated), we let the onclick-handler remove itself. Complicated enough, right?

NOT COMPLETE CODE, but just to show you the idea:

// event handler that runs when the drag is initiated
function onDragStart (args) {
  // get the dragged element
  // do some checking if it's a link etc
  // store it in global var or smth

  // write the onclick handler to link element
  linkElement.writeAttribute('onclick', 'removeClickHandler(this); return false;');
}

// run from the onclick handler in the a-tag when the onMouseUp occurs on the link
function removeClickHandler (element) {
  // remove click handler from self
  element.writeAttribute('onclick', null);
}

// event handler that runs when the drag ends
function onDragEnds (args) {
  // get the target element

  // check that it's not the a-tag which we're dragging,
  // since we want the onclick handler to take care of that case
  if (targetElement !== linkElement) {
    // remove the onclick handler
    linkElement.writeAttribute('onclick', null);
  }
}

I hope this gives you an idea of how this can be accomplished. As I said, this is not a complete solution, just explaining the concept.

Beutner answered 19/1, 2012 at 14:18 Comment(0)
Y
4

The problem is there's an element. It needs to respond to clicks. It also needs to be dragged. However, when it's dragged, it needs to not trigger click when it is dropped.

A little late, but maybe it'll help someone else. Make a global variable named "noclick" or something and set it to false. When dragging the item, set noclick to true. In your click handler, if noclick is true, set it to false and then preventDefault, return false, stopPropagation, etc. This won't work on Chrome though since Chrome already has the desired behavior. Just make it so that the drag function only sets noclick to true if the browser isn't Chrome.

Your click handler will still get fired, but at least it has a way to know that it just came back from drag and behave accordingly.

Yamada answered 20/5, 2012 at 22:58 Comment(2)
And when should noclick be unset? click fires after the drop (mouseup) for me.Rhine
This seems to work for me: setTimeout(function() { noClick = false; }, 0);Berryberryhill
R
4

As they are different events, you cannot cancel onclick from onmouseup, if you call preventDefault or cancelBubble, or whatever, you are stopping the onmouseup event from being processed any further. The onclick event is still pending, yet to be fired, so to speak.

What you need is your own boolean flag, e.g. isDragging. You can set this to true when dragging starts (e.g. within onmousedown, or whatever).

But if you reset this to false directly from onmouseup, you will not be dragging any more when you receive your onclick event (isDragging == false), because onmouseup fires before onclick does.

So what you need to do is use a short timeout (e.g. setTimeout(function() {isDragging = false;}, 50);), so when your onclick event is fired, isDragging will still be true, and your onclick event handler can simply have if(isDragging) return false; before it does anything else.

Rodriguez answered 17/6, 2012 at 20:21 Comment(1)
The onmouseup and onclick events will be queued together, so no timeout value will be necessary, you just need to create a timeout to allow onclick to be called before isDragging is set back to false, but the timeout can be 0.Rodriguez
T
3

Option #1:

Probably the easiest solution of all is Event.stopImmediatePropagation(). While this comes with the caveat that all other click event listeners on that same element will be canceled when called, in this specific case it would work.

<div id="test">test</div>
<script>
$("#test").mouseup (function (e) {
  var a = 1;
});

$("#test").click (function (e) {
  // You should add a check here to only stop
  // if the element was actually moved
  e.stopImmediatePropagation();
});

$("#test").click (function (e) {
  var a = 2;
});

As an example, see this codepen:
https://codepen.io/guidobouman/pen/bGWeBmZ

Option #2:

If you own the other click handler, an alternative (safer) solution would be to check in the click handler (the one you want to prevent) if the event default action was prevented.

<div id="test">test</div>
<script>
$("#test").mouseup (function (e) {
  var a = 1;
});

$("#test").click (function (e) {
  // You should add a check here to only prevent
  // if the element was actually moved
  e.preventDefault();
});

$("#test").click (function (e) {
  if(e.defaultPrevented) {
    return;
  }

  var a = 2;
});
Tindal answered 8/7, 2021 at 16:39 Comment(0)
A
0
$(document).mouseup(function(event){

    event.preventDefault(); // this prevents only a default action but previously assigned listeners will be called
    event.stopImmediatePropagation() // if there are  others listeners which that shouldn't call 
}
Amalekite answered 27/12, 2011 at 11:30 Comment(0)
F
0

It might be possible but I'm not sure if you can handle this kind of evt management with jQuery. This code example should not be far away from your expectation or at least give you a direction.

function AddEvent(id, evt_type, ma_fonction, phase) {
    var oElt = document.getElementById(id);
    // modèle W3C mode bubbling
    if( oElt.addEventListener ) {
        oElt.addEventListener(evt_type, ma_fonction, phase);
    // modèle MSIE
    } else if( oElt.attachEvent ) {
        oElt.attachEvent('on'+evt_type, ma_fonction);
    }
    return false;
}

function DelEvent(id, evt_type, ma_fonction, phase) {
    var oElt = document.getElementById(id);
    // modèle W3C mode bubbling
    if( oElt.removeEventListener ) {
        oElt.removeEventListener(evt_type, ma_fonction, phase);
    // modèle MSIE
    } else if( oElt.detachEvent ) {
        oElt.detachEvent('on'+evt_type, ma_fonction);
    }
    return false;
}

    var mon_id = 'test';
    var le_type_evt = "mouseup";
    var flux_evt = false; // prevent bubbling
    var action_du_gestionnaire = function(e) {
        alert('evt mouseup on tag <div>');

        // 1ère méthode : DOM Lev 2
        // W3C
            if ( e.target )
                e.target.removeEventListener(le_type_evt, arguments.callee, flux_evt);
        // MSIE
            else if ( e.srcElement )
                e.srcElement.detachEvent('on'+le_type_evt, arguments.callee);

        // 2ème méthode DOM Lev2
        // DelEvent(mon_id, le_type_evt, action_du_gestionnaire, flux_evt);
    };


    AddEvent(mon_id, le_type_evt, action_du_gestionnaire, flux_evt);
Frentz answered 30/12, 2011 at 23:50 Comment(0)
L
0

i recently faced with the same problem. Here's my solution :)

    initDrag: function (element) {
        var moved = false,
            target = null;

        function move(e) {
            // your move code
            moved = true;
        };

        function end(e) {
            var e = e || window.event,
                current_target = e.target || e.srcElement;

            document.onmousemove = null;
            document.onmouseup = null;

            // click happens only if mousedown and mouseup has same target
            if (moved && target === current_target) 
                element.onclick = click;

            moved = false;
        };

        function click(e) {
            var e = e || window.event;

            e.preventDefault();
            e.stopPropagation();

            // this event should work only once
            element.onclick = null;
        };

        function init(e) {
            var e = e || window.event;
            target = e.target || e.srcElement;

            e.preventDefault();

            document.onmousemove = move;
            document.onmouseup = end;
        };

        element.onmousedown = init;
    };
Lickspittle answered 29/7, 2012 at 13:28 Comment(0)
L
0

This is my solution for drag and click on same element.

$('selector').on('mousedown',function(){
  setTimeout(function(ele){ele.data('drag',true)},100,$(this));
}).on('mouseup',function(){
  setTimeout(function(ele){ele.data('drag',false)},100,$(this));
}).on('click',function(){
  if($(this).data('drag')){return;}
  // put your code here
});
Latin answered 27/6, 2017 at 9:48 Comment(0)
U
0

the solution that i use is the following:

var disable_click = false;

function click_function(event){
    if (!disable_click){
        // your code
    }
}

function mouse_down_function(event){
    disable_click = false; // will enable the click everytime you click
    // your code
}

function mouse_up_function(event){
    // your code
}

function mouse_drag_function(event){
    disable_click = true; // this will disable the click only when dragging after clicking
   // your code
}

attach each function to the appropriate event according to the name !

Unloosen answered 25/4, 2018 at 21:27 Comment(0)
D
0

My solution doesn't require global variables or timeouts or changing html elements just jquery which surely has some aquivalent in plain js.

I declare a function for onClick

function onMouseClick(event){
     // do sth
}

I declare a function for MouseDown (u may also do the same in mouse up) to decide if to handle an onclick event or not

function onMouseDown(event){
     // some code to decide if I want a click to occur after mouseup or not
    if(myDecision){
        $('#domElement').on("click", onMouseClick);
    }
    else $('#domElement').off("click");
} 

Quick Note: you should make sure that
$('#domElement').on("click", onMouseClick); is not executed multiple times. Seems to me that in case it is the onMouseClick will be called multiple times too.

Derwin answered 9/9, 2018 at 17:34 Comment(2)
Other than the fact that "myDecision" would be based on global variables (or some global state of the drag-drop process) :)Throughway
Having it in mouseDOwn defeats the purpose of many common use cases I think. Like I have an element that’s both a button and a drag handle. I’d only like to fire onClick if the user hasn’t dragged more the 3 px or taken mode then 50ms to release the mouse. The test would be in mouseUp. Therein lies the rub, as onClick would already be queued by that point.Counterproductive
C
-1
$(document).mouseup(function(event){ // make sure to set the event parameter
    event.preventDefault(); // prevent default, like you said
});

The important thing to note is the event parameter.

EDIT: You want to cancel the drag?

The way I know to do this is to either use bind() (for older jQuery versions) or on() (for jQuery 1.7+) to attach the mousedown event, then use unbind() or off() respectively to detach it.

$(document)
    .on("mousedown", function(){...})
    .on("mouseup", function(){
        $(document).off("mousedown");
    });
Concertina answered 27/12, 2011 at 10:54 Comment(1)
I don't want to cancel the drag, I want to cancel the click if a drag happened.Throughway
L
-1

If you wish suppress the click event you have add the statement event.preventDefault() in the click event handler only and not the mouseup event handler. Use the below code you will get it working.

<div id="test">test</div>
<script>
$("#test").mouseup (function (e) {
  var a = 1;

});
$("#test").click (function (e) {
 e.preventDefault();
  var a = 2;
});

Hope this solves your problem.

Lilongwe answered 28/12, 2011 at 7:47 Comment(2)
doesn't really. I want the click event handler to not get called at all.Throughway
"I'd like to cancel the click events in my mouseup handler" - pretty clear.Indentation
T
-2

how you do it is pretty simple, you add a addEventListener to all objects and listen for the 'mousedown' and 'mouseup' events. If your 'mousedown' event function is called you show a DIV window with 'style.position = "absolute", must be transparent, and taking entire window. And set the Z-Order to something like 9999999. If you hide the DIV at a 'MouseLeave' the Click could raise. So listen for the created DIV its mousedown also and hide the div also just to be sure. Then the 'Click' event of the object that you want to suppress will not raise. In 'mouseup' you hide your created div again.

This tip is only used to write applications with the WebView2 object where you dont have control over the html website that you showing, but injecting javascript to it. For all else, if you have control over the html source, just set a var in mousedown of object that you use to cancel or not, Then in 'click' event you simple look at the var what to do.

Thicket answered 3/12, 2021 at 15:22 Comment(8)
Hi @Michiel, welcome to SO! Just a comment on you answer: Adding an eventhandler to all objects of a web page could significantly reduce performance. Think about webpages with hundreds or thousand elements in it ...Spastic
i used this before on movie streaming websites packed with advertises and fake download buttons and popups for my own ads blocker. And i can tell you you wouldnt even notice it, attaching a function to a object goes insane fast, windows has no difficulties doing that. Even if the page has hundreds or thousands of objects. And you know, there isnt any other way to do it if you do not control the website yourself.Thicket
I think you thinking about it it wrong, you not adding thousands of extra objects, you just attach the mousedown and up event for all the objects in the page to also call your function. For example when you have a Input File at SOME website, and you want to block the event when people click at it to come up with the Windows common dialog. The only would be this method to Cancel the event going to the input box, as the user that uploaded this question asking for.Thicket
a tip, when you attached your function to a element, just use some element property to store a value of 1, so that you know you hooked that element. That way if you write a refresh function that checks all the objects again in the page you already know if that element is attached already to your functions. And to come back at speed, Windows should be able to do this many thousands of times in the blink of the eye.Thicket
a element its click will ONLY raise if both the mousedown and mouseup of that object have raised. And since you cannot REMOVE functions from elements that you didnt add yourself if you dont control the page you visiting. To come back to my Input File example, if the owner of the website (and most do) has set that at the Click() event it must show the windows common dialog. THEN there is no way for you to write javascript to remove that event or change it. You can only if you used the AddEventHandler also, and since you didnt you dont have the right pointers to remove the function.Thicket
so the ONLY way would be to make sure the MouseUp never raises of the element. And that you can ONLY do with a hidden div that you show at the mousedown that gets on top of that element. When calling your function in the MouseDown(this) you just check the 'this' element for its InnerHTML and stuff, if you allow the click you dont show your hidden DIV over the element, and if you dont want to allow the click you simple show your transparant DIV, when the webbrowser gets the mouseup event, it gets send to your DIV, and not to the element underneath it. And that is also when you hide your DIV.Thicket
with the webbrowser it was very easy to deny Click events, even the mousedown and mouseup. But not in Edge, because there is no events for it anymore. You have to inject your own javascript to the page. - And i know what you thinking ''But what if i subclass the Edge browser to simple reject the WM_LButtonDown event, or make it a combination with the element its mousedown and interprocessing and the WM_LButtonDown - You cant, i already tried that in the past, but the Edge webbrowser doesnt raise mouse events like that. Use Microsoft Spy++ on a Edge browser and you see you get no mouse events.Thicket
with the webbrowser = with IE webbrowser *Thicket

© 2022 - 2024 — McMap. All rights reserved.