Backbone.statefulEvents, Part 1 — how we built our first Backbone plugin

This is an essay describing how (and why) I build my first Backbone.js plugin. It’s mainly written for readers who want to write extensions themselves, but can’t figure out how to get started—this post mainly describes the design process of the plugin. My next post will be about the implementation.

Quick note: you need to know the basics of Backbone.js in order to understand most of what’s goin on.

The problem

While writing indite, I found myself doing this quite often inside views:

var SomeView = Backbone.View.extend({
  events: { 'click' : 'clicked', 'keydown' : 'keyPressed' },

  click: function (e) {
    // ignore clicks when editing
    if (globalState == 'editing') return;
    else doSomething();
  },

  keyPressed: function (e) {
    // ignore keypresses when not editing
    if (globalState != 'editing') return;
    else doSomethingElse();
  }
});

Forgive the contrived nature of the above example, but I hope you get the point: my DOM event handlers were often based on the application state — and many of them had similar checks in place.

This got tiring, fast. I needed something that would automatically handle this for me.

Investigation & initial design

Backbone’s source is surprisingly readable. I realized it would be pretty easy to extend Backbone and build a ‘stateful’ view.

I started with defining what the view should do. At this stage, I wasn’t really worried about how I could achieve this, I wanted to figure out how I would use the final plugin.

My first idea was something like this:

var StateBasedView = Backbone.View.extend({
  // Basic spec:
  //   - define a dispatchBasedOnState method that
  //     automatically maps events/states to methods.
  //   - The method for a given event should be
  //     named as: _stateName_eventName
  //   - Need to ensure that the child views know how to find
  //     current state
});

var SomeView = StateBasedView.extend({
  events: {
    'click'   : 'dispatchBasedOnState',
    'keydown' : 'dispatchBasedOnState'
  },

  // define a way to get state
  getState: function () { return globalState; },

  _notediting_click: function (e) {
    // should be called on click when application
    // state is 'notediting'
  },

  _editing_keydown: function (e) {
    // should be called on keydown
    // when application state is 'editing'
  }
});

This was a very simplistic approach, and had several problems:

  • The method names were ugly.
  • Mapping events to a generic method and dispatching them automatically to other methods with ‘special names’ is not readable.
  • There was a possibility of clashes, as the dispatchBasedOnState method didn’t consider the selector — both these ‘event’ bindings would look the same to it: click, click .closeButton.

Finding a better approach

I decided to add a statefulEvents dictionary to the view, that would behave just like the events dictionary. The dictionary keys would need to define the state and the event name, at a minimum, with an optional selector.

var StateBasedView = Backbone.View.extend({
  // Do some magic
});

var SomeView = StateBasedView.extend({
  statefulEvents: {
    'notediting click'             : 'doSomething',
    'notediting click #editButton' : 'startEditing',
    'editing keydown'              : 'doSomethingElse'
  },

  // define a way to get state
  getState: function () { return globalState; },

  doSomething: function () {},
  doSomethingElse: function () {},
  startEditing: function () {}
});

This solution feels pretty elegant to me, and more in line with Backbone’s own idioms. The next post will describe the implementation process, but the impatient can check out the source code on Github right now.