Patterns for Managing Large Scale Backbone Applications
24 Mar 2013Backbone is frickin' awesome. What I feel makes it awesome is the fact that it helps you write and structure better JavaScript code and not better "framework-x" code. It's amazingly intelligent but not opinionated, which is what causes problems for a lot of people when getting to grips with it (myself included). The site itself and corresponding docs don't really go beyond the syntax and methods Backbone provide, not much in terms of architecture and structure. Head on over to Google and you'll find a plethora of Backbone tutorials, most go over theory and the syntax, a few go over building a small app (a Todo app - the new "Hello World"), but very very few actually delve into building multi-regioned, multi-stated, authentication backed Backbone applications.
I've had the pleasure of building one of the latter described applications for the past few months at KashFlow and have picked up a few tips and patterns along the way which have made building this large scale application a heck of a lot easier.
Quick caveat: They've worked for me. They may not work for you. This is ok.
So, without further waffle, let's get started...
Object Structure
I house all of my Backbone app code under one global object, ie:
window.App = {
Models: {},
Collections: {},
Routers: {},
Views: {},
init: function(){
App.router = new App.Routers.main();
Backbone.history.start();
}
};
$(function() {
App.init();
});
If you notice, I use CamelCase for Classes and Objects and lowercase for instances of those Objects and Classes, so at quick glance, you can see what's what.
A Model declaration/instantiation for example:
App.Models.Quote = Backbone.Model.extend({});
and
App.quote = new App.Models.Quote();
Namespacing
Chances are, if you're building a large scale application, you'll be saving data back to a server for data persistence and if you're doing that and using Backbone, chances are you're communicating with a REST API. So, you're familiar with the idea of resources. To make everything a little easier, I'll use examples from the actual application I'm building; online accounting software. Namespace the crap out of everything: Object, files, folders, Views, routes, etc...keep them all under namespaces. If you hit /quotes/ then the app's router will direct you towards the "quotes" function in the router, perform a .fetch() on the "quotes" Collection and load up "app.Views.quotes" to handle and delegate the responsibility for what the user is about to see; a list of their quotes. Namespacing is quite an obvious one but I think it's so important to firm this one up.
Folder Structure
Nothing too original but something a little like this:
/models/
/quote.js
/collections/
/quotes.js
/views/
/quotes/
/quotes.js
/quotesDetails.js
Again, pretty standard, but within Views, I add an extra folder layer where I group the resource name, ie. quotes, customers, etc...
Regions and DOM Event Management
When I started with Backbone, I quickly realised that I was going to need a robust way of managing content within certain areas of the application's chrome (the layout - sidebar, header, footer, content, etc...). Luckily, Dan Harper was on hand to give me some indispensible code he'd written and was using in his own Backbone applications. Dan since put it on GitHub so you can use this resource in your applications; Backbone ViewManager. The general idea revolves around two concepts: Regions and one Base View.
Adding Regions (Regions are shortcuts to DOM elements):
Backbone.ViewManager.Core.addRegion('panel', '#panel');
Backbone.ViewManager.Core.addRegion('sidebar', '#sidebar');
Backbone.ViewManager.Core.swap('panel', new App.Views.quotes());
Backbone.ViewManager.Core.swap('sidebar', new App.views.sidebarMain());
The "swap" method essentially takes the render().el of the passed-in View and inserts it into the DOM element. However, not before emptying the DOM element of the previous View and turning off all the events that were bound to it.
All your views inherit from one Base view, like so:
App.Views.quotes = Backbone.ViewManager.BaseView.extend({});
This allows the ViewManager to call methods like view.off() and we have the off() method declared on the Base View, so all sub views inherit from it.
I've found Dan's method to be substantial enough for me and haven't found any performance bottlenecks so far or zombie views. However, if you do want something a little more feature rich, check out Marionette.js.
Templates
I use Handlebars and up until very recently, I used to house all of my templates in script tags in the index.html file, grab them by ID with JavaScript in my View's render function and compile them on the fly. Now I'm cool and use Grunt.js. So I use the Grunt Contrib Handlebars plugin for Grunt which means that I can keep all of my templates in separate files, ie.
/templates/
/quotes/
/quotesDetails.hbs
Which Grunt will precompile those template files into a global "JST" JavaScript Object, which means that in my view, I can access the template via:
var template = JST["templateName"];
and pass in data as before. Minimal change for performance increases and better file organisation.
What I don't use
Require.js - This is probably the biggest thing I get suggested to me, but so far, I've not seen enough of it's "wins" to convince me to retrofit it to this application. However, being the naive one that I am, it's probably amazing and hopefully some cleverer people in the comments section will point out it's use cases and advantages.
Conclusion
Get your application to the point, maybe by using the above tips and patterns, where it doesn't feel like you're building a large scale application. Every moving part should feel very modular and self contained, even just by, initially, putting each View into separate files. This process is not a concrete or final solution and as with the internet, I imagine that in a couple of weeks, a new way of building large scale Backbone apps will emerge and we'll all be able to take bits from it. But, until that point, take what you'd like from the above mind dump and let me know if any of it needs further expansion or I've missed any tips or patterns that you've used successfully.
Tweet:/patterns-for-managing-large-scale-backbone-applications.html
I'm available for hire