jQuery Event Bindings On Javascript Objects With A Length Property

Posted April 26, 2010 at 9:19 AM by Ben Nadel

Tags: Javascript / DHTML

The other day, I ran into a very interesting behavior involving jQuery event binding and Javascript objects. While it is still an open question as to whether or not jQuery truly supports non-DOM-object event binding, it is a feature that appears to be actively included in the jQuery library (even by John Resig himself). That said, I found myself stuck for a good 20 minutes the other day trying to figure out why an event binding on a given Javascript object was not working.

 
 
 
 
 
 
 
 
 
 

After a good amount of debugging, I finally narrowed the problem down to the Length property. One of the Javascript objects to which I was binding events had a length property with a default value of zero. It appears that if the Javascript object has a length property of zero, triggered events on the object don't actually fire. It's as if jQuery is using the length property of the composed object rather than that of the parent jQuery collection. To demonstrate this, take a look at the code below:

  • <!DOCTYPE HTML>
  • <html>
  • <head>
  • <title>Javascript Object Binding Caveat</title>
  • <script type="text/javascript" src="jquery-1.4.2.js"></script>
  • <script type="text/javascript">
  •  
  • // Create a basic Javascript object. Notice that this one
  • // does not have a length property.
  • var foo = {
  • size: 0
  • };
  •  
  • // Create another basic Javscript object. This time, we are
  • // going to create a LENGTH property.
  • var bar = {
  • length: 0
  • };
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // Now that we have our Javascript objects, we are going to
  • // bind an events to them. First, we have to wrap them in
  • // a jQuery object for the bind/triggering.
  • var eventHandler = function( event, parent ){
  • console.log( parent + " : Event triggered!" );
  • };
  •  
  •  
  • // Bind both event handlers.
  • $( foo ).bind( "custom", eventHandler );
  • $( bar ).bind( "custom", eventHandler );
  •  
  •  
  • // Now that we have bound our event handler, let's trigger
  • // the custom event on each object.
  • $( foo ).trigger( "custom", "foo" );
  • $( bar ).trigger( "custom", "bar" );
  •  
  • </script>
  • </head>
  • <body>
  • <!--- Intentially left blank. --->
  • </body>
  • </html>

As you can see, I have created two Javascript objects - one with a size property and one with a length property (each defaulted to zero). Then, I bound a "custom" event to each object and tried to trigger it. When I do this, I get the following console output:

foo : Event triggered!

As you can see, the event on the "bar" object never fired.

To dig into the problem a bit deeper, I decided to log each object to the console after the page had loaded. I thought this might give me some more insight as to where things were going wrong. Here is what I get for the Foo object:

jQuery1272285849095: 1
size: 0

You can see in this console output that jQuery has added the "expando" property to the Foo object. This is the property that it uses to maintain all of the data associated with the object. Part of that data is the "event" key which contains the event handlers bound to this object.

When I do the same with the Bar object, here is the output that I get:

length: 0

As you can see here, jQuery did not add the "expando" property to the bar object. From this, we can conclude that the problem was not with the event triggering but rather with the event binding. After all, we cannot trigger an event that was never bound to the target object.

As a further experiment, I tried to default the length property of the bar object to a non-zero value. When I do this, I actually get the following Javascript error:

jquery-1.4.2.js (line 1560)
elem is undefined
if ( elem.nodeType === 3 || elem.nodeType === 8 ) {

This is in the jQuery.event.add() method. While I am not 100% sure how all the event binding works, I am pretty sure that this exception is being raised in the event binding, not the event triggering phase of the configuration. Looking at the underlying jQuery code, it looks like this is where jQuery is attempting to use the length property of the underlying Javascript object.

Being able to leverage jQuery's event binding / handling mechanism on Javascript objects is something that is very powerful. It appears, however, that there are some caveats that cause this mechanism to fail critically. I, for one, would love to see this feature supported on Javascript objects in its completeness. Of course, doing that might take too much rewiring of the existing event handling code.




Reader Comments

Apr 26, 2010 at 9:49 AM // reply »
11 Comments

I decided to dig into this myself and I think I've narrowed it down to the jQuery.merge function which is used when you pass ANYTHING with a non-null (not undefined or null) length property as the first argument to $().

jQuery.merge will assume that it's an array-like object and will continue to go through it and "merge" its array items with the current jQuery instance. Of course, your {length:0} object has no array items -- jQuery thinks it's just an empty array, and so what you get is an empty jQuery instance.

To get around this issue, you could pass the object within its own array to make it absolutely clear that you want your object to be a part of the jQuery instance:

So instead of:

$({length:0});

Do:

$([{length:0}]);


Apr 26, 2010 at 9:52 AM // reply »
13 Comments

I think it must be how jQuery uses the native length property for sanity checks internally.

For fun if you were to set bar's length property to 20... when you trigger your custom event, it should run it 20 times?


Apr 26, 2010 at 10:27 AM // reply »
11,238 Comments

@James,

Yeah, good idea. I tried that as a step to debug and noticed that it worked, but then forgot about it. What you're saying about reason it works makes total sense.

Thanks for digging into that deeper!

@Garrett,

Unfortunately, if you set the length to a non-zero value, it errors out trying to iterate over a non-populated array.


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 19, 2013 at 2:31 PM
My Experience With AngularJS - The Super-heroic JavaScript MVW Framework
It's funny really just how well that image describes the way I would imagine most people that go with angular for some project is. I have had a similar roller-coaster ride with it as well, but not qu ... read »
May 17, 2013 at 7:42 PM
HashKeyCopier - An AngularJS Utility Class For Merging Cached And Live Data
Ben - thanks so much for posting these Angular articles and findings, they've been a huge help towards learning one of the more 'complex' JavaScript frameworks out there (IMO). I have been using Angu ... read »
May 16, 2013 at 5:01 PM
UPDATE: Parsing CSV Data Files In ColdFusion With csvToArray()
Your code was the closest thing I've found to obtaining some direction for converting ISO fields to values that CF can translate properly. Thank you for posting! ... read »
May 15, 2013 at 10:37 PM
Very Simple Pusher And ColdFusion Powered Chat
hi id making plz easy ... read »
May 15, 2013 at 6:07 PM
Making SOAP Web Service Requests With ColdFusion And CFHTTP
Ben, you once again saved my bacon at work. Thank you, thank you, thank you! ... read »
May 15, 2013 at 4:15 PM
What If All User Interface (UI) Data Came In Reports?
@Josh, Thanks! @Ben, I definitely recommend the David West book "Object Thinking" I've been quoting from. It goes deeply into the philosophy and history of OO programming. His breadth ... read »
May 15, 2013 at 11:36 AM
Ask Ben: Print Part Of A Web Page With jQuery
I found this helpfull when you need to keep (refresh) the original parent page after closing the iframe child print dialog (Hoping you're not using a form at this time so it won't submit again): On ... read »
May 14, 2013 at 7:13 PM
What If All User Interface (UI) Data Came In Reports?
@Jonah, If there's any books you'd recommend on the subject of domain modelling, I'd love to hear it. I just downloaded the free PDF of "Domain Driven Design Quickly". Figured I'd give it ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools