Sharing Event Handlers Across jQuery Event Bindings To A Single Element

Posted December 18, 2009 at 9:24 AM by Ben Nadel

Tags: Javascript / DHTML

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

Dec 18, 2009 at 10:29 AM // reply »
29 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.


Dec 18, 2009 at 10:31 AM // reply »
10,640 Comments

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


Dec 18, 2009 at 12:51 PM // reply »
3 Comments

@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');


Dec 18, 2009 at 12:54 PM // reply »
10,640 Comments

@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!


Mar 30, 2010 at 4:06 PM // reply »
1 Comments

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?


Apr 19, 2010 at 10:47 PM // reply »
10,640 Comments

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


Jun 14, 2010 at 12:42 PM // reply »
1 Comments

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.


Jun 14, 2010 at 9:50 PM // reply »
10,640 Comments

@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?


Jun 15, 2010 at 11:52 AM // reply »
2 Comments

This issue seems to have been sorted in jQuery 1.4x


Jun 16, 2010 at 9:02 AM // reply »
10,640 Comments

@Nick,

Ahh, ok, so it was just a bug for a bit. Thanks for the insight.


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
InVision App - Prototyping Made Beautiful With Prototyping Tools Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
Feb 12, 2012 at 3:37 AM
Learning ColdFusion 8: CFImage Part III - Watermarks And Transparency
Hi Ben, Just to ask currently it is placed bottom right corner, if i need to replace the same rendered image on the bottom left side or in the bottom center, how that can be calculated. bottom ce ... read »
Feb 11, 2012 at 9:29 PM
Use jQuery's SlideDown() With Fixed-Width Elements To Prevent Jumping
I can't say how glad I am that I found your post. Thank you very much. ... read »
Feb 10, 2012 at 7:21 PM
jQuery AJAX Strips Script Tags And Inserts Them After Parent-Most Elements
Update! Instead of $(eval(options.insertAfter)).after(data['insertData']); I now use: var ajaxNode = document.createElement('span'); var parent = $(eval(options.insertAfter))[0].parentNode; ... read »
Feb 10, 2012 at 6:18 PM
jQuery AJAX Strips Script Tags And Inserts Them After Parent-Most Elements
encountered this same, what I consider, jQuery bug last week. I'm building a site in which I load some content via AJAX. This content contains Linkedin share button placeholders which Linkedin API ne ... read »
Feb 10, 2012 at 11:30 AM
Cross-Origin Resource Sharing (CORS) AJAX Requests Between jQuery And Node.js
After you understand the concepts here, this is an awesome cheatsheet for enabling CORS in just about anything http://enable-cors.org/ ... read »
JM
Feb 10, 2012 at 9:10 AM
My Safari Browser SQLite Database Hello World Example
@Amy, Here is a very good tutorial on how to use JOIN: http://www.sqltutorial.org/sqljoin-innerjoin.aspx ... read »
Feb 10, 2012 at 4:42 AM
Building A Twitter-Inspired RESTful API Architecture In ColdFusion
This is great, very useful Ben. I spotted a small typo in the api.cgm listing: <cfthrow type="Unauthroized" /> Cheers Stefan ... read »
Feb 9, 2012 at 10:35 PM
CFDirectory Filtering Uses Pipe Character For Multiple Filters (Thanks Steve Withington)
I was wondering if there would be a filter you could apply so that you got everything but what you included in the filter. As in show me all docs that are not a .pdf. ... read »