//

BACKBONE CUSTOMIZING COLLECTIONS

Backbone collections are an ordered set of models, which only use one type of model. Customizing these collections can help your Backbone application to leverage data the way you want.

Let's assume you have an Appointment application that serves several appointments from a server which you don't have much control of the data.

Collection Parsing

If you want to start paginating appointments instead of just returning all of them when we fetch the Appointments collection, ake a look at the JSON the server is responding with below:

// Get /appointments
{
  "per_page": 10, "page": 1, "total": 50,
  "appointments": [
    { "title": "Ms. Kate Wilson", "cancelled": false, "identifier": 1 }
  ]
}

Let's modify the parse function to set properties on the collection instance for per_page, total, and page.

var Appointments = Backbone.Collection.extend({
  parse: function(response){
    this.perPage = response.per_page;
    this.page = response.page;
    this.total = response.total;
    return response.appointments;
  }
});

Great! Now to finish the job, just return the appointments array from the parse function, instead of the entire response.

var Appointments = Backbone.Collection.extend({
  parse: function(response){
    this.perPage = response.per_page;
    this.page = response.page;
    this.total = response.total;

    return response.appointments;
  }
});

How do we fetch with extra params?

Assume that the server team has implemented a feature for limiting the appointments pulled down based on the appointment date. In the code below, update the fetch call to pass an extra param so that the URL is like: /appointments?since=2013-01-01

// Get /appointments
{
  "per_page": 10, "page": 1, "total": 50,
  "appointments": [
     { "title": "Ms. Kate Wilson", "cancelled": false, "identifier": 1 }
  ]
}
var appointments = new Appointments();
appointments.fetch({data: {since: '2013-01-01'}});

We can limit the number of appointments returned by passing in a per_page parameter also. Go ahead and construct the fetch call below to create a URL that looks like /appointments?since=2013-01-01&per_page=10

var appointments = new Appointments();
appointments.fetch({data: {since: "2013-01-01", per_page: 10}});

Render the Next Link

Your client has requested that we add a link to the application that will show the next 10 appointments. Luckily we already can paginate through appointments bypassing the per_page and page params to the server when we fetch the collection.

Let's start to implement this feature by adding a template to the AppointmentListView below. The template should have a link that looks like this:

/appointments/p<%= page %>/pp<%= per_page %>">View Next</a>

// AppointmentList.js
var AppointmentList = Backbone.Collection.extend({
  parse: function(response){
    this.per_page = response.per_page;
    this.page = response.page;
    this.total = response.total;
    
    return response.appointments;
  }
});

var AppointmentListView = Backbone.View.extend({
  initialize: function(){
    this.collection.on('reset', this.render, this);
  },
  render: function(){
    this.$el.empty();
    this.collection.forEach(this.addOne, this);
  },
  addOne: function(model){
    var appointmentView = new AppointmentView({model: model});
    appointmentView.render();
    this.$el.append(appointmentView.el);
  },
  template: _.template('<a href="#/appointments/p<%= page %>/pp<%= per_page %>">View Next</a>')
});

Great! Now that we have the template, let's add some code in the render function to append the generated HTML from the template into the AppointmentListView $el. Make sure you pass in the page and per_page properties to the template function, getting those values from this.collection.page + 1and this.collection.per_page respectively.

var AppointmentListView = Backbone.View.extend({
  template: _.template('<a href="#/appointments/p<%= page %>/pp<%= per_page %>">View Next</a>'),
  initialize: function(){
    this.collection.on('reset', this.render, this);
  },
  render: function(){
    this.$el.empty();
    this.collection.forEach(this.addOne, this);
    this.$el.append(this.template({page: this.collection.page + 1, per_page: this.collection.per_page}));
  },
  addOne: function(model){
    var appointmentView = new AppointmentView({model: model});
    appointmentView.render();
    this.$el.append(appointmentView.el);
  }
});

Now that we are rendering the link, we need to implement the route to handle appointments/p: page/pp:per_page and have the route function fetch the new appointments based on the parameters. Name this new function page.

var AppRouter = new (Backbone.Router.extend({
  routes: { 
    "": "index", 
    "appointments/p:page/pp:per_page" : "page",
  },
  initialize: function(options){
    this.appointmentList = new AppointmentList();
  },
  index: function(){
    var appointmentsView = new AppointmentListView({collection: this.appointmentList});
    appointmentsView.render();
    $('#app').html(appointmentsView.el);
    this.appointmentList.fetch();
  },
  page: function(page){
    this.appointmentList.fetch({data: {page: page, per_page: page}});
  }
}));

Sorting By Date

Our appointments are being rendered in a pretty haphazard way. So we need to always have our appointments sorted by the date. Add the code below to accomplish this.

var Appointments = Backbone.Collection.extend({
    comparator: 'date'
});

Sorting Order

If you want to reverse the order, Update the comparator below to sort by date in reverse order

var Appointments = Backbone.Collection.extend({
  comparator: function(apt1, apt2) {
    return apt1.get('date') < apt2.get('date');
  }
});

Counting Up

If your client tells us to implement a function on the collection to count up the number of cancelled appointments. Implement this function in the collection class below and call it cancelledCount.

var Appointments = Backbone.Collection.extend({
  cancelledCount: function(){
    return this.where({cancelled: true}).length;
  }
});