Wednesday, October 12, 2011

Filtering Backbone.js Collections with Routes

‹prev | My Chain | next›

I received some great feedback on last night's poor man's caching solution in Backbone.js. Before implementing some of those suggestions, I think I need a better mechanism for expressing which month my calendar application is currently displaying.

I think Backbone routes are probably the best way to go.

So first up, I rip out the existing Navigation view, which binds events to next and previous links:
        var CalendarNavigation = Backbone.View.extend({
          events: {
            'a:click': 'preventDefault',
            'click .previous': 'handlePrevious',
            'click .next': 'handleNext'
          },
          preventDefault: function(e) { e.preventDefault(); },
          handlePrevious: function() {
            console.log("[handlePrevious]");
            var date = Helpers.previousMonth(appointments.getDate());
            draw_calendar(date);
            appointments.setDate(date);
          },
          handleNext: function () {
            console.log("[handleNext]");
            var date = Helpers.nextMonth(appointments.getDate());
            draw_calendar(date);
            appointments.setDate(date);
          },
          render: function() {
            this.el.html(
              '<div class="previous"><a href="#">previous</a></div>' +
              '<div class="next"><a href="#">next</a></div>'
            );
            this.el.find("a").bind('click', function(e) {e.preventDefault();});
            return this;
          }
        });
In its place I build a route based view, with some dummy values for the next and previous dates:
        var CalendarNavigation = Backbone.View.extend({
          initialize: function(options) {
            this.collection = options.collection;
          },
          template: _.template(
            '<div class="previous">' +
              '<a href="#month/<%= previous_date %>">previous</a>' +
            '</div>' +
            '<div class="next">' +
              '<a href="#month//<%= next_date %>">next</a>' +
            '</div>'
          ),
          render: function() {
            $(this.el).html(this.template({
              previous_date: "2011-09",
              next_date: "2011-11"
            }));

            return this;
          }
        });
I will dynamically grab the dates from the collection once I have this hooked together.

Loading the page and inspecting the DOM, I see that I am on the right track:

To handle these URLs, I need to set up a router to respond to #/month/YYYY-DD formatted URLs. The Backbone router for this should look something like:
      var Routes = Backbone.Router.extend({
        routes: {
          "month/:date": "setMonth"
        },

        setMonth: function(date) {
          console.log(date);
        }
      });
Loading up the page and clicking "next" and "previous", I do see the change in month:


Replacing the tracer bullets in setMonth() with real code involves only re-drawing the calendar and telling the collection to use the new date:
      var Routes = Backbone.Router.extend({
        routes: {
          "month/:date": "setMonth"
        },

        setMonth: function(date) {
          console.log("[setMonth] %s", date);
          draw_calendar(date);
          appointments.setDate(date);
        }
      });
And that is all that is needed. After clicking the (still hard-coded) previous and next links several times, I see the one November appointment as expected:

And, checking Chrome's Javascript console, I see that my caching is still working, but now with the updated router:
The first time that I request September and November, my poor man's cache results in a cache miss and a subsequent XHR request. After that, however, subsequent requests of those months result in a cache hit and no more XHR requests are necessary.

That will do as a stopping point for tonight. Up tomorrow, I will attempt to adapt this router based filter for a better caching solution.

Day #172

No comments:

Post a Comment