Observer/Listener Pattern in Javascript

At Ubernote, KeepFu, and my current exercise tracking side project, I write a lot of interactive software in Javascript that requires the use of the Observer pattern. An observer pattern real world example that everybody doing web development knows is the DOM Event system. What the observer allows you to do is register a callback function that is called whenever a particular event happens. This capability is invaluable whenever you are writing a system and the code that generates the event does not (and maybe should not) know about all the players that care about the event happening. The observer pattern promotes a separation of concerns and easy extensibility of systems.

This is the second one I have written, and it is a work in progress. Those familiar with jQuery's event system will find the syntax very familar. At the moment, it only allows for the binding and triggering of events, not unbinding. The reason this is so similar to jQuery's syntax is because I was experimenting one day and tried using the jQuery event system on non-DOM objects and to my surprise, it worked in Chrome, and Firefox 3.5, but in Firefox 3.0 I had a lot of 'infinite recursion' errors. I wanted to use the same syntax and didn't want to rewrite any of my in place code to do it. There are no shortage of Observers on the net, but many are pretty heavy weight, and none conform to jQuery's syntax, at least that I have found.

The current code can be found at http://shanetomlinson.com/static/observer/Message.js. An example showing the usage is at http://shanetomlinson.com/static/observer/listener_observer_example.html.

And now for the rundown. The code uses an entry function Message that you call on the object you want to bind listeners to. To bind a listener to a message on an object:

    Message( object ).bind( message_name, callback );
To trigger an event:
    Message( object ).trigger( message_name, data );
Example:
    var testObject = {};
    Message( testObject ).bind( 'testmessage', function( event, value1, value2 )
    {
        alert( 'event: ' + event.message + ' value1: ' + value1 + ' value2: ' + value2 );
    };

    Message( testObject ).trigger( 'testmessage, [ 'testvalue1', 'testvalue2' ] );

The code:

/**
* 
* Message - An observer/listener pattern for Javascript.
*    Usage: 
*    To bind a listener to a message on an object:
*        Message( object ).bind( message_name, callback );
*    To trigger all listeners bound on a message on an object:
*        Message( object ).trigger( message_name, data );
*    When calling a callback, it will be called in the contxt of the
*        object being registered upon.  The input parameters will be:
*        event, data1, data2, ..., dataN
*    Example:
*        var testObject = {};
*        Message( testObject ).bind( 'testmessage', function( event, value1, value2 )
*        {
*            alert( 'event: ' + event.message + ' value1: ' + value1 + ' value2: ' + value2 );
*        };
*        
*        Message( testObject ).trigger( 'testmessage, [ 'testvalue1', 'testvalue2' ] );
*/
Message = function( object )
{
  if( !object.__objectID )
  { // Gives the object an ID if it doesn't already have one.
    object.__objectID = ( Message.currID++ ).toString();
  } // end if

  // return an object that contains the functions that can be worked with.
  return {
    bind: function( message, callback )
    {
      var handlers = Message.messages[ message + object.__objectID ] = Message.messages[ message + object.__objectID ] || [];
      handlers.push( callback );
      return this;
    },

    trigger: function( message, data )
    {
      var handlers = Message.messages[ message + object.__objectID ];
      if( handlers )
      {    
    var event = { 
      target: object,
      type: message 
    };
    var callbackArgs = [ event ].concat( data || [] );

    // go through all the handlers and call them with the arguments list we made up.
    for( var index = 0, handler; handler = handlers[ index ]; ++index )
    {
      handler.apply( object, callbackArgs );
    } // end for
      } // end if
      return this;
    }
  };
};

/**
* static variables used for the message processor.
*/
Message.messages = {};
Message.currID = 0;

A quick review of the code.

To make everything work, we have to keep a database of callback functions for every message/object pair. Javascript provides us with an Object which is a nice storage device we can use as a database. Since we may want multiple listeners for each message/object pair, we use an array to hold the list of callback functions. What this means - use a single object as a database, the keys in this object are message/object pairs. The values for each message/object pair is an array that holds a list of callback functions. To use a Javascript Object as a database the keys have to be text or numeric, this presents a problem because we are registering our listeners on objects, which are neither text nor numbers. To get around this, we give each object that is registered a unique text ID. We attach the ID as a property of the object named __objectID. With the object having a text ID, we can create message/objectID keys into the database.

Message is our accessor function which allows us to bind listeners and trigger events. Our database is stored as a static variable on the Message function. To ensure that we are also generating unique IDs for every object registered, Message also stores a static variable called currID. Note that we are storing the list of callbacks in an external object and not on the object itself. On the object itself, we store a text ID. The reason for this is that I want to be able to use this system to bind custom events to DOM elements, and I wanted to avoid IE memory leak involving circular references and DOM elements.

Whenever binding a listener to an object or triggering an event, we first look at the object to see if it has been assigned a unique ID. If not, generate one. When binding a listener, look in the database to see if a list of callbacks already exists for the given object/message pair. If not, create an array and store it in the database under the key/object ID pair. We then add the callback to the array.

Whenever triggering an event for an object, we look in the database to see if there is a callback list for the given object/message pair. If there is not, do nothing. If there are callbacks, we do a little bit of magic to make this a bit more similar to a jQuery events. First we create an event object that contains a type and the target. The type is the message that was triggered, the target is the object the event was registered on. Once we have that, we make an array out of the event, and each item in the 'data' argument that is given to the trigger function. We then iterate through each callback in the list, passing it the array we generated as its arguments.

Right now my biggest concern is that I am unsure of what the memory usage will be if registering thousands of callbacks. I make use of closures, which can eat up a fair amount of memory, so I will have to see what effect this has.

I plan on a lot of future improvements to this. Two of the more immediate needs are adding the ability to unbind a callback as well as adding the facility to trigger an event asynchronously. The asynchronous behavior will add an amount of overhead, but I have found with Ubernote that it can really help with the responsiveness of DOM events.

If you have any feedback/suggestions/ideas, please let me know. My email address is set117@yahoo.com