DailyJS

Node Tutorial Part 22: Backbone (Again)

Alex R. Young

Subscribe

@dailyjs

Facebook

Google+

tutorials server node lmawa nodepad backbone.js

Node Tutorial Part 22: Backbone (Again)

Posted by Alex R. Young on .
Featured

tutorials server node lmawa nodepad backbone.js

Node Tutorial Part 22: Backbone (Again)

Posted by Alex R. Young on .

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.

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('Show All');

      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.