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

Sunday, October 30, 2011

Let the Custom Events Fly in Backbone.js

‹prev | My Chain | next›

Yesterday, I was able to use custom events in my Backbone.js calendar application to further decouple my design. Today, I hope to use them to add an actual feature. Specifically, I would like to add a filter view that can be used to highlight matching appointments on the calendar.

If I filter on the word "beta", for instance, then the last appointment in October should be highlighted:


I start with a simple filter view that contains just a text field for the text snippet being used to filter and a "Filter" button:
    var CalendarFilter = Backbone.View.extend({
      template: template(
        '<input type="text" name="filter">' +
        '<input type="button" class="filter" value="Filter">'
      ),
      render: function() {
        $(this.el).html(this.template());
        return this;
      },
      events: {
        'click .filter':  'filter'
      },
      filter: function() {
        var filter = $('input[type=text]', this.el).val();
        $('.appointment').trigger('calendar:filter', filter);
      }
    });
In there, I attach an event listener that fires the filter() method when the "Filter" button (with class="filter") is clicked. The filter() method triggers events on all of the appointments in the calendar.

I kinda want to trigger a calendar:filter:foo event when the user supplies "foo" as the filtering text. Somehow that just feels cleaner. Unfortunately, there is no way to listen for wildcard events (e.g. "calendar:filter:*)—jQuery events simply do not work with those. Anyhow, triggering an "calendar:filter" event with "foo" as the event data is not that much different.

It also feels a little wrong to trigger events on items with an "appointment" class. Explicitly targeting the class assigned by my Appointment view class is coupling the two classes. Unfortunately, I see no way around this. I do not know any way to broadcast events other than actually grabbing every element in the current DOM. That, of course, would introduce another set of problems (too many events, events bubbling over each other, etc).

Anyhow, to use that view, I add it to the list of things to be created by the application view:
    var Application = Backbone.View.extend({
      initialize: function(options) {
        // ...
        this.initialize_filter();
      },
      // ...
      initialize_filter: function() {
        $(this.el).after('<div id="calendar-filter">');

        var filter = new CalendarFilter({
          el: $('#calendar-filter')
        });
        filter.render();
      },
      // ...
    });
In there, I do what's becoming a mini pattern for me: I create the holder <div> tag, create the CalendarFilter object and render() it. With that, I have my filtering UI:


To actually get the appointments to filter themselves, I need the Appointment class to respond to the "calendar:filter" event, which I tie to a filter() method:
    var Appointment = Backbone.View.extend({
      // ...
      events: {
        'click': 'handleClick',
        'calendar:filter': 'filter'
      },
      // ...
      filter: function(evt, str) {
        var regexp = new RegExp(str, "i");
        if (this.model.get("title").toString().match(regexp)) {
          $(this.el).addClass("highlight");
        }
        else {
          $(this.el).removeClass("highlight");
        }
      },
      // ...
    });
In that filter() method, I create a regular expression that will perform a case-insensitive match on the filter term. If the current appointment matches the regular expression, then I add the "highlight" class to it. Otherwise, I make sure that the term is not highlighted.

With that, I have my highlighting working:


Cool beans. Custom events are certainly a useful tool. Somebody should write recipe about them. Preferably by tomorrow.


Day #190

Saturday, October 29, 2011

Simple Custom Events in Backbone.js

‹prev | My Chain | next›

After refactoring the views in my appointment calendar Backbone.js application, I am ready to move onto to other topics. Except that I forgot a TODO that I left myself in the month View:
    var CalendarMonth = Backbone.View.extend({
      tagName: 'table',
      initialize: function(options) {
        this.date = options.date;
      },
      render: function() {
        // TODO:
        // $('span.year-and-month', 'h1').html(' (' + this.date + ')');

        // ..
      }
    });
Ah. My old template-based view was doing so much that I had not felt bad about adding a side-effect in there as well. This particular side-effect adds the date to the page's <h1> tag.

I want a Backbone view that is dedicated to updating the page's title—a title view. If the page title has a year-and-month span in it:
<h1>
  Funky Calendar
  <span class="year-and-month"></span>
</h1>
Then I want my title view to populate said <span> tag whenever the month changes.

The appointment collection only holds appointments from the current month. It also exposes a getDate() getter for the current month. I should be able to use this to update the title:
    var TitleView = Backbone.View.extend({
      tagName: 'span',
      initialize: function(options) {
        $('span.year-and-month', 'h1').
          replaceWith(this.el);
      },
      render: function() {
        $(this.el).html(' (' + this.collection.getDate() + ') ');
      }
    });
My main Application view is injected with the month appointments collection:
appointments = new Collections.Appointments({date: year_and_month}),
application = new Views.Application({
  collection: appointments,
  el: root_el
});
It can then inject the same collection into the title view when it creates it:
    var Application = Backbone.View.extend({
      initialize: function(options) {
        // ...
        this.initialize_title();
      },
      // ...
      initialize_title: function() {
        new TitleView({collection: this.collection});
      },
      // ...
    });
But how to get that to change when the user navigates between months? When it was a side-effect of the calendar render(), that was easy. But now?

The answer is to use events—this is Backbone after all. In the appointment collection, I trigger a custom event, calendar:change:date, after successfully fetching a month's appointments:
    var Appointments = Backbone.Collection.extend({
      model: Models.Appointment,
      // ...
      fetch: function(options) {
        options || (options = {});

        var data = (options.data || {});
        options.data = {date: this.date};

        var collection = this;
        var success = options.success;
        options.success = function (resp, status, xhr) {
          collection.trigger('calendar:change:date');
          if (success) success(collection, resp);
        };

        return Backbone.Collection.prototype.fetch.call(this, options);
      },
      // ...
    });
By triggering the event in the success callback of my fetch() class, I ensure that the date in the calendar's title will only change if the next month is successfully obtained.

The dance of saving existing success callbacks might be unnecessary future-proofing on my part. I have gotten in the habit of doing that, especially in my models where is makes more sense. I am not currently calling this collection's fetch() with any options, so ensuring that a success callback is retained is definitely overkill. If I ever did want to pass in a success callback, I would likely check to ensure that fetch() was not overridden and, if it was, that it supported the success callback. Still, this feels like a good habit to have.

The event itself, calendar:change:date is chosen to avoid conflicting with "real" events. If the model's date is updated in the application, the model automatically emits a change:date event. To prevent confusion with that "real" event, I opt for a clearer namespaced event. There is no need to update the title of the page if the user updates an appointment!

With my collection successfully generating the event, I can now bind to that event in my view:
    var TitleView = Backbone.View.extend({
      tagName: 'span',
      initialize: function(options) {
        options.collection.bind('calendar:change:date', this.render, this);

        $('span.year-and-month', 'h1').
          replaceWith(this.el);
      },
      render: function() {
        $(this.el).html(' (' + this.collection.getDate() + ') ');
      }
    });
With that, I have dates back in the title of funky calendar:
For good measure, I also tie my calendar navigation view to listen for this event (so that next and previous links can use the proper months).

I really felt bad about updating the title as a side-effect of rendering the new calendar month. This is a bit more code, but it feels much cleaner—mainly because it only has a single responsibility. In addition to clearer intent (what could be clearer than "TitleView"?), I am secure knowing that, should I need to make this more sophisticated in the future, I know where changes need to go.


Day #189

Thursday, October 27, 2011

Temporarily Overriding Template Settings in Underscore.js & Backbone.js

‹prev | My Chain | next›

I had planned on moving onto to model relations today, but... While trying to finish off the underscore.js chapter for Recipes with Backbone, I came across a bit of a conundrum.

I really hate ERB syntax, which is the default in underscore templates:
<div class="previous">
  <a href="#month/<%= previous_date %>">previous</a>
</div>
<div class="next">
  <a href="#month/<%= next_date %>">next</a>
</div>
Embedding pseudo-HTML tags inside real tags hurts my eyes.

I much prefer the mustache style syntax:
<div class="previous">
  <a href="#month/{{ previous_date }}">previous</a>
</div>
<div class="next">
  <a href="#month/{{ next_date }}">next</a>
</div>
It is much easier to pick out the variables to be interpolated because mustache uses different delimiters than plain old HTML.

Happily, underscore makes it easy to change the template interpolation scheme. I only need to change _.templateSettings:
      _.templateSettings = {
        interpolate : /\{\{([\s\S]+?)\}\}/g
      };
The trouble is, not everyone agrees with me. There are deluded people out there who might actually prefer ERB. I think the only explanation can possibly be that they simply don't know any better, but who knows? Maybe there really is a legit reason.

Actually, no. There isn't.

Anyhow, no matter what my personal feelings are, I must play nice with others. If I am writing a library for reuse or want to use libraries that might employ ERB under the covers, I cannot make changes to _.templateSettings without fear of breaking other code.

Happily, that is not a problem. Inside my Views namespace closure, I define a template() helper function. This helper squirrels away the global value of _.templateSettings, applies sane settings, and then restores the previous settings:
    function template(str) {
      var orig_settings = _.templateSettings;
      _.templateSettings = {
        interpolate : /\{\{([\s\S]+?)\}\}/g
      };

      var t = _.template(str);

      _.templateSettings = orig_settings;

      return t;
    }
Now, when I am building my template function in my views, I can use this helper method so that I get my nice, mustache-y syntax:
    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() {
        var date = this.collection.getDate();
        $(this.el).html(this.template({
          previous_date: Helpers.previousMonth(date),
          next_date: Helpers.nextMonth(date)
        }));

        return this;
      }
    });
