Enable And Disable jQuery Event Handlers (Rather Than Bind And Unbind)

Posted December 4, 2009 at 10:11 AM by Ben Nadel

Tags: Javascript / DHTML

Earlier this week, I was listening to the YayQuery pod cast - I think it was episode 3. If you haven't yet checked it out, I highly recommend it; Paul, Rebecca, Adam, and Alex are some bright guys (and gals). One of my favorite segments on the show is Paul Irish's "jQuery Anti-Patterns". In this segment, Paul discusses commonly misused techniques in your jQuery code and how they can be improved.

In this particular edition of jQuery Anti-Patterns, Paul talked about binding and unbindng event handlers. Event handlers are not always required throughout the life cycle of a given page and, as such, people tend to bind and unbind them as necessary. Apparently, this binding and unbinding of event handlers has a significant performance cost (hence the Anti-Pattern). To improve performance, Paul suggested that rather than changing the bindings, you can simply enable and disable event handlers as needed.

 
 
 
 
 
 
 
 
 
 

I had never thought of approaching event handling in this way, so it really got the machinery firing. The first thing I came up with was to play off the lexical-binding of Javascript methods and create a boolean variable that would be updated to True or False as required by the executing environment. In this example, I have a page that opens up a modal window and then closes it when you click outside of the modal window area. The close-action works by binding the "mousedown" event on the root document.

  • <!DOCTYPE HTML>
  • <html>
  • <head>
  • <title>Enable And Disable jQuery Event Handlers</title>
  • <style type="text/css">
  •  
  • #modal {
  • background-color: #FAFAFA ;
  • border: 1px solid #C0C0C0 ;
  • display: none ;
  • height: 80px ;
  • left: 50% ;
  • margin: -51px 0px 0px -151px ;
  • padding: 20px 20px 20px 20px ;
  • position: absolute ;
  • text-align: center ;
  • top: 50% ;
  • width: 260px ;
  • z-index: 100 ;
  • }
  •  
  • </style>
  • <script type="text/javascript" src="jquery-1.3.2.js"></script>
  • <script type="text/javascript">
  •  
  • jQuery(function( $ ){
  •  
  • // Get a reference to the modal window.
  • var modal = $( "#modal" );
  •  
  •  
  • // This is a variable to determine if the given event
  • // handler is active or disabled. This will be used
  • // by the event handler for conditional execution.
  • var isHandlerActive = false;
  •  
  •  
  • // Bind the trigger link to show the modal window.
  • $( "a" )
  • .attr( "href", "javascript:void( 0 )" )
  • .click(
  • function( event ){
  • // Show modal window.
  • modal.show();
  •  
  • // Now that modal window is shown, we need
  • // to activate its event handler.
  • isHandlerActive = true;
  •  
  • // Prevent default.
  • event.preventDefault();
  • }
  • )
  • ;
  •  
  •  
  • // Bind a mouseUp event on the document so that we
  • // can close the modal window when it is open.
  • $( document ).bind(
  • "mousedown",
  • function(){
  • // Check to see if this event handler is
  • // "active". If it is not, then exit.
  • if (!isHandlerActive){
  • return;
  • }
  •  
  • // Log to console for debugging.
  • console.log( "Event handled." );
  •  
  • // Close the modal window.
  • modal.hide();
  •  
  • // Now that the modal window is hidden, we
  • // need to disable its event handler.
  • isHandlerActive = false;
  • }
  • );
  •  
  •  
  • // Bind the mousedown event the modal window so we can
  • // stop it from bubbling up to the document (ie. you
  • // should be able to click IN the modal window without
  • // it closing.
  • modal.bind(
  • "mousedown",
  • function( event ){
  • event.stopPropagation();
  • }
  • );
  •  
  • });
  •  
  • </script>
  • </head>
  • <body>
  •  
  • <h1>
  • Enable And Disable jQuery Event Handlers
  • </h1>
  •  
  • <p>
  • <a id="trigger">Show modal window</a>
  • </p>
  •  
  • <div id="modal">
  • Hello, this is a modal window.
  • </div>
  •  
  • </body>
  • </html>

