Build a Contacts Manager Using Backbone.js: Part 2

Build a Contacts Manager Using Backbone.js: Part 2

Tutorial Details
  • Applications Used: Backbone.js, jQuery
  • Difficulty: Intermediate
  • Completion Time: 30 Minutes

Final Product What You'll Be Creating

This entry is part 2 of 5 in the Getting to Know Backbone.js Session
« PreviousNext »

Welcome back to part two of this tutorial; in part one we looked at some of the model, collection and view basics for when working with Backbone and saw how to render individual contact views using a master view bound to a collection.

In this part of the tutorial, we’re going to look at how we can filter our view based on user input, and how we can add a router to give our basic application some URL functionality.
We’ll need the source files from part one as we’ll be building on the existing code for this part. I’d strongly recommend reading part one if you haven’t already.


Reacting to User Input

You may have noticed in part one that each of our individual models has an attributed called type which categorises each model based on whether it relates to a friend, family member of colleague. Let’s add a select element to our master view that will let the user filter the contacts based on these types.

Now, we can hardcode a select menu into our underlying HTML and manually add options for each of the different types. But, this wouldn’t be very forward thinking; what if we add a new type later on, or delete all of the contacts of a certain type? Our application doesn’t yet have the capability to add or remove contacts (part three spoiler alert!), but it’s still best to take these kinds of things into consideration, even at this early stage of our application.

As such, we can easily build a select element dynamically based on the existing types. We will add a tiny bit of HTML to the underlying page first; add the following new elements to the contacts container:

<header>
    <div id="filter"><label>Show me:</label></div>
</header>

That’s it, we’ve an outer <header> element to act as a general container, within which is another container with an id attribute, and a <label> with some explanatory text.

Now let’s build the <select> element. First we’ll add two new methods to our DirectoryView mater view; the first one will extract each unique type and the second will actually build the drop-down. Both methods should be added to the end of the view:

getTypes: function () {
    return _.uniq(this.collection.pluck("type"), false, function (type) {
        return type.toLowerCase();
    });
},

createSelect: function () {
    var filter = this.el.find("#filter"),
        select = $("<select/>", {
            html: "<option>All</option>"
        });

    _.each(this.getTypes(), function (item) {
        var option = $("<option/>", {
            value: item.toLowerCase(),
            text: item.toLowerCase()
        }).appendTo(select);
    });
    return select;
}

The first of our methods, getTypes() returns an array created using Underscore’s uniq() method. This method accepts an array as an argument and returns a new array containing only unique items. The array we pass into the uniq() method is generated using Backbone’s pluck() method, which is a simple way to pull all values of a single attribute out of a collection of models. The attribute we are interested in here is the type attribute.

In order to prevent case issues later on, we should also normalise the types to lowercase. We can use an iterator function, supplied as the third argument to uniq(), to transform each value before it is put through the comparator. The function receives the current item as an argument so we just return the item in lowercase format. The second argument passed to uniq(), which we set to false here, is a flag used to indicate whether the array that is being compared has been sorted.

The second method, createSelect() is slightly larger, but not much more complex. Its only purpose is to create and return a new <select> element, so we can call this method from somewhere else in our code and receive a shiny new drop-down box with an option for each of our types. We start by giving the new <select element a default <option> with the text All.

We then use Underscore’s each() method to iterate over each value in the array returned by our getTypes() method. For each item in the array we create a new <option> element, set its text to the value of the current item (in lowercase) and then append it to the <select>.

To actually render the <select> element to the page, we can add some code to our master view’s initialize() method:

this.$el.find("#filter").append(this.createSelect());

The container for our master view is cached in the $el property that Backbone automatically adds to our view class, so we use this to find the filter container and append the <select element to it.

If we run the page now, we should see our new <select> element, with an option for each of the different types of contact:


Filtering the View

So now we have our <select menu, we can add the functionality to filter the view when an option is selected. To do this, we can make use of the master view’s events attribute to add a UI event handler. Add the following code directly after our renderSelect() method:

events: {
    "change #filter select": "setFilter"
},

