Searching With A BackboneJS Collection

In my last post on composite JS apps, I mentioned needing a search and search results section of my app. I’m starting off with a very simple search box that will let a user enter a name or description of an item, and the back-end server will return a JSON result set that I display on the screen. Since Backbone can encapsulate all of the server communication for me, I wanted to take advantage of this and set up a Backbone.Collection to do the search.

Fetch And Url

Calling the `fetch` method on a collection will cause the collection to make a call back to the server to retrieve the requested collection. The collection that is returned is determined by the `url` attribute of the collection. This attribute can either be a string or a function.

If you’re dealing with a model and a RESTful back-end, it’s often easy enough to set the collection’s url to a string, such as “/images”. This would load all of the images from the server, as a JSON document, and populate the collection.

If you’re doing something more complex and you need to figure out the url at runtime, though, you can set the `url` attribute to a function. My search process requires me to pass urls that look like this: “/items/{search-term}”, where {search-term} is the actual search term typed in to the search box. To get the url formatted like this, I need to append the search term to a base url string when the `url` function is called. Unfortunately, there’s no way to pass parameters to the `url` function. To make this work, then, I rely on my collection to hold the bits of search info that I need and then I have my `url` function read them:

SearchResults = Backbone.Collection.extend({
  url: function(){
    return "/items/" + this.searchTerm;
  }
});

With this function in place, I can create an instance of my collection, set the .searchTerm on it and then call .fetch:

var results = new SearchResults();
results.searchTerm = "some search term";
results.fetch({
  success: someView.showTheResults
});

However, I’m not very happy with having to remember to do things like this whenever I want to do a search. I would rather have a simple “search” method that I can call, which handles all of these details for me.

Encapsulating Search

To encapsulate the search process, including the creation of a collection, setting the .searchTerm and then calling .fetch, I want to create a function that hangs directly off of the SearchResults collection. This way I can simply call `SearchResults.search(…)` and get a result set back. To do this, I need to use the 2nd parameter of the Backbone.Collection.extend method. This parameter is an object literal, just like the first parameter. But, unlike the first parameter, it adds the functionality you specify directly to the collection’s constructor function, and not to an instance of the collection.

SearchResults = Backbone.Collection.extend({
  url: function(){
    return "/items/" + this.searchTerm;
  }
}, {
  search: function(searchTerm){
    var results = new SearchResults();
    results.searchTerm = "some search term";
    results.fetch({
      success: function(){
        MyApp.vent.trigger("search:results", results);
      },
      error: function(collection, response){
        MyApp.vent.trigger("search:error", response);
      }
    }); 
  }
});

Here you can see my SearchResults object with the `url` function in the collection instance, and the `search` function in the collection definition. I’ve also added a call to my event aggregator to notify my application when the search results have returned or have produced an error.

Performing The Search

Now I can call my search and subscribe to the event that tells me when they have returned, so I can display them:

// handle the search results in a view
Backbone.View.extend({
  initialize: function(){
    MyApp.vent.bind("search:results", this.showResults, this);
  },

  showResults: function(results){
    this.collection = results;
    this.render();
  },
  
  render: function(){
    var html = $("#some-template").tmpl(this.collection.toJSON());
    $(this.el).html(html);
  }
});

// do the actual search, based on a search
// term that was entered in to the search box
SearchResults.search("some search term");

Of course, I’m sure there are many other ways of accomplishing the same thing with a Backbone.Collection. This is the solution that I’m currently using, but I would love to see the solutions that you’ve come up with for searches. Drop a comment here and let me know how you’re doing it!


Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Derick Bailey

Derick Bailey is an entrepreneur, problem solver (and creator? :P ), software developer, screecaster, writer, blogger, speaker and technology leader in central Texas (north of Austin). He runs SignalLeaf.com - the amazingly awesome podcast audio hosting service that everyone should be using, and WatchMeCode.net where he throws down the JavaScript gauntlets to get you up to speed. He has been a professional software developer since the late 90's, and has been writing code since the late 80's. Find me on twitter: @derickbailey, @mutedsolutions, @backbonejsclass Find me on the web: SignalLeaf, WatchMeCode, Kendo UI blog, MarionetteJS, My Github profile, On Google+.
This entry was posted in Backbone, Javascript, JSON. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • Yoeri

    …search: function(searchTerm){    var results = new SearchResults();    results.searchTerm = “some search term”;…i would pass in the searchTerm parameter instead of the fixed string ;-)Nice post!

    • http://mutedsolutions.com Derick Bailey

      oh right. so, instead of:

      var searchResults = new SearchResults();
      searchResults.searchTerm = “some search term”;

      it would be:

      var searchResults = new SearchResults(“some search term”);

      I like that. Thanks, Yoeri!

      • http://www.facebook.com/leo.garcia.crespo Leonardo Garcia Crespo

        I think he meant that in the code example you’re having a parameter in the search method but still assigning a hard coded string to results.searchTerm in here: 
        results.searchTerm = “some search term”;

        Anyway you get the idea of the post!

  • Goran Stamenkovski

    I really don’t like your “search:results” event, or to be precise event naming. I like events to be in past and to avoid actual implementation class name in event name as much as possible. Your events should be more in relation to your “domain language”. So I think that better name would be “searched”, “item:searched” (if you have a need to distinguish different search types) or “search:finished”.

    Great post by the way :)

    • http://mutedsolutions.com Derick Bailey

      +1. I prefer my events to be the same way. I sat and thought about the name for 5 or 10 minutes and couldn’t find anything that I liked, in this case. So, I tossed in the first thing that came to mind… I still don’t like it, but at least it’s functional. :P

  • Rajiv Kurian

    Great post

  • oak

    nice . another approach will be bind the sync on collection.