Node Tutorial Part 22: Backbone (Again)

25 Apr 2011 | By Alex Young | Tags server node tutorials lmawa nodepad backbone.js

Welcome to part 22 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.

Search

I hadn’t finished rebuilding the search interface using Backbone. The old code used simple jQuery events and Ajax to manage the search field, form submission, and document list. The search text input management looked like this:

$('input[name="s"]').focus(function() {
  var element = $(this);
  if (element.val() === 'Search')
    element.val('');
});

$('input[name="s"]').blur(function() {
  var element = $(this);
  if (element.val().length === 0)
    element.val('Search');
});

This translates quite easily to Backbone using a view and some events:

var SearchView = Backbone.View.extend({
  el: $('#header .search'),

  events: {
    'focus input[name="s"]': 'focus',
    'blur input[name="s"]': 'blur'
  },

  focus: function(e) {
    var element = $(e.currentTarget);
    if (element.val() === 'Search')
      element.val('');
  },

  blur: function(e) {
    var element = $(e.currentTarget);
    if (element.val().length === 0)
      element.val('Search');
  }
}

The view’s main element is the search form, and the focus and blur events are bound in a similar way to the old code.

Next I instantiate the class in AppView, which acts as a main container view:

AppView = Backbone.View.extend({
  initialize: function() {
    this.documentList = new DocumentList();
    this.searchView = new SearchView();
  }
});

Working With DocumentList

The existing DocumentList manages the left-hand-side list of documents, which the search code needs to work with. Using the old code could have confused things a little bit here, because it altered the raw HTML rather than working through Backbone.

This is the perfect place for the Show All link that appears when a search is active:

DocumentList = Backbone.View.extend({
  el: $('#document-list'),
  Collection: Documents,

  // This supports the search system:
  events: {
    'click #show-all': 'showAll',
  },

  initialize: function() {
    _.bindAll(this, 'render', 'addDocument', 'showAll');
    this.Collection.bind('refresh', this.render);
  },
  
  // ...

  showAll: function(e) {
    e.preventDefault();
    this.el.html('');
    this.Collection.fetch();
    appView.searchView.reset();
  }

This code is mostly the same, but it adds the showAll method and event binding. Using this.Collection.fetch will reload all the documents from the server.

As an aside: I was looking at a way of making this work with a subclassed version of the DocumentList collection that uses /search.json instead of the usual document list URL, but I couldn’t find a way of making Backbone POST with a request parameter.

Searching

To actually search, I’ve used the original jQuery Ajax call:

SearchView = Backbone.View.extend({
  el: $('#header .search'),

  events: {
    'focus input[name="s"]': 'focus',
    'blur input[name="s"]': 'blur',
    'submit': 'submit'
  },

  initialize: function(model) {
    _.bindAll(this, 'search', 'reset');
  },

  focus: function(e) {
    var element = $(e.currentTarget);
    if (element.val() === 'Search')
      element.val('');
  },

  blur: function(e) {
    var element = $(e.currentTarget);
    if (element.val().length === 0)
      element.val('Search');
  },

  submit: function(e) {
    e.preventDefault();
    this.search($('input[name="s"]').val());
  },

  reset: function() {
    this.el.find("input[name='s']").val('Search');
  },

  search: function(value) {
    $.post('/search.json', { s: value }, function(results) {
      appView.documentList.el.html('<li><a id="show-all" href="#">Show All</a></li>');

      if (results.length === 0) {
        alert('No results found');
      } else {
        for (var i = 0; i < results.length; i++) {
          var d = new Document(results[i]);
          appView.documentList.addDocument(d);
        }
      }
    }, 'json');
  }
});

I’ve also added the reset method which sets the search input to Search after pressing Show All.

Notice that DocumentList.addDocument is reused here. In a larger application, this kind of reuse is very important and is where Backbone starts to show its value. Another thing to note is SearchView could easily be in its own file, allowing us to avoid a monolithic client-side JavaScript file that becomes hard to manage.

Conclusion

Writing good code with client-side frameworks like Backbone requires a lot of work, but it does take away some of the drudgery when working with data and binding it to interfaces. The search code looks cleaner than it did before, but even though it’s quite simple it would be interesting to refactor it.

This week’s commit was 2b8e083.


blog comments powered by Disqus