Initializing Backbone Applications

For one moment, forget the Models, the Controllers, the Routers and think about how they come to be. It isn’t magic: something, somewhere, reaches out to each component, taps it on the shoulder, and invites it to come be a part of the project. Call it the capital-A-Application.

If you’re short on time, the Backbone Boilerplate (BBB) provides a battle-tested Application for pure-JS projects (and wraps it up in AMD-goodness to boot). It’s a case-study in good practices, and–while it isn’t a hard dependency for the Application outlined here–it provides much better implementations of many of the core features that are glossed over here. Serious projects would do well to start with BBB, adding support for additional features only as they discover a need.

Let’s get started.

Overview

Think of the Application as a stand-alone command-and-controller with three distinct roles. Prior to initialization, it will provide a namespace and central point-of-reference for the rest of the application. During initialization, the Application will configure and fire up core application features like routing and views. And after initialization it will act as a central mediator for the rest of the application.

Application
  |-- pre-initialization
  |   |-- create namespace for other modules
  |   |-- provide dispatcher for other modules
  |   `-- receive/load initial state
  |-- initialization
  |   |-- configure application layout
  |   |-- create routers
  |   `-- initialize routing
  `-- normal operations
      `-- dispatch application events

Getting started

There are many ways to scaffold out the base application. The Boilerplate approach is to create a namespace from a simple object; we’ll add an initialization function to receive initial options and fire up the application whenever the page is loaded.

(function (window) {

    var app = {};

    app.defaults = {
        // default options go here
    };

    app.init = function (opts) {
        this.options = _.extend({}, this.defaults, opts);
    };

    window.Application = app;

})(this);

We could very well bind app.init() to fire immediately, but there may be some benefits to waiting. Take, for instance, the case of an application whose behavior will change depending on the user agent accessing it. In this case, each case will need to be tested for and an appropriate response provided. Implementing the detection code outside the Application helps isolate the (presumably) brittle tests and makes it easier to outsource their maintenance. Defering initialization until after test results are available makes describing the environment a matter of setting a few parameters:

jQuery(document).ready(function ($) {
    Application.init({
        isMobile: (screen.width < 600)
    });
});

Before initializing

As application modules download, the Application’s first responsibility is to provide a namespace for registering each new arrival. So long as it was loaded before any other modules, the Application counts. Registering it as a dependency within other AMD modules will take care of this nicely, but non-AMD projects should simply take care that it’s declared first.

As soon as the Application module is loaded, its namespace is available for use by others. Here, a module containing all of the project’s Car-related functionality binds its contents to the Application:

(function (Application) {

    Application.Cars = {};

    Application.Cars.Model = Backbone.Model.extend({});

    Application.Cars.Collection = Backbone.Collection.extend({
        model: Application.Cars.Model
    });

    Application.Cars.Router = Backbone.Router.extend({
        routes: { 
            '/cars' : 'index' 
        },
        index: function () {
            console.log(carsIndex);
        }
    });
})(Application);

Mediation, too.

Most modular designs make use of a central mediator for subscribing to and publishing new application events. Making sure that such a system is available during load will provide a central point for modules to connect to as soon as they load. Events won’t be flying until after the application has initialized, of course, but it never hurts to get started off early.

In large applications with more complicated messaging, it may make sense to roll a custom pub/sub dispatcher. Backbone’s Events module is perfectly sufficient for supplying simple event channels, however.

 _.extend(app, Backbone.Events);

Bootstrapping data

Namespace, check. Mediator, check. One more common pre-initialization task is the delivery of pre-loaded (“bootstrapped”) data. If there’s one big “don’t” in Backbone, it’s “don’t fetch() your initial application”. So, to keep load times fast and trim, the models and collections that an application requires usually end up bootstrapped directly into the initial page request to avoid the delay of an initial asynchronous request.

Chances are that the server will not be pumping out Backbone code directly. It’s used to serving up JSON, or XML, or whatever might be the flavor of the weak, and there’s no reason to swim upstream. Instead, the Application can provide a simple adapter for formatting and storing data bootstrapped by the back end into a Javascript-friendly form. For a server that speaks JSON, the adapter is as simple as:

app.bootstrap: function (key, val) {
    this.data = this.data || {};
    this.data[key] = val;
};

Now, the server-side application can deliver initial content simply by dumping JSON data into a call to Application.bootstrap. In Rails, for instance,

<script>
Application.bootstrap('Cars', <%= @cars.to_json %>);
</script>

Will generate an appropriate call.

<script>
Application.bootstrap('Cars', [
    { make: "Ford", type: "model A" },
    { make: "Ford", type: "model T" }
]);
</script>

Loading initial state

Nice as that temporary store is, it won’t do much on its own. We’ll need to update the app.init() method to convert the javascript objects currently in app.data into Backbone-ready form. A simple convention might be to create a data collection for each key supplied to the application. We’ll update app.init() accordingly:

app.init = function (opts) {
    this.options = _.extend({}, this.defaults, opts);
    for (key in this.data) {
        this.data[key] = new this[key].Collection(this.data[key]);
    }
};

Most projects will demand a more sophisticated data store than this key-value hash. Anything more complicated than the simplest case, and it will probably make sense to extract the adapter to its own function.

While Initializing

During initialization, the Application must make sure that any functionality that other modules need access to is available. One nearly-universal function is layout management.

Backbone Boilerplate uses the Backbone.LayoutManager plugin to manage application views, and even projects not using BBB should probably consider following that lead. LayoutManager is good. But whatever layout library is in use, the Application should be able to create and maintain a basic layout. It’s a two-step process:

  1. Setup the layout
  2. Prove some way to assign views to it

In other words, if you have a layout along these lines…

<!-- in HTML layout (or JST, etc..) -->
<script id="layout" type="text/template">
  <div class="layout">
    <div class="form"></div>
    <div class="todos"></div>
  </div>
</script>

…the Application will need to bring it to life.

We’ll use a configuration setting–layout–to select and build a layout:

// in `app.init()`
var template = $(this.options.layout).text();
this.$layout = $(template).appendTo('body');

A very simple helper for updating its views might simply replace the contents of a selector with a new view:

// this would be a good place to shim in LayoutManager's `setView` method
app.show = function (selector, view) {
    this.$layout.find(selector).empty().append(view.el);
};

Routing

Backbone ably handles routing through the Backbone.history singleton. Many applications end up patching the History object to refine event triggering or track clicks, but–while this is perfectly functional–it may make better design sense to extract a custom routing function into the Application object.

For instance, a wrapper for Backbone.history that adds pre-and post-route event triggers might look something like this:

// a serious implementation should check that "path" matches 
// a defined route before actually `trigger`ing anything
app.navigate = function (path) {
    this.trigger('app:navigate-before', path);
    Backbone.history.navigate(path, { trigger: true });
    this.trigger('app:navigate-after', path);
};

A custom navigate function is also a good place to hook into access control, page view analytics, or perform any other tasks associated with routing. By relying on Application.navigate instead of Backbone’s built-in navigate method, these features may be delivered without needing to hook into Backbone’s core.

Initializing Routers

Speaking of routers, it’s time to add a little more code to app.init. This time, we’ll check each module for a Router object, instantiate any that turn up, and call Backbone.history.start to put them in action.

app.init = function (opts) {

    var options = _.extend({}, this.defaults, opts);

    var template = $(options.layout).text();
    this.$layout = $(template).appendTo('body');

    for (key in this.data) {
        this.data[key] = new this[key].Collection(this.data[key]);
    }

    // Initialize routers
    for (key in this) {
        if (this[key].Router) { 
            new (this[key].Router); 
        }
    }

    // Start routing
    if (Backbone.history) {
        Backbone.history.start( /*{ pushState:true }*/ );
    }
};

During runtime

The Application has provided a central namespace, initialized the project, and exposed navigate, show, and any other useful helper methods (hint: localization) that the project may need. Things are off and running. But the Application’s utility isn’t quite used up. Remember extending it with Backbone.Events? Using the Application as a mediator can help solve the sticky problem of allow modules to share events.

Consider the case where the sale of a car in one module (Application.Salesman) should trigger the production of a new car in another module(Application.Factory). If the salesman calls the factory directly, it will introduce unecessary coupling and upset the Manager module that was left out of the loop. A better design might have the Salesman simply announce the sale by triggering an event on the Application:

// in Application.Salesman.Model
sellCar: function (carModel) {
    var sale = {
        salesman: this,
        car: carModel
    };

    Application.trigger('app:car/sold', sale);
}

Similarly, instances of the Factory and Manager models can listen for sales events and use them to take appropriate action:

// in Application.Factory.Model
initialize: function () {
    Application.on('app:car/sold', this.onCarSold, this);
},
onCarSold: function (sale) {
    buildNewCar(sale.car.constructor);
}

// in Application.Manager.Model
initialize: function () {
    Application.on('app:car/sold', this.onCarSold, this);
},
onCarSold: function (sale) {
    var price = sale.car.get('price');

    Application.trigger('app:commission/create', {
        value: price * 0.05,
        salesman: sale.salesman
    });

    alert('Cashed in +$' + (price * 0.95));
}

Working through the Application minimizes coupling between modules and allows modules to independently determine how events should be used.

Wrapping up

So there you have it–a brief outline of one approach for getting Backbone up and running.

Let’s keep in touch

Reach out on Twitter or subscribe for (very) occasional updates.

Hey, I'm RJ: sometimes-writer and intermittent micropoet. Broadcasts live from the Rose City.