Backbone is suitable for projects of just about any size, ranging from the most complex browser-based applications to quite humble little features. In this chapter we will use Backbone and CoffeeScript to build an example of the latter: a user feedback widget that will run right here in the page. Creating it will take us on a speedy tour through Backbone's components: Model, View, Collection, and Router. When we're done, I hope you will use the widget we've built to let me know how you liked the experience.

Let's start with the Backbone model.

Model

The most essential Backbone component is the model. A model holds data and is the correct home for domain logic that operates on that data. A model should never concern itself with display logic, and especially should never make any reference to the DOM. Backbone gives its models the ability to broadcast events in order to preserve this separation.

It is incredibly easy to create a model instance. We don't need to extend a framework base class, define a schema, or do anything other than call the Backbone.Model constructor, passing an object literal containing our data.

model-object
suggestion = new Backbone.Model subject: "Backbone + CoffeeScript"

suggestion.get('subject').should.equal "Backbone + CoffeeScript"

Wow, that first line was easy. In classic CoffeeScript style, the parentheses and curly braces are implicit. You can add them if you like: new Backbone.Model({subject: "Backbone + CoffeeScript"}) is also valid CoffeeScript. The code examples in this book are live editors, and I encourage you to tinker with them. Go ahead, get your hands dirty. Be curious. Change something. Then click the Run button to check your work. To revert back to the original content, click the dropdown arrow just to the right of the Run button and choose Restore to original example.

The second line above is less straightforward. First, it calls get('subject') to retrieve the value that we just passed to the constructor. Then, it invokes a should assertion on the returned string. These assertions are available on all objects in the editors, courtesy of the Chai library. They verify what is happening when you run the code. If an assertion turns out to be false, you will see an error. Paste 'one'.should.equal 'two' into the editor above and run it to see what I mean.

If should syntax is more than you want to get into right now, alert is a fine alternative. For example, add the line alert suggestion.subject to the editor above and click Run. You will see that the ordinary property subject is undefined. Backbone attributes are not ordinary JavaScript properties, so we can't access them using dot or bracket notation.

Updating an attribute with set is similar to using get.

model-set
suggestion.set 'message', "What the world needs now..."

suggestion.get('message').should.equal "What the world needs now..."

With the constructor, get, and set, we have everything we need from model in order to build our widget. We won't be defining validation, domain logic, custom events, or independent persistence for models for this feature, so we have no need to extend Backbone.Model with a new subclass. A lot more about models will be explained in the next chapter.

What's next?

Template

With client-side JavaScript, we're spoiled. We're not really having fun until we see something happen on the page. Now, it would be easy enough to use jQuery to create some markup for our model data, but there are good reasons why we should avoid doing this. The most obvious is the difficulty of maintaining HTML embedded in code. Templates let us reverse the situation, and embed the code in the markup.

Templates aren't officially part of Backbone, but they are so central to its use that this book will treat them as a first-class member of the Backbone family. Since Backbone requires Underscore, and Underscore includes a template function, in a sense Backbone does indeed provide templates. But Backbone itself says nothing about templates, which is good, because tastes in style differ, and there are many good approaches and libraries from which to choose.

This book will focus on Eco (Embedded CoffeeScript templates) for rendering dynamic HTML. Here is an Eco template for displaying a single instance of our model.

suggestions/show
<h3>
  <%= @model.get('subject') %>
</h3>
<p>
  <%= @model.get('message') %>
</p>

Eco leverages all of CoffeeScript's clean, readable power for a solution that gets the job done with a minimum of fuss. Our task now is to render our Eco template with a model context, then attach the output to the DOM. For that, we need a view.

Views

The most important thing to know about views is that they operate within the scope of a single associated HTML element. Within the scope of this element and its children, a view may handle events, render content, and interact with the DOM. The view holds a reference to the element in a special property called el.

In the view below, you will see a slightly expanded version of our Eco template, which has been inlined as a CoffeeScript block string (commonly called a heredoc). This is a simple, effective solution to the problem of packaging template assets. However, views are typically more of a place for "glue" code than for actual UI code, and it is much nicer to work on templates in their own dedicated files. Most server-side platforms offer some variety of asset packager to assist with this. The following view examples in this chapter will retrieve templates from a global JST object, per the convention established by the Jammit asset packaging library for Rails.

Enough talk, let's take a look.

view-show
ShowView = Backbone.View.extend

  template: eco.compile '''
    <h3>
      <%= @model.get('subject') %>
    </h3>
    <p>
      <%= @model.get('message') %>
    </p>
    <a href="#" class="btn">
      Back
    </a>
  '''

  render: ->
    @$el.html @template(model: @model)
    this

The render method requires some explanation. That cryptic @$el at the start breaks down as follows:

  • @ is an alias for this in CoffeeScript, which is the current context, in this case the ShowView instance
  • $el is a property that caches the jQuery wrapper around el
  • @$el is basically equivalent to calling $(this.el), with the added benefit of caching the result

On this wrapped el (by default, a div generated by the view), we call jQuery's html function to append the rendered template content. After seeing that we manually update the DOM with new content, you may feel disappointment (or perhaps relief) that there is not much magic involved. Remember, the core value proposition of Backbone is to provide organization and some helpful glue to support pretty much any (jQuery-compatible) UI implementation. It leaves the DOM manipulation to us.

By convention, a view's render method returns the context this, expected to be the view, to allow chaining of the el property in statements such as view.render().el. Like Ruby, CoffeeScript does not require an explicit return statement, but always returns the last expression in a function.

I think we have waited long enough see our model, template, and view in action. Go ahead and click the Run button below.

view-show-test
suggestion = new Backbone.Model subject: "Hello", message: "I am a Model."
view = new ShowView model: suggestion

$('.modal-content').html view.render().el
$('#chapters-modal').modal('show')

Hey, how awesome is that? Backbone on the page! You may be wondering about the modal dialog. Say hello to Bootstrap, which will be assisting us in delivering the final product of this chapter. Also, in case you were wondering, the Back button in our template doesn't do anything yet. We'll enable it later, when we get to our router, at the end of the chapter.

But first, we need a collection and a couple more views.

Collection

So far we have been operating within a little sandbox. We have instantiated a model and rendered its data locally. This is a nice accomplishment, but unworthy of the Internet, which after all is about collaboration and sharing. Backbone applications are intended to integrate with backend systems, and if your system has an API that is JSON-based and RESTful, Backbone makes integrating with it easy to do. The server hosting this page provides an API for a suggestions resource. This means there might be suggestions right now on the server, just sitting there, waiting patiently for us to retrieve and enjoy them. How do we get them?

We merely extend Backbone.Collection and declare a url property with the relative path to our resource.

collection
Suggestions = Backbone.Collection.extend

  url: '/api/feedback/suggestions'

Let's see what our collection can do by calling its fetch method with a pair of success and error callback functions. The appropriate callback will be invoked asynchronously after we get a response, so the built-in editor.log function provides a way to display what we get back.

collection-test
new Suggestions().fetch

  success: (collection) ->
    editor.log "There are now #{collection.length} suggestions in our collection."

  error: (collection, response) ->
    editor.log "Sad face! Server says #{response.status}."

I think by now you know the drill. We have incoming models. Let's render them to the modal dialog with an Index template and view.

Index View

It's time to turn up the power a notch with Eco and CoffeeScript. We will pass our collection as an initialization option to an index view, which in turn will render itself with the template shown below. Collections proxy Underscore's each function, providing an elegant way to iterate over the models we retrieve from the server.

suggestions/index
<ol>
  <%= @collection.each (item) ->: %>
    <li>
      <a href="#/show/<%= item.id %>">
        <%= item.get('subject') %>
      </a>
    </li>
  <% end %>
</ol>
<a href="#/new" class="btn btn-primary">
  New Suggestion
</a>

As you can see, the plan is to build an ordered list of suggestions. Thanks to our ability to embed our devastatingly elegant iteration code in the template, the view for rendering this list is extremely simple.

view-index
IndexView = Backbone.View.extend

  template: JST['suggestions/index']

  render: ->
    @$el.html @template(this)
    this

Let's call our collection's fetch method again, but this time, instead of calling editor.log, our success callback will create an instance of the Index view and render it unto the Bootstrap modal. Take a look, then click Run.

view-index-test
new Suggestions().fetch

  success: (collection) ->
    view = new IndexView collection: collection

    $('.modal-content').html view.render().el
    $('#chapters-modal').modal('show')

  error: (collection, response) -> editor.log "Sad face! Server says #{response.status}."

We just fetched and displayed actual server data on the page using Backbone. Applause.

You may have tried clicking the New Suggestion button in the view we just displayed. Like the other buttons and links we've rendered, it navigates to a hashtag, and is waiting for everything to be wired together by a router. We're getting close, but we still need one last view, the one that will enable us to submit that new suggestion to the server.

New View

Our final view is the most complex. We need inputs that can accept the subject and message for a new suggestion, and a create action that can create both a new model in our local collection and a new record in the server's database.

suggestions/new
<div class="alert alert-error" style="display: none;"></div>
<label for="subject">
  Subject
</label>
<input id="subject" class="controls" type="text">
<label for="message">
  Message
</label>
<textarea id="message" class="controls" rows="6"></textarea>
<a href="#" class="btn">
  Cancel
</a>
<a id="new-submit" class="btn btn-primary">
  Submit
</a>

The HTML above contains inputs and a Submit button, but there is no enclosing form. Don't worry, Backbone has us covered when it comes to user interaction. In our view we just need to declare the special property events, initializing it with an object that maps event-selector keys to action values. For example, 'click .delete-button' to 'destroy', or 'click #new-submit' to 'create' (which is exactly what we have below.)

view-new
NewView = Backbone.View.extend

  events:
    'click #new-submit': 'create'

  template: JST['suggestions/new']

  render: ->
    @$el.html @template
    this

  create: (event) ->
    event.preventDefault()

    suggestion =
      subject: @$('#subject').val()
      message: @$('#message').val()

    options =
      wait: true
      error: (model, response) ->
        json = jQuery.parseJSON response.responseText
        @$('.alert').show().html json.errors.join('<br>')

    @collection.create suggestion, options

The create action halts the propagation of the click event in the DOM, collects the user inputs into a suggestion object, and sets up some Ajax options. The wait: true option prevents the collection from adding the newly built model to itself until it receives a success response from the server. The create action then calls our collection's create method.

Now then, I hope you are working on a suggestion or comment about this chapter to share, because we are sitting on a feedback form that is ready to go. However, instead of running it just yet, let's power through to the completed feature. But first, a brief introduction to Backbone's custom events.

Events

Backbone applications deal with both user-triggered DOM events, as we just saw in the view above, and Backbone's own custom named events. The latter are provided by the Backbone.Events module, which is included by default in Backbone.Model, Backbone.Collection, and Backbone.Router. The Backbone.Events module provides just three methods, on, off, and trigger. In many applications we might only use on. Here is a very brief demonstration. Go ahead and run it.

collection-events-test
suggestions = new Suggestions

suggestions.on 'add', (model) -> editor.log model.get('subject')

suggestions.add subject: "The event fired!"

Using on, we register a handler for Backbone's built-in add event on our collection, then trigger the event by invoking the add method on the same collection. Clear enough? If not, don't worry. We'll discuss Backbone custom events more thoroughly in a later chapter. Let's get back to completing our Feedback widget!

Router

We understand models, we have our collection, we have templates, and we have views. What we need now is a component to bring them all together. Enter the router.

The router that follows contains a fair amount of code, but don't worry. We are already familiar with most of it. The router's index, new, and show methods are fairly similar to the view usage examples above, in which we created views and rendered them unto the modal dialog. Take a quick look at those methods in the lower half of the editor, then we'll go over the less familiar code in the upper half.

router
window.App = Backbone.Router.extend

  routes:
    '':         'index'
    'new':      'new'
    'show/:id': 'show'

  initialize: ->
    @suggestions = new Suggestions()

    # Go to show after model in collection is saved
    @suggestions.on 'add', (model) =>
      @navigate "show/#{model.id}", trigger: true

  index: ->
    @suggestions.fetch
      success: (collection) ->
        $('.modal-content').html new IndexView(collection: collection).render().el
      error: (collection, response) ->
        throw new Error(response.status + ' ' + response.responseText)
      silent: true

  new: ->
    # good to create new view each time due to the timing with which dom events are initialized
    $('.modal-content').html new NewView(collection: @suggestions).render().el

  show: (id) ->
    suggestion = @suggestions.get(id)
    $('.modal-content').html new ShowView(model: suggestion).render().el

What remains to discuss is the routes property and the initialize method. The initialize method is called when we instantiate the router, and it in turn creates a suggestions collection. It then registers a handler for the add event on this collection. This handler will be invoked whenever the collection successfully creates a model with the server. The result is to show the details of the model after it is created in the New view.

The heart of the router is its routes configuration. Its value is an object that maps URL paths, some of which may contain parameter placeholders, to router actions. Since this is client-side routing, the paths for our application are actually hashtags appended to the URL for this page. Keep an eye on the browser bar as you click around in the widget to see what I mean.

The Application

This is the moment we've been working for: It's time to run the Feedback widget. The code that runs will be assembled from the editors in this chapter. If you have modified any editors, please restore them to their original content before proceeding, either individually using the dropdown in each Run button, or by refreshing the page in your browser. After you've seen the feature working, I encourage you to return to the editors, change things that make you feel curious, and run it again. That is the whole point of ScriptyBooks.

This is a real feature, in a real system, with a real database on the other end. To successfully save a suggestion I ask that you please first sign in to this site using a GitHub account. Please do not hesitate to submit as many suggestions or comments as you like, but keep in mind that they will be public, and our feature does not yet support update or destroy actions. That said, be bold! I sincerely hope to hear from you.

OK, let's run this thing!

application
app = new window.App

# Clean up after any previous runs
window.location.hash = ''
Backbone.history.stop()

Backbone.history.start()

$('#chapters-modal').modal('show')

$('.suggestions-modal-btn').show()

The last line reveals a light blue Feedback button on the right side of this page. After you run the example once to load the code, you can use the button to re-open the widget after you close it. The call to stop the History instance is not typically needed in an application. It's there just in case you run the example more than once.

Conclusion

If you made it this far, give yourself a hand. It's highly likely that you now know much more about Backbone than you did when you started. We've covered every major component of Backbone, becoming familiar with the workings of models, collections, views, and routers. In our haste, we've skipped over many important details, so the upcoming chapters slow things down to teach you about the Backbone components separately and in greater depth.

The next chapter, Setup and Structure, covers the basics of getting started with Backbone and CoffeeScript. It also addresses an area of frequent concern to many Backbone developers, but one for which Backbone provides very little guidance: Organizing and packaging your application. The options in this department can seem overwhelming, so we'll discuss the most popular patterns and solutions, ranging from simple namespacing to sophisticated asynchronous module loaders. If this sounds like something you would rather read about at a later time, you can safely skip ahead to the chapter on Models and Domain Logic, which resumes the interactive teaching approach.


This chapter is part of the interactive book Backbone + CoffeeScript.