Blog

Backbone.js: lessons learned

After working on several large SPA Web projects, here are some Backbone.js lessons learned:

1) Backbone is largely unopinionated in its approach and conventions, which is part of the reason we chose it. It provides good separation of concerns, in its own MV* way, but does little else.

For large projects/teams, do yourselves a favor and have your own opinions on conventions, structure, file names, etc. The consistency will pay dividends later and make it much easier for developer to move between projects, as well as spinning up new developers on the project(s).

Some of this can be enforced via tooling, such as JSCS and JSHint, as well as through code reviews.  Anytime you can automate these things, it’s a win.

2) Have a good understanding of views and child views and what constitutes one vs. the other. Again, Backbone doesn’t have an opinion on the matter, but spend any amount of time developing a serious SPA and you’ll soon learn the importance of dividing views into smaller, more manageable units. It’s no surprise that some of the more opinionated Backbone-based frameworks, most notably Marionette, provide facilities for this common concept/problem. In addition to easier to manage and maintain modules, it also promotes reusable components. You will also find that it makes invalidating only part of a parent view/page much easier, which as it turns out, can be a big deal. Likewise, unit testing these smaller pieces is much easier. Seriously, the upfront time to determine parent/child views and how those map to application pages and components is time well spent.

3) Keep nearly all logic in the view (effectively the controller) and models/collections. You may benefit from view-model type objects as well. Let the router do… routing.

4) While you can certainly use jQuery within a view, if it’s being done frequently, it’s a sign that you’re probably doing something wrong. Don’t store your state in the DOM. Store state as first class citizens (in a model, cookie, local storage, etc.) – when state changes, invalidate the view. Direct DOM manipulation, in many cases, tends to be error/defect prone. When we do use jQuery, we never allow global jQuery usage, instead always use the view’s scoped instance, as provided by Backbone: $(‘#mySelector’) vs. this.$(‘#mySelector’).

5) Eventing is argumably the best part of Backbone. Aside from the built-in eventing encountered in models and collections, we also use it as pub/sub pipe for cross-view communication, amoung other things. Our apps have a global event bus that any component can pub/sub to. This again allows for decoupled components and facilitates unit testing. It also it super handy for global events and notifications. Lost connectivity to the server? Simply author a shared component that monitors for interesting events and publishes them when they occur. Other listeners can act on those interesting, global events. If there’s one thing to take advantage of with Backbone, it’s the eventing infrastructure.

6) Backbone’s documentation is (purposefully?) very basic and scant. Sure, you can find answers to common questions easily enough, it’s worth the time to read through the Backbone source code. Compared to other projects, Backbone source is very accessible, readable and well commented. Quite a bit of knowledge about how Backbone works, what to expect (as well as what NOT to expect…) can be gleaned directly from 1609 lines of JavaScript.

7) If you haven’t already, define a BaseView that all other views extend from. This is particuarly good for clean-up (preventing zombie views, cleaning up resources) and also helps centralize any core, view-specific functionality and logic. Our BaseView currently looks like this:

/**
* @file Defines a base Backbone view that all views should 'inherit' from. Functionality common to all views
* should be defined here.
* @author P.J. Little <pj.little@base2s.com> */
'use strict';

/**
* Require jQuery & Backbone, and provide jQuery to Backbone
*/
var $ = require('jquery');
var Backbone = require('backbone');

Backbone.$ = $;

module.exports = Backbone.View.extend({
/**
* Boilerplate Backbone close implementation.
* Closes the view, removing it from the DOM, unbinding any events and invokes an onClose callback,
* if it exists.
*/
close: function() {
this.remove();
this.unbind();
this.stopListening();
if (this.onClose) {
this.onClose();
}
},

/**
* Base render function; auto-sizes all tables present in the view.
*/
render: function() {
if (this.onRender) {
this.onRender();
}
// do interesting, common things here, when rendering views...
return this;
},

/**
* Renders a child view, using the provided selector as the containing element.
* @param childView The child view to render.
* @param selector The jquery selector to the element that will contain the child view.
*/
renderChild: function(childView, selector) {
// NOTE: calls to this.$el.html() will remove all child event bindings (this is how jquery's html() works).
// using setElement (as opposed to appending the child view's this.el) rebinds all events, each time
// .html() is invoked (typically called on each render).
if (childView) {
childView.setElement(this.$(selector)).render();
}
}
});

8) While not directly related to Backbone per se, I can’t iterate enough how helpful Browserify has been in our Web app development process. All javascript objects are published as NPM modules. It is an “all-in” stack/tool.  Browserify is a required build step that makes these modules runnable in a browser.  This allows us to have proper dependency management a la CommonJS (module.exports = {..} / require(‘foo’)). Again, this lends itself to smaller, more modularized components that are more manageable/maintainable and easier to unit test. On top of that, you get usage checking (i.e. using a variable that hasn’t been required) and Browserify spiders your dependency graph, only including files that are required, resulting in one, tidy javascript file that needs to be referenced in the HTML.  I can’t stress enough what a game changer this has been for us.  We use NPM to drive all other dependencies, plus we can now leverage the myriad of modules already available on NPM.  Win win win.

Until ECMAScript 6 is out and widely supported, it’s hard to imagine using anything else.

Any other SPA/Backbone tips that have served you well?  Please share them!

Leave Reply