jQuery Event Binding to a Prototypical Instance

jQuery is a powerful framework designed to take the pain out of writing cross-platform Javascript and 99% of the cases it does so extremely successfully. There are however certain instances where you want to do something that's a little bit of a fringe case and you're left scratching your head. The only way to get past the roadblock is to take a deep breath and dive into the source code, which thanks to jQuery's open source nature is actually an awesome thing to be able to do.

In this particular instance I was trying to bind a custom event to an instance of a prototype. The guys behind jQuery it seems, haven't really paid this case much attention as it's probably a rather unusual situation.

Let's rewind a bit. Why is this weird—people bind events every day. Javascript developers learn event binding fairly quickly:

$( '#SomeID' ).on( 'click', function(){…} );

The above snippet is the simplest of event bindings with jQuery. It binds the click event to a DOM element with the ID "SomeID".

jQuery in it's greatness takes events one step further and allows the binding of custom events.

$( '#SomeID' ).on( 'someEvent', function(){…} );

Here, a custom event called "someEvent" has been bound to the DOM element in the previous example. How useful is this if this event doesn't exist as part of the set of DOM events that browsers implement? Very, it turns out because jQuery complements event binding with event triggering. Via its trigger mechanism, you can trigger the custom event so that anything listening for that event on the given DOM element will fire. Awesomeness.

$( '#SomeID' ).trigger( 'someEvent' );

Triggering events with jQuery is that easy. But why stop there? If using the DOM as a middleman for your custom event binding/triggering seems like an uneccessary step you'd be right—why not do away with it? Again, the creators of jQuery have thought of this and the framework allows you to bind and trigger events on non-DOM objects. Crazy right? In one clever, simple extension of the event framework, jQuery allows you to implement message buses out of the box.

For example, components of a web app can listen to a custom message bus for the loaded event, upon which they may execute some code.

var messageBus = {};
$( messageBus ).on( 'application.loaded', function(){…} );

Thus when the application loaded, it could trigger the loaded event to notify its components

$( messageBus ).trigger( 'application.loaded' );

This whole system works really well and 99% of the time it will make you very happy. I did however come across one instance where it leaves you wondering what you did wrong. That case—as mentioned above—is when you try to bind and trigger events to object instances created from prototypes.

Considering the following code which creates an object prototype:

var person = function( name )
{
  this.name = name;
  this.happy = '';
}

person.prototype.setHappy = function( happy )
{
  this.happy = happy === true;
}

Then, to create an instance:

var paul = new person( 'paul' );
paul.setHappy( true );

Creates an instance of person called Paul and set's Paul's happiness to true. This all works great and is the way that Javascript allows you to instantiate objects—given it is different to the more common class based model.

The problem comes when you try to trigger an event on Paul inside the prototype definition. For some reason, jQuery falls flat on its face for reasons which at first glance seem nonsensical. The following will not work:

person.prototype.walked = function()
{
  $( this ).trigger( 'walk' );
};
var peter = new person( 'peter' );
peter.walked();

Upon inspection of the jQuery source code, the reason becomes clear. In order to work its magic and allow event binding to non DOM objects, jQuery tries to detect what type of object is dealing with. Unfortunately it doesn't managed to detect that the instance of "person" is a Javascript object, and the reason is simple: having failed to identify it as either a jQuery object or a simple object (e.g. {}), as a last resort it tests for the "length" property and if it can't find it, it gives up.

So, the solution it would seem, is easy though a bit annoying and crude. To trick jQuery into identifying the instance as an object onto which a custom event can be bound/triggeres, you need to set the length property either on the property or—more sensically—on the prototype.

person.prototype.length = 1;

I hope that this helps anyone else who found themself scratching their head when trying to do this.

Happy coding!

Comments

No comments have been posted yet. Please be the first to post a comment!

Post a comment

Comment details
anti-spam code. you need to be able to view images to post a comment. sorry.