Organizing Backbone.js Applications

When it comes to organizing applications, Backbone.js doesn’t come with an exhaustive list of “thou shalts”. As near as I know, no definitive guidelines have emerged for putting the pieces together. But just because there isn’t a right way, doesn’t mean that there aren’t plenty of approaches available.

To these, add the rut I’ve been stuck in recently. As I’ve worked more and more with Backbone, I’ve settled into an organizational scheme that:

  1. Ensures consistency by recycling common themes, names, and functions
  2. Maximizes portability through modularization
  3. Improves sanity by simplifying maintenance and encouraging reuse in the codebase

Phwaw—what a load of buzzword hooey. Let’s get down to business (fiddle here).

Application Layout

At the top level, each applications tends to follow a pretty predictable, granular structure:

application/
  |--modules/
  |  |--MyModule.js
  |  `--AnotherModule.js
  `--application.js

No surprises there.

Building a module

Each of the project modules will generally define at least a model, collection, and views for both. Sometimes the collection isn’t needed; sometimes more than one model will find its way in. There are plenty of ways to write each of these components, but I typically bundle them up as follows:

// modules/MyModule.js
(function(module) {

  // a model
  module.model = Backbone.Model.extend({
    // model options
  });

  // a collection of models
  module.collection = Backbone.Model.extend({
    model: this.model
  });

  // the view for a single model
  module.modelView = Backbone.View.extend({
    render: function() {
      // do some rendering
      return this;
    },
    template: _.template('...')
  });

  // the view for a collection of models
  module.collectionView = Backbone.View.extend({

    tagName: 'ul',

    initialize: function() {
      this.views = [];
      _.bindAll(this);
      this.collection.bind('add', this.add);
      this.collection.bind('remove', this.remove);
    },

    // create a view for the model and redraw the collection
    add: function(model) {
      var view = new module.modelView({ model: model, tagName: 'li' });
      this.views.push(view);
      this.render();
    },

    // remove the view for the model and redraw
    remove: function(model) {
      var view = _.find(this.views, function(view){ return view.model == model; });
      this.views = _.without(this.views, view);
      this.render();
    },

    // redraw the entire collection
    render: function() {
      var self = this;
      this.$el.empty();

      _.each(this.views, function(view) {
        self.$el.append(view.render().el);
      });
      return this;
    }
  });
})(NS.module('MyModule'));

Aside from the collectionView there isn’t much to say. Models and views vary significantly from one application to the next. Their implementation is an exercise left up to the reader. Collections, on the other hand, don’t take much imagination. Beyond adding handlers for add and remove events, I find that collections tend to change very little. That means that it’s possible to cover many common usage cases with a similarly unimaginative view. The collectionView given here won’t be perfect for all applications. It’s a shim. Notice that render() is being called whenever a collection changes? If a large collection is updated frequently, it will be much more efficient to save a few DOM insertions by appending and remove-ing elements on demand. But for generic applications, this view is a pretty good place to start.

Accessing modules

When the anonymous function wrapping the module is called, its lone parameter is the return value of an as-of-yet-undefined method—NS.module. This isn’t any more complicated than a pass-by-reference strategy for grabbing the module’s exports, but the operation is a bit subtle. Check out the method definition:

// in application.js
var NS = new function() {
  var _modules = [];

  // retrieve or initialize a module
  this.module = function(key) {
    if (!_modules[key]) {
      _modules[key] = {};
    } 
    return _modules[key];
  }
};

The first time that NS.module is called for a particular key, it won’t find a corresponding module. Instead, it will tie a new object to the _modules array and return it. That’s the module argument that turns up in the module outlined above. On subsequent calls, the module’s exports will be returned instead. If that seems like massive overkill, consider this: by lazily defining the module, other pieces of the application can refer to a module before it’s been instantiated. It can’t use any of the modules methods, of course, but neither will it throw an error message. Handy? It is.

Now that NS.module has been defined, each module’s exports will be available in other parts of the application:

// somewhere else
var myCollection = NS.module('MyModule').collection;

Conclusion

Backbone isn’t designed to enforce the rules, and different applications will require different approaches to application development. But as I’ve found myself repeating the same patterns over and over, it made sense to write them down. Got comments? Suggestions? Better ways to do things? All lines are open.

Featured