Adventures in HttpContext All the stuff after 'Hello, World'

Organizing Javascript for Event Pooling with jQuery

It turns out my most popular article of the past year was Event Pooling with jQuery’s Bind and Trigger.  I wanted to write a follow up article taking this approach one step further by discussing how to logically organize the relationship between binders and triggers on a javascript heavy UI.  It’s important to properly design the code structure of your javascript to create a flexible and maintainable system.  This is essential for any software application.  For javascript development, you don’t want to end up with odd dependencies hindering changes or randomly bubbled events causing bugs.

Event Pooling: A Quick Review

What is event pooling and why is it important?  Quite simply it’s a way to manage dependencies.  You create a loosely coupled system between the thing which triggers an action to happen and the thing which responds to the action, called the binder.  jQuery has some cool bind and trigger functionality which allows you to create custom events for event pooling- and you can use this to easily wire up multiple functions to write complex javascript with ease.  I encourage you to check out the original how-to article Event Pooling with jQuery’s Bind and Trigger to learn more.  It’s a very powerful technique.

Now, as we all know, with great power comes great responsibility.  If you structure the relationship between binders and triggers incorrectly you’ll end up with a mess on your hands, a spider web of logic which is almost impossible to unwind.  Here are some tips to better structure your binders and triggers for a logical and maintainable application

Presenting the Problem

Stupid Form

Let’s take a look at an example app.  Here’s a simple form where a user can draft a report, save it for later, or send it immediately. There are three options to enter data in the name/email/department field.  The user can enter it manually, choose from the autosuggest area at left, or simply select one of the favorite links.  The autosuggest/favorite link would pre-populate the textboxes used in the form.  If all the data is there, the send button is enabled.  If not, it’s disabled.

Let’s discuss how we’d deal with enabling and disabling the send button.  There are many ways to approach this.  A traditional way is to wire up some standard event to our textboxes:

$('#name').keyup(function(){
    //Logic (hopefully structured in a reusable way across all controls).
});
//Do this for all other input controls.

This solution is perfectly valid and works, but it has some shortcomings.  First, you need to wire up all three controls and route them to the same function.  Next, even though the keyup will handle user input, the textboxes could change in other ways- either from the favorite link or the auto suggest box.  We’ll need to call our validation function in numerous places.  There may also be other logic we want to incorporate within the text change event unrelated to enabling of the send button- email validation, for instance, which is only applicable to the email textbox, or a certain length requirement on the name textbox.  What happens if there’s a new requirement where the subject/body must be filled to enable the send button? Where does all this functionality fall into our larger requirements?

You can imagine the complexity of writing the javascript required for this UI.  We need a slew of functions to deal with validation logic: Both for enabling the send button and other input controls.  We need to wire up all our onkeyup and on click events across the auto suggest field, textboxes, and favorite links.  Function calls are everywhere.  It will take all your code complete skills to manage those dependencies and keep this code lean.

This is where event pooling comes into play: instead of direct dependencies between these controls and logic, you bubble events to a middle man and allow interested parties to respond accordingly.  Instead of all the controls telling the send button to enable/disable itself, the send button “listens” for changes it cares about- when the value of the textbox changes- and responds accordingly.  The responsibility is reversed- the function which needs to be called can choose when it’s called, rather than waiting for something to call it.

Dependencies Between Binders and Triggers

With event pooling there are two things you need to be conscience of:  the events themselves and the data passed between the binder and trigger.  These items form the two dependencies between binders and triggers.  The event serves as a link between the binder and trigger and the data is the information which is passed between the two.  It’s essential to properly structure your events and choose the right data transfer option to avoid pitfalls over the life of the application.  We’ll deal with each aspect individually.  This post will be about the structure of events, and I’ll write various ways to pass data between parties in another post.

Structuring Custom Events

Most people are familiar with the standard javascript events: click, blur, enter, exit, etc.  These are what you usually wire up to functions when you want to do something.  However, they only go so far- you need custom events when you have a lot of stuff happening.  Why use them?  Quite simply, it’s more logical for your application control flow.  For our send button functionality, we want our validation function to act when something happens.  This “something” will be a custom event we create.  We have two options for naming our event: we can name the event after what is intended, like “ENABLE_DISABLE_SEND_BUTTON”, or name the event after what has happened, like “NAME_CHANGED” or “FAVORITE_LINK_SELECTED”.  The former option requires multiple events to be fired when something happens.  The autosuggest box, for instance, would require the ENABLE_DISABLE_SEND_BUTTON trigger, a SELECTED_CONTACT event for setting the textboxes, and whatever else happens after a contact is selected.  The latter option requires just one trigger to be fired, but the binder must subscribe to multiple events.  The choice can also be presented like this: do we want each element to fire a trigger for each action which should occur, or have a function to listen for multiple triggered events?