With that, I have my lovely mustache style templates generating my navigation HTML:


And, poor lost souls can continue to use ERB:


Hopefully that finishes off my underscore templating exploration. I really am keen to move onto model relations.


Day #188

Fancy Names for Simple Backbone.js Techniques

‹prev | My Chain | next›

One of the dumber things that I have been doing in my Backbone.js application is hard-coding the element that holds the application:
        var CalendarMonth = new (Backbone.View.extend({
          el: $('#calendar'),
          // ...
        });
That does work. It attaches the calendar to my very simple HTML without a hitch:
<h1>
  Funky Calendar
  <span class="year-and-month"></span>
</h1>

<div id="calendar"></div>
The problem is mostly one of maintenance. Re-use is also going to be a problem, but my main problem with the current approach is that my HTML and my Javascript are in separate files. If I want to change the id attribute, I need do it in two files. Two files need changing and I have to dig through the Javascript to find where that change would need to be made (currently line 94).

The solution, of course, is to pass the element to the main application constructor:
    window.calendar = new Cal($('#calendar'));
To support this, my constructor now needs to accept a root_el parameter, which it passes along to the top-level Application view:
    var Cal = function(root_el) {
      var Models = (function() { /* ... */ })();

      var Collections = (function() { /* ... */ })();

      var Views = (function() { /* ... */ })();

      var Routes = Backbone.Router.extend({ /* ... */ });

      var Helpers = (function() { /* ... */ })();

      // Initialize the app
      var year_and_month = Helpers.to_iso8601(new Date()).substr(0,7),
          appointments = new Collections.Appointments({date: year_and_month}),
          application = new Views.Application({
            collection: appointments,
            el: root_el
          });

       //...
     };
The Application then uses that element to attach everything else.

Passing the root element along like this is also known as injecting the dependency on the root element into the constructor. Or dependency injection for short.

The main benefit of dependency injection is that I no longer need to touch the Javascript if I change the HTML. If I change the ID to calendar-widget in the HTML, all that has to change is the <script> tag (which is also HTML):
<script type="text/javascript">
  window.calendar = Cal($('#calendar-widget'));
</script>
It also makes testing Backbone applications easier if the DOM element is more flexible.

Dependency injection is one of those things that everybody already does, but doesn't realize that it has a fancy name. Still, it is pretty darn useful—especially when coupled with last night's precipitation pattern, but that is a tale best told in Recipes with Backbone!

That's a fine stopping point for tonight. Up tomorrow: I get started with model relations. Should be fun!


Day #187

Wednesday, October 26, 2011

Abusing Singleton Views in Backbone.js

‹prev | My Chain | next›

Like any newbie, I am prone to fixate on a fascinating idea or concept. I have done that several times as I explore Backbone.js and the many recipes in Recipes with Backbone. One such instance is the Singleton View.

I continue to use the singleton view to manage the add/edit jQuery UI dialog forms in my calendar application:

There will only ever be a single form. Each time a user user edits an appointment, it re-uses that same form, populating the fields with different values. But it is always the same form. Hence the value of the singleton view.

Where it is not appropriate is the main calendar view, in this case, the month view:
        var CalendarMonth = new (Backbone.View.extend({
          el: $('#calendar'),
          setDate: function(date) {
            this.date = date;
            this.render();
          },
          render: function() {
            $('span.year-and-month', 'h1').html(' (' + this.date + ')');

            $(this.el).html('<table></table>');

            var table = $('table', this.el);

            var header = new CalendarMonthHeader()
            header.render();
            $(table).append(header.el);

            var body = new CalendarMonthBody({date: this.date});
            body.render();
            $(table).append(body.el);

            return this;
          }
        }));
This is a singleton since I am assigning an instance of an anonymous View class to the CalendarMonth variable:
        var CalendarMonth = new (Backbone.View.extend({ /* ... */ });
The first sign of trouble is the element attribute, which is set to an existing DOM element:
        var CalendarMonth = new (Backbone.View.extend({
          el: $('#calendar'),
          // ...
        });
To be clear, there are some legitimate reasons to do something like that, but it is a bit of a code smell. Recipes with Backbone might view this as a slight violation of the the Precipitation Pattern—that lower order objects should have no knowledge of higher order concepts. Normally, the Precipitation Pattern applies to models, which should have no knowledge of collections, views and routes. But it can also apply to views (e.g. the day view should remain blissfully unaware of the month view).

I can also tell that I have gone sideways in that my render() method is doing more than simply rendering itself. It also causes a side-effect (modifying the page's existing <h1> tag) and creates a new <table> container:
var CalendarMonth = new (Backbone.View.extend({
          // ...
          render: function() {
            $('span.year-and-month', 'h1').html(' (' + this.date + ')');

            $(this.el).html('<table></table>');

            var table = $('table', this.el);

            // Add a  CalendarMonthHeader to the table container
            // Add a CalendarMonthBody to the table container

            return this;
          }
        }));
All that this view should do is, starting with an unattached <table> tag, build up a table that a higher order object can insert a month view object appropriately.

If I remove all of that extra stuff, I am left with a much more focused view:
        var CalendarMonth = Backbone.View.extend({
          tagName: 'table',
          initialize: function(options) {
            this.date = options.date;
          },
          render: function() {
            var header = new CalendarMonthHeader()
            header.render();
            $(this.el).append(header.el);

            var body = new CalendarMonthBody({date: this.date});
            body.render();
            $(this.el).append(body.el);

            return this;
          }
        });
This has the side benefit of following last night's use of the precipitation pattern better as well (doing naught but appending the sub-view to the current view).

Since the calendar month view is no longer responsible for inserting itself in the DOM, something else has to do it. Following along with the precipitation pattern, I do this in my "Application" view, which is already responsible for establishing other views:
        var Application = Backbone.View.extend({
          // ...
          setDate: function(date) {
            this.collection.setDate(date);
            this.render();
          },
          render: function() {
            var date = this.collection.getDate();

            var month = new CalendarMonth({date: date});

            $(this.el).html(month.render().el);
          },
          // Other views here...
        });
And now, my highest order object in the Backbone stack, the router, needs to manipulate that application view:
      var Routes = Backbone.Router.extend({
        initialize: function(options) {
          this.application = options.application;
        },

        routes: {
          // ...
          "month/:date": "setMonth"
        },

        setMonth: function(date) {
          console.log("[setMonth] %s", date);
          this.application.setDate(date);
        }
      });
That feels better—a more cohesize top-down approach. I will call it a night here and pick back up with this refactoring tomorrow.


Day #186

Tuesday, October 25, 2011

Precipitating Backbone.js Views

‹prev | My Chain | next›

I really need to cut down on my posting to focus on my Recipes with Backbone writing. So far, my co-author Nick Gauthier is doing most of the writing at that's not cool. Still, I must appease the gods of my chain...

I undertook a significant refactoring of some hideous Backbone.js code last night. For the most part, I liked my smaller, focused Backbone views. It certainly beats a huge template full of evaluate-able code.

One thing that bothers me is that my sub-views need to know about the parent view. Per the Precipitation Pattern in Recipes with Backbone, it is OK for higher order objects to reference lower order objects, but not the other way around.

Consider, for example, my calendar:

The lowest level thing in the calendar is the day view. But my day view knows about week view that called it:

        var CalendarMonthDay = Backbone.View.extend({
          tagName: 'td',
          initialize: function(options) {
            this.parent = options.parent;
            this.date = options.date;
          },
          render: function() {
            this.parent.append(this.el);

            this.el.id = Helpers.to_iso8601(this.date);
            var html = '<span class="day-of-month">' + this.date.getDate() + '</span>';
            $(this.el).html(html);

            return this;
          }
        });
Really, my first clue should have been the reference to parent. You should never see a reference to a parent object in Backbone code.

The problem is that both sides of the relationship need to know about each other, compounding the likelihood that something is going to go wrong in one place or the other. If nothing else, it makes for more code than is necessary.

The month/week view in my app needs to know how to render itself into the parent view in addition to knowing that the day sub-views needs a reference to the week view:
        var CalendarMonthWeek = Backbone.View.extend({
           // ...           
           render: function() {
            for (var i=0; i<7; i++) {
              var day = new CalendarMonthDay({
                parent: $(this.el),
                date: date
              });
              day.render();
              $(this.el).append(day.el);

              date = Helpers.dayAfter(date);
            }
          });
That's just silly.

If I refactor my lowest order object, the day-view, so that it is completely unaware of the higher order week view, then it looks like:
        var CalendarMonthDay = Backbone.View.extend({
          tagName: 'td',
          initialize: function(options) {
            this.date = options.date;
          },
          render: function() {
            this.el.id = Helpers.to_iso8601(this.date);
            var html = '<span class="day-of-month">' + this.date.getDate() + '</span>';
            $(this.el).html(html);

            return this;
          }
        });
That is 3 fewer lines of code and one less responsibility (inserting into the parent object's HTML node).

In the week view, I no longer pass the week view's DOM el to the day view and I no longer have to insert the week view's DOM el into the month view's parent el. All I have to do is insert the day view's el (a table td cell) into the week view's tr table row:
        var CalendarMonthWeek = Backbone.View.extend({
          tagName: 'tr',
          initialize: function(options) {
            this.date = options.date;
          },
          render: function() {
            var date = this.date;
            for (var i=0; i<7; i++) {
              var day = new CalendarMonthDay({date: date});
              day.render();
              $(this.el).append(day.el);

              date = Helpers.dayAfter(date);
            }
          }
        });
Single responsibility with an assist from the Precipitation Pattern nets me 4 fewer lines of code. Nice.

I work through the remaining views (in all, there are CalendarMonth, CalendarMonthHeader, CalendarMonthBody, CalendarMonthWeek, and CalendarMonthDay). The only one that gives me any trouble is the CalendarMonthBody because it has no DOM el. Its purpose is to reside a the same abstraction layer as the CalendarMonthHeader and to proxy the week rows back to the CalendarMonth.

The solution there turns out to create a fake el that holds an array of week DOM elements:
        var CalendarMonthBody = Backbone.View.extend({
          initialize: function(options) {
            this.date = options.date;
            this.el = [];
          },
          render: function() {
            // ...
            while (date == firstSunday || date.getMonth() <= month) {
              var week = new CalendarMonthWeek({date: date});
              week.render();
              this.el.push(week.el);

              date = Helpers.weekAfter(date);
            }
          }
        });
I can get away with this because, at each higher order order layer, I am using jQuery's append() to insert the lower layer's DOM element. Conveniently, append accepts an array of elements:
        var CalendarMonth = new (Backbone.View.extend({
          el: $('#calendar'),
          setDate: function(date) {
            this.date = date;
            this.render();
          },
          render: function() {
            $('span.year-and-month', 'h1').html(' (' + this.date + ')');

            $(this.el).html('<table></table>');

            var table = $('table', this.el);

            var header = new CalendarMonthHeader()
            header.render();
            $(table).append(header.el);

            var body = new CalendarMonthBody({date: this.date});
            body.render();
            $(table).append(body.el);

            return this;
          }
        }));
The month-header and month-body insertions look exactly the same: $(table).append(lower_layer_view.el) even though one is a single element and the other is an array of week rows. Some might think of that as a cheat. I choose to think of it as a pattern.

Anyhow, my quick refactoring has netted me a total of 12 new lines of code and 29 deleted lines of code, for a net loss of 17 lines of code that also serves to decrease responsibility through an entire vertical slice of my Backbone app. That is a big win.


Day #185

Monday, October 24, 2011

Refactoring Large Backbone.js Views into Sub-Views

‹prev | My Chain | next›

Tonight, I continue to refactor the calendar view in my Backbone.js application:


The appointments on the calendar are small Backbone views. The calendar itself is a recent convert to a Backbone view, the template of which is:
<script type="text/template" id="calendar-template">
<table>
  <thead>
    <tr>
      <th>S</th>
      <th>M</th>
      <th>T</th>
      <th>W</th>
      <th>T</th>
      <th>F</th>
      <th>S</th>
    </tr>
  </thead>
  <tbody>
  {[ _([0,1,2,3,4,5]).each(function(i) { ]}
  <tr class="week{{ i }}">
    <td class="sunday"></td>
    <td class="monday"></td>
    <td class="tuesday"></td>
    <td class="wednesday"></td>
    <td class="thursday"></td>
    <td class="friday"></td>
    <td class="saturday"></td>
  </tr>
  {[ }); ]}
  </tbody>
</table>
</script>
After the Calendar view renders, I have a fixer-upper method work with that empty calendar to add date IDs to the <td> cells and to draw the day of the month number inside the cells:
        var Calendar = new (Backbone.View.extend({
          el: $('#calendar'),
          template: _.template($('#calendar-template').html()),
          setDate: function(date) {
            this.date = date;
            this.render();
          },
          render: function() {
            $('span.year-and-month', 'h1').html(' (' + this.date + ')');

            $(this.el).html(this.template({
              date: this.date
            }));

            this.addDates();
            this.hideEmptyWeeks();

            return this;
          },
          addDates: function() {
            var firstOfTheMonth = Helpers.firstOfTheMonth(this.date),
                month = firstOfTheMonth.getMonth(),
                firstSunday = new Date(firstOfTheMonth.getTime() -
                                firstOfTheMonth.getDay()*24*60*60*1000);

            var date = firstSunday;
            _([0,1,2,3,4,5]).each(function(week) {
              var week_el = $('.week' + week, this.el),
                  day_elements = week_el.find('td');

              _([0,1,2,3,4,5,6]).each(function(day) {
                var day_element = day_elements[day],
                    other_month = (date.getMonth() != month),
                    html = '<span class="day-of-month">' + date.getDate() + '</span>';

                $(day_element).
                  html(html).
                  attr('id', Helpers.to_iso8601(date)).
                  addClass(other_month ? 'other-month' : '');

                date = Helpers.dayAfter(date);
              });
            });
          },
          hideEmptyWeeks: function() {
            // remove whole week from next month
            var week5_other_month = _($('td', '.week5')).all(function(el) {
              return $(el).hasClass('other-month')
            });
            if (week5_other_month) $('.week5').hide()
          }
        }));
Yikes! One thing I definitely notice (aside from tons to code) it that I repeat the iteration of the weeks both in the tempate and in the fixer-upper method. What this tells me is that I either need to move all of the calculation into the template or into Backbone views. Somehow doing this in templates feels wrong, so I give Backbone views a try.

First, I eliminate the template and all of that fixer-upper code. My calendar's render() then becomes nothing more than a call to two other views: a calendar header view (the days of the week at the top of the calendar) and the calendar body:
        var Calendar = new (Backbone.View.extend({
          el: $('#calendar'),
          setDate: function(date) {
            this.date = date;
            this.render();
          },
          render: function() {
            $('span.year-and-month', 'h1').html(' (' + this.date + ')');

            $(this.el).html('<table></table>');

            var table = $('table', this.el);

            var header = new CalendarMonthHeader({parent: table})
            header.render();

            var body = new CalendarMonthBody({
              parent: table,
              date: this.date
            });
            body.render();

            return this;
          }
        }));
That is considerably less code than my template + fixer-upper. But how much work is needed in the various sub-views?

The calendar header is blissfully simple:
        var CalendarMonthHeader = Backbone.View.extend({
          tagName: 'tr',
          initialize: function(options) {
            this.parent = options.parent;
          },
          render: function() {
            $(this.el).html('<th>S</th><th>M</th><th>T</th><th>W</th><th>T</th><th>F</th><th>S</th>');
            this.parent.append(this.el);
          }
        });
Simple, but then again, there are no calculations required. What about the rest of the calendar?

The body view class delegates all rendering to sub-views. Specifically, it iterates over weeks, creating new calendar month week views:
        var CalendarMonthBody = Backbone.View.extend({
          initialize: function(options) {
            this.parent = options.parent;
            this.date = options.date;
          },
          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({
                parent: this.parent,
                date: date
              });
              week.render();

              date = Helpers.weekAfter(date);
            }
          }
        });
Most of the effort in that code is date calculations, which are a pain in Javascript.

The week view does nothing more than create a table row to which day views are attached:
        var CalendarMonthWeek = Backbone.View.extend({
          tagName: 'tr',
          initialize: function(options) {
            this.parent = options.parent;
            this.date = options.date;
          },
          render: function() {
            this.parent.append(this.el);

            var date = this.date;
            for (var i=0; i<7; i++) {
              var day = new CalendarMonthDay({
                parent: $(this.el),
                date: date
              });
              day.render();

              date = Helpers.dayAfter(date);
            }
          }
        });
And finally, the day view build table cells with the ISO 8601 date for the ID (used by the appointment views to attached to the calendar) and a <span> tag containing the day of the month:
        var CalendarMonthDay = Backbone.View.extend({
          tagName: 'td',
          initialize: function(options) {
            this.parent = options.parent;
            this.date = options.date;
          },
          render: function() {
            this.parent.append(this.el);

            this.el.id = Helpers.to_iso8601(this.date);
            var html = '<span class="day-of-month">' + this.date.getDate() + '</span>';
            $(this.el).html(html);

            return this;
          }
        });
Phew! Amazingly, that works:
I am none too certain that this is all that superior a solution to the template. The view code in the template was not too overwhelming. Still, each of these views is small and self-contained. Small and self-contained makes for easier testing and maintainability.

Day #184

Sunday, October 23, 2011

Replacing Legacy Views with Backbone.js

‹prev | My Chain | next›

Yesterday, I got started looking at Underscore.js templating for my Backbone.js calendar application. It was trivial to replace the default ERB style <%= ... %> variable interpolation with mustache style {{ ... }} structures.

I also replaced <% ... %> code evaluation structures with the un-mustache like {[ ... ]}. I hope to use that to replace the legacy calendar "view" with which I have been stuck. The appointments, add/update forms, and month navigation controls all come from Backbone code in my app:

The calendar itself comes from a pre-Backbone static HTML:
<h1>Funky Calendar<span class="year-and-month"></span></h1>
<table id="calendar">
<thead>
  <tr>
    <th>S</th>
    <th>M</th>
    <th>T</th>
    <th>W</th>
    <th>T</th>
    <th>F</th>
    <th>S</th>
  </tr>
</thead>

<tr id="week0">
  <td class="sunday"></td>
  <td class="monday"></td>
  <td class="tuesday"></td>
  <td class="wednesday"></td>
  <td class="thursday"></td>
  <td class="friday"></td>
  <td class="saturday"></td>
</tr>
<!-- .... -->
<tr id="week5">
  <td class="sunday"></td>
  <td class="monday"></td>
  <td class="tuesday"></td>
  <td class="wednesday"></td>
  <td class="thursday"></td>
  <td class="friday"></td>
  <td class="saturday"></td>
</tr>
</table>
I probably need to keep the header row containing the days of the week, but the 6 table rows containing the actual calendar days can get DRYed up. Last night, I converted this to an underscore template (making use of the un-mustache {[ ... ]} construct):
<script type="text/template" id="calendar-template">
<table>
  <thead>
    <tr>
      <th>S</th>
      <th>M</th>
      <th>T</th>
      <th>W</th>
      <th>T</th>
      <th>F</th>
      <th>S</th>
    </tr>
  </thead>
  <tbody>
  {[ _([0,1,2,3,4,5]).each(function(i) { ]}
  <tr class="week{{ i }}">
    <td class="sunday"></td>
    <td class="monday"></td>
    <td class="tuesday"></td>
    <td class="wednesday"></td>
    <td class="thursday"></td>
    <td class="friday"></td>
    <td class="saturday"></td>
  </tr>
  {[ }); ]}
  </tbody>
</table>
</script>
Tonight I would like to create a Backbone view to make use of this template.

So, I replace all of that static HTML with:
<h1>
  Funky Calendar
  <span class="year-and-month"></span>
</h1>

<div id="calendar"></div>
To populate the now empty <div id="calendar"> tag, I start with the Router which now needs to tell the calendar view to render itself for the correct date:
      var Routes = Backbone.Router.extend({
        routes: {
          // ...
          "month/:date": "setMonth"
        },
        // ...
        setMonth: function(date) {
          console.log("[setMonth] %s", date);
          Views.Calendar.setDate(date);
          appointments.setDate(date);
        }
      });
Here I am going to call the Views.Calendar as a singleton view (one of the recipes from Recipes with Backbone). I define that view simply as:
        var Calendar = new (Backbone.View.extend({
          el: $('#calendar'),
          template: _.template($('#calendar-template').html()),
          setDate: function(date) {
            this.date = date;
            this.render();
          },
          render: function() {
            $('span.year-and-month', 'h1').html(this.date);

            $(this.el).html(this.template({
              date: this.date
            }));

            return this;
          }
        }));
The setDate() method is a simple wrapper to set the date instance variable and to tell the view to render itself. I explicitly tell the view to attached itself to the '#calendar' element. At some point I should make that dynamic so that it can configured, but that will do for now. With that, I have my empty calendar:


To get the dates added to the calendar, as well as to assign the ISO 8601 date as the ID for each of the cells in the calendar, I pull in some of the old app code:
        var Calendar = new (Backbone.View.extend({
          // ...
          render: function() {
            // ...
            $(this.el).html(this.template({ date: this.date }));

            this.addDates();
            this.hideEmptyWeeks();

            return this;
          },
          addDates: function() {
            var year = parseInt(this.date.split(/\\D/)[0], 10),
                month = parseInt(this.date.split(/\\D/)[1], 10) - 1,
                firstOfTheMonth = new Date(year, month, 1),
                firstSunday = new Date(firstOfTheMonth.getTime() -
                                firstOfTheMonth.getDay()*24*60*60*1000);

            var date = firstSunday;
            _([0,1,2,3,4,5]).each(function(week) {
              var week_el = $('.week' + week, this.el),
                  day_elements = week_el.find('td');

              _([0,1,2,3,4,5,6]).each(function(day) {
                var day_element = day_elements[day],
                    other_month = (date.getMonth() != month),
                    html = '<span class="day-of-month">' + date.getDate() + '</span>';

                $(day_element).
                  html(html).
                  attr('id', Helpers.to_iso8601(date)).
                  addClass(other_month ? 'other-month' : '');

                date = new Date(date.getTime() + 24*60*60*1000);
              });
            });
          },
          hideEmptyWeeks: function() {
            // remove whole week from next month
            var week5_other_month = _($('td', '.week5')).all(function(el) {
              return $(el).hasClass('other-month')
            });
            if (week5_other_month) { $('.week5').hide() }
          }
        }));
Now, that is some ugly, non Backbone-y code, but it works:
I am torn between trying to do all of that inside a template or leave it more or less as is. That seems like it could get ugly real quick in a template. After all, it is already pretty ugly.

I will sleep on that and pick back up here tomorrow.


Day #183

Saturday, October 22, 2011

Underscore.js Templates in Backbone.js

‹prev | My Chain | next›

Up tonight, I would like to take some time to explore using Underscore.js templates in my Backbone.js application. At this point, I have a fairly involved calendar application mostly based on Backbone:


Most of the generated HTML in that application is not from underscore templates. The month view is dynamic, but I mostly built that prior to switching to Backbone. Really, the only part that is done entirely in the usual Backbone way is the individual appointment views. For those, I have a simple underscore template:
<script type="text/template" id="calendar-appointment-template">
  <span class="appointment" title="<%= description %>">
      <%= title %>
      <span class="delete">X</span>
  </span>
</script>
I really dislike the ERB style of embedding executable or interpolated bits of code inside angle brackets. It is hard for my brain to process and can even fool syntax highlighting in Emacs on occasion. So first up, I am going to follow along with the underscore documentation suggestion of switching to a saner mustache style that surrounds variables to be interpolated with double curly braces:
<script type="text/template" id="calendar-appointment-template">
  <span class="appointment" title="{{ description }}">
      {{title}}
      <span class="delete">X</span>
  </span>
</script>
Ahhhh... much better. Of course to actually make that work, I need to teach underscore to use mustache rather than silly old ERB. The default in underscore comes from:
  // By default, Underscore uses ERB-style template delimiters, change the
  // following template settings to use alternative delimiters.
  _.templateSettings = {
    evaluate    : /<%([\s\S]+?)%>/g,
    interpolate : /<%=([\s\S]+?)%>/g
  };
To override, I add the following to my page:
<script>
$(function() {
  _.templateSettings = {
    interpolate : /\{\{([\s\S]+?)\}\}/g
  };
});
</script>
Since I want this to apply everywhere, that does not have to be inside my backbone code. There are some benefits to global variables.

After replacing a couple of other small ERB templates, I would like to tackle something a bit more dynamic—namely the month calendar view:
<script type="text/template" id="calendar-template">
<h1>
  {{ title }}
  <span class="year-and-month">{{ date }}</span>
</h1>
<table>
  <thead>
    <tr>
      <th>S</th>
      <th>M</th>
      <th>T</th>
      <th>W</th>
      <th>T</th>
      <th>F</th>
      <th>S</th>
    </tr>
  </thead>
  <tbody>
  {[ _([0,1,2,3,4,5]).each(function(i) { ]}
  <tr class="week{{ i }}">
    <td class="sunday"></td>
    <td class="monday"></td>
    <td class="tuesday"></td>
    <td class="wednesday"></td>
    <td class="thursday"></td>
    <td class="friday"></td>
    <td class="saturday"></td>
  </tr>
  {[ }); ]}
  </tbody>
</table>
</script>
That is going to require looping over 6 weeks to build up the month calendar view. For that, I add an "evaluate" mapping in underscore's template settings:
    _.templateSettings = {
      evaluate : /\{\[([\s\S]+?)\]\}/g,
      interpolate : /\{\{([\s\S]+?)\}\}/g
    };
I am not wed to the {[ ... ]} format for evaluatable mustache-y code, but it does work.

With a decent understanding of how to implement dynamic underscore views, I call it a night here. I will pick back up tomorrow converting my non-Backbone calendar view into a legitimate Backbone view using this template.


Day #182

Friday, October 21, 2011

Tidy Memoized View Signatures in Backbone.js

‹prev | My Chain | next›

Up tonight, I hope to tidy up the Memoized View Signature pattern for Backbone.js that I worked through last night. The idea behind View Signatures is to only draw or build views when the underlying data has changed. More specifically, the views should only update the DOM if attributes being displayed are updated—no need to update a title-only view if the updated timestamp has changed.

I am still using my Backbone calendar to explore this concept:

Working down from the top, when a new month is displayed, a "clear" event is triggered on all calendar appointments:
      var Routes = Backbone.Router.extend({
        routes: {
          // ...
          "month/:date": "setMonth"
        },
        // ...
        setMonth: function(date) {
          console.log("[setMonth] %s", date);

          $('.appointment').trigger('clear');
          draw_calendar(date);
          appointments.setDate(date);
        }
      });
Down in the view, I listen for the "clear" event, removing the element when received:

        var Appointment = Backbone.View.extend({
          // ...
          events: {
            // ...
            'clear span': 'remove'
          },
          // ...
          remove: function() {
            $(this.el).empty();
          }
        });
As Nick Gauthier (my Recipes with Backbone co-author) pointed out in comments on last night's post, empty() is probably not the right choice here. When switching months, I do not want to simply empty last month's elements, I want to remove them entirely.

The most obvious solution would be to call remove() on the element. In fact, this is what Backbone does by default:
    // Remove this view from the DOM. Note that the view isn't present in the
    // DOM by default, so calling this method may be a no-op.
    remove : function() {
      $(this.el).remove();
      return this;
    },
The problem with this solution is that, even though the element is removed from the DOM, its is not gone. The view's el attribute still references the DOM object. To illustrate this, I drop into Chrome's Javascript console. In there, I assign one of my appointments to a variable. I then manually remove() all appointments. The DOM element is still in memory by virtue of that local variable (even though it is not longer attached to the page's DOM):

Now, in this case, this is not a huge deal—the appointment itself is not consuming tons of resources. But what if it was? To prevent those no longer needed resources from lingering, I do want to empty the element of content (jQuery also removes associated events) and remove it from the page's DOM:
          remove: function() {
            $(this.el).remove().html('');
            return this;
          }
Oh, and because I was looking through the Backbone code at the remove() method, I was reminded that my remove() method also needs to return this to properly support chaining.

That covers removing the elements. To add the appointment back when the user goes back a month, I respond to a custom "cached" event (from my collection cache work earlier this week):
        var Appointment = Backbone.View.extend({
          initialize: function(options) {
            _.bindAll(this, 'html');
            // bind to model events here
            options.model.bind('cached', this.render, this);
            options.model.bind('change', this.update, this);
          },
          // ....
        });
The bindAll is necessary for the memoize to work (as we'll see in a second).

The render() method calls the update method (in addition to some other DOM manipulation unimportant here):
          render: function() {
            this.update();
            // ...
          }
So, all of the important work for the memoized View Signature is done in that update() method:

          update: function() {
            var signature = $.param({
              title: this.model.get('title')
            });

            $(this.el).html(this.memoized_html(signature));

            return this;
          }
First, I calculate a view "signature" that will only change when the attributes that I care about in the model change. In this case, I only want the signature to change when the title has been updated. I pass that signature to the memoized_html() method. That method should generate new HTML if the signature is different from one that has already been drawn (or if it has never been drawn). Otherwise, it should re-use the previously used HTML.

To achieve that, as the name suggests, I make use of the underscore.js memoize() function:

          html: function() {
            return this.template(this.model.toJSON());
          },
          memoized_html: function() {
            this._memoized_html || (this._memoized_html = _.memoize(this.html));

            return this._memoized_html(_.last(arguments));
          },
          // ...
        });
The vanilla html() method does a normal template build. The memoize_html method remembers the result of that build, storing it by the argument passed—the signature. Thus, if the signature changes (by virtue of a model's title changing), memoize won't find the result internally and will re-invoke html().

As mentioned earlier, the bindAll for the html is necessary because of the memoization done here. Without it, memoized calls of this.html would lose the context of the current object. Which would be bad.

The complete implementation of the View Signature bit is then:
        var Appointment = Backbone.View.extend({
          initialize: function(options) {
            _.bindAll(this, 'html');
            // bind to model events here
            options.model.bind('cached', this.render, this);
            options.model.bind('change', this.update, this);
          },
          events: {
            // ...
            'clear span': 'remove'
          },
          render: function() {
            this.update();
            // ...
          },
          update: function() {
            var signature = $.param({
              title: this.model.get('title')
            });

            $(this.el).html(this.memoized_html(signature));

            return this;
          },
          html: function() {
            return this.template(this.model.toJSON());
          },
          memoized_html: function() {
            this._memoized_html || (this._memoized_html = _.memoize(this.html));

            return this._memoized_html(_.last(arguments));
          },
          remove: function() {
            $(this.el).remove().empty();
            return this;
          }
        });
I am satisfied with that implementation. At least satisfied enough to start adding it to Recipes with Backbone. It still may change so you should still buy it!

Up tomorrow, I am going to take a quite tour of Underscore.js and then, who knows? There is always fun to be had with Backbone.js.

Day #181

Thursday, October 20, 2011

Memoize Backbone.js View Signatures

‹prev | My Chain | next›

Last night I implemented a version of the View Signature pattern from the forthcoming Recipes with Backbone. The basic idea is to limit the amount of individual view redrawing as much as possible by checking a signature that only changes when attributes of concern in the underlying model change.

For my Backbone.js calendar, this might mean that I only redraw the appointment when the title changes, but not when the description changes (because it does not show in month view):

One of the many nice aspects of the View Signature recipe is that no special handling is required for the DOM element. Even when switching months and detaching the appointment from the displayed DOM, the Appointment view still retains a reference to the DOM that displays the appointment. Thus, when a "change" event is seen by a view with no signature change (e.g. the title did not change), the update() method can do nothing:
      var Views = (function() {
        var Appointment = Backbone.View.extend({
          initialize: function(options) {
            // ...
            options.model.bind('change', this.update, this);
          },
          // ...
          update: function() {
            var signature = $.param({
              title: this.model.get('title')
            });

            if (this.signature === signature) return this;

            console.log("[Appointment#update] sig change: " + this.signature + " -> " + signature);

            this.signature = signature;

            $(this.el).html(this.template(this.model.toJSON()));

            return this;
          }
        });
But what if the view's DOM is completely wiped away (perhaps in an attempt to keep the memory usage of the application down). If the "hide" event triggers an empty() call:
      var Views = (function() {
        var Appointment = Backbone.View.extend({
          // ...
          events: {
            'hide span': 'remove'
          },
          remove: function() {
            console.log('[remove]');
            $(this.el).empty();
          }
        });
...then my moving between months suddenly results in an empty calendar:

The solution to that is to regenerate the HTML each time, but that breaks the view signature. To get around this, I break out the underscore.js memoize() function:
      var Views = (function() {
        var Appointment = Backbone.View.extend({
          initialize: function(options) {
             _.bindAll(this, 'html');
            // ...
            options.model.bind('change', this.update, this);
          },
          // ...
          update: function() {
            var signature = $.param({
              title: this.model.get('title')
            });

            $(this.el).html(this.memoized_html(signature));

            return this;
          },
          html: function() {
            console.log('new HTML!!!');
            return this.template(this.model.toJSON());
          },
          memoized_html: function() {
            this._memoized_html || (this._memoized_html = _.memoize(this.html));

            return this._memoized_html(_.last(arguments));
          },
          // ...
        });
I continue to use the same signature (which is just this appointment title). But instead of checking the signature and then conditionally rendering or doing nothing, I now always replace the view's HTML with the result of memoized_html.

The memoized_html method does just that, it memoizes the this.html() function. If the argument to memoized_html is the same as has already been called, then memoized_html will simply return the value from the last time without the overhead of having to build the HTML from a template inside the html() method. If the argument, and here I am cleverly using the signature as the only argument, has not been seen before (e.g. it has not been called before or if the signature has changed), then the html() method is called and memoized all over again.

And it works. In the Javascript console, when the calendar is first loaded, I see the debug statement in the html() method:


After navigating through the month to force a collection cache refresh, I no longer see the html() debugging statements because my method has been memoized!

That is not a bad overall approach to View Signatures regardless of the DOM manipulation. Memoizing the view signature eliminates quite a bit of conditional code. I like.



Day #180

Wednesday, October 19, 2011

Limiting Backbone.js Redraws with View Signatures

‹prev | My Chain | next›

Up tonight, a bit of research for Recipes with Backbone. As Nick Gauthier continues to work through new recipes, I am working through some of our existing chapters to beef them up. Tonight, I am going to get started on the View Signature recipe.

The idea behind the view signature is to retain information from previous renders to decide if a model change requires an re-render. In my calendar app:


...the easiest way to trigger a change event is to navigate to a later month, change an appointment in a separate window, then navigate back to the same month. The collection caching solution that I have implemented will display the original view immediately, then fetch any changes from the server, forcing a re-render.

Currently, updates are implemented by binding the View's update() method to the model's change event:
        var Appointment = Backbone.View.extend({
          initialize: function(options) {
            // ...
            options.model.bind('change', this.update, this);
          },
          // ...
          update: function() {
            console.log("[Appointment#update]");
            $(this.el).html(this.template(this.model.toJSON()));
            return this;
          },
          // ...
        });
But, this will cause re-renders even if minor details change. Really, the only reason that I would want to update the Appointment view is if the appointment's title changes.

For that, I build up a view signature that is tied only to the title. If this title-bound signature changes after a change event, only then will I redraw the appointment:
        var Appointment = Backbone.View.extend({
          // ...
          update: function() {
            console.log("[Appointment#update]");

            var signature = $.param({
              title: this.model.get('title')
            });

            if (this.signature === signature) return this;

            console.log("[Appointment#update] sig change: " + this.signature + " -> " + signature);

            this.signature = signature;

            $(this.el).html(this.template(this.model.toJSON()));

            return this;
          },
          // ...
        });
Now, if I update the title of an appointment in a separate window, force a collection cache refresh in my current window, I see the signature has changed in Chrome's Javascript console:


More importantly, the appointment in the calendar is updated.

But, if I repeat the exercise, only updating the description of the appointment (and not the title):


Then a collection cache refresh omits the redraw since the view's signature has not changed:


With a view signature implementation in place, I call it a night here. Up tomorrow: taking this up a notch.

Day #179

Tuesday, October 18, 2011

A Default Route in Backbone.js

‹prev | My Chain | next›

Tonight, I take a break from my Backbone.js collection caching exercise to fix a routing bug. Ever since I set up routing in my app, I have been getting double renders when starting on a page that matched a route (e.g. /#month/2011-10).

One of the renders arises from the Collection fetch in the app initialization:

      // 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});

      appointments.fetch():
The other came from the call to setDate() (which also calls fetch) in the matching route:
      var Routes = Backbone.Router.extend({
        routes: {
          "month/:date": "setMonth"
        },

        setMonth: function(date) {
          console.log("[setMonth] %s", date);
          draw_calendar(date);
          appointments.setDate(date);
        }
      });
Tonight, I hope to figure out how to set up a default Backbone route to avoid the initialization fetch. Maybe this is as simple as setting an empty string route?
      var Routes = Backbone.Router.extend({
        routes: {
          "": "setDefault",
          "month/:date": "setMonth"
        },

        setDefault: function() {
          console.log("[setDefault]")
        },

        setMonth: function(date) {
          console.log("[setMonth] %s", date);
          draw_calendar(date);
          appointments.setDate(date);
        }
      });
Looking for the console.log() output in Chrome's Javascript, I see that I have, indeed, matched my default route:
So I set the window.location to the current month: 2011-10 (which is the first 7 characters of the ISO 8601 date):

      var Routes = Backbone.Router.extend({
        routes: {
          "": "setDefault",
          "month/:date": "setMonth"
        },

        setDefault: function() {
          console.log("[setDefault]");
          var month = Helpers.to_iso8601(new Date).substr(0,7);
          window.location = '/#month/' + month;
        },

        setMonth: function(date) {
          console.log("[setMonth] %s", date);
          draw_calendar(date);
          appointments.setDate(date);
        }
      });
And that works! When I access the root URL (http://localhost:3000) without any path info, this default route kicks in and directs me to October:
That was easy. I really should have at least tried that the night I first ran into trouble. Ah well.

Day #178

Monday, October 17, 2011

Backbone.js Collection Cache Invalidation

‹prev | My Chain | next›

I have been experimenting with caching Backbone.js collection caching for the past few days. The general idea being the caching collection delegates actual fetching to sub-collections. In my calendar application, the month sub-collection fetches data into the caching collection:
Last night, I got the calendar to render any cached data immediately, then re-fetch the data in the background, re-rendering if needed.

I am somewhat concerned that this exercise can quickly devolve into an exploration of caching rather than Backbone. Given that, I am unsure if I want to build a complete caching solution. But before I give up, I have to explore adding and removing from caching. That is, if the fetch-after-rendering-cached data turns up a new record, that new record should be displayed. Similarly, records deleted should be removed from the display.

For that, I add two more methods to the caching collection's refreshCache() method. One method will create newly found models in the collection. The other will remove now missing models from the collection. The two methods will be named createFound() and removeMissing():
          refreshCache: function(date) {
            console.log("[refreshCache] " + date);
            this.cached[date].fetch({
              success: _.bind(function(collection, resp) {
                this.replace(collection.models);
                this.createFound(collection);
                this.removeMissing(collection);
              }, this)
            });
          },
I leave removeMissing() an empty method for now, so that I can start on createFound(). "Found" records will be those whose IDs are in the updated sub-collection, but not in the current cache. To calculate that, I employ pluck() and difference from Underscore.js (pluck() is also in Backbone):
          createFound: function(updated_subcollection) {
            var found_ids = _.difference(
              updated_subcollection.pluck("id"),
              this.existingIds()
            );
            console.log("[createFound] " + found_ids.join(", "));
            console.log("[createFound] " + updated_subcollection.pluck("id").join(", "));
          },
To test this out, I open an incognito window to create a new appointment:
Then, in my usual window, I navigate back to October. Checking the Javascript console in Chrome, I see that I have found the newly created appointment:
To actually get that added to the collection, I need to iterate through each found ID, pull the model from the sub-collection and add it to the caching collection. Thanks to underscore, the necessary code reads almost like that sentence:
          createFound: function(updated_subcollection) {
            var found_ids = _.difference(
              updated_subcollection.pluck("id"),
              this.existingIds()
            );
            console.log("[createFound] " + found_ids.join(", "));

            _.each(found_ids, function(id) {
              var new_model = updated_subcollection.get(id);
              this.add(new_model);
            }, this);
          },
Now, if I delete the "New #1" appointment, repeat the icognito create, and navigate back to October, I see the "New #2" appointment added to the calendar:
Ah, the "magic" of Backbone. Since appointment views are already bound to the "add" event, I do not need to do anything for the appointment to show after it is added to the collection:
          initialize_appointment_views: function() {
            this.collection.
              bind('add', _.bind(this.render_appointment, this));
            this.collection.
              bind('reset', _.bind(this.render_appointment_list, this));
          }
For completeness, this is my removeMissing() method:
          removeMissing: function(updated_subcollection) {
            if (updated_subcollection.length == 0) return;

            var startDate = updated_subcollection.at(0).get("startDate"),
                month = startDate.substr(0,7),
                ids_in_month = this.
                  select(function(model) {
                    return model.get("startDate").indexOf(month) == 0;
                  }).
                  map(function(model) {
                    return model.id;
                  }),
                missing_ids = _.difference(
                  ids_in_month,
                  updated_subcollection.pluck("id")
                );
            console.log("[removeMissing] " + missing_ids.join(", "));

            this.remove(missing_ids);
          }
This is complicated by the need to first filter only this month's IDs from the cached collection. I still plow through the implementation because, what can I say? I get a kick out of mucking with iterators.

I enjoyed myself immensely playing with underscore. I did not even really need it as much as I used it, but that is just a testament to how fun Underscore is. Anyhow, that may conclude my caching experiment. Up tomorrow, I have a routing bug that can only be solved with the help of a recipe from Recipes with Backbone!


Day #177