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

2 comments:

  1. I just wanted to post some information for anyone else that needed to do this. I was using this method that Chris described and it worked well until I needed to integrate this with some other views. The settings wouldn't return to the original state after calling _.template()

    However, I was digging into the underscore code and noticed that you can pass settings to the function and it works as a temporary call as mentioned above.

    Could be that underscore has updated a lot over the past year and it's "new" anyway, to do this now you'll want to do something like this:

    template: _.template( $('#your_template').html(), null, { interpolate : /\{\{([\s\S]+?)\}\}/g };),

    ReplyDelete
  2. ofcourse it will not work, because he tries to copy an object simply by save it in a variable. But what he actually do is that he creating another variable that links to the SAME OBJECT.
    var orig_settings = _.templateSettings;
    lol

    ReplyDelete