The events attribute accepts an object of key:value pairs where each key specifies the type of event and a selector to bind the event handler to. In this case we are interested in the change event that will be fired by the <select element within the #filter container. Each value in the object is the event handler which should be bound; in this case we specify setFilter as the handler.

Next we can add the new handler:

setFilter: function (e) {
    this.filterType = e.currentTarget.value;
    this.trigger("change:filterType");
},

All we need to do in the setFilter() function is set a property on the master view called filterType, which we set to the value of the option that was selected, which is available via the currentTarget property of the event object that is automatically passed to our handler.

Once the property has been added or updated we can also trigger a custom change event for it using the property name as a namespace. We’ll look at how we can use this custom event in just a moment, but before we do, we can add the function that will actually perform the filter; after the setFilter() method add the following code:

filterByType: function () {
    if (this.filterType === "all") {
        this.collection.reset(contacts);
    } else {
        this.collection.reset(contacts, { silent: true });

        var filterType = this.filterType,
            filtered = _.filter(this.collection.models, function (item) {
            return item.get("type").toLowerCase() === filterType;
        });

        this.collection.reset(filtered);
    }
}

We first check whether the master view’s filterType property is set to all; if it is, we simply repopulate the collection with the complete set of models, the data for which is stored locally on our contacts array.

If the property does not equal all, we still reset the collection to get all the contacts back in the collection, which is required in order to switch between the different types of contact, but this time we set the silent option to true (you’ll see why this is necessary in a moment) so that the reset event is not fired.

We then store a local version of the view’s filterType property so that we can reference it within a callback function. We use Underscore’s filter() method to filter the collection of models. The filter() method accepts the array to filter and a callback function to execute for each item in the array being filtered. The callback function is passed the current item as an argument.

The callback function will return true for each item that has a type attribute equal to the value that we just stored in the variable. The types are converted to lowercase again, for the same reason as before. Any items that the callback function returns false for are removed from the array.

Once the array has been filtered, we call the reset() method once more, passing in the filtered array. Now we’re ready to add the code that will wire up the setType() method, the filterType property and filterByType() method.


Binding Events to the Collection

As well as binding UI events to our interface using the events attribute, we can also bind event handlers to collections. In our setFilter() method we fired a custom event, we now need to add the code that will bind the filterByType() method to this event; add the following code to the initialize() method of our master view:

this.on("change:filterType", this.filterByType, this);

We use Backbone’s on() method in order to listen for our custom event. We specify the filterByType() method as the handler function for this event using the second argument of on(), and can also set the context for the callback function by setting this as the third argument. The this object here refers to our master view.

In our filterByType function, we reset the collection in order to repopulate it with either all of the models, or the filtered models. We can also bind to the reset event in order to repopulate the collection with model instances. We can specify a handler function for this event as well, and the great thing is, we’ve already got the function. Add the following line of code directly after the change event binding:

this.collection.on("reset", this.render, this);

In this case we’re listening for the reset event and the function we wish to invoke is the collection’s render() method. We also specify that the callback should use this (as in the instance of the master view) as its context when it is executed. If we don’t supply this as the third argument, we will not be able to access the collection inside the render() method when it handles the reset event.

At this point, we should now find that we can use the select box to display subsets of our contacts. The reason why we set the silent option to true in our filterByType() method is so that the view is not re-rendered unnecessarily when we reset the collection at the start of the second branch of the conditional. We need to do this so that we can filter by one type, and then filter by another type without losing any models.


Routing

So, what we’ve got so far is alright, we can filter our models using the select box. But wouldn’t it be awesome if we could filter the collection using a URL as well? Backbone’s router module gives us this ability, let’s see how, and because of the nicely decoupled way that we’ve structured our filtering so far, it’s actually really easy to add this functionality. First we need to extend the Router module; add the following code after the master view:

var ContactsRouter = Backbone.Router.extend({
    routes: {
        "filter/:type": "urlFilter"
    },

    urlFilter: function (type) {
        directory.filterType = type;
        directory.trigger("change:filterType");
    }
});

The first property we define in the object passed to the Router’s extend() method is routes, which should be an object literal where each key is a URL to match and each value is a callback function when the URL is matched. In this case we are looking for URLs that start with #filter and end with anything else. The part of the URL after the filter/ part is passed to the function we specify as the callback function.

Within this function we set or update the filterType property of the master view and then trigger our custom change event once again. This is all we need to do in order to add filtering functionality using the URL. We still need to create an instance of our router however, which we can do by adding the following line of code directly after the DirectoryView instantiation:

var contactsRouter = new ContactsRouter();

We should now be able to enter a URL such as #filter/family and the view will re-render itself to show just the contacts with the type family:

So that’s pretty cool right? But there’s still one part missing – how will users know to use our nice URLs? We need to update the function that handles UI events on the <select element so that the URL is updated when the select box is used.

To do this requires two steps; first of all we should enable Backbone’s history support by starting the history service after our app is initialised; add the following line of code right at the end of our script file (directly after we initialise our router):

Backbone.history.start();

From this point onwards, Backbone will monitor the URL for hash changes. Now, when we want to update the URL after something happens, we just call the navigate() method of our router. Change the filterByType() method so that it appears like this:

filterByType: function () {
    if (this.filterType === "all") {
        this.collection.reset(contacts);

        <b>contactsRouter.navigate("filter/all");</b>

    } else {
        this.collection.reset(contacts, { silent: true });

        var filterType = this.filterType,
            filtered = _.filter(this.collection.models, function (item) {
                return item.get("type") === filterType;
        });

        this.collection.reset(filtered);

        <b>contactsRouter.navigate("filter/" + filterType);</b>
    }
}

Now when the select box is used to filter the collection, the URL will be updated and the user can then bookmark or share the URL, and the back and forward buttons of the browser will navigate between states. Since version 0.5 Backbone has also supported the pushState API, however, in order for this to work correctly the server must be able to render the pages that are requested, which we have not configured for this example, hence using the standard history module.


Summary

In this part of the tutorial, we looked at a couple more Backbone modules, specifically the Router, History and Events modules. We’ve now looked at all of the different modules that come with Backbone.

We also looked at some more Underscore methods, including filter(), which we used to filter down our collection to only those models containing a specific type.

Lastly, we looked at Backbone’s Router module, which allowed us to set routes that can be matched by our application in order to trigger methods, and the History module which we can use to remember state and keep the URL updated with hash fragments.

One point to take away is the loosely coupled nature of our filtering functionality; when we added filtering via the select menu, it was done in such a way that it was very quick and easy to come along afterwards and add a completely new method of filtering without having to change our filter() method. This is one of the keys to successfully building non-trivial, maintainable and scalable JavaScript applications. If we wanted, it would be very easy to add another, completely new method of filtering, which having to change our filtering method.

In the next part of this series, we’ll go back to working with models and see how we can remove models from, and add new ones to the collection.

