21

Is there an elegant way to temporarily suppress jQuery events? I use code like this:

$(element).unbind(event, function1).unbind(event, function2);
// code for which the event is suppressed
$(element).bind(event, function1).bind(event, function2);

but I find it a bit clumsy and not very scalable to many events. Why do I want to suppress events temporarily? I use BlockUI plugin to block UI during Ajax access. This is done with: $().ajaxStart($.blockUI).ajaxStop($.unblockUI) as proposed by BlockUI.

However, one Ajax access is special, so I need a different message. The ajaxStart and ajaxStop events interfere with the message code (nothing is shown):

function message(text, callback) {
  if (text == undefined) {
     $.unblockUI();
     return;
  }

  // $().unbind('ajaxStop', $.unblockUI).unbind('ajaxStart', $.blockUI);
  $("#message_text").html(text);
  $.blockUI({message: $("#message")});
  $("#message_ok").click(function() { 
    // $().bind('ajaxStop', $.unblockUI).bind('ajaxStart', $.blockUI);
    $.unblockUI();
    if (callback != undefined) callback();
  });
}

Only if I uncomment the unbind() and the bind() lines, it is working.

nalply
  • 26,770
  • 15
  • 78
  • 101

6 Answers6

15

I realize this question is old, but I found it while seeking an answer to the same question, and ended up finding a different solution that works well in simple applications of event handlers.