As you can see in the above code, I am creating an activation variable, isHandlerActive, which is accessible to all of the defined event handlers. When the modal window is open, I set isHandlerActive to true and when the modal window is closed, I set isHandlerActive to false. Then, within the modal window mouseDown event, I check to see if the handler is active (ala isHandlerActive) and if so, I execute the remainder of the function body, close the modal window, and disable the handler.

This approach definitely works, but there is something about it that just wasn't sitting right with me. The idea that a given event handler has to check and update (disable) its own activation status seems like an improper separation of concerns. I wanted the event handler to be as pure as possible. After thinking about the problem for a while, what I came up with was the concept of a proxied event handler in which the primary event handler is left clean and the proxy is what takes care of managing execution.

After playing around with some ideas, I came up with a jQuery plugin, bindIf(). The bindIf() plugin takes the event type and callback (I didn't worry about optional data) just as bind() would; but, it also takes an additional callback which will be used a conditional pre-check for the handler execution:

jquery.bindif.js

  • // Wrap the plugin definition in a callback bubble so we can bind
  • // it to the dollar sign.
  • (function( $ ){
  •  
  • // This jQuery plugin creates proxied event handlers that
  • // consult with an additional conditional callback to see if
  • // the original event handler should be executed.
  • $.fn.bindIf = function(
  • eventType,
  • eventHandler,
  • ifCondition
  • ){
  •  
  • // Create a new proxy function that wraps around the
  • // given bind callback.
  • var proxy = function( event ){
  •  
  • // Execute the IF condition callback first to see if
  • // the event handler should be executed.
  • if (ifCondition()){
  •  
  • // Pass the event onto the original event
  • // handler.
  • eventHandler.apply( this, arguments );
  •  
  • }
  •  
  • };
  •  
  • // Bind the proxy method to the target.
  • this.bind( eventType, proxy );
  •  
  • // Return this to keep jQuery method chaining.
  • return( this );
  • };
  •  
  • })( jQuery );

As you can see, the bindIf() plugin creates a proxy method that ultimately gets bound as the event handler. This proxy method, when executed, turns around and calls the ifCondition() callback; if the ifCondition() method returns true (ie. the event handler is active), the proxy method then executes the original event handler, passing on the appropriate context and arguments. This allows the handler activation / deactivation to be delegated to the ifCondition() callback and leaves the primary event handler free from any extraneous logic.

NOTE: The event handling work flow in jQuery is easily more complicated than this as far as extra data and return values is concerned; this is merely a proof of concept / exploration of the activate-deactivate event binding approach.

Taking the bindIf() jQuery plugin, I then reworked my first example:

  • <!DOCTYPE HTML>
  • <html>
  • <head>
  • <title>Enable And Disable jQuery Event Handlers</title>
  • <style type="text/css">
  •  
  • #modal {
  • background-color: #FAFAFA ;
  • border: 1px solid #C0C0C0 ;
  • display: none ;
  • height: 80px ;
  • left: 50% ;
  • margin: -51px 0px 0px -151px ;
  • padding: 20px 20px 20px 20px ;
  • position: absolute ;
  • text-align: center ;
  • top: 50% ;
  • width: 260px ;
  • z-index: 100 ;
  • }
  •  
  • </style>
  • <script type="text/javascript" src="jquery-1.3.2.js"></script>
  • <script type="text/javascript" src="jquery.bindif.js"></script>
  • <script type="text/javascript">
  •  
  • // When the DOM is ready, intialize document.
  • jQuery(function( $ ){
  •  
  • // Get a reference to the modal window.
  • var modal = $( "#modal" );
  •  
  •  
  • // Bind the trigger link to show the modal window.
  • $( "a" )
  • .attr( "href", "javascript:void( 0 )" )
  • .click(
  • function( event ){
  • // Show modal window.
  • modal.show();
  •  
  • // Prevent default.
  • event.preventDefault();
  • }
  • )
  • ;
  •  
  •  
  • // Bind a mousedown event on the document so that we
  • // can close the modal window when it is open.
  • $( document ).bindIf(
  • "mousedown",
  • function(){
  • // Log to console for debugging.
  • console.log( "Event handled." );
  •  
  • // Close the modal window.
  • modal.hide();
  • },
  • function(){
  • return( modal.is( ":visible" ) );
  • }
  • );
  •  
  •  
  • // Bind the mousedown event the modal window so we can
  • // stop it from bubbling up to the document (ie. you
  • // should be able to click IN the modal window without
  • // it closing.
  • modal.bind(
  • "mousedown",
  • function( event ){
  • event.stopPropagation();
  • }
  • );
  •  
  • });
  •  
  • </script>
  • </head>
  • <body>
  •  
  • <h1>
  • Enable And Disable jQuery Event Handlers
  • </h1>
  •  
  • <p>
  • <a id="trigger">Show modal window</a>
  • </p>
  •  
  • <div id="modal">
  • Hello, this is a modal window.
  • </div>
  •  
  • </body>
  • </html>