Note: Want to add some source code? Type <pre><code> before it and </code></pre> after it. Find out more
  • Ren

    Great tutorials! The demo isn’t working though.

  • http://palbakulich.me/ palhmbs

    Seems that the demo is broken….

  • mcrk

    there’s no need to use “toLowerCase()” func here:

    value: item.toLowerCase(),
    text: item.toLowerCase()

    when You already lowercased items before in getTypes()


    Thanks for the second part, You’re doing a great job mate! :)

    • http://www.danwellman.co.uk Dan Wellman

      Thanks for spotting that, I added the toLowerCase() there, before adding it to getTypes, forgot to remove…

    • http://ibigfat.com bigfat

      no…. the getTypes() lowercase is for _.uniq comparing. and the return value hav’t lowercased.

      • http://yassershaikh.com Yasser

        spot on !

  • Mike Neyens

    Access denied to demo file…

  • daa

    Very nice tutorial. Like the others have mentioned, the demo is broken and looks like some of the characters have not been html escaped. This is was the first time i came across some thing like this:
    select = $(“”, {
    html: “All”
    });

    Can you please elaborate on this sort of initialization please?

    • http://www.danwellman.co.uk Dan Wellman

      There may have been a hiccup with the CMS, there code sample you reference now appears correct:

      select = $(“<select/>”, {
      html: “<option>All</option>”
      });

      It’s my fault, out of laziness I only escaped the opening curly quotes before submitting ><

      Apologies for the confusion. The demo now seems to be working correct as well

      Thanks for reading :)

  • krunal

    great demo, thanks for tutorial. it helped me in my project.

  • http://christopherdesigner.blogspot.com Christopher

    Interesting Tutorial, but the demo isn’t working….

  • http://www.bmatrailers.com Syed Sumair Zafar

    Demo link is broken giving xml error

  • mcrk

    I noticed that the code given on this page is lil’ different that available source code..
    One very important line missing in render() of DirectoryView is:

    this.$el.find(“article”).remove();

    Which removes all contact before rendering, so You won’t end up with endless appending..

    The best practice actually is to create a dedicated view for select form. I unserstand this was done for simplicity..

    • http://www.danwellman.co.uk Dan Wellman

      That line should actually probably have been added in the first part. Well spotted though, thanks for highlighting :)

      • http://rommelxcastro.com Rommel

        but the line is anywhere, and the code in the demo is different than the tut

  • http://charlieperrins.com Charlie

    Thanks for the tutorial Dan – great series.

    I added a line to the ContactsRouter object so that the select element will update if the URL fragment changes:

    urlFilter: function (type) {
    directory.filterType = type;
    directory.trigger(“change:filterType”);
    $(directory.el).find(‘#filter select’).val(type);
    }

    • http://www.danwellman.co.uk Dan Wellman

      Great addition :)

      Thanks for treading

    • Alchmication

      Very nice, thanks! Never thought of that Senior ;)

  • mcrk

    OK, now the last thing I noticed and I don’t know if it’s a good practice, but is it correct to put filter form INSIDE contacts view?

    Since we rendering the whole contact view every time, when selecting option.. that means, the form itself is re-render.. which I don’t think is a good way of doing this.

    I’m sorry, I’m also a newbie to backbone.js, but I would like to know some good practices, in case of future code re-factoring.. thanks

  • http://www.danwellman.co.uk Dan Wellman

    Sorry, which filter form? The HTML mark-up for the filter is added to the header of the outer view so it is only rendered once when the page loads initially. Both the methods for building the select and filtering the contacts being displayed are located in the DirectoryView class?

    • mcrk

      ohh I’m sorry, You’re corrects. I was trying to redesign form to append it as a separate view, I messed up the code, thinking it was the original state :)

      Now I can see the form appending is outside render.. kudos

  • http://martinodf.com MartinodF

    Thanks for the great article. I would have appreciated a link to the first part of the tutorial though!

    • http://www.danwellman.co.uk Dan Wellman

      Just click on my name at the top of the page and you’ll see part 1 in the list :)

      Thanks for reading

  • http://e-karmy.com Stefan

    Very useful article! Thanks

  • http://www.netd.it/sviluppo/ Netdesign (Fabio Buda)

    Backbone.js is an amazing project! developing a web app becomes faster and easier thanks to MVC paradigm offered by backbone.js!!!

    Have a nice day!!!

  • http://tommybrunn.com Tommy Brunn

    Great tutorial. What I’d love to see next is how to use a REST API as a data source for the models.

    • http://www.danwellman.co.uk Dan Wellman

      That’s a popular request and it will be covered in Part 5.

      I’m sorry if advanced/familiar Backbone users don’t want to wait for part 5, but we have to make sure that newcomers are up to speed on the fundamentals before getting into some of the more advanced aspects

      Thanks for reading :)

      • http://tommybrunn.com Tommy Brunn

        Not a problem at all. I, for some reason, just thought that the series was only a two-parter, and that this was the last entry. Glad to see that it’s going to continue.

  • MPorras

    Why you didn’t use templating? E.g. When generating the drop box

  • Eillen

    Great tutorial. But I find the more times the same option clicked,the more times render method called.

  • hoppster

    I’m looking forward to reading the rest of the series.

    FYI… the path to the second image in the tut is wrong/broken. Looks like it should be http://d2o0t5hpnwv4c1.cloudfront.net/1143_bb2/1.png

  • http://twiik.co.nz Brad

    Two of the little errors I found have been covered above. Thought I’d point this out too:

    After adding contactsRouter = new ContactsRouter(); the tutorial says “We should now be able to enter a URL such as….”

    This won’t actually work until the next step where we start Backbone.history as mentioned later (“From this point onwards…”

    Thanks for the awesome tutorial. For a backbone n00b like myself, when you follow the tutorial word for word..things like the above can be a bit confusing till you read ahead. But you’ve managed to make this tutorial simple and yet informative so thanks :)

    • haji

      Thanks for this! I was banging my head against a wall trying to figure out why it wasn’t working.

  • http://shaheenghiassy.com Shaheen Ghiassy

    Thanks for the tut!

    Small Problem. The DirectoryView.render function has to be updated from Part I to Part II of this tut in order for the app to work. The difference is that the line of code: “this.$el.find(‘article’).remove();” needs to added.

    Without the update to the DirectoryView.render function, filtering the contacts will only append the filtered contact list, thereby making a longer list. Instead, the current contacts have to be removed and the new ones added.

  • Matt

    Great set of tutorials! However, I’m trying to follow along and type it out as I go, only to find out it’s not the working code.

    Being new to Backbone, I have yet to be able to pick up on the little details and differences just at a glance. So I’ve been basically been going line by line with your source code to spot the changes.

    If you wouldn’t mind, could you please update the code snippets in the post so that they at least match what you have in the source. That way I know that I messed something up when the code doesn’t work.

    Thanks and keep up the good work!

    • Niklas

      I agree with Matt. I am also following along, writing every line in my own texteditor only to find that it does not work. It is pretty frustrating to not know whether it’s my fault or not. I don’t want to be nagging, I love nettuts and this series, but as you see all these comments, please change the code not only in the demo, but in the tutorial aswell.

      Thank you for otherwise great tutorial!

  • Matt

    One bug that is slightly annoying dealing with the user-flow of the back button is that the drop down menu doesn’t return to its previous state, but the filtered content does.

    • http://alexconcepts.com alex

      Just add the following line at the end of the urlFilter method in the ContactsRouter class.

      directory.$el.find(“#filter select”).val(type);

      It will keep the combo in synch.

      • rowild

        Good solution! It just doesn’t keep the DOM updated (“option” won’t get a “selected” attribute)

  • Jesse Breuer

    It says:

    Add the following code directly after our renderSelect() method:

    There is no renderSelect() method.
    You meant return select;

    • Andrea

      I guess he meant “createSelect” method

  • Bobby G.

    First of all, thanks for the tutorials. For the community, what the preferred method for debugging backbone code?

  • http://actionauta.com Sebastian

    Hi, just a little fix:

    The 8th line in the second block of code should be:

    var filter = this.$el.find(“#filter”),

    instead of

    var filter = this.el.find(“#filter”),

    Note the lacking $ for the “el” object.

    • Andrea

      yep, I was about to comment the same thing

  • Oleg

    Just following the tutorial and noticed that the select All option didn’t have a value. That was causing me to get no items when All was selected:

    createSelect: function () {
    var filter = this.$el.find(“#filter”),
    select = $(“”, {
    html: “All”
    });

    • Oleg

      Stripped out all the tags:

      “All”

    • Andrea

      Yes, in the createSelect method, the default ALL option has no value. It should be
      html: “<option value=’all’>All</option>”

  • Alan

    Hello. Great tutorial. But why would you want to use:

    this.$el.find(‘#filter’)

    when:

    $(‘#filter’)

    will just work?

    • http://28inch.co.uk 28inch

      You`re right but the first one is faster. If you want to know why check out Paul Irish`s screencast on the subject.

      10 Things I Learned from the jQuery Source:
      http://www.youtube.com/watch?v=i_qE1iAmjFg

    • Brian Frichette

      As was mentioned it is slightly faster, although you’ll never see a real world difference using an ID. I think he did it this way to show you that the backbone class automatically creates a cached jQuery reference to the element.

      I really love this overview, although there are a few redundancies and typos.

      Thanks for the crash course. I’ll definitely be using this!

  • Davide

    Hi, i’ve some problem on filtering method, i have followed your code before the routing part, but when i select a type the system append the contacts type selected instead of replace the currents, where i might be wrong? i have checked each methods and attributes but the code is the same. thank you!

    • pinksy

      @Davide the following is missing from the top of DirectoryView.render():

      $(this.el).find(“article”).remove();

      That will remove any previously rendered tags before re-rendering.

      Also, the ‘select’ variable declaration in DirectoryView.createSelect() should be:

      var select = $(“”, {
      html: “All”
      });

      (note the value=’all’ attribute in the tag. Also note that the ‘filter’ declaration is not needed in the createSelect() function)

    • pinksy

      @Davide the following is missing from the top of DirectoryView.render():

      $(this.el).find(“article”).remove();

      That will remove any previously rendered tags before re-rendering.

      Also, the ‘select’ variable declaration in DirectoryView.createSelect() should be:

      var select = $(“<select/>”, {
      html: “<option value=’all’>All</option>”
      });

      (note the value=’all’ attribute in the tag. Also note that the ‘filter’ declaration is not needed in the createSelect() function)

      • Ciaran

        Nice one @pinksy I was stuck on this for a while but got it working with your suggestions

  • Bess

    I am only getting ‘family’ back in the createSelect method and populated in the dropdown. I even just copied straight in from the source of this demo. Anyone else having this issue?

    _.each(this.getTypes(), function (item) {
    console.log(item)
    var option = $(“”, {
    value: item,
    text: item
    }).appendTo(select);
    });

    The console.log only returns ‘family’. Seems like getTypes is only returning the first result. Totally stumped..

    • Bess

      Figured out my problem… there was a conflict with the stylish select plugin for jquery. DIdn’t dive into the plugin code to figure out the root, but when I removed that file it worked just fine.

  • Henry

    is underscore’s uniq same as jquery’s grep in terms of functionality?

  • phillyDesignr

    Add this line in your filterByType method if you want your select to properly display the correct filter if you just went directly to /#filter/:type

    $(“#filter select”).find(“option[value='" + this.filterType + "']“).attr(‘selected’, ‘selected’);

    PS nettuts there are tons of typos and such in this tut

    • http://www.bartlewis.me Bart Lewis

      That works fine for the initial navigation, but then runs unnecessarily when the select box is changed, and does not update at all on browser back button.

  • backbonenoob

    Thanks for the tutorial.

    Please update this line in createSelect function:
    var filter = this.$el.find(“#filter”),

    ( $ was missing before el)

  • abc

    Looks like Backbone.history.start(); is needed for the filter in the URL to work.

    In tutorial after adding var contactsRouter = new ContactsRouter(); if I go and add the filter in the URL it didn’t work but when I started the history service it worked. My question is it always required to start the history service for the router to work?

  • SohelElite

    Great article.

    But i m facing issue with the Router. If there are “empty space” in the dropdown list for example

    family friends

    render function under DirectoryView is triggering twice with empty result set.

  • http://www.perisicdesigns.com Stevo

    In the createSelect fn on line 8 i believe, missing a $ in front of the el, should be like this:

    var filter = this.$el.find(“#filter”), <- The missing $
    select = $("”, {
    html: “All”
    });

    This caused the select box not to render.

    • tote

      you don’t even need that line in the create select b/c you don’t use the filter variable anyway,

      <pre name=”code” class=”js”>
      createSelect: function () {
      var select = $(“<select/>”, {
      html: “<option value=’all’>All</option>”
      });

      _.each(this.getTypes(), function (item) {
      var option = $(“<option/>”, {
      value: item,
      text: item
      }).appendTo(select);
      });

      return select;
      },

      </pre>

  • yun

    This series are very helpful for a beginner to understand how to code with Backbone.js because it solved many problems often encountered in MVC models, at least as I imaged. Thanks a lot.

    But there are still some places that I would like to mention.

    1. When the url filter changes, the select value should be changed in the same time. It is not so difficult but we need to refer to the direcotryView.

    2. The Router should be passed its specified direcotryView through its options. Then we don’t need to use the instant directoryView.

    3. Since without Backbone.history.start() the Router does not work, is it better to put Backbone.history.start() in the initialization of the Router?

  • bogdan

    the downloaded zip is very much inconsistent with the tutorial! It needs to be updated!

  • http://briancampbell.name Brian

    FYI, there is no renderSelect() method. That should probably be corrected…

  • http://briancampbell.name Brian

    There’s an error in the createSelect method…missing the “$” before the “el”…

    createSelect: function () {
    var filter = this.el.find(“#filter”),

    …should be…

    createSelect: function () {
    var filter = this.$el.find(“#filter”),

    • daltonrenaldo

      this piece of code doesn’t even seem to be necessary. it doesn’t do anything.

  • Andrew

    I must say that I am a little disappointed by the quality of this tutorial, I expect more from nettuts+, and whilst I appreciate that we all write buggy code at times, I think this was rushed a little.

  • http://www.benoitchabert.ca Benoit

    Prettier:

    filterByType: function(){
    if(this.filterType === “all”){
    this.collection.reset(contacts);
    }else{
    this.collection.reset(contacts, {silent:true});

    var filterType = this.filterType,
    filtered = _.filter(this.collection.models, function(item){
    return item.get(“type”).toLowerCase() === filterType;
    });
    this.collection.reset(filtered);
    contactsRouter.navigate(“filter/” + filterType);
    $(‘option’).each(function(){
    if($(this).val() === filterType)
    {
    $(this).attr(“selected”, “selected”);
    }
    });
    }
    }

    Now your select also changes on url request. :D !

    • http://www.benoitchabert.ca Benoit

      filterByType: function(){
      if(this.filterType === “all”){
      this.collection.reset(contacts);
      $(‘option’).each(function(){
      if($(this).val() === filterType)
      {
      $(this).attr(“selected”, “selected”);
      }
      });
      }else{
      this.collection.reset(contacts, {silent:true});

      var filterType = this.filterType,
      filtered = _.filter(this.collection.models, function(item){
      return item.get(“type”).toLowerCase() === filterType;
      });
      this.collection.reset(filtered);
      contactsRouter.navigate(“filter/” + filterType);
      $(‘option’).each(function(){
      if($(this).val() === filterType)
      {
      $(this).attr(“selected”, “selected”);
      }
      });
      }
      }

      Fixed for all method as well

      • http://www.benoitchabert.ca Benoit

        post before has the wrong code.

        filterByType: function(){
        if(this.filterType === “all”){
        this.collection.reset(contacts);
        $(‘option’).each(function(){
        if($(this).val().toLowerCase() === this.filterType)
        {
        $(this).attr(“selected”, “selected”);
        }
        });
        contactsRouter.navigate(“filter/” + this.filterType);
        }else{
        this.collection.reset(contacts, {silent:true});

        var filterType = this.filterType,
        filtered = _.filter(this.collection.models, function(item){
        return item.get(“type”).toLowerCase() === filterType;
        });
        this.collection.reset(filtered);
        contactsRouter.navigate(“filter/” + filterType);
        $(‘option’).each(function(){
        if($(this).val() === filterType)
        {
        $(this).attr(“selected”, “selected”);
        }
        });
        }
        }

      • rowild

        Thanks for that contribution! Even better might be another function on the Master VIew that also removes a “selected” attribute:

        setSelected: function(filterType){
        $(‘option’).each(function(){
        if($(this).val() === filterType) {
        $(this).attr(“selected”, “selected”);
        } else {
        $(this).removeAttr(“selected”);
        }
        });
        },

        // call the function in the filterByType, at the end after allt he if-else logic,
        // because it needs to be called anyway:

        filterByType: function(){
        setSelected(filterType);
        },

      • rowild

        Sorry:

        filterByType: function(){
        this.setSelected(filterType);
        },

      • rowild

        This is the best working solution at the moment:


        setSelected: function(filterType){
        $('#filter option').each(function(){
        // to update the selected value of the select box
        this.selected = (this.text == filterType);
        // to add/remove the attribute in the DOM
        if($(this).val() === filterType) {
        $(this).attr("selected", "selected");
        } else {
        $(this).removeAttr("selected");
        }
        });
        },

  • http://hnordt.com Héliton Nordt

    Great tutorial. :)

    In the start of the tutorial:

    createSelect: function () {
    var filter = this.el.find(“#filter”),
    select = $(“”, {
    html: “All”
    });

    Should be:

    createSelect: function () {
    var filter = this.$el.find(“#filter”),
    select = $(“”, {
    html: “All”
    });

    • http://hnordt.com Héliton Nordt

      Also:

      Filtering the View
      (…) Add the following code directly after our renderSelect() method:

      Should be:
      Filtering the View
      (…) Add the following code directly after our createSelect() method:

    • http://hnordt.com Héliton Nordt

      Once the array has been filtered, we call the reset() method once more, passing in the filtered array. Now we’re ready to add the code that will wire up the setType() method, the filterType property and filterByType() method.

      Should be:

      Once the array has been filtered, we call the reset() method once more, passing in the filtered array. Now we’re ready to add the code that will wire up the setFilter() method, the filterType property and filterByType() method.

  • TP

    Hopefully i know js and backbone enough to locate and correct errors in this tutorial….
    So here is the positive side – it made me think more :)
    Still i think net+ should pay more attention to released tutorials..as not only code is broken, also approaches are quite controversial (i.e select population)

  • Darragh Flynn

    Hi Dan, Great series!

    It’s really helping me get into Javascript more hands on!

    Just to note, that the code is different in the demo than it is in the tutorial! I was challenging myself trying to see if I could figure out what was wrong and ended up looking over and over the tutorial, I ended up comparing to the demo files and voila, a tidier, neater, working file!

    Thanks for the help!

  • tommytwoeyes

    I pulled a good bit of my hair out trying to understand why, in my implementation of the app, using the filter to narrow the list of contacts by category didn’t exactly work as intended – the filtered contacts were appended to the original list, rather than replacing it. At first, I thought maybe the first call to this.collection.reset() within the else{ } branch of the filterByType() method was unnecessary.

    Then, after studying the source of app.js in the demo and comparing it to my code, I realized that in the demo’s DirectoryView’s render() method, it was executing this line (which was missing from my code):

    this.$el.find(“article”).remove();

    That’s definitely the missing piece. I checked Part 1 of the tutorial, and that line is missing from the walk-through of the DirectoryView.render() method.

    Also, I thought I’d de-couple the render() method’s code a bit from the HTML presentation layer, by replacing the this.$el.find(‘article’).remove() with:

    var cv = new ContactView();
    this.$el.find(cv.tagName).remove()
    (It doesn’t appear to be possible to access the value of tagName through ContactView.tagName – only through an instance. Why is that?)

    IMHO, I think it makes the code slightly more robust.

    Thanks for the intro to Backbone! It’s my first foray into JavaScript MVC (or MVanything, in terms of JavaScript), and I’m really liking the organization and cleanliness it bring to my apps!

    • tommytwoeyes

      I hope my previous comment did not sound sarcastic at the end. I did genuinely want to thank you for the very informative article – I should have said that right at the beginning of my comment.

    • Trevor

      I ran into this same problem. Looking at the source code from the demo, I also noticed the createSelect() method was missing a key item. The line html: “All” needed to be replaced with html: “All” to make the dropdown work properly when All was re-selected.

  • http://twitter.com/PenseePlastique PaT

    Hi guys!

    First of all, thanks for writing such an interesting tutorial on Backbone.js, it sure is a promising library.

    But i’m trying to find out why I am stuck at the “select” list step … And when I compare my app.js file to the one provided, I really can’t figure out what I’m doing wrong.

    This is the error, located to the createSelect method, according to firebug: “this.el.find is not a function; var filter = this.el.find(“#filter”),”

    Can anyone help ?

    • Mike

      Change to filter = this.$el.find(“#filter”),”

  • SPeed_FANat1c

    Mistake in createSelect method:

    var filter = this.el.find("#filter")

    fix:

    var filter = this.$el.find("#filter")

  • SPeed_FANat1c

    “Add the following code directly after our renderSelect() method:”

    The author meant – after createSelect() method

    On the other hand – mistakes of the author has its advantages – we pay more attention to code and learn better :) but for newbies to javascript or frameworks it might be difficult.

  • SPeed_FANat1c

    oh, I noticed in othere comments that mistakes which I posted fixes are already posted long time ago. And the article is still not fixed. BAD :P