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,243 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 22, 2013 at 5:35 PM
Script Tags, jQuery, And Html(), Text() And Contents()
This is still an issue 2 years later. jQuery is supposed to remediate these cross browser issues, no? I have been unable to find any statement from the jQuery team calling this behavior "by de ... read »
May 22, 2013 at 12:44 PM
Ask Ben: Query Loop Inside CFScript Tags
In cf10, if you call a function that has: local.result = {}; local.result.msg = ""; local.svc = new query(); local.svc.setSQL("SELECT * FROM..."); local.obj = local.svc.exe ... read »
May 22, 2013 at 12:29 PM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@Ben: What version of Java are you using? Also, did you test users.id to see what Java reports as the data type? I wonder if it's not a Java primitive data type, but getting returned as something ... read »
May 22, 2013 at 11:47 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@Dana, Awesome - so it looks like this bug was fixed in ColdFusion 10. Thanks so much for double-checking that. ... read »
May 22, 2013 at 11:37 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
When I c&p and run on cf10, I get: Selected User IDs: 1,4 User 1 selected: YES - YES User 2 selected: NO - NO User 3 selected: NO - NO User 4 selected: YES - YES User 5 selected: NO - ... read »
May 22, 2013 at 11:27 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@Tom, Good thought, but no dice. Both of these still exhibit the same behavior: users.id[ users.currentRow ] users[ "id" ][ users.currentRow ] It's just something whacky happening with ... read »
May 22, 2013 at 11:07 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
Could your problem be that "users.id" is actually an ARRAY, not a single value? Perhaps try it again with "users.id[1]" (I only have CF8 here at work). ... read »
May 22, 2013 at 7:52 AM
Nested Views, Routing, And Deep Linking With AngularJS
Hi, Just a quick thank you. As it happens, for my own purposes, the pending ui-router work being done in native angular is likely the one I'll adopt, but your exploration, code and documentation of ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools