Node Tutorial Part 20: Backbone.js

11 Apr 2011 | By Alex Young | Tags server node tutorials lmawa nodepad

Welcome to part 20 of Let’s Make a Web App, a tutorial series about building a web app with Node. This series will walk you through the major areas you’ll need to face when building your own applications. These tutorials are tagged with lmawa.

Click to show previous tutorials.

Backbone Persistence

I haven’t yet hooked up our interface and models to Backbone’s persistence layer. I generally work by relying on model.save(attributes) and model.destroy(). The save method knows when to create or update based on if the id attribute has been set — remember this, because prior to this tutorial Nodepad was using _id which confuses Backbone.

The add/remove document toolbar could use a skeleton Backbone view like this:

ListToolBar = Backbone.View.extend({
  el: $('#left .toolbar'),

  events: {
    'click #create-document': 'add',
    'click #delete-document': 'remove'
  },

  initialize: function(model) {
    _.bindAll(this, 'add', 'remove');
    this.model = model;
  },

  add: function(e) {
    // TODO: Create a new document
  },

  remove: function(e) {
    e.preventDefault();
    if (confirm('Are you sure you want to delete that document?')) {
      this.model.destroy();
    }
  }
});

The destroy method will delete documents using a HTTP DELETE, the same way as my previous jQuery implementation. We actually need to instantiate ListToolBar with a model though, so where should that happen? I decided to put it in the DocumentRow view:

open: function() {
  $('#document-list .selected').removeClass('selected');
  $(this.el).addClass('selected');
  this.model.display();
  this.toolbar = new ListToolBar(this.model);
}

Every time a document is selected, a toolbar will be instantiated. Now there’s a relationship between the toolbar view and the current document.

We still need to create a new document when the + button is pressed… This is basically a case of instantiating a document and calling save:

add: function(e) {
  e.preventDefault();
  var d = new Document({ title: 'Untitled Document', data: '' });
  d.save();

  // Add it to the collection
  Documents.add(d);

  // addDocument is a new method I've added to DocumentList which just appends
  // the right elements to the unordered list
  appView.documentList.addDocument(d);

  // Trigger an open
  d.rowView.open();
}

There’s some housekeeping going on there, but notice that we basically just call save to make Backbone do all the boring Ajax work for us.

DocumentControls

I’ve also added a view called DocumentControls which manages updating documents. By now nothing in this should really surprise you:

DocumentControls = Backbone.View.extend({
  el: $('#controls'),

  events: {
    'click #save-button': 'save'
  },

  initialize: function(model) {
    _.bindAll(this, 'save', 'showHTML');
    this.model = model;
  },

  save: function(e) {
    this.model.set({
      title: $('input.title').val(),
      data: $('#editor').val()
    });
    
    this.model.save();
    this.model.rowView.render();
    e.preventDefault();
  },

  showHTML: function(e) {
    e.preventDefault();
    // TODO
  }
});

The line that reads this.model.rowView.render() is just triggering the DocumentRow to update its contents. For clarity, it reads like this:

render: function() {
  $(this.el).html(this.template({
    id: this.model.id,
    title: this.model.get('title')
  }));
  return this;
}

Conclusion

I hope it’s now clear that working with Backbone and REST APIs can be less work than a mess of Ajax calls and CSS selectors.

This week’s code was commit 7d5cc3d.


blog comments powered by Disqus