jQuery Custom Event Handlers to the Rescue!

What do we do when we have a form that is highly customizable based on numerous inputs on the screen and needs to change on the fly as the user selects different options? First we curse at the business people who dreamed this up. Then we go get some coffee (or wine if we are working from home). Finally, we leverage jQuery to make what could be a tangled mess of tightly coupled JavaScript into distinct loosely coupled components that don't know or care about each other.

In this case, we will look at just one of the many fields on the hypothetical screen that is causing so much grief.  Let's say that the Driver field has a few dozen possible values and each of those values has a number of configurations associated with it that are stored all over creation in the back-end legacy system.  Our first thought might be to dump all the possible configurations into the page and write a large amount of complex javascript to find the right configurations on change and adjust the screen accordingly.  Then it hits us...we are professionals and actually know how to build robust solutions.

Our second thought leverages the awesome power of jQuery to make this daunting task fairly simple.  Our Driver select is populated on page load with all the possible values, so we don't need to worry about that.  All we need to do is make an Ajax call to our back-end system that will gather all the configurations and jam them into a JSON representation of the Driver object, then fire a custom event handler when the response comes back that has listeners that will run custom logic for each component on the screen.  It would all look something like this:

var Driver = {   init: function() {     $("select[name='Driver']").change(Driver.get);

  },

  get: function() {
    $.getJSON("/drivers", { id: this.value }, Driver.update);
  },

  update: function(driver) {
    if (driver && driver.value == $("select[name='Driver']").val()) {
      //Do whatever needs to be done here...

      //Invoke the custom event when the Ajax return is ready.
      $("select[name='Driver']").invoke("driver_callback", driver);
    }
  }
}; $(Driver.init);

var Passenger = {
  init: function() {
    $("select[name='Driver']").bind("driver_callback", Passenger.update);
  },

  update: function(event, driver) {
    if (driver && driver.passenger) {
      //fancy logic here
    }
  }
}
$(Passenger.init);
The first thing to note is that we have created two objects that are somewhat analogous to static classes in other languages.  These are meant to represent the functionality of the distinct fields. Each one has an "init" function that is called when the DOM is ready.  In both objects, the init method is used to attach event handlers wherever necessary.

In the Driver object, the important thing to note is the "update" function.  This performs two tasks.  One is to verify that the JSON object we got back actually matches the currently selected value on the screen.  This is merely a safeguard for when the user selects more than once in quick succession and the server cannot respond in time or responds out of order.  The other is the invocation of the "driver_callback" event handler.  This will cause anything that was bound to the Driver select with that handler name to be executed and passed the JSON object. This allows flexibility for some "js" files to be loaded and attach the event handlers prior to the existence of the Driver object in the JavaScript.

The Passenger object binds to the "driver_callback" event in the init method.  This is a good way to do it because it maintains the loose coupling with the Driver field.  Whether that field actually exists or the driver_callback ever gets fired doesn't really matter.  That allows us to include a lot of functionality in this "js" file for multiple screens and only the logic that is relevant to this screen will be invoked.  Also, we can have any number of methods bound to that event, so we can switch out functionality just by changing JavaScript include tags.