As you can see, I have removed all of the "isHandlerActive" references and replaced them with the one ifCondition() callback:

  • function(){
  • return( modal.is( ":visible" ) );
  • }

This checks the modal window visibility and only allows the original event handler to execute only if the modal window is visible. The rest of the code is simple and executes without the concept of any conditional logic.

This is the first time that I have thought about this, so I am sure there are feasibility issues with more complicated pages. But, there is something that I like about this approach, particularly that the primary event handler is void of logic that, I think, it shouldn't have.




Reader Comments

Dec 4, 2009 at 10:46 AM // reply »
5 Comments

Very nice! I like that a lot.

While keeping the flag inside the handler is a little messy, it's certainly far faster than the unbind/rebind pattern.

I like your bindIf abstraction, though.. Slickness. :)


Dec 4, 2009 at 10:52 AM // reply »
11,246 Comments

@Paul,

Thanks. I have a feeling it would get a bit more complex the more states you have that would affect activate / deactive status. But, it just makes me feel comfortable having the logic outside the primary event handler.


Dec 4, 2009 at 10:52 AM // reply »
11,246 Comments

@Paul,

... also, really loving the pod cast by the way. You guys are rocking it!


Dec 4, 2009 at 8:54 PM // reply »
5 Comments

Sick as always Ben.


Dec 4, 2009 at 9:28 PM // reply »
11,246 Comments

@Garrett,

Thanks my man.


Dec 5, 2009 at 10:42 AM // reply »
3 Comments

@Ben,

I had been doing this the way you first showed in your demo, but I really like the bindIf idea. As always thanks for the morning inspiration!


Dec 5, 2009 at 11:16 PM // reply »
39 Comments

I'm not sure I totally agree with you on this. Though maybe it's just the specific example.

In the example above, I think it make more logical sense to make the callback smart enough to not hide an invisible element, and not have a proxy that needs to add in an extra step to figure out if it should run or not. (Especially if this function is placed inline in the onClick, and not reused.)

I'm trying to think beyond the example, but I'm having a hard time thinking of when I would want a function that didn't have it's own built in checks like that. But maybe it's just late, and the problem truly is my being unable to think of an example.

I would just think it's the actions responsibility to know if/what it should run, rather then the binding even if it comes down to something like:
if(!actionShouldRun('CloseXyzDialog')){return;}
in the onclick function.

Though, again, it's probably not Should/Shouldn't and just personal taste.


Dec 7, 2009 at 8:24 AM // reply »
11,246 Comments

@Tim,

Yeah, it's just a personal decision of comfort. To me, is just feels like the primary handler should only worry about doing its job and not worry about *when* it should do its job.

I think about it sort of like the "Each" method in jQuery:

$.each( collection, callback )

In this model, the Callback only cares about doing its job - it never worries about whether or not it should be running; that is a responsibility of the each() method itself. To put the run/not-run login in the handler, to me, is akin to have an each callback like this:

function( i, value ){
. . . // Should I even be running?
. . . if (i > collection.length){ return; }
. . . // Ok, run my callback logic.
}

Hopefully we can look at this and say, "that's crazy! - the callback shouldn't care about when it's called, it should only care about doing its job." ... so to me, I guess I see the primary event handler in my demo as the same thing.

But of course, this was my first time even approaching it this way, so whose to say my opinion won't change as I start to use this in production.


Jun 18, 2010 at 3:53 AM // reply »
1 Comments

Thank you - I was looking for something like that :)


Jun 20, 2010 at 9:17 PM // reply »
11,246 Comments

@Jakub,

Cool. Let us know what kinds of stuff you might be doing with this.


Jun 29, 2010 at 4:42 PM // reply »
1 Comments

Fantastic. Thanks so much!