Naming events may seem like an arbitrary and thoughtless act, but it’s essential to have a naming strategy with event pooling.  Just like wiring up functions directly can be unwieldy, so can a slew of events wired up every which way.

For event pooling, it’s more important to name events after what has happened, rather than what is intended. So the correct approach is to go with things like “NAME_CHANGED”, “CONTACT_SELECTED”, or “EMAIL_CHANGED”.  The rationale has to do with dependencies themselves:  we don’t want functions called from random parts of the system via specific events:  this is no better than calling functions directly from disparate parts of the system.  And with specific action related events like “ENABLE_SEND” you just know someone is going to take a shortcut and wire some random binder to a totally unrelated trigger, because that trigger is fired from the control they want to monitor.  Binders- and the functions they are wired to- should proactively “listen” for things which it’s interested in.  This allows you to easily know why a function is being called.  If you need to change why or how something is handled, you go to the recipient, not the caller.  The caller, after all, could be anything, and more importantly the caller could be triggering multiple things.

//Funnel all possibilities to single custom trigger
$('#name').bind('keyup enter', function() {
 $(document).trigger('NAME_CHANGED');
});

//Funnel all possibilites to single custom trigger
$('#email').bind('keyup enter', function() {
$(document).trigger('EMAIL_CHANGED');
});

//I know why this is being called because I've subscribed
//to the events I'm interested in.
$(document).bind('NAME_CHANGED EMAIL_CHANGED', function()
{
 //Handle validation, etc.
});

The cool thing about this approach is there may be something else which will cause an email or name changed event to happen: specifically, when someone has selected a contact from the autosuggest list or a favorite link is selected.  You can fire the name_changed event from multiple places and not worry about wiring up any new triggers.  The code would look something like this:

//Fired from elsewhere
$(document).bind('FAVORITE_LINK_SELECTED', function()
{
  //Handle selection
  //Set Name, Email, etc.
  //Fire event:
  $(document).trigger('EMAIL_CHANGED');
  $(document).trigger('NAME_CHANGED');
});

How nice:  another part of the system changes the name indirectly, and we don’t have to worry about hooking anything up because the binder is already subscribed to the NAME_CHANGED event.

Note the name doesn’t necessarily correspond to a specific element: It’s not a textbox_name_changed event, nor are any specific html id’s involved except for wiring up a trigger from a standard keyup event.  This is an important difference: we could rename the id’s, or switch to some other input control, and not have to rewire everything.  We don’t care about the textbox nor that the textbox’s value has changed.  We care that the textbox respresents the name entered or the email address- and we want to know when the name or email has changed.  The favorite link clicked is a good example of this nuanced difference.  Take a look at the following html:

<a href='#' id='fav1' class='favorite'>My Favorite 1</a>
<a href='#' id='fav2' class='favorite'>My Favorite 2</a>

Imagine we’ve wired the click event to fire a FAVORITE_LINK_SELECTED trigger.  With this trigger, we don’t care that the link with id fav1 or fav2 has been clicked, nor the a.favorite selector has been clicked.  We don’t care about ids or css classes.  We care a FAVORITE_LINK_SELECTED event has happened because that’s what the id fav1 and favorite class represents- the favorite link- and we want to know when that has been selected.  We can rename the id, change the class, or even change the entire element.  As long as FAVORITE_LINK_SELECTED is fired we’re good to go.  Our custom FAVORITE_LINK_SELECTED trigger is the abstraction which creates the loosely coupled system.

Conclusion

One thing I haven’t discuss is how data is passed from trigger to binder.  It’s the other important dependency between triggers and binders which we’ll discuss in another post.  The important thing to take away is why you’d want to use custom named events to create a loosely coupled system.  For very straightforward pages it’s probably not worth the overhead- the abstraction isn’t required.  However, in a complex form where there are lots of validation dependencies, or many routes to the same function, or when multiple events can trigger an update- you want to use event pooling.  More importantly, you want to think about your strategy when it comes to naming events, because it’s the description linking the caller and method together.  You do not want to code yourself into a corner!