$(element).click(function(e) {
  e.preventDefault();

  // Check for fired class
  if ($(this).hasClass('fired') == false) {
     // Add fired class to all instances of element
     $(element).addClass('fired');

     // Do the rest of the function...

     // Remove fired class to 'reactivate' events
     $(element).removeClass('fired');   
  }
}

Simply adding a class to your 'element's while the code from the event is firing allows you to prevent further code from being executed as the result of another click event. So while you're not actually preventing any other click events, you're preventing the resulting code from running. Simple, crude, ignores the heavily preached use of bind and unbind, but works.

Eric
  • 907
  • 7
  • 13
  • Why yes *edits answer*, isn't that what I said? I got all caught up in event handlers and all the firing they do that I misused the word. – Eric May 23 '12 at 15:30
  • If you only want to suspend click event you can use the css pointer-events property as described here : http://stackoverflow.com/a/25095924/4306452 – John-Philip Dec 07 '16 at 13:37
  • That does indeed work, but at the time of writing, I was supporting IE8 with the above solution. Looks like pointer-event support in IE is only as of IE11. http://caniuse.com/#feat=pointer-events – Eric Dec 08 '16 at 13:53
10

cost me finish this script, I hope it's useful:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script> 
<script type="text/javascript">
$(function(){

    //ini plugin

    jQuery.event.freezeEvents = function(elem) {

        if (typeof(jQuery._funcFreeze)=="undefined")
            jQuery._funcFreeze = [];

        if (typeof(jQuery._funcNull)=="undefined")
            jQuery._funcNull = function(){ };

        // don't do events on text and comment nodes
        if ( elem.nodeType == 3 || elem.nodeType == 8 )
            return;

        var events = jQuery.data(elem, "events"), ret, index;

        if ( events ) {

            for ( var type in events )
            {
                if ( events[type] ) {

                    var namespaces = type.split(".");
                    type = namespaces.shift();
                    var namespace = RegExp("(^|\\.)" + namespaces.slice().sort().join(".*\\.") + "(\\.|$)");

                    for ( var handle in events[type] )
                        if ( namespace.test(events[type][handle].type) ){
                            if (events[type][handle] != jQuery._funcNull){
                                jQuery._funcFreeze["events_freeze_" + handle] = events[type][handle];
                                events[type][handle] = jQuery._funcNull;
                            }
                        }
                }

            }
        }
    }

    jQuery.event.unFreezeEvents = function(elem) {

        // don't do events on text and comment nodes
        if ( elem.nodeType == 3 || elem.nodeType == 8 )
            return;

        var events = jQuery.data(elem, "events"), ret, index;

        if ( events ) {

            for ( var type in events )
            {
                if ( events[type] ) {

                    var namespaces = type.split(".");
                    type = namespaces.shift();

                    for ( var handle in events[type] )
                        if (events[type][handle]==jQuery._funcNull)
                            events[type][handle] = jQuery._funcFreeze["events_freeze_" + handle];

                }
            }
        }
    }

    jQuery.fn.freezeEvents = function() {

        return this.each(function(){
            jQuery.event.freezeEvents(this);
        });

    };

    jQuery.fn.unFreezeEvents = function() {

        return this.each(function(){
            jQuery.event.unFreezeEvents(this);
        });

    };

    //end plugin

    jQuery("#test1").ajaxStart(function test1(){
        jQuery("#result").append('test1 ajaxStop<br>');
    });

    jQuery("#test1").ajaxStop(function test2(){
        jQuery("#result").append('test1 click<br>');
    });

    jQuery("#test1").bind("click", function test3(){
        jQuery("#result").append('test1 click<br>');
    });

    jQuery("#test2").bind("click", function test5(){
        jQuery("#result").append('test2 click<br>');
    });

    jQuery("#freez").click(function(){
        jQuery("#test1").freezeEvents();
        jQuery("#test2").freezeEvents();
    });

    jQuery("#unfreez").click(function(){
        jQuery("#test1").unFreezeEvents();
        jQuery("#test2").unFreezeEvents();
    });

});
</script>
</head>
<body>
<button id="freez">freez</button>
<button id="unfreez">un freez</button>
<br />
<div id="test1">test1 click mousemove</div>
<div id="test2">test2 click mousemove</div>
<br />
<div id="result"></div>
</body>
</html>
andres descalzo
  • 14,887
  • 13
  • 64
  • 115
  • Wow! You wrote a small jQuery plugin! So you think that there is no easy trick to generally suspend/resume (or as you call freeze/unfreeze) event handling in jQuery? – nalply Nov 27 '09 at 21:48
  • reviewing the jQuery library, I saw it best to make this plugin. Must try to find out if you is useful – andres descalzo Nov 28 '09 at 11:25
  • How do freeze and unfreeze work? By the way, do you know the CopyEvents jQuery plugin? http://plugins.jquery.com/project/copyEvents – nalply Nov 30 '09 at 20:28
  • The plugin copy a functions to an array with the index "event type" and "handle" that is unique. then assigns a null function of the element to the event. when doing unfreezeevent, reassigns the functions found in the array. I do not know but I will get to investigate – andres descalzo Nov 30 '09 at 21:23
  • If you only want to suspend click event you can use the css pointer-events property as described here : http://stackoverflow.com/a/25095924/4306452 – John-Philip Dec 07 '16 at 13:37
5

I really liked Andres' approach to this problem, but it appears that since he came up with that code, the jQuery event model has changed, so in recent versions of jQuery his code doesn't work. Here it is modernised. Tested (lightly) in 1.8.2:

$.event.freezeEvents = function(elem) {

        if (typeof($._funcFreeze)=="undefined") {
            $._funcFreeze = [];
        }

        if (typeof($._funcNull)=="undefined") {
            $._funcNull = function() {};
        }                

        // don't do events on text and comment nodes
        if ( elem.nodeType == 3 || elem.nodeType == 8 ) {
            return;
        }

        var events = $._data(elem, "events");

        if (events) {
            $.each(events, function(type, definition) {
                $.each(definition, function(index, event) {
                    if (event.handler != $._funcNull){
                        $._funcFreeze["events_freeze_" + event.guid] = event.handler;
                        event.handler = $._funcNull;
                    }
                })
            })
        }
    }

    $.event.unFreezeEvents = function(elem) {

        // don't do events on text and comment nodes
        if ( elem.nodeType == 3 || elem.nodeType == 8 )
                return;

        var events = $._data(elem, "events");

        if (events) {
            $.each(events, function(type, definition) {
                $.each(definition, function(index, event) {
                    if (event.handler == $._funcNull){
                        event.handler = $._funcFreeze["events_freeze_" + event.guid];
                    }
                })
            })
        }
    }

    $.fn.freezeEvents = function() {
        return this.each(function(){
            $.event.freezeEvents(this);
        });
    };

    $.fn.unFreezeEvents = function() {
        return this.each(function(){
            $.event.unFreezeEvents(this);
        });
    };

I haven't tested this beyond an element with two events, but it works fine for me. Hope this helps someone.

Andrew Plank
  • 942
  • 10
  • 22
  • I removed the namespacing check, I used jQuery's each method for iterating over the events, and refactored expressions to use the new jQuery event model. If you compare the code, you'll see the differences, they're quite obvious. – Andrew Plank Nov 06 '12 at 10:42
  • Thanks. I was too lazy for a diff. – nalply Nov 06 '12 at 14:50
5

The simplest way to do this is to add a check at the beginning of the bound method to determine if the method should run. If not, simply return from the method.

var doThing = true;

$("#foo").bind("click", function(e) {
    if(!doThing){return;}
    //do thing
}

function bar(){
    //temp unbind click
    doThing = false;

    //do stuff...

    //rebind click
    doThing = true;
}
cmcculloh
  • 47,596
  • 40
  • 105
  • 130
  • 1
    This is a well-known idiom. It is neccessary if the event handler somehow triggers its own event and therefore gets invoked recursively, leading to infinite loop, rendering the browser unresponsive. But this is not my problem. I don't have unwanted recursion. My problem is that other event handlers interfere with my code, so I need to suppress event handling temporarily. The code of the other event handler is in BlockUI itself, so, no, I cannot modify it. – nalply Nov 27 '09 at 17:30
2

You can simply save and restore the entire events object:

var events=$._data(elem, 'events'); // save events
$._data(elem, 'events', {}); // cancel all events
// any code here will not fire events
$._data(elem, 'events', events); // restore events

This worked for me but may have unknown side effects ...

kofifus
  • 17,260
  • 17
  • 99
  • 173
1

So is the idea to block the page, fire multiple ajax requests (chained or otherwise) and then unblock the page?

If that is the case (and I've understood the question correctly) then you could use a block counter?

e.g. var blockcounter = 0;

function block(){
if(blockcounter==0)
 {$.blockUI({message: "Hai", showOverlay:true});}
blockcounter++;
}

function unblock(){
blockcounter--;
if(blockcounter==0)
{$.unblockUI();}
}

then in your code

function dostuff(){
block();
... do whatever ajax you need
... call unblock() in the success event of your ajax call
}

function dostuff2(){
block();
... do some more ajax - this might take a bit longer to complete though
... call unblock() in the success event
}

dostuff();
dostuff2();

Blocking should occur once only then and you're free to do whatever in the middle.

I use this method successfully on a website where there are multiple events happening between blocking and unblocking and where the different ajax events can complete in varying times. If you wrap the whole lot up in a namespace you can also avoid littering the place with global vars.

Reza
  • 181
  • 1
  • 6
  • 1
    Yes, I used a block counter first. But $().ajaxStart($.blockUI).ajaxStop($.unblockUI) is neater - it does the blocking and unblocking for you, but will interfere like it happened to me in message(). Please let's stick with the original question: how to suppress event handling temporarily. Perhaps in the sense of a suspend/resume mechanism. – nalply Nov 27 '09 at 18:37