Skip to main content
Ben Nadel at cf.Objective() 2009 (Minneapolis, MN) with: Dan Vega
Ben Nadel at cf.Objective() 2009 (Minneapolis, MN) with: Dan Vega

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

By
Published in Comments (20)

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.

Want to use code from this post? Check out the license.

Reader Comments

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

15,848 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.

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!

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.

15,848 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.

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.

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

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!

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

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

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel