Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at BFusion / BFLEX 2010 (Bloomington, Indiana) with:

Sharing Event Handlers Across jQuery Event Bindings To A Single Element

By Ben Nadel on

This really just isn't my week when it comes to jQuery event management. Yesterday, I found out that I had a complete misassumption as to how jQuery passed around Event objects; now, this morning, I discovered that I have another misassumption at to how jQuery manages event bindings to a single element. In particular, I don't have a clear picture as to how the additional event data that one might provide during an event binding gets associated with the given event handler. This became obvious when I tried to share a pre-defined event handler between two event bindings on the same element for the same event type.

 
 
 
 
 
 
 
 
 
 

To demonstrate my misunderstanding in jQuery event binding, I have created a very simple demo. In the following code, I have defined a stand-alone Javascript function to act as an event handler. Then, I grab a paragraph element and bind a "click" event to it twice, each binding using the same pre-defined function as its event handler. The only difference between the separate event bindings is that they each provide unique additional event information to be available in the subsequent "event.data" collection.

  • <!DOCTYPE HTML>
  • <html>
  • <head>
  • <title>Sharing Event Handlers In jQuery Event Binding</title>
  • <script type="text/javascript" src="jquery-1.4a1.js"></script>
  • <script type="text/javascript">
  •  
  • // When the DOM is ready, initialize the document.
  • jQuery(function( $ ){
  •  
  • // Gather all paragraphs.
  • var paragraphs = $( "p" );
  •  
  •  
  • // Define the event handler method that will be used
  • // in both of the bindings below.
  • var clickHandler = function( event ){
  • console.log( "Which:", event.data.whichHandler );
  • };
  •  
  •  
  • // Bind first click handler. NOTICE that we are using
  • // additional event data.
  • paragraphs.bind(
  • "click",
  • {
  • whichHandler: "first"
  • },
  • clickHandler
  • );
  •  
  •  
  • // Bind second click handler. NOTICE that we are
  • // using different, additional event data from above.
  • paragraphs.bind(
  • "click",
  • {
  • whichHandler: "second"
  • },
  • clickHandler
  • );
  •  
  • });
  •  
  • </script>
  • </head>
  • <body>
  •  
  • <p>
  • Katie, your wet t-shirt is incredibly provocative!
  • </p>
  •  
  • </body>
  • </html>

As you can see, each "click" event binding uses the same event handler, clickHandler; but, each event binding provides a different "whichHandler" data point. When I click the paragraph within the above page, I get the following console output:

Which: second

As you can see, only one of the two "click" event handlers was triggered and it used the last-bound event data collection. It turns out that event binding on a given element has to be unique; and, that only the event type and event handler play a part in that uniqueness - any additional event data provided to the event binding is coincidental and not part of the actual binding definition.

As with my previous jQuery event binding misconception, this makes perfect sense in hindsight. If you look at the way jQuery allows you to unbind a specific event handler, you'll see that it allows you to use an event type and event handler:

unbind( type, fn )

If this form of the unbind() invocation targets a specific event handler (which it does), then event-binding-uniqueness can only be defined as a combination of event type and event handler. Taking this into account, in order to fix my above demo, I would have to wrap my shared event handler in unique, anonymous function decorators:

  • <!DOCTYPE HTML>
  • <html>
  • <head>
  • <title>Sharing Event Handlers In jQuery Event Binding</title>
  • <script type="text/javascript" src="jquery-1.4a1.js"></script>
  • <script type="text/javascript">
  •  
  • // When the DOM is ready, initialize the document.
  • jQuery(function( $ ){
  •  
  • // Gather all paragraphs.
  • var paragraphs = $( "p" );
  •  
  •  
  • // Define the event handler method that will be used
  • // in both of the bindings below.
  • var clickHandler = function( event ){
  • console.log( "Which:", event.data.whichHandler );
  • };
  •  
  •  
  • // Bind first click handler. NOTICE that we are using
  • // additional event data. To ensure that our event
  • // binding is unique, I am wrapping the handler in its
  • // own unique, anonymous function.
  • paragraphs.bind(
  • "click",
  • {
  • whichHandler: "first"
  • },
  • function( event ){
  • return( clickHandler( event ) );
  • }
  • );
  •  
  •  
  • // Bind second click handler. NOTICE that we are
  • // using different, additional event data from above.
  • // To ensure that our event binding is unique, I am
  • // wrapping the handler in its own unique, anonymous
  • // function.
  • paragraphs.bind(
  • "click",
  • {
  • whichHandler: "second"
  • },
  • function( event ){
  • return( clickHandler( event ) );
  • }
  • );
  •  
  • });
  •  
  • </script>
  • </head>
  • <body>
  •  
  • <p>
  • Katie, your wet t-shirt is incredibly provocative!
  • </p>
  •  
  • </body>
  • </html>

As you can see in the above code, I am taking the pre-defined event handler, clickHandler(), and wrapping it in an anonymous function. When doing this, I just have to be sure to pass along the event object and return the resultant value. Wrapping the event handler in this way creates the desired uniqueness per-event-binding, and when we click on the paragraph this time, we get the following console output:

Which: first
Which: second

As you can see this time, each "click" event handler was triggered and referenced its own unique event data.

This might seem like an "out there" problem, and is indeed one that I have only just run into after a long period of jQuery usage; but, I came across it when researching custom jQuery event types that needed to be customized per-binding on a given element. Now that I have a better understanding of how jQuery event binding works, I think I might have to rethink the custom event binding stuff I've been looking into... but more on that later.




Reader Comments

Interesting observation Ben. I didn't know you could provide additional information with an event-binding and it would have made sense if it has created a new event handler when this information changes. Looking at unbind explains it all. Although it is a bit of a dodgy design choice.

Reply to this Comment

@Martin,

I suppose it's really an outlier of an issue. I mean who would have thought that the same exact handler would be bound twice to the same element for the same event :) I only ran into it because I am experimenting with custom jQuery events that can be customized per-binding.

We'll see what I can do with that.

Reply to this Comment

@Ben... :) I've run into this before...
As far as being able to unbind the events after the fact, check this out...
http://docs.jquery.com/Namespaced_Events

so your script block from above would look like the following...
<!--script-->
jQuery(function( $ ){

// Gather all paragraphs.
var paragraphs = $( "p" );


// Define the event handler method that will be used
// in both of the bindings below.
var clickHandler = function( event ){
console.log( "Which:", event.data.whichHandler );
};


// Bind first click handler. NOTICE that we are using
// additional event data. To ensure that our event
// binding is unique, I am wrapping the handler in its
// own unique, anonymous function.
paragraphs.bind(
"click.namespace1",
{
whichHandler: "first"
},
function( event ){
return( clickHandler( event ) );
}
);


// Bind second click handler. NOTICE that we are
// using different, additional event data from above.
// To ensure that our event binding is unique, I am
// wrapping the handler in its own unique, anonymous
// function.
paragraphs.bind(
"click.namespace2",
{
whichHandler: "second"
},
function( event ){
return( clickHandler( event ) );
}
);

});
<!--/script-->

Then if you wanted to unbind say the second one...
you would use the following syntax...

paragraphs.unbind('click.namespace2');

Reply to this Comment

@Timothy,

Ah, very interesting. I have not used name spaces before, but it seems cool, especially when you can single out event handlers that are created by a given plugin.

I think I might be able to leverage this namespace idea to work with my custom event experiment. Thanks!

Reply to this Comment

Another interesting problem is: what happens there is a html binded event handler, and using jQuery I assign another one?

<input id="x" onclick="something();" ...>

$("#x").click(another);

or - how can I call the original handler?

Reply to this Comment

@Hernyák,

That's an interesting question. I don't know off the top of my head. Typically, I stick to one style (jQuery or non-jQuery). I am pretty sure you can't double-up with things like body/onload. But, inline clicks might be able to exist in parallel with jQuery events. Not really sure.

Reply to this Comment

I am using Namespaced events but seem to be having the same problem. The second binding in the example below does not get binded -

jQuery('foo.bar', foo);
jQuery('foo.bar2', foo);

When 'foo.bar2' is triggered, the callback function foo does not receive the event.

Using jQuery 1.3.2. I have to wrap in an anonymous function as well.

Reply to this Comment

@Dan,

This seems to work for me. This is what I am doing - to make sure we are on the same page as to what is being discussed:

var f = function( event ){ alert( event.type ); }

$( "body" )
.bind( "bar.foo", f )
.bind( "bar.foo2", f );

$( "body" ).trigger( "bar.foo2" );
$( "body" ).trigger( "bar" );

The first only triggers the second binding. The latter triggers both. Is that what you are talking about?

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
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.