Kicking butt with CoffeeScript, Underscore, and Backbone

Posted by Venerable High Pope Swanage I, Cogent Animal of Our Lady of Discord 23 May 2011 at 03:22PM

I've recently had the opportunity to work with some really exciting JavaScript tools that make developing apps that run in the browser much more powerful and managable than I've been accustomed to working with in the past. The trio of tools I am specifically interested in is CoffeeScript, Underscore.js and Backbone.js. I am specifically targeting technical audiences for this work, I will be assuming a good deal of comfort working with JavaScript, the DOM, and jQuery already. Additionally I will assume you understand Rails well enough to build a trivial app in it.


I'll start with a high level overview of the purposes of each of these tools.

  • CoffeeScript is a language built on top of JavaScript which puts a large deal of syntactic sugar in place to ease its crafting. It additionally emits JavaScript with several best practice idioms, and its output is generally built in order to pass Lint checks. The syntax is highly reminiscent of Ruby. When integrating CoffeeScript to a Rails app with Sprockets, source files will be compiled down to JavaScript on the fly providing a pleasantly responsive development cycle.
  • Underscore.js is a library of clever utility functions. It includes mechanisms for templating, functional programming, and compensating for some of the curious interactions of JavaScript operating against a DOM in a browser. Many of its offerings seem redundant when compared with jQuery, and often in fact are. jQuery strongly focuses on DOM elements however, while Underscore plays very nicely with arbitrary javascript objects. This makes it a valuable tool when you start to embed non-UI logic in JavaScript applications.
  • Backbone.js is a MVC library for JavaScript. Compared with SproutCore or Cappuccino, it is lighter weight and significantly decoupled. (To be fair, SproutCore is currently undergoing modularization efforts to reduce it's footprint.) Backbone.js features Models and Collections for modeling your data, Controllers for routing to functions, and Views for binding DOM fragments to functions and model data.


CoffeeScript

CoffeeScript is a language built on top of JavaScript to provide tools for easily creating code which adheres to best practices. It features:

  • Syntactic whitespace. Indenting a level creates a new JavaScript block.
  • Terse syntax. Common tasks like defining functions and referring to this are conveniently abbreviated.
  • Stricter scoping. JavaScript eagerly places things in global scope, but CoffeeScript generally creates immediate functions. By placing code in immediate functions, CoffeeScript constrains your work to local scopes by default.
  • Controlling binding. When binding functions of an object to events on some DOM element in JavaScript, this will, without some intervention, be bound to the DOM element instead of the object the function is defined for.
  • Splats. You can define functions of variable optional arguments using ellipsis.
  • Default returns. Much like Ruby, the last expression evaluated in a function is automatically returned to the caller.
  • Destructuring assignment. CoffeeScript makes it easy to pull apart maps and arrays
  • String interpolation. CoffeeScript lifts Ruby's string interpolation mechanics out wholesale.
This is a high level manifest of what you can get by using CoffeeScript. For more details, visit the website.


Backbone.js

Backbone is an MVC library which provides strong, decoupled foundations for building UIs. Its chief components are:

  • An event system which can be mixed into arbitrary objects using _.extend()
  • Controllers, which route from location hashes to controller methods.
  • Models and Collections, both designed to proxy remote objects communicated with JSON. If you subscribe to the contract of accessing all model attributes using the get and set methods, then event propagation will occur to queue changes in your views.
  • Views, designed to bind together a DOM element and an object, to catch events in the relevant fragment of the DOM, and to invoke changes to the model.
  • History, creating a chain of discrete UI states that can be reloaded and navigated using browser history functions.


We'll cover relevant tools in Underscore as we cross them. It is such a mixed bag that I do not see much value in providing an overview.


Let's build something!

I'll build a super trivial kanban board for this app. Our domain will consist of cards and lanes. Cards will have a name and a description. They will be placed in one of four lanes: Open, Development, Test, Approved. New cards will arrive in Open, and then transitions will move them forward and backward in the path until they reach Approved. My architecture will be built out of a Rails server which will serve up static resources to form the client app, and provide JSON endpoints for managing a shared state of the board. All user facing work will be done in CoffeeScript, or in HAML to produce templates that will end up as the content of Backbone views. Rails will not render any HTML as a result of actions in this example, an artificial restriction put in place to more put more weight on JavaScript. My work will be published as a github project for reference so that you can compare notes.

I started by making a new Rails 3.1 app. I used rspec and haml for testing and templating respectively. I set up Card and Lane models and controllers. Lanes have many cards, and optionally previous and next lanes. Seed data creates an Open lane by default, and all cards are by default placed in the Open lane. I set up a default application layout and a Templates controller for serving up HTML fragments that will make up the UI. If you'd like to play along at home you can look at the state of the project at this point here. I have:

  • Seed data for 4 lanes.
  • A JSON endpoint that lists all the lanes
  • A default route going to the templates controller, and a default layout that includes our application.js


Getting down to brass tacks

I'll start off with getting my dependencies in place. Rails 3.1 ships with jQuery and CoffeeScript as the defaults, this part is already taken care of for me! I'll need to add Backbone and Underscore still. I pull down underscore and backbone to the app/assets/javascript directory:

wget -O app/assets/javascripts/underscore.js http://documentcloud.github.com/underscore/underscore.js
wget -O app/assets/javascripts/backbone.js http://documentcloud.github.com/backbone/backbone.js
Next I'll need to add require statements to app/assets/javascripts/application.js:
//= require underscore
//= require backbone

As a first cut of getting a handle on backbone goodies, I'll set up a trivial first action for our backbone app. I want to do the following:

  • Trigger a default controller action.
  • Pull down all the lanes into a Backbone collection.
  • Create a view on that collection that logs it's contents to the browser console.
This won't be much of a view, but it demonstrates a lot of the basic functionality of Backbone. It will not demonstrate the binding of model data to views a user can see.

The controller is pretty simple, let's take a quick look:

#Define LanesController on this to put it in the global scope.
this.LanesController = Backbone.Controller.extend
  # Set up the routing map. The default route will go to the
  #  LanesController.index() function.
  routes:
    "": "index"

  # Fetch the data for the initial page, and display a view with it.
  index: ->
    lanes = new Lanes()
    lanes.fetch()
    board = new LanesIndex(model: lanes)
Some pieces to note here:
  • When CoffeeScript compiles to JavaScript, it wraps everything in an Immediate Function to keep it from polluting the global namespace. It also takes the binding of "this" from the scope where that Immediate Function is called, and propagates it into the body of the function, so that this is going to be bound either to window in the case of a browser, or globals in node.js. By defining things on this, you explicitly put them in the global namespace.
  • The default route in Backbone is the empty string. To initialize CoffeeScript's controller logic, you initialize one or more controllers, and then call Backbone.history.start(). It will then route to the default route if there is one, and start monitoring the location hash in order to trigger changes in the controller.
  • The index function is pretty brief, it just reads some data and then hands it to a view.

Let's take a look at the models now.

this.Lane = Backbone.Model.extend
  # When a backbone model receives a JSON blob in
  # its constructor, it shoves all of the JSON properties
  # into an internal attributes hash. These are exposed by
  # get(name) and set(name, value) methods on the model.
  # You can directly access the attributes hash, but if 
  # you shove values directly into it event propagation won't
  # happen. SO DON'T DO IT! 
  name: ->
    @get("name")

# Lanes is a collection of multiple Lane instances
this.Lanes = Backbone.Collection.extend
  # Request URL with XHR to pull down, returns
  # a JSON blob.
  url: "/lanes.json"
  # Specify what this collection is a model of.
  # With this set, you can hand JSON blobs to the collection
  # and it will add new Backbone Models instantiated
  # from those JSON blobs.
  model: Lane
  # Parse accepts a JSON blob representing a collection of multiple
  # model objects. It returns a list of instantiated models. We get
  # an Array of JSON blobs back from Rails, so we can just shove
  # them in objects and we are off to the races.
  parse: (response)->
    _.map(response, (laneJson) ->
      new Lane(laneJson))
This is pretty trivial. I just want to expose the name of a lane with a convenience method, fetch a list of all the lanes from the server as a JSON result, and create lanes from the JSON result.

Backbone is smart enough to do the right thing with the url property regardless of whether it is a string or a function. If you define url as a function, you can issue dynamic queries for filtered collections.

Finally I'll implement half of a view. Why half? Views do two things: they bind to data, and they bind to HTML in the page. They are not responsible for specifying a presentation (unless you implement it yourself). They do provide convenience methods for declaratively binding to events in the DOM element which they own, and they will also conveniently bind this to the view, instead of the element which fires the event. First I'd like to demonstrate the binding to data, and then we can create some HTML and present it to the user in a later step.

this.LanesIndex = Backbone.View.extend
  # Initialize is called at the end of the view
  # constructor to set up the new view. You can bind to
  # events on the model this view was constructed for,
  # and do other housekeeping tasks here.
  initialize: ->
    # bindAll accepts an object and a list of function names.
    # It then binds this in the body of each of those functions
    # to its first argument. In the example below, without using
    # bindAll this would evaluate to the collection firing the event
    # instead of the view to be rendered.
    _.bindAll(@,"render")
    # When a collection fetches its contents from the server, it
    # fires a "refresh" event upon successfully parsing the contents
    # of the response. We re-render this view when the contents change
    # to keep the presentation to the user up to date.
    @model.bind("refresh", @render)
  # Render is a no-op by default, but the intent is to stuff data
  # from the view's model into the HTML element the view is holding onto.
  # At this stage in the example app, we are simply logging all of the Lane
  # objects to the browser console.
  render: ->
    _.each(@model.models, (model) ->
      console.log(model.name()))
    @

Now I have all the code I need. I put my backbone files in controllers, models, and views directories under the javascript directory, and then embed them in application.js like so:

//= require models/lanes
//= require views/lanes-index
//= require controllers/lanes-controller
I'm almost done. Now that all the backbone work exists, I need to invoke it. It's pretty easy! Just initialize the controllers that need to be active in the application (no need to hold onto the instances though!), and then call Backbone.history.start()
new LanesController()
Backbone.history.start()
The default route on LanesController will fire when the page is first visited, and if you load up the app it will log the names of the seed lanes to the console. For those playing along at home, you can check out the tree at this point. If you load it up, you should see the lane names logged to the console.

Now let's start working on presenting information. Your options with generating content to stick in the page are enormous, there are numerous templating systems available for JavaScript. If you'd like you could build dom elements dynamically and insert content as appropriate. I'm going to use underscore's templating system, for two simple reasons. First, we need underscore for Backbone, so there's no extra effort to incorporate it. Second, underscore's templating performs reasonably well in the spectrum of Javascript templating tools. I have experienced some issues with the stack depth when using underscore templating though, so if you have nested templates, contemplate the appropriateness of using underscore before you commit to it.

I like to take the approach of putting templates on the server and pulling them down when the app loads. I added a "lane" action to the templates controller, it returns the following HTML fragment:

<div class='lane'> 
 <div class='lane-name'> 
  <%= lane.name() %> 
 </div> 
 <div class='lane-cards'>
 </div> 
</div>
That ERBish looking bit is recognized by underscore as an escape. When you turn this into an underscore template, it will call the name method on lane, and fill in the content with the return value of name.

Next, here's a little bit of code to prefetch all of the templates before starting up the app.

this.KoffeeTemplates = {
  templatesUrls:
    lane: "/templates/lane"
  triggerReady: ->
    @trigger("ready")
  init: ->
    _.after(_.keys(@templatesUrls).length, @triggerReady)
    _.each(@templatesUrls, (path, name) ->
      $.get path, (data) ->
        KoffeeTemplates[name] = data
        KoffeeTemplates.triggerReady()
    )
}
_.extend(KoffeeTemplates, Backbone.Events)
This little bit of utility lets me specify templates on templateUrls. It will fetch the contents of those urls and add them to the KoffeeTemplates object as strings. I can add more templates by defining them on templatesUrls. E.g. if I wanted a card template, I would change
  templatesUrls:
    lane: "/templates/lane"
to
  templatesUrls:
    lane: "/templates/lane"
    card: "/templates/card"
With this change, when the "ready" event fires KoffeeTemplates.lane will return the lane template, and KoffeeTemplates.card will return the card template. I'll need to change our initialization in application.js to fetch the templates and then initialize the application. After the appropriate changes, application.js looks as follows:
//= require jquery
//= require jquery_ujs
//= require underscore
//= require backbone
//= require koffee-templates
//= require models/lanes
//= require views/lanes-index
//= require controllers/lanes-controller
KoffeeTemplates.bind("ready", function() {
  new LanesController();
  Backbone.history.start();
})
KoffeeTemplates.init();

As I'm no longer logging things to the console, it'd be good to have my content actually affecting the DOM. I'm going to hook up the view's element to be the contents of the body tags by changing the LanesController.index method as follows:

  index: ->
    lanes = new Lanes()
    board = new LanesIndex(model: lanes)
    $('body').append(board.el)
    lanes.fetch()
Finally, I rewrote the view to render as a function of the lanes it holds. The new LaneIndex view looks like this:
this.LanesIndex = Backbone.View.extend
  tagName: "div"
  id: "board"
  # Initialize is called at the end of the view
  # constructor to set up the new view. You can bind to
  # events on the model this view was constructed for,
  # and do other housekeeping tasks here.
  initialize: ->
    # bindAll accepts an object and a list of function names.
    # It then binds this in the body of each of those functions
    # to its first argument. In the example below, without using
    # bindAll this would evaluate to the collection firing the event
    # instead of the view to be rendered.
    _.bindAll(@,"render")
    # When a collection fetches its contents from the server, it
    # fires a "refresh" event upon successfully parsing the contents
    # of the response. We re-render this view when the contents change
    # to keep the presentation to the user up to date.
    @model.bind("refresh", @render)
  # Render is a no-op by default, but the intent is to stuff data
  # from the view's model into the HTML element the view is holding onto.
  # At this stage in the example app, we are simply logging all of the Lane
  # objects to the browser console.
  render: ->
    laneTemplate = _.template(KoffeeTemplates.lane)
    _.each(@model.models, (model) =>
      $(@el).append(laneTemplate(lane: model)))
    @
With this code in place, it renders full page columns for our cards to live in, one column for each lane. As usual, those following the examples can view the tree here.