JSBroadcaster - broadcasters & listeners in JavaScript

Posted on January 01, 2006

Here's a JavaScript version of the actionscript broadcaster/listener model "ASBroadcaster", unimaginatively called "JSBroadcaster". It's particularly useful for getting around the problems caused by having multiple functions that need to be called when a document loads, or resizes, or whatever. It provides a higher level of abstraction and can make working with multiple, reusable JavaScript files easier.

In this example, two separate functions are called when the document loads: the background colour is changed to grey, and a line of text is inserted using the DOM. Not much to look at, I know, but bear with me.

onload limitations

Because a HTML document can only have one onload function, developers often define one function to be called when the document is loaded, which in turn calls a list of further required functions. For example, the following may be inserted into the head of a document:

window.onload = function () {
	function1();
	function2();
	function3();
}

An advantage of this technique is that we can see in one place which functions are called when the document loads. However, it all gets a bit untidy when we are working with external .js files that require an onload function. The onload function has to "know" the name of the function it is calling within the external script (in this case function1, function2, function3), which leads to the external script not being as self-contained as we would like. Subsequent changes to the external script may force us to edit the head script across several documents.

The broadcaster/listener model

The broadcaster/listener model avoids this problem because the broadcasting object doesn't have to know who is listening, much as a radio station doesn't have to individually address the radios of it's audience. The audience actively tunes in, and in the same way the listener object actively "registers" itself with the broadcaster, without the broadcaster having to explicitly "know".

Implementation

Obviously the listener has to know what the broadcaster is called, just as the radio audience has to know how to tune into the station. For the onload problem we create a generic broadcaster object called onLoadObj.

The first step is to include the JSBroadcaster.js script into the head of the document. This creates an object called JSBroadcaster.

<script type="text/javascript" src="JSBroadcaster.js"></script>

We can then use the JSBroadcaster object to create a our broadcaster.

<script type="text/javascript">
var onLoadObj = {};
JSBroadcaster.initialize( onLoadObj );

The initialize function turns the object passed as it's argument into a broadcaster. We can now add an onload function that tells our broadcaster to send a message.

window.onload = function () { onLoadObj.broadcastMessage( "onLoad" ); }

When the document loads onLoadObj sends the message "onLoad" to all of it's listeners. Only it doesn't have any yet. The listeners have to register themselves first.

To become listeners, objects register themselves with a broadcaster using the public method broadcaster.registerListener( obj ). In the example I've created there are two objects in the head of the document that register themselves, but for the sake of the discussion let's assume that they are in two separate external files. Here's the code that creates the first object, myListener:

var myListener = {};
myListener.onLoad = function () {
	// change background color
	var arr = document.getElementsByTagName("body");
	arr[0].style.backgroundColor = "#eee";
}
onLoadObj.addListener( myListener );

It's important that the function to be called has the same name as the message that is broadcast, in this case onLoad. The broadcaster can broadcast any number of messages - if a listener has a corresponding function, it will be called.

The second listener object is created in the same way, and when the document loads, both onLoad functions are triggered.

JSBroadcaster compared to other approaches

This technique is similar to Simon Willison's much-praised addLoadEvent, which actually inspired me to implement JSBroadcaster to address this problem. The addLoadEvent function has a smaller footprint, and takes a simple function as it's argument rather than an object. However, it is tied to the onload event. With the broadcaster/listener model we can listen for any event we like. For example, an AJAX request retrieves data from the server and broadcasts an "onSuccess" message. A listener object removes a loading graphic, another validates the new data, and another displays the data via the DOM.

Another important feature of the JSBroadcaster is the ability to remove listeners. When the generic AJAX request function is completed and the loading graphic removed, we may not want to remove that particular loading graphic the next time the AJAX function is called. So we "unregister" the graphic controller object using the removeListener function.

var ldGraphic = {};
ldGraphic.onRequest = function () {
	// called when the AJAX function starts the request
	// and broadcasts "onRequest"
	//
	// do some DOM stuff to show the graphic
}
ldGraphic.onSuccess = function () {
	// called when the AJAX function receives data
	// and broadcasts "onSuccess"
	//
	// do some DOM stuff to remove the graphic
	
	ajaxObj.removeListener( ldGraphic );
}

...

function getData() {
	ajaxObj.addListener( ldGraphic );
	ajaxObj.doRequest( "foobar" );
}

When the request fired by getData is successful, ldGraphic unsubscribes itself. The beauty of it is that the (fictional) ajaxObj is blissfully unaware (from a hard-coded perspective) of what the ldGraphic is up to. We can add, remove or change the behaviour of the listeners while leaving the generic AJAX object untouched in it's external .js file, and in doing so have created a more abtracted, flexible and portable application.

Here's the script.



Comments (1)

Laboratik said

This is amazing.A great idea and very usable!
One question though, can there be parameters passed to the function that is being passed through?eg.ajaxObj.doRequest( "foobar('get')" );

Anyways, this is sure to be used.Simply love it!

Posted by: Laboratik at February 22, 2007 11:20 PM .