Sunday, August 28, 2011

Getting Started with Backbone.js View Templates

‹prev | My Chain | next›

I take a little time today to pretty up the Funky Calendar application:
OK. It is not that pretty, but I do take some time to ensure that the UI elements are consistent throughout the DOM tree:
I do this not because I expect to do anything real with Funky Calendar. Rather, I would like to begin playing with some the Backbone.js view stuff tonight. The hope is that a more consistent DOM will better lend itself to Backbone views.

From yesterday, I have Backbone view code that adds calendar events via a not-too-compact render() method:
    window.AppView = Backbone.View.extend({
      initialize: function() {
        Events.bind('all', this.render, this);
        Events.fetch();
      },
      render: function() {
        Events.each(function(event) {
          var start_date = event.get("startDate"),
              title = event.get("title"),
              description = event.get("description"),
              el = '#' + start_date;

          $('<span class="event" title="' + description + '">' +
              title +
            '</span>').appendTo($(el))
        });
      }
    });

    window.App = new AppView;
One thing that I can do to realize some cleaner code is to separate that view "template" out into a real template:
|<script type="text/template" id="calendar-event-template">
|  <span class="event" title="<%= description %>">
|      <%= title %>
|  </span>
|</script>
That is not so much a Backbone.js thing as it is a simple Underscore.js template. It is a bit of a cheat. It is a script that will not be executed because it is type="text/template". Since it is not evaluated, the contents remain undisturbed as a string child element, which can subsequently be extracted via a jQuery html() call.

To use that template, I replace the existing code with:
          var event_html = _.template($('#calendar-event-template').html());
          $(event_html({title: title, description: description})).appendTo($(el))
The _.template() Underscore method takes as its argument the "html" extracted from the type="text/template" <script> tag. The result of the _.template() call is a function that will return a string with the things inside <%= ... > replaced with key-values passed into the function.

Since the template only requires that title and description values be defined (and since I conveniently have local variables named title and description), I can invoke the function as event_html({title: title, description: description}). To append that to the existing table cell (whose ID is stored in the local variable el), I convert the resulting HTML string into a jQuery object and append it to the cell element:
$(event_html({title: title, description: description})).appendTo($(el))
At this point, I have separated (at least some of) my view logic out into a template. This does not quite qualify as the "Backbone way".

What I think qualifies as more of the Backbone way is to create an individual view that corresponds to single calendar event. The results is much simpler conceptually. All that I need implement in the view class is a template attribute and a render method:
    window.EventView = Backbone.View.extend({
      template: _.template($('#calendar-event-template').html()),
      render: function() {
        $(this.el).html(this.template(this.model.toJSON()));
        return this;
      }
    });
The template attribute is the very same template function from earlier. The render method becomes just as simple—it extracts the attributes from the model (which include the necessary title and description attributes) and passes them to the template function. Nice!

To instantiate and render these view objects, I need to tell last night's collection to fetch itself. Once retrieved, the building and rendering can commence:
    Events.fetch({success: function(collection, response) {
        collection.each(function(event) {
          var view = (new EventView({model: event, id: event.get("startDate")}));
          view.render();
        });
      }
    });
(I am pretty sure that some of that needs to go into an "Application View" class, but I'll worry about that another day)

And when I load up my page, I get... nothing. Cleverly placed console.log() statements reveals that the elements to which I am binding my view objects are not the <td> elements in the calendar table. Rather, they are new <div> tags:
This situation is addressed in the Backbone.js documentation on view constructors. To ensure that my existing ID is used rather than creating a new one, I need to pass in the element, not the ID:
        collection.each(function(event) {
          var el = $('#' + event.get("startDate")),
              view = new EventView({model: event, el: el});
          view.render();
        });
I might argue that, if the ID already exists, it should certainly be used rather than creating a new, identical identity element, but I am prolly not proficient enough with Backbone to be able to quibble yet.

Anyhow, I have reduced my code fingerprint significantly with the view code rework. Up tomorrow, I think, I will begin investigating CRUD with Backbone.

Day #127

No comments:

Post a Comment