May 20, 2011 at 3:16 PM // reply »
2 Comments

@Ben

Nice post! I see this was written in 2009... is this still the best way to conditionally bind jQuery event handlers? or does the library now offer a built-in solution? I haven't found anything in the documentation to support otherwise.


Jun 22, 2011 at 3:36 AM // reply »
6 Comments

Hi Ben,

First of all pls correct me if im wrong.
I think this plugin can be renamed as executeIf :-), because, the bind statement is always getting executed irrespective of the if condition. Only the execution of the handler(proxy) is determined by the ifCondition.

Thanks


Jun 22, 2011 at 3:40 AM // reply »
6 Comments

forgot to say, the concept was cool.


Jul 3, 2011 at 9:15 PM // reply »
1 Comments

Very cool Ben, thanks!


Oct 21, 2011 at 5:58 PM // reply »
1 Comments

Ben,

Awesome technique!

I am trying to do something similar except that I need to disable a container's click event after its clicked and need to re-bind the click afterwards.

Consider a <div> tile. When I click on it, I add css:opacity over the div and a facebook dialog pops up. At this time I wish to unbind the click from the div.

If I hit cancel on the facebook dialog, the div should remove the css:opacity and re-bind the click event to the div.

I could do this using bind/unbind.
But after going through your post I wish to write a plugin to handle this on the similar lines as you described. Any help is appreciated!


May 31, 2012 at 8:59 AM // reply »
1 Comments

thanks a lot ben it help me so much ,you are RONALDO of jquery :)


sru
Apr 15, 2013 at 2:19 AM // reply »
1 Comments

Hai..I am have a problem.in my project i have lot of links that navigate to another page.here problem is if i click on one link it opens in new window then i have to disabled all the links present in that page.i want to enabled only one link that is what i clicked.can anybody help me plzzz.......if you know answer plz send a mail to sura_srujana902@gmail.com..


sru
Apr 15, 2013 at 2:42 AM // reply »
1 Comments

Hai..I am have a problem.in my project i have lot of links that navigate to another page.here problem is if i click on one link it opens in new window then i have to disabled all the links present in that page.i want to enabled only one link that is what i clicked.can anybody help me plzzz.......if you know answer plz send a mail to sura.srujana902@gmail.com..



Post A Comment

Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
May 24, 2013 at 5:39 PM
Ask Ben: Manually Enforcing Basic HTTP Authorization In ColdFusion
@Adam Oops! My mistake! I hadn't gotten that far in my testing - I'm still baby stepping my way through the process. ... read »
May 24, 2013 at 5:13 PM
Ask Ben: Manually Enforcing Basic HTTP Authorization In ColdFusion
Hi Jason, Thanks for checking up on that, but I still stand firm on my position. :) There are actually two listLast()'s in use, and you're right that the one using a space as a delimiter is fine. ... read »
May 24, 2013 at 4:45 PM
Ask Ben: Manually Enforcing Basic HTTP Authorization In ColdFusion
@Ben I have been lurking your site for quite some time, and haven't stepped up to comment until today. Thanks for all the great info - keep it up! @Adam I believe you are mistaken... as the commen ... read »
May 24, 2013 at 11:21 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@WebManWalking, Ha ha, let's us never speak of justifying "##" notation again :P ... read »
May 24, 2013 at 11:18 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@Ben, Ah, so it was indeed how I vaguely remembered it to be: A direct assignment value = users.id[ i ] causes value to retain the sticky datatype of the query column. Although unnecessary in ... read »
May 24, 2013 at 9:11 AM
Preventing Links In Standalone iPhone Applications From Opening In Mobile Safari
@Brandon, Hi, No, I haven't been able to do that. I have just kept it as it is. ... read »
May 23, 2013 at 9:52 PM
Preventing Links In Standalone iPhone Applications From Opening In Mobile Safari
@Muhmmadibn Did you figure out a solution to launching PDFs? I am running into the same issues myself. There is no way to close the PDF or go back once you launch it. Thanks in advance! ... read »
May 23, 2013 at 6:06 PM
The Girl Who Broke My Heart, And Made Me A Better Person
Good day,ladies and gentle men, my name is Dr AMADI the great spell caster in Africa, i have help so many people for different kind of problems,who say there is no solution to problems on earth, that ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools