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 the jQuery Conference 2010 (Boston, MA) with:

You Cannot Bind The Submit Event To Objects Using jQuery

By Ben Nadel on

One of the coolest things about jQuery is that you can both bind and trigger events on non-DOM-node objects. However, over the weekend, while I was working on my SRCHR submission - a client-only, YQL-powered search engine - I found out that this technique does not work with the "submit" event in IE. If I look at the line of code in the jQuery core library that is erroring, it appears to be part of the new "submit propagation" functionality, which I think is being used for live-style submit handling.

 
 
 
 
 
 
 
 
 
 

To see this in action, take a look at this small demo. In the following code, I am going to be instantiating a controller object for a given paragraph. This component, as a whole, will announce a "submit" event when the user double-clicks on the associated paragraph.

  • <!DOCTYPE HTML>
  • <html>
  • <head>
  • <title>Binding The Submit Event On Objects In jQuery</title>
  • <script type="text/javascript" src="jquery-1.4.2.js"></script>
  • <script type="text/javascript">
  •  
  • // I am a tiny class to control a paragraph.
  • function ParaController( target ){
  • var self = this;
  •  
  • // Keep the target reference as the UI aspect of this
  • // controller instance.
  • this.ui = target;
  •  
  • // Bind the double-click event to the UI aspect.
  • this.ui.dblclick(
  • function( event ){
  •  
  • // Use this internal, UI-click as a reason to
  • // trigger a custom event on this component.
  • jQuery( self ).trigger({
  • type: "submit",
  • text: jQuery.trim( self.ui.text() )
  • });
  •  
  • }
  • );
  • }
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // When the DOM is ready initialize the scripts.
  • jQuery(function( $ ){
  •  
  • // Create an instance of the para controller.
  • var paraController = new ParaController( $( "p" ) );
  •  
  • // Bind to the custom click event on the component.
  • $( paraController ).bind(
  • "submit",
  • function( event ){
  •  
  • // Alert the text submitted with this event.
  • alert( event.text );
  •  
  • }
  • );
  •  
  • });
  •  
  • </script>
  • </head>
  • <body>
  •  
  • <h1>
  • Binding The Submit Event On Objects In jQuery
  • </h1>
  •  
  • <p>
  • This is a paragraph.
  • </p>
  •  
  • </body>
  • </html>

As you can see, when the user double-clicks on the paragraph, the component traps the click event and announces a "submit" event, adding the text of the paragraph as part of this "custom event." This works fine in FireFox, but in IE, it throws the error:

Line: 2200
this.nodeName is null or not an object.

Looking at line 2200, it appears that jQuery is trying to implement "submit" propagation for browsers that don't inherently support it (which I guess IE does not). To get around this, all you have to do is rename the event to not use the "submit" event type. When you do that, jQuery no longer tries to propagate the event - I believe, by default, jQuery doesn't propagate any custom event types.

I am pretty sure this is a bug; jQuery is probably supposed to be checking the type of the object in the binding before it attempts to use it as a DOM node. This was most likely just an oversight in the new "live" functionality. On a personal note, live-binding a form submission just feels somewhat awkward to me. This is entirely emotional, I understand that, but a form submission doesn't feel like it's in the same league as an event like click. I can understand live-binding click; but submit? It just doesn't feel right.




Reader Comments

Interesting find - I'm inclined to call that a bug as well. Did you submit a support ticket yet? Regarding the reasoning behind delegated submit events, I think I would defend it for two main reasons:
1) It maintains consistency with the rest of the events API.
2) There are some scenarios in which it is actually very useful. Ajaxified comment threads are a good example - it's nice to be able to simply spin up comment forms anywhere, and not worry about rebinding each of them.

Reply to this Comment

@Jeremy,

I have never submitted a jQuery bug ticket before, I will definitely look into it if you think it's a bug as well.

As for submit-propagation, I know what you are saying; that's why I freely admit that my adverse reaction is entirely emotional :) I'll get past it.

Reply to this Comment

So I'm relatively, but not 100%, sure that wrapping jQuery around an arbitrary object isn't actually supported. If you look at the code that inits a jQuery based on (selector,context), you won't see any logic there for handling generic objects. So while it does sorta-kinda work at present, I'm not entirely sure you'll end up seeing a change to the event propagation model to make an unsupported feature work right

Reply to this Comment

Also, if you look at the docs, it says the following types of arguments are allowed to pass to jQuery():

selector - a selector string
element - a DOM node
elementArray - an array of DOM nodes
jQuery object - an existing jQuery object (pointless, but supported)
[no arguments] - returns an empty jQuery object
html - an HTML string
callback - a function. This is a shortcut to $(document).ready();

Reply to this Comment

@Adam,

Wow - that just shot some adrenaline up my spine :)

Ok, after a little bit of digging, according to this blog post, although no specific recording has been made, apparently John Resig said that this feature would be embraced and supported:

http://www.mrspeaker.net/2009/11/14/selecting-javascript-objects-with-jquery/

In the comments to that blog, they even make reference to a bug that this stopped being supported, but then I guess was eventually solved (status of linked ticked was changed to "solved.").

It also appears that John Resig filed a bug and a fix for the unbind() to plain javascript objects:

http://dev.jquery.com/ticket/6184

So, while this might be stated explicitly in the documentation, it looks like people, including John, keep taking steps to ensure that it *is* supported in the core.

Perhaps it's time that this DID get put in the documentation?

Reply to this Comment

Ya - for some reason I thought you were binding/triggering against "self.target", not "self". Not sure whether or not this snippet really makes any sense, but you do something similar to this example by keeping all the event logic DOM-centric: http://jsbin.com/uneke/2/edit

Reply to this Comment

@Jeremy,

That definitely works; but, part of my issue with that approach is that it forces the calling code to make assumptions about how the component, ParaController, will be implementing events as well as how the component will be implementing the UI. This creates coupling between the calling code the internals of the ParaController component.

By keeping the API at the "component" level, it provides for good "implementation hiding".

That said, I am now very curious to see if this object-binding is even officially supported by the jQuery team? If not, then your approach is going to be the only thing inherently available (unless custom event frameworks are added to the objects).

Reply to this Comment

@Jeremy,

Ha ha - just saw your second comment :) Hey, every post is really about *good conversation*... and that's what we've got going on here!

Reply to this Comment

@Jeremy,

Very interesting! If I had to guess, I'd say when running the extend() method, you are adding all the DOM-native event methods and node properties to the Javascript object. Very funky :) So, I guess, when the code looks at the ParaController instances, it "looks" like a P tag.

Reply to this Comment

@Ben,

Right - the main thing it buys you in this case is that anything jQuery needs for the binding is now available. You basically get to treat it just like the p element itself. You of course have to be careful about overriding stuff, and certain things that are set in the paragraph's constructor might not work. It's not an efficient solution either. Just... interesting.

Reply to this Comment

Excellent post and the comments very informative. Really need to go back to jQuery docs and do some reading.

Reply to this Comment

@Patrick,

Thanks my man.

@Kevin,

There's still some disagreement as to whether this feature is actually supported. I've tried contacting some jQuery team members of their input, but have not heard back just yet.

Reply to this Comment

Yeah support may be questionable, but I look at from a pragmatic standpoint. If the environment you are developing for is closed and standards are not 100% mandated why not.

After all if you are in an Oracle shop do you write SQL that works on all platforms or do you optimize for Oracle (this was the case when I was a contractor for US Gov't agency).

The key here is that it is decoupled, so at least you can later go back and replace with a more standards friendly version.

Reply to this Comment

@Kevin,

You make a really good point! Of course, when it comes to any framework, the hope is that you have something that can constantly be upgraded as new versions come out. Having to constantly re-edit the event framework, and then re-minify the JS file would just be a pain... and probably open us up to error.

Ideally, I just want the jQuery people to come out and support this publicly.

Reply to this Comment

This is very simple, and is working without problems in my form.

$("#apDiv12").click(function(){
sb = $("#Submit").click();
$(this).bind(sb.click);

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.