Web App Development #2: Backbone Framework
This post is Part 2 in a series on tools and techniques we find useful at NewBay for developing single page web applications.
The UI team at NewBay specializes in developing these applications, leveraging all the goodness the latest advances in HTML5 and JavaScript bring, along with all the great new frameworks available in this space. See Part 1 for information on Single Page Web Apps.
Frameworks
At NewBay we’re big fans of jQuery. The ease at which it allows us to manipulate the Document Object Model (DOM) with minimum amount of code, and the massive number of good quality plugins available have helped us produce many great web apps over the years.
It’s not all rosy in the jQuery garden however. jQuery provides a great mechanism of manipulating the DOM, via a loosly defined mechanism of creating plugins (effectively javascript objects exposing public members and events), and a great UI toolkit in jQuery UI.
However, it doesn’t enforce a structure (or architecture) on your app’s entire code. The larger the app being developed the easier it is to fall into the trap of producing multiple plugins listening to each other in a bowl of event listening spagetti code. Luckily, there are a number of great JavaScript frameworks for creating Model-View-Controller (MVC) style web apps which can provide structure to your code.
The popular Backbone framework works well in combination with jQuery, and we’ve used it for a number of projects in the NewBay UI team.
Backbone
Backbone brings a lot to the party:
- Structure, with its Model View Router (MVC-like) architecture, but not “too much” structure, allowing us to take a flexible approach to suit our needs. It’s very loosely coupled, which means you can use almost all of your existing tools in conjunction with Backbone.
- Extremely simple integration with RESTful APIs (as I’ll go into below)
- And my personal favorite is the way it de-couples the DOM from infecting the business logic of your app through its models and collections – something which is difficult using jQuery alone as its so DOM-centric
We use Backbone for both mobile and desktop web apps in NewBay. It’s relatively lightweight, and this lends itself to use in mobile web apps, especially in combination with the lightweight Zepto framework (which can be used as a replacement for jQuery if you feel your app is unnecessarily large).
Backbone gives you a few key objects to help you structure your code: Models, Collections and Views. You can extend from these base objects, and in combination with a built-in event architecture, it provides you with all you need for building clean Javascript clients.
Backbone and NewBay RESTful Apis
To illustrate Backbone usage, here’s a simple example of using it with NewBay’s APIs, to search for, and to retrieve a list of files from Digital Vault.
Model: File
The Backbone Model object simply represents your applications data, as well as allowing you to implement business logic around this data if required.
For example, to create a File object (representing a file in NewBay’s Digital Vault product), you just need to extend from the Backbone.Model class as follows:
var File = Backbone.Model.extend({});
You can also specify default values for a model – I find it useful to help describe the role of a model. Also note the use of namespacing:
var model = model || {};
model.File = Backbone.Model.extend({
defaults: {
name: null,
extension : null,
parentPath: null
},
initialize: function () {
console.log ("File object initialized!!");
}
});
You can construct the model with values, and call get and set methods to retrieve and get its data:
var myFile = new model.File ({name: "myfile.txt"});
var fileName = myFile.get ("name");
myFile.set ({name: "myfile.txt", extension: "txt"});
You can provide your own custom methods for business logic on your model. Additionally models allow:
- built-in validation
- events can be listened to for changes to its data
- models data can be easily retrieved/saved from/to an external source through an ajax call
Collection: FileSearchList
The Backbone Collection object represents a collection of a specific type of Model. The Collection object gives you a lot of extra functionality over the traditional native array method of organizing lists of objects, such as:
- the ability to populate or save itself from/to an external source using ajax (similar to single Model capability),
- exposes events which can be listened to for changes to its data
- sorting capability
When creating a Collection, you extend from the Backbone.Collection object. We will create a collection of files called FileSearchList as it is a list of model.File created from a search in Digital Vault. Before we can do this we need to ensure any ajax calls made to the Digital Vault RESTful API have the appropriate accept and content type http headers:
$.ajaxSetup({headers:{
"accept": 'application/vnd.newbay.dv-1.0+json',
"content-type": 'application/vnd.newbay.dv-1.0+json'}
});
Our FileSearchList collection will get its data from a GET API call, at the url:
http://myapiserver.com/vault/user//search?query=(name:)
var collection = collection || {};
collection.FileSearchList = Backbone.Collection.extend({
// this tells the Collection which type of Models it holds
model: model.File,
// this tells the Collection the api location where to get its data from
url: "http://myapiserver.com/vault/user/example_user_id/search"
});
We can now call the Collection fetch method on an instance of our collection. We can pass our search string GET parameter to the fetch method in a similar fashion to a jQuery $.ajax call (indeed this is what Backbone is doing under the hood). This will make the appropriate API call, and with the response attempt to populate the collection with model.File objects:
var myFiles = new collection.FileSearchList ();
myFiles .fetch ( {data: {query: "(name:searchstring)"}} );
However, this will fail. We need to convert the JSON returned by the API call into the simple JSON format as defined by the model.File object. We can achieve this by implementing the Collection parse method which is called when the fetch call responds successfully:
var collection = collection || {};
collection.FileSearchList = Backbone.Collection.extend({
model: model.File,
url: "http://myapiserver.com/vault/user/example_user_id/search",
parse: function (response) {
var converted = [];
if (response) {
var files = response.searchResults.file;
files = files ? (((!$.isArray(files)) ? [files] : files)) : [];
for(var i in files) {
converted.push ({
name: (file.name || file.filename).$,
extension : (file.extension || { $: null }).$,
parentPath: (file.parentPath || { $: '' }).$
});
}
}
return converted;
}
});
So, now we can call the fetch method with a search parameter , and the collection.FileSearchList instance will populate itself with model.File objects.
Note that the fetch method defaults to using ajax when communicating with the server, but it’s easy to replace this with whatever approach you require –a number of great open source adapters are available for websocket connections, for example Backbone.ioBind. This is the business part of our mini-web app, now we need to build a UI. This is where we introduce the Backbone.View.
View: HTML & CSS
Firstly, we’ll need to create the HTML and CSS to render our view. Let’s create a simple app that displays a search box, allowing the user to specify a search query, resulting in the display of a list files:
Views in Backbone don’t contain the html for your app – they are used to manage your models by defining how and when they should be visually displayed on the page. HTML generation is usually achieved using JavaScript templating, such as jQuery tmpl, or Mustache. The HTML for our app consists of (simplified for illustration):
The HTML to display the list of files will be inserted by our Backbone View object into the fileView DOM element. We specify a template for this HTML in fileViewTemplate. I’m using the “out of the box” template engine made available by the Underscore library that comes with Backbone.
View: FileSearchView
A View is responsible for manipulating the DOM with model data. The View can listen for changes to Models or Collections, and on a change event (change/reset/add etc) can render as required.
This is very powerful, as a single View can be responsible for changing a Model, which can be easily reflected in other “listening” Views on the UI.
Views are very flexible in how they can be used, and don’t impose too much structure on our code. The View is also the only part of Backbone where the DOM should be manipulated. In other words: only use jQuery, jQuery plugins, jQuery UI and so on from within your View code.
We will create a FileSearchView object to listen for changes in our FileSearchList collection, and render the contents:
var view = view || {};
view.FileSearchView = Backbone.View.extend({
el: $('#fileView'),
events: {
'keypress #search': '_search'
},
initialize: function () {
_(this).bindAll('render');
this.collection.bind('reset', this.render);
this.template = _.template($('#fileViewTemplate').html());
},
render: function () {
this.el.html(
this.template ({
files: this.collection.toJSON()
})
);
return this;
},
_search: function (e) {
if (e.keyCode == 13) { // return key press
this.collection.fetch ({data: {
query: " (name: "+ $(e.target).val()+")"
}});
}
}
});
Let’s look at this in detail. In the following code we define the View container (the part of the DOM which the View represents) into the el variable. We can also bind DOM events to functions using the events variable. In our app we listen for a keypress on the search input, and call the _search function:
el: $('#fileView'),
events: {
'keypress #search': '_search'
},
The initialize method is called when a View is constructed. We can access the Views collection object (FileSearchList) using this.collection and bind to its reset event. In other words, when the collection is reset, we call our Views render method to display the changes. Additionally, we use Underscore to compile our HTML template, ensuring its ready for use:
initialize: function () {
_(this).bindAll('render');
this.collection.bind('reset', this.render);
this.template = _.template($('#fileViewTemplate').html());
}
When the user enters search text and presses return, we call fetch on our FileSearchList collection. The collection will call the Digital Vault API, and populate itself with File models, at which point the reset event will be triggered:
_search: function (e) {
if (e.keyCode == 13) { // return
this.collection.fetch ({data: {
query: " (name: "+ $(e.target).val()+")"
}});
}
}
Our render method will get called on collection reset. We get our FileSearchList JSON, pass it to our template engine, generating HTML which is inserted into the DOM, and rendered for the user:
render: function () {
this.el.html(
this.template ({
files: this.collection.toJSON()
})
);
return this;
}
As mentioned in the Backbone documentation: “Sometimes you’ll create a view for each model … sometimes you’ll have a view that renders thousands of models at once, in a tight loop. Both can be appropriate in the same app, depending on the quantity of data involved, and the complexity of the UI.”
Alternatives
This simple example doesn’t cover other great features such as Backbone Routers and History, which harnesses the HTML5 history API to allow you to change the browser URLs (effectively managing the “back button”).
We like Backbone in NewBay because of its simplicity, flexibility and tiny size – 5k gzipped! It also easily retrofits into existing projects that were implemented pre-Backbone. However, there are many popular alternatives to Backbone that are worth considering: check out Javascript MVC, Cappuccino, Sproutcore, Spine, and Knockout.
A word on Modernizr
Modernizr is not an MVC style alternative to Backbone, but is an essential tool in a web developer’s armory, and is worth mentioning here. Developing HTML5 apps that can run on numerous browsers on numerous devices with varying capabilities can be a daunting task.
Modernizr is a small JavaScript library that detects the availability of native implementations for HTML5 and CSS3 features on a browser. The traditional mechanism for determining browser capability is by checking the browser’s “User-Agent” property. This can be highly unreliable, and the preferred approach is to leave it to Modernizr to detect if features are available. This is not always practical, especially given the wide variety of device and browser types, but it should always be your initial approach.
It brings other benefits too which are extemely useful, my favorite being its modification of the DOM in Internet Explorer to allow you to specify and style HTML5 DOM section elements such as <nav> <header> <footer> (http://www.w3.org/TR/html5/sections.html). This allows you to assume the underlying browser will cater for the most uptodate html5 DOM standard, ensuring you don’t get held back by an inferior browser.
In the next post of this web app series I’ll be looking into performance tuning (which is particularly important for mobile web app development), where techniques such as minimizing hits on the server, and reducing client code size, can have a positive effect on the user experience.
Leave a comment
CATEGORIES
MOST RECENT
- NewBay discusses Cloud Services for Operators at Mobile World Congress 2012
- Top User Needs for Cloud Backup Services
- Cloud Services Best Practice #3: Drive ongoing usage – facilitate service usage
- Cloud Services Best Practice #2: Capture your customer – remove purchasing barriers
- Quaking in your shoes . . . . Google Drive commeth?
MOST POPULAR
TAG CLOUD
adaptation adm Analytics Android APNS Apple Backup best practices C2DM Cloud cloud content Cloud Services codec Content content services Developer Developer Program digital-vault Facebook Google HTML5 iCloud iPhone itunes JavaScript LG lifecache Marketing Messaging Notifications notifier photo upload SMS SNG Social networking social networking gateway Storage streaming Transcoding Twitter US Cellular user experience verizon Video Web App
WP Cumulus Flash tag cloud by Roy Tanck requires Flash Player 9 or better.








