DailyJS

Let's Make a Framework: Custom Events

Alex R. Young

Subscribe

@dailyjs

Facebook

Google+

tutorials frameworks lmaf

Let's Make a Framework: Custom Events

Posted by Alex R. Young on .
Featured

tutorials frameworks lmaf

Let's Make a Framework: Custom Events

Posted by Alex R. Young on .
*Let's Make a Framework* is an ongoing series about building a JavaScript framework from the ground up. These articles are tagged with [lmaf](http://dailyjs.com/tags.html#lmaf). The project we're creating is called [Turing](http://github.com/alexyoung/turing.js). Documentation is available at [turingjs.com](http://turingjs.com/).

Way back at the start of this series I created a basic DOM events
implementation, in Let's Make a Framework:
Events
. Dealing with
browser events is mostly a case of providing a simple API and patching
browser quirks. As we saw, various frameworks implement this
differently. In particular, jQuery has an almost completely custom event
handling system, which allows it to support lots of useful features in
almost every browser.

When building modern web applications we often need to deal with events
that don't apply to DOM elements. Sometimes we create abstract objects
and want to emit or listen for events. This is becoming more common with
the growing popularity of Node and its EventEmitter class.

Let's add something like EventEmitter to Turing, so
developers can make use of customised event handling for their own
objects.

EventEmitter

The EventEmitter class can be found in
lib/events.js in Node and there's also documentation in
events.EventEmitter.

It's actually very easy to use EventEmitter. I think a lot
of people find it confusing at first, because they hear about "evented
JavaScript" and think Node's little EventEmitter is a
magical tool that makes everything fast.

In reality, it's basically this:

var EventEmitter = require('events').EventEmitter,
    bomb = new EventEmitter();

bomb.on('explode', function() {
  console.log('BOOM!');
});

bomb.emit('explode');

This is similar to what CommonJS
Events/A
describes.

Structure

In EventEmitter, event handlers are stored in an object
that contains arrays of events indexed by the event names. Some
additional complexity comes from optimisations. For example, one
optimisation is for the case where there's only one listener:

EventEmitter.prototype.addListener = function(type, listener) {
  // ...
  if (!this._events[type]) {
    // Optimize the case of one listener. Don't need the extra array object.
    this._events[type] = listener;
  } else if (isArray(this._events[type])) {

    // If we've already got an array, just append.
    this._events[type].push(listener);

Then, when an event is emitted:

EventEmitter.prototype.emit = function(type) {
  if (!this._events) return false;
  var handler = this._events[type];
  if (!handler) return false;

  if (typeof handler == 'function') {
    // The optimised case
    switch (arguments.length) {
      // fast cases
      case 1:
        handler.call(this);
      // ... snip

  } else if (isArray(handler)) {
    // An array of handlers has been added
    var listeners = handler.slice();
    for (var i = 0, l = listeners.length; i < l; i++) {
      listeners[i].apply(this, args);
    }
    return true;

API Design

Let's follow the basics of Node's API:

  • addListener(event, fn), aliased as on: Register an event handler
  • emit(event, [arg1], [arg2], [...]): Emit an event with optional arguments
  • removeListener(event, fn): Remove the handler
  • removeAllListeners(event): Removes all handlers for the event

These methods should provide enough functionality to do some cool stuff.

Conclusion

Whether we're using events in DOM programming, EventEmitter
in Node, or even similar techniques in other languages (I keep finding
myself working with NSNotification in Objective-C), events
are an incredibly useful paradigm.

Next week I'll start building our own events class!

References