JavaScript Event Bus

I was taught some time ago to always focus on decoupling my code.  Your server and the client (browser) should not need to know anything about the structure of one another.  They can pass information back and forth, but that should be the full extent of their integration.  There are several reasons for this, but the most important of which is modularity – you can drop features or entire sections of code from one without damaging the other.

Once your server-side and client-side code is decoupled, you can start packaging your object-oriented code in each module.  This way, you can plug-and-play with different processes and you don’t have to worry about breaking your code.  The easiest way to do this is by breaking up your code and having each object listen for and respond to global events rather than trying to interact with other objects in your code.

You can do this by building an event bus – rather than communicating directly with other objects on the page, each object communicates with this central event management system.  An object asks the event bus to notify it when something happens.  It also tells the event bus whenever it does something important so other listeners can respond in turn.  JavaScript is easily given to this kind of event-driven architecture, so I thought it would be easy to find a pre-built event bus.

I was wrong.

Some systems, like jQuery, have very advanced event handlers built in to the library.  Unfortunately, many of these are focused on DOM-related events (when things change in the document itself) rather than scripting events (when things happen in different JavaScript functions).  As much as I tried, it turned out none of the pre-existing event management systems did what I wanted them to do.  So I wrote my own.

This is a very rough framework that I expect to spend the next month or so refining and developing as a full library.  At the moment, it satisfies very basic needs – you can register an event listener, you can trigger custom events, and you can remove an event listener if needed.  The full system will feature live debugging features that can be built in to any page – basically, you’ll be able to watch as events fire, modify which listeners are registered, and modify page behavior on the fly in real time.  Not there yet, but here’s the basic skeleton:

[cc lang=”javascript”]
// Register the namespace
var parseNamespace = parseNamespace || function(root, ns) {
var nsParts = ns.split(“.”);

for (var i = 0; i < nsParts.length; i++) {
if (typeof root[nsParts[i]] == “undefined”)
root[nsParts[i]] = new Object();
root = root[nsParts[i]];
}
}
parseNamespace(window, “JDM.EventBus”);

// Main namespace and class
JDM.EventBus = {
// Listener object, contains actual listener references and methods for adding/removing listeners as well as binding the listeners to their appropriate
// triggers at run-time.
Listeners: {
// Instantiates the listener object – every event handler is registered and listed in this object.
List: {},

// Adds a function with an associated handler nickName and execution priority to the list of listeners.
Add: function (eventName, funcName, fn, priority) {
parseNamespace(this.List, eventName + “.” + priority + “.” + funcName);
this.List[eventName][priority][funcName] = fn;
},

// Removes the function associated with a particular event listener nickName. The event listener will still be registered with the system,
// but the trigger function will be triggering a null function, so it won’t do anything.
Remove: function (eventName, nickName) {
for (var priority in this.List[eventName]) {
this.List[eventName][priority][id] = null;
}
}
},

// Checks for priority settings, if none given, add a listener to the list with a very low priority
Subscribe: function (eventName, functionName, fn, priority) {
if (!priority)
priority = 10;
this.Listeners.Add(eventName, functionName, fn, priority);
},

// Trigger an event
Broadcast: function (eventName, args) {
if (!this.Listeners.List[eventName]) return;
for (var i = 0; i <= 10; i++) {
var funcHolder = this.Listeners.List[eventName][i];
if(funcHolder)
for (var fn in funcHolder) {
if (funcHolder[fn])
if(args) {
funcHolder[fn](args.eventArgs);
} else {
funcHolder[fn]();
}
}
}
},

// Remove an event listener
Unsubscribe: function (eventName, nn) {
this.Listeners.Remove(eventName, nn);
}
}
[/cc]

This JavaScript class will allow you to create a custom event listener tied directly to a named custom event. For example, you can set the page to display an alert every time an image loads on the page. First, specify your listener:

[cc lang=”javascript”]
JDM.EventBus.Subscribe(‘customEvent’, ‘functionNickName’, function() { alert(‘Image Loaded’); });
[/cc]

Simple enough. This creates an anonymous function that the EventBus names as “functionNickName” and will call whenever the “customEvent” event is triggered. You can use a system like jQuery to fire the new event whenever an image loads like so:

[cc lang=”javascript”]
$(‘img’).load(function() {
JDM.EventBus.Broadcast(‘customEvent’);
});
[/cc]

This code snipped adds the custom event trigger to the load() event of every <img> tag on the page. Now, you’ll see a custom alert whenever an image finishes loading … and if you’re using AJAX to dynamically load images you’ll see the event then as well.

Wiring a custom alert to an image load is a trivial example of this system’s power. In reality, you can now add custom events to the entire lifecycle of your page, your AJAX calls, and any partial postbacks your page makes during typical use. This allows you to tie functions in one object to functions in another without the two objects knowing anything about one another.

Can’t see how that could be useful?  Take this illustration for example:

You build a website that has a standard DOM setup and uses several jQuery-dependent functions to control an animated menu at the top of the page.  The thing is, you have a second menu in your sidebar, and clicking elements in one should change the display of elements in the other.  The old way of doing things was to tie mouseclick events in the sidebar directly to the display functions of the top menu.  But let’s say, for some reason, you decide to remove your top navigation menu on one page to display a special movie.

Your site breaks, chaos ensues, and no one’s happy.

With an event bus, both menu systems are 100% isolated.  Your sidebar menu publishes events, and your top-level navigation menu listens for events.  If the events aren’t there, no one cares.  Nothing breaks, the world keeps spinning, and you can now add or subtract your menus at will without breaking anything.

For anyone who wants to use this script in their own systems, please remember that it is copyrighted material and must be accompanied by this notice:
[cc lang=”javascript”]
/*!
* JDM EventBus Library
*
* Copyright 2010, Eric Mann
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* “Software”), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
[/cc]