Get Source View Demo

Foreword

This is part 2 of an ongoing experiment, where I document my steps building with Backbone JS – a client side framework that I’m beginning to love. Each part will build upon the previous, and will ultimately evolve into a little sample users-directory application.

  1. Set up, retrieving, and outputting users (read here)
  2. Refactoring the app, and implementing sorting
  3. Searching/filtering the users list (coming soon)
  4. Adding, editing, and deleting users (coming soon)
  5. Single view of users (coming soon)

To keep up to date with future progress in this series, sign up to the newsletter.

Recap From Last Time

At the end of part 1, we successfully read in our data from an external JSON file and output it into our app. To do this, we created a model, a collection, and a view, using the users first name as the comparator. In this tutorial, we're going to create a simple action input that allows us to sort our users by either their first name, last name, or email address. We'll also create a search bar that allows us to perform a very basic filter on our output. Let's dive in!

Restructuring Our App

Before moving on, some important points were taken into consideration with the future of the app in mind. Firstly, I've imported jQuery and removed Backbone native. This helps out greatly with using Underscore templates, bringing us to the second point. I'm now using an Underscore template to allow quicker and easier creation of User models. Here's what it looks like:

<script type="text/template" id="user-template">
  <td><%= firstname %></td>
  <td><%= lastname %></td>
  <td><%= email %></td>
</script>

This template is placed in the index.html file, and will be referenced in the app.js file to model the data and insert the correct variables in the correct places. Notable changes in the index.html file also include some renaming of ID's, and the addition of a form that will allow us to sort by various parameters. Here's what our new section looks like:

<div id="users">
  <form id="users-sort" action="#">
    <label for="sort">Sort by:</label>
    <select name="sort" id="sort">
      <option value="firstname">First name</option>
      <option value="lastname">Last name</option>
      <option value="email">Email address</option>
    </select>
  </form><!-- /#users-sort -->
  <table id="users-table">
    <thead>
      <tr>
        <th>First Name</th>
        <th>Last Name</th>
        <th>Email Address</th>
      </tr>
    </thead>
    <tbody id="users-table__body"></tbody>
  </table><!-- /#users-table -->
</div><!-- /#users -->

Over in our app.js file, some more construction work took place. We'll go through each of those step by step, but here's the overview of the process:

  1. I now have a User model, which will model the data for a single user.
  2. The collection was renamed to UsersCollection to be more indicative.
  3. There's now a UsersView view, which acts as a parent view for all individual user views. This will give us greater control over singular user actions when the time comes.
  4. Each user is now generated in a singular UserView.

Let's tackle each of these point by point now.

The User Model

The User model is pretty much the same as before, and here's what it looks like:

var User = Backbone.Model.extend({
  defaults: {
    firstname: '',
    lastname: '',
    email: ''
  }
});

This will model the data for each user, ultimately creating and appending them to the necessary container. Let's move onto the collection.

The Users Collection

As before, this is just a collection of dummy data from a generated JSON file, stored in the assets directory. By default, we're sorting by first name. Here's what the collection looks like:

var UsersCollection = Backbone.Collection.extend({

  model: User,

  url: 'assets/users.json',

  comparator: 'firstname'

});

This time though, we're going to create a new instance of this collection so that there is a more global access to it. We can do that like this:

var Users = new UsersCollection();

Now, we're ready to fetch the data and output it again.

The Users View

As I mentioned above, the UsersView is a parent view to all individual User models, generated by the UserView. We'll start off by defining it, and setting the governing element to the #users element seen above in the index.html file:

var UsersView = Backbone.View.extend({

  el: '#users'

});

Last time, we fetched the user collection, and only on success, we output the data. This time though, we're going to think with sorting in mind. Running fetch on a collection with a comparator defined triggers a sort event, which means that we can actually listen for the sort event first time around to render the data. This also sets us up nicely for the future, because we'll be able to listen for the same event, thus running the same call to render. We'll handle the event listening in our initialize function like this, adding it to our UsersView:

initialize: function() {
  this.listenTo(Users, 'sort', this.render);
  Users.fetch();
}

Take a look at what's happening here. We're adding an event listener called sort on the Users collection previously defined, and running the render callback when that event is fired. Let's now take a look at the render function.

When we are rendering the entire users table, we want to make sure that the HTML is cleared first. After that, we want to loop over the collection and create a new user view for each user, appending it to the necessary element. Here's the shell of the render function:

render: function() {
  this.$('#users-table__body').html('');

  Users.forEach(function(model) {

  });

  return this;
}

Before we complete this function though, we need to take a look at the single User view.

The Single User View

Instead of outputting the entire collection into one view, we've separated concerns. The UsersView, as seen above, is acting as a parent view to all the users and potential sorting actions on the collection. The UserView will act as a view for a single user in the table, thus adding some greater control to possible future actions on the user. Here's what it looks like:

var UserView = Backbone.View.extend({

  tagName: 'tr',

  template: _.template($('#user-template').html()),

  render: function() {
    this.$el.html(this.template(this.model.attributes));
    return this;
  }

});

This time, we're creating a new tr tag and making use of the template defined earlier. In the render function, we're filling up the tr element with the template and passed in values, populated by the model's attributes. The model's attributes are fetched by calling this.model.attributes, which means that in the iterative process in the UsersView, we need to supply the model. In our case, that's the User model. Let's do that now.

Outputting The Collection

Back in the render function in our UsersView, we can now create individual view/model pairings, and append them to the table body. Because our collection is based on the User model, we can supply that model directly in the forEach loop, and pass it into new instances of the UserView. We can then fetch the rendered content from that view and append it to the table body. Here's the update render function:

render: function() {
  this.$('#users-table__body').html('');

  Users.forEach(function(model) {
    var user = new UserView({
      model: model
    });
    $('#users-table__body').append(user.render().el);
  });

  return this;
}

Now the only thing left to do is to actually run the app. This can be done by calling a new instance of the UsersView like this:

new UsersView();

Our users should now be displayed nicely and alphabetically by first name, just like last time. This time though, we have the added benefit of an enhanced structure, which will take us forward nicely. Let's now implement that sort functionality.

Implementing Sort

In our index.html file, we created a simple form with a select input. Here's what it looks like:

<form id="users-sort" action="#">
  <label for="sort">Sort by:</label>
  <select name="sort" id="sort">
    <option value="firstname">First name</option>
    <option value="lastname">Last name</option>
    <option value="email">Email address</option>
  </select>
</form><!-- /#users-sort -->

In our UsersView, we want to listen for a change on the select field, and sort the list accordingly. We can do that by using Backbone's events, tuning in for a change on the sort field, and triggering a sort function. Here's how we'd set up the event inside the UsersView:

events: {
  'change #sort': 'sortCollection'
}

Now, we need to create the sortCollection function as well. In the UsersView, we'll add that function, and pass through the event. We're looking for the event target's value, and we'll want to change the comparator on the Users collection to reflect this. Finally, we can fire a sort event by calling sort on the Users collection, thus re-rendering the collection with the correct comparator. Here's the function in action:

sortCollection: function(e) {
  Users.comparator = e.target.value;
  Users.sort();
}

Try it out now, you should be able to sort easily!

Wrap Up

And that’s a wrap! In this part of the series, we covered quite a bit. We refactored our code to make it more future proof, looked at some ways to use events and listen for changes on collections and models, and implemented a sorting function. Next time, we'll look into some other operations like adding, editing, and deleting users.

Don’t forget, you can view the demo and download the source by clicking the links below, and if you have and questions, comments, or feedback, you can also leave them below.

Get Source View Demo