Monday, October 31, 2011

Javascript Dates are Still a Pain in Backbone.js

‹prev | My Chain | next›

Up tonight, the briefest of posts to appease the god of my chain. They have been good to me, so I must honor them, but I also have quite a bit of work left before the beta release of Recipes with Backbone.

Fortunately, I have left a bug or two to address tonight. First up is navigating to December in my calendar application. When the calendar first loads, the default Backbone route redirects to the current month. I can click the "next" link to get to November. Clicking the next link from November, however, locks the browser:


The collections setDate() method is being reached, as evidenced by the console.log statement in Chrome's javascript console. The XHR request for December is not making it back to the browser however, though it is requested from the server.

This has got to be an endless loop because of the way the browser locks. Since this only occurs on December, I suspect that I have reached a boundary condition.

At first, I suspect my navigation view since it needs to calculate the next month. Sadly, when I enter a dummy date for the next month:
    var CalendarNavigation = Backbone.View.extend({
      initialize: function(options) {
        options.collection.bind('calendar:change:date', this.render, this);
      },
      template: template( /* ... */ ),
      render: function() {
        var date = this.collection.getDate();
        $(this.el).html(this.template({
          previous_date: Helpers.previousMonth(date),
          next_date: "2012-01" //Helpers.nextMonth(date)
        }));

        return this;
      }
    });
I am still greeted with an "Aw, Snap" message after manually entering December's URL:


The next place to check is the Month "Body" (i.e. not the header row) of the calendar. For that view, I iterate over weeks until the start of the next week is in the following month:
    var CalendarMonthBody = Backbone.View.extend({
      render: function() {
        var firstOfTheMonth = Helpers.firstOfTheMonth(this.date),
            month = firstOfTheMonth.getMonth(),
            firstSunday = new Date(firstOfTheMonth.getTime() -
                            firstOfTheMonth.getDay()*24*60*60*1000);

        var date = firstSunday;
        while (date == firstSunday || date.getMonth() <= month) {
          var week = new CalendarMonthWeek({date: date});
          week.render();
          this.el.push(week.el);

          date = Helpers.weekAfter(date);
        }
      }
    });
Ah, that's my problem. Stupid Javascript dates. The month, for December is 12 (well 11 for Javascript), but the date of the weeks being added to the calendar will always be less than or equal to December. Hence my infinite loop.

To resolve this, I exploit the fact that the date stored for the Calendar Month view is an ISO 8601 format month (e.g. "2011-12"). The great thing about ISO 8601 dates is that, even though they are strings, they can still be compared. That is, the string "2011-12" is less than the string "2012-01".

So all that I need to do it dump the weird conditional that I had in my while block for an ISO 8601 month comparision (the first 7 characters of a date):
    var CalendarMonthBody = Backbone.View.extend({
      // ...
      render: function() {
        // ....
        var date = firstSunday;
        while (Helpers.to_iso8601(date).substr(0,7) <= this.date) {
          // ...
        }
      }
    });
With that, I have December again:
I call it a night there. Back to proof reading Recipes with Backbone.


Day #181

No comments:

Post a Comment