We’re Hiring!

New Bamboo Web Development

Bamboo blog. Our thoughts on web technology.

Custom event emitters in Javascript

by Ismael Celis

Websockets-based activity dashboard app. Read on to know more about the ideas behind it

In this post I'll attempt to summarise some patterns for designing event-based Javascript applications extracted from our projects and previous blog posts on the subject. I'll end with an overview of the techniques described and how they play together in a real-world application. This post is a follow-up from a presentation at the London Javascript User Group.

Background

We've blogged about Javascript patterns for complex applications before. If you're short on time, that post describes the way we lay out our client-side apps here at New Bamboo. It's an MVC-like setup where we have Model objects doing the persistence and data binding, and View objects listening to state change in those models via the observer patterns and custom events. If you haven't, I strongly recommend you read that post before this one.

The key component of this approach is using custom events to decouple the communication between layers. This is a simple View example *:

* The examples in this page will use jQuery's event triggering and DOM functions for simplicity.
1 var UserPane = function(event_source, $container){
2   
3   function update(event, user){
4     $container.append('<li&gt:'+user.name+'</li&gt:')
5   }
6   
7   $(event_source).bind('user_added', update)
8 }

And you would use it like:

1 new UserPane(document, $('ul#user_list'));
2 
3 // Wrap DOM events and re-trigger with useful data using jQuery
4 $('#users input:radio').change(function(evt){
5   $(document).trigger('user_added', {name: $(this).val()});
6 });

The example defines a View class that knows in which DOM element to render content ($container) and binds itself to the 'user_added' event triggered on the document object by other layers in the application.

We're passing the document element as the source of events, but you're free to swap it by anything that you can bind to and trigger events from. It doesn't even need to be a DOM element, and this is where OO and evented programming starts to look less counter-intuitive than one might have thought: you can build your own Event Emitters and model your domain around them.

Event Emitters In the wild

That is exactly what libraries such as Node.js are doing with an EventEmitter class you can extend from. The following is an object that emits a "tick" event with a random number every 5 seconds.

 1 var EventEmitter = require('events').EventEmitter,
 2     puts = require('sys').puts;
 3 
 4 var Ticker = function( interval ){
 5   var self = this, 
 6       nextTick = function(){
 7     self.emit('tick', Math.random() * 1000);
 8     setTimeout(nextTick, interval);
 9   }
10 
11   nextTick();
12 };
13 
14 // Extend from EventEmitter 'addListener' and 'emit' methods
15 Ticker.prototype = new EventEmitter;
16 
17 // A ticker instance with an interval of 5 seconds
18 var ticktock = new Ticker( 5000 );
19 
20 // Bind an event handler to the 'tick' event
21 ticktock.addListener('tick', function( number ) {
22   puts('number emitted: '+ number);
23 });

The nice thing about this is that objects interacting with the Ticker instance only need to bind themselves to events emitted by it. There's no need for public methods apart from addListener (or $.bind in our previous jQuery example).

How we use Event Emitters

Back in the browser side of things, you can build event emitters that represent objects in your domain and trigger events when their status changes. Then other parts of the application can observe those events and react accordingly. A nice abstraction of this pattern is js-model, an ORM-like Javascript library we developed to model domain objects with extra server persistence baked-in.

 1 // Js-model example
 2 var Post = Model("post")
 3 
 4 Post.bind("add", function(new_post) {
 5   addObjectIntoUI(new_post)
 6 })
 7 
 8 // This will trigger the 'add' event on the Post object, 
 9 // executing any handlers observing it.
10 var post = new Post({ foo: "bar" })

More info on js-model on its documentation page.

This is also the approach that we've taken with our Pusher websockets service. With it you can bind your local objects to events coming from the server in a completely transparent way.

1 var server = new Pusher('you app key', 'some channel');
2 
3 server.bind('user_added', function(user){
4   $('ul#user_list').append('<li&gt:'+user.name+'</li&gt:')
5 })

