Saturday, October 15, 2011

Debugging Minor View Issues in Backbone.js

‹prev | My Chain | next›

Last night, I was able to work up a rather nice Backbone.js collection caching solution for my calendar application:
The solution is built on sub-collections that do the work of loading a month's worth of calendar appointments. Once successful, the main Backbone appointment collection adds the appointments from the sub-collection to its own internal store and triggers events that render the appointments on the calendar. For good measure, a successful fetch of a month also triggers pre-fetching of the previous and next month's appointments.

The solution felt fairly strong because each layer had a definite purpose. Methods were small. The only "magic" came by a reliance on Backbone's conventions (in particular the success() callback). But reliance on convention isn't magic—it is good coding.

Anyhow, tonight I have a look through some of the dusty corners to see if I have a robust solution. The first bit that I know to be wonky is loading the default (non-route) page. Currently, it loads a blank October:
The routes already do everything that I want this view to do—tell the calendar to draw the correct month and point the appointments collection to the same month:
      var Routes = Backbone.Router.extend({
        routes: {
          "month/:date": "setMonth"
        },

        setMonth: function(date) {
          console.log("[setMonth] %s", date);
          draw_calendar(date);
          appointments.setDate(date);
        }
      });
So maybe I can simply call that method directly?

      // Setup the appointments collection
      var year_and_month = Helpers.to_iso8601(new Date()).substr(0,7),
          appointments = new Collections.Appointments(undefined, {date: year_and_month});

      // Setup the views
      new Views.Application({collection: appointments});

      // Default route
      Routes.prototype.setMonth(year_and_month);

      // Setup routing
      routes = new Routes();
      Backbone.history.start();
It turns out, I can. After reloading the page, I now see all of my October appointments (as well as pre-fetched September and November ones):

And the Javascript console in Chrome confirms that this is behaving as expected:

Easy enough. I cannot say with certainty that this is the ideal solution for a default route in a Backbone application, but I cannot argue with the results.

The other problem may prove a bit more challenging. When I navigate to the next month, I should see two appointments: one to get started on the final copy of Recipes with Backbone on the November 1 and one to deliver the final version of the book on November 30.

I see neither of the expected appointments, but do see several ghost October appointments:

The Javascript console indicates that the collection remains intact (and December is pre-fetched):

So this must be a problem with triggering the appointment views. Since this bug is only seen on the second page, I eschew the debugger for fear of the F8 (continue) key being worn out on my keyboard. Instead, I add logging to the render method:
        var Appointment = Backbone.View.extend({
          // ...
          render: function() {
            console.log(this.model.toJSON());
            console.log(this.el);
            console.log(this.container);
            $(this.el).html(this.template(this.model.toJSON()));
            this.container.append($(this.el));
            return this;
          },
          // ...
        });
Checking the Javascript console for the first November appointment, I see that it thinks the containing element is December 6!


To resolve, I need to make the container element a method to be calculated each time:
        var Appointment = Backbone.View.extend({
          // ...
          render: function() {
            $(this.el).html(this.template(this.model.toJSON()));
            this.container().append($(this.el));
            return this;
          },
          container: function() {
            return $('#' + this.model.get('startDate'));
          },
          // ...
        });
With that, I can navigate between months:

Nice! Yesterday's caching solution caused neither of these two bugs. I only needed to call a default route and to use a dynamic container method for the appointment views. Maybe my caching solution is... not bad? I will find out tomorrow... when I attempt cache invalidation.


Day #175

No comments:

Post a Comment