jQuery Evented Programming Primer

Yikes! Sounds scary right? Don’t worry, I’m not going to drop anything you are incapable of understanding. It’s been a while since we’ve had activity here and evented JavaScript is so awesome that I had to get a post around.

The brain is an amazing thing. It has this uncanny ability to relate information that seems to be unrelated and pop it into consciousness just when you need it. A month back or so I was reading Design Patterns in Ruby by Russ Olsen. So what does that have to do with a post that is going to be about JavaScript and jQuery?

Chapter 5 of the book tells the story of the observer pattern. I read it, thought it was interesting and moved on with life. A few weeks later, while hacking out some JavaScript on a project, I was getting annoyed at how much I was duplicating myself.

The Problem

The project is a single page application. My definition of a single page app is one that rarely does a full refresh of the page. This is one of the first projects of this type that I have worked on so I didn’t have a library of techniques to fall back on.

Most of it was up and running smoothly, but I quickly found it tedious to observe each form that was getting added. Typically when I observe a form, I do something like this (very simplified):

$('#someform').submit(onSubmit);

function onSubmit(event) {
  event.preventDefault();
  var form = $(this);
  form.ajaxSubmit({
    dataType: 'json',
    beforeSend: function() {
      // disable submit buttons or show some activity indicator
    },
    success: function(json) {
      // handle form success
    }, 
    error: function(response, status, error) {
      // handle validation or application errors
    },
    complete: function() {
      // enable submit buttons or hide some activity indicator
    }
  });
  
  return false;
}

The ajaxSubmit is part of the jQuery forms plugin, which is pretty great. Each form in the app needs to do the same type of thing listed above, but there was just enough of a difference on each form that I found myself repeating the steps above instead of doing something on a more global level.

Out of nowhere, the chapter I read over a month ago came back to me. I won’t get into it here, but you can read more about the observer pattern on wikipedia. The key line from wikipedia is:

An object, called the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes.

The Solution

It occurred to me that the best way to abstract all this duplication was to have one live observer on the form tag itself that implemented the duplicated functionality and triggered custom events for that which was unique. What I ended up with is something like this:

$("form").livequery('submit', function(e) {
  e.preventDefault();
  var form = $(this);
  form.ajaxSubmit({
    dataType: 'json',
    beforeSend: function() {
      form.trigger('form:beforeSend');
    },
    success: function(json) {
      Layout.applyJSON(json);
      form.trigger('form:success', [json]);
    }, 
    error: function(response, status, error) {
      form.trigger('form:error', [response, status, error]);
    },
    complete: function() {
      form.trigger('form:complete');
    }
  });
  
  return false;
});

The first thing you’ll note is that I used the livequery plugin. Unfortunately, jQuery’s live event functionality does not support submit as of now. The second thing you’ll notice is that I’m triggering custom events on the forms in the ajaxSubmit callbacks instead of implementing unique functionality.

Just like that 90% of the work that is involved with making a new form submit through ajax instead of a page refresh is out the window. This means that with each new form that is added, I can focus on the activity that is unique to that form which obviously speeds up development and removes tedium.

Customize Where Needed

Let’s say I wanted to implement a new form that allowed a user to update their status. The project we are working on utilizes XMPP extensively and when the new status is created, a notification needs to be sent to the appropriate people of the change. Also, the form is in a simple dialog (jQuery UI Dialog), which should be closed on success. The code for that looks kind of like this:

$('#new_status')
  .bind('form:beforeSend', function() {
    save_button.text('Saving...');
    buttons.disable();
  })
  .bind('form:success', function(event, json) {
    new_status_dialog.dialog('close');
    new_status_form.get(0).reset();
    
    new Notification('status_updated').send({body: json.body});
  })
  .bind('form:complete', function() {
    save_button.text('Save');
    buttons.enable();
  });

Now, only the #new_status form will send the status_updated notification, but the rest of the functionality (submitting via ajax, updating the page, handling errors) is all wrapped up in one place. Pretty cool. I ended up doing the same thing for the edit user form as well, which looked something like this:

$('form.edit_user').bind('form:success', function() {
  new Notification('profile_updated').send();
});

bind and trigger

So how does all this work? You pretty much only need to know two things: bind and trigger. bind observes events and calls functions when they are triggered. trigger fires the events. Using just these you can literally build your entire application in an evented manner.

One note is if you want to fire a global event not tied to a specific element, just use document. We fire several app level events like so:

// bind document to custom event
$(document).bind('app:status_updated', function(event, status) {
  // log status to the console
  console.log(status)
});

$(document).trigger('app:status_updated', [{from:'john', body:'Yo!'}]);
// => {from:'john', body:'Yo!'}

The Benefit

Using events has really cleaned up the code on this project. Each piece of code does specifically what it needs to do and fires events that the rest of the code can hook into to customize. Each JavaScript file that we include has a specific purpose and binds to and triggers certain events. If that file is removed, the app still functions perfectly as it doesn’t hurt anything to bind to events that never get triggered. No more searching to see if any methods in the file are being called somewhere else. No errors because you forgot to search.

I hope that I’ve explained myself well enough and that you are as stoked about events as I am. I can’t overstate how useful this has been so if you think I’m nuts, chances are I just wasn’t clear enough. I am curious if others have started moving to a more evented way of life in JavaScript or if this is kind of new to the majority. Let me know with a comment below either way.