And of course you would build your own objects to do the binding and keep your main app clean and expressive.

1 new UserPane( server, $('ul#user_list') );

I talk about how this is done in this post.

Do it yourself

Because Pusher.js is framework-agnostic, we don't use jQuery's event-binding API. Instead, Pusher implements its own bind method. But once you're using this pattern in one part of your app there's no reason you couldn't use it for the rest of your domain objects. The only thing they need to do is implement binding and triggering of events. This is a simple abstract prototype you can use in your own objects. This is the Ticker example again using that snippet:

 1 /* Periodically send out dummy events 
 2 --------------------------------------------*/
 3 var Ticker = function( interval ){
 4   var self = this, 
 5       nextTick = function(){
 6     self.trigger('tick', Math.random() * 1000);
 7     setTimeout(nextTick, interval);
 8   }
 9 
10   nextTick();
11 };
12 
13 // Extend from AbstractEventsDispatcher 'bind' and 'trigger' methods
14 Ticker.prototype = new AbstractEventsDispatcher;
15 
16 var ticktock = new Ticker( 5000 );
17 
18 ticktock.bind('tick', function( number ) {
19   puts('number emitted: '+ number);
20 });

The only reason I've changed 'addListener' to 'bind' and 'emit' to 'trigger' is to keep it familiar for jQuery users.

What's important here is that once you model your objects around this minimal event-centric interface ('bind' and 'trigger') it makes little difference what your event emitter objects actually do -and how they do it. You app can be fully evented while retaining the best bits of object orientation such as polymorphism and inheritance.

A working application

I've recently built an activity dashboard app that uses Pusher when deployed to production but a mocked, in-browser event emitter object in development to simulate a stream of events coming from the server. My View objects are oblivious to the origin of those events and just focus on binding to them and rendering them onscreen as they come. You can see it working on the video above.

It starts by defining a series of self-contained widget objects. I then loop through them and instantiate each with the same instance of an event emitter -in this case Pusher or a mock server implementing bind for custom events.

This is the basic code:

1 var server = new MockServer() // or new Pusher('app_key', 'channel_name'), or your own event emitter
2 
3 // Instantiate widgets
4 for(widget in Widgets.available){
5   new widget( server );
6 }

And this is a simplified widget that listens to 'closed_order' events and plays a sound using HTML5's audio tag:

 1 Widgets.available.soundAlert = function( server ){
 2   
 3   // add audio files to document
 4   var $audio = $('<audio src="/audio/alert.mp3" preload="true" />').appendTo('body');
 5 
 6   function play(data){
 7     $audio.load().play();
 8   }
 9   
10   // bind to events you want to alert
11   server.bind( 'order_closed', play )
12 }

I can add as many widgets as I want (and even remove them, pause them and inspect them in run-time), with no dependencies between them. Because they bind themselves to events they're interested in, each one is completely orthogonal to the system.

Finally, this is an example of the MockServer object I use to replace Pusher for development purposes:

 1 /* Periodically send out dummy events 
 2 --------------------------------------------*/
 3 var MockServer = function(){
 4 
 5   var self = this, interval, event_names = ['order_closed', 'order_cancelled', 'order_shipped'];
 6 
 7   function randomEvent(){
 8     var event_name = event_names[Math.round(Math.random()*2)];
 9     
10     self.trigger(event_name, {
11       info: 'Mock order, 3 products',
12       total: Math.random() * 1000
13     });
14   }
15 
16   interval = setInterval(randomEvent, 2000);
17 };
18 // Extend event-binding interface
19 MockServer.prototype = new AbstractEventsDispatcher;

Wrap up

This illustrates the general architecture that permeates all recent Javascript projects that we've developed. In future articles we'll talk about techniques and specific problems in more detail.

Even though I've included an abstract class that I used for the examples on this page, the emphasis is on the patterns more than on particular implementations and APIs.