Event Messaging on the client side with jQuery

events

How many times have you come across the following kind of code in JavaScript

// code in script-1.js

// eek, two global variables!
var dialog, 
    updateMessage = function updateMessage (message) {        
      dialog.text(message);    
    }

// function to run when the DOM has loaded
$(function () {
    // #dialog is just a simple <p> element
    dialog = $('#dialog');
});

then in another script file

// code in script-2.js

// function to run when the DOM has loaded
$(function () {
    $('select').change(function () {
        // update the dialog with the new selection
        updateMessage('the new value is ' + this.value);
    });
});

All we’re doing here is updating the text of a <p> element in the DOM whenever the value of a <select> element changes. It’s a contrived and trivial example but it perfectly demonstrates the tight coupling between these two script files; if the updateMessage function is renamed, removed or modified to reorder the parameters (let’s put aside the use of arguments for a moment), this is going to break script-2 at runtime. What’s more, usually with these kinds of changes, it’s too easy to forget to search through all script and markup-related files for function usages and modify accordingly. And further still, it's polluted the global namespace with two additional variables (a big no no in JavaScript development), dialog and updateMessage, to allow script-2 access to the updateMessage function (which needs access to the dialog variable). There has to be a better way, and there is.

Custom Events

Custom events have been in jQuery core for as long as I can remember, at least back as far as 1.2.6 in 2008. They are similar to an event bus for sending messages (read: objects) when something happens on the client side and subscribing to knowing when that something happens. Let’s look at a revised version of the previous code, using jQuery custom events

// code in script-1.js

// function to run when the DOM has loaded
$(function () {
   // #dialog is just a simple <p> element
    var dialog  = $('#dialog');

    // bind a handler to the custom namespaced event "dialog.updateMessage"
    $(document).on('dialog.updateMessage',  function (e, message) {
        dialog.text(message);
    });
});

and now in script 2,

// code in script-2.js

// function to run when the DOM has loaded
$(function () {
    $('select').change(function () {
        // raise the dialog.updateMessage and pass it a message
        $(document).triggerHandler('dialog.updateMessage', ['the new value is ' + this.value]);
    });
});

In script 1, we set up an event handler function on document to listen for the namespaced event ‘dialog.updateMessage’. This takes the event object, e,  and an additional parameter, message, which it uses to update the text of dialog. In script 2, we set up an event handler on <select> elements so that whenever the change event is raised, the ‘dialog.updateMessage’ event is triggered, passing in a string that includes the new values as the first item in an array of additional data arguments. The end result? See for yourselves (hit the play icon on the right of the jsfiddle window):

Behaviourally, we can see no difference at all. From a development and maintenance point of view however we have kept our scripts loosely coupled and avoided any runtime errors that may occur from refactoring our code. We’ve also made it easier to extend our code and bind additional handler functions to events, something that can be particularly useful for implementing multiple callbacks. Granted, we can still make changes to the code that could cause things to cease functioning as expected, such as renaming the custom event in one location and not in others, but such a change will not throw an error as in the previous example, potentially halting execution of any JavaScript code that follows.

Comments

comments powered by Disqus