View Helpers For Underscore Templates

Underscore’s template engine lets you run any arbitrary JavaScript code that you want, within a template. You could write an entire JavaScript application within an underscore template if you want. But this is a really bad idea. Templates should be clean and simple. Some people go so far as to say “logic-less templates”, but I honestly don’t think that’s reasonable. I also don’t think it’s reasonable or responsible to have a bunch of JavaScript code littered throughout the template, though. 

Fortunately, it’s easy to build helper methods for Underscore templates (and they end up looking a lot like Rails view helpers).

Logic In Templates: A Bad Idea

On a client project recently, I had a JavaScript object that needed to be bound to my template. The object looked somewhat like this:

var data = {
  type: "something",
  name: "whatever",
  addresses: [
    {
      type: "read_address",
      address: "1/2/3",
      value: "10"
    },
    {
      type: "write_address",
      address: "1/2/4"
    }
  ]
}

I needed to take all of the “addresses” and find each specific address within the array, by name, so that I could get the right information in place at the right time in my template.

The first pass of code that I wrote to do this, looked like this:

<script id="my-template" type="text/template">
  <p>type: <%= type %></p>
  <p>name: <%= name %></p>
  <p>read address: <%= _.find(this.addresses, function(addr){ return addr.type == "read_address" }).address %> </p>
  <p>read address value: <%= _.find(this.addresses, function(addr){ return addr.type == "read_address" }).value %> </p>
  <p>write address: <%= _.find(this.addresses, function(addr){ return addr.type == "write_address" }).address %> </p>
</script>

And this worked, but it’s a bad idea. I have a lot of duplication in this template and I have a lot of logic and processing of data in the template, too. This is going to make for a very painful setup when it comes to maintenance – especially if / when I have to change how the address is looked up.

View Helper Methods: A Better Idea

In asking around on twitter for some suggestions on how to clean this up, I received several responses. Out of everything that everyone said, though, I liked the response I got from DaveTheNinja because of it’s simplicity.

The basic idea is to recognize that the data I send to my template is a JavaScript object. Since this is an object, it can have methods. If I can have methods on my object, why not put methods on it as view helper methods?

var viewHelpers = {
  getAddress: function(type){
    var address = _.find(this.addresses, function(addr) { 
      return addr.type == type 
    });
    return address;
  }
}


_.extend(data, viewHelpers);

_.template(myTemplate, data);

The helper method – “getAddress” – is pretty much the same code. But now it’s consolidated in to a single location and I’m able to give better formatting for it which makes it easier to read and understand.

You can also see that I’ve kept the data separate from the objects until just before rendering the template. I’m using Underscore’s “extend” method to copy all of the methods from my “viewHelpers” object on to my data object.

Then when I pass the data object to my template, I can call my “getAddress” method like this:

<script id="my-template" type="text/template">
  <p>type: <%= type %></p>
  <p>name: <%= name %></p>
  <p>read address: <%= getAddress("read_address").address %> </p>
  <p>read address value: <%= getAddress("read_address").value %> </p>
  <p>write address: <%= getAddress("write_address").address %> </p>
</script>

And we’re done! I now have view helper methods that I can call from within my templates.

Using This With Backbone

I use this technique with Backbone a lot. The code that I showed above is really no different than what I do with my Backbone views, either. When you get to the point where you need to render your template, just extend the view helper methods on to your data before doing the actual rendering:

Backbone.View.extend({
  template: "#my-template",

  render: function(){

    var data = this.model.toJSON();
    _.extend(data, viewHelpers);

    var html = _.template($(this.template), data);
    this.$el.html(html);

  }
});

Using This With Backbone.Marionette

I’ve added this functionality in to Backbone.Marionette as well. ItemView, CompositeView and Layout will all look for a `templateHelpers` attribute on a view definition. If it finds it, it will mix that object in to the data that is used to render the template: 

Backbone.Marionette.ItemView.extend({

  template: "#my-template",
  templateHelpers: viewHelpers

});

You can specify a reference to the viewHelpers object as shown in this example, provide an object literal as the templateHelpers directly, or specify a function that returns an object with the helper methods that you want. For more information see the documentation for Marionette.

Other Template Engines

This basic technique will probably work with other template engines, too. Some engines like Handlebars, though, have this idea baked in to them directly. But if you’re using Underscore’s templates, then this is a good way to keep your templates clean while still providing the logic you need.


Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Derick Bailey

Derick Bailey is an entrepreneur, problem solver (and creator? :P ), software developer, screecaster, writer, blogger, speaker and technology leader in central Texas (north of Austin). He runs SignalLeaf.com - the amazingly awesome podcast audio hosting service that everyone should be using, and WatchMeCode.net where he throws down the JavaScript gauntlets to get you up to speed. He has been a professional software developer since the late 90's, and has been writing code since the late 80's. Find me on twitter: @derickbailey, @mutedsolutions, @backbonejsclass Find me on the web: SignalLeaf, WatchMeCode, Kendo UI blog, MarionetteJS, My Github profile, On Google+.
This entry was posted in Backbone, Javascript, Marionette, Underscore. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • http://chaliy.name Mike Chaliy

    Foregive me this self promotion :), I just published ASP.NET bundler for underscore templates ( https://github.com/chaliy/aspnet-underscore-bundler ). It precompiles templates to javascript function and combine templates to single file. What do you think?

    • http://mutedsolutions.com Derick Bailey

      that looks kind of awesome! i just started up an asp.net project w/ backbone and underscore templates. hoping i have time to try this out :)

  • http://twitter.com/ryanimated Ryan Smith

    Way cool! I hadn’t thought of this aspect of utilizing underscore templates – thanks!

    My first thought on reading this was that you could also then pass in other compiled underscore templates as helpers for rendering partials within the template. 

    • http://mutedsolutions.com Derick Bailey

      now there’s a fun idea! :)

  • S Satya Suman

    Awesome!!!!! is all i can say

  • S Satya Suman

    Hi i am extending the view handler exactly the same shown here but i get  
    Uncaught ReferenceError: getProperty is not defined
    error. I checked the object in the console but the getProperty is defined as a function. I know i am asking a forum question here but had no other choice, pardon my ignorance…

    var viewHelper = {    getProperty:function(propertyName, object) {        if(typeof(object) === “undefined”){            object = this["attributes"];        }        for (i in object) {            if (_.isObject(object[i])) {                var currentObj = object[i];                if(_.has(currentObj, propertyName)){                    return currentObj[propertyName];                }                this.getProperty(propertyName, currentObj);            }        }    }}var CustomerDetailView = Backbone.View.extend({    tagName : “div”,    template: “#customer-detail-view-template”,    initialize: function() {        _.bindAll(this, ‘render’);        this.model.bind(‘change’, this.render);        this.initializeTemplate();    },    initializeTemplate: function() {        this.template = _.template($(this.template).html());    },    render: function() {        var data = this.model.toJSON();        _.extend(data, viewHelper);        console.log(data);        var html = _.template($(this.template), data);        $(this.el).html(html);        return this;    }})

                                         Full Name :                 Country Code :                 street :                 maiden_name :                                         marital_status_code :                 tax_id_number :                 primary_phone :                 customer_number :                        

  • Anonymous

    Arrived here after tracing through the marionette source to handle exactly this case. Always great to see it already there.

    • Anonymous

      Spoke too soon. I’m looking to expose access to the isNew() method on the underlying model. Is there a way to do this? TemplateHelpers don’t work, because they only have access to the JSON attributes, not methods.

      Or is there a better way to approach templates which need to render new, unsaved objects differently? I realize getTemplate() could be used to render an entirely different template, but that would result in duplication since the majority of the template is common.

      • http://mutedsolutions.com Derick Bailey

        the isNew() function does exactly this:

        isNew: function() {
        return this.id == null;
        },

        http://backbonejs.org/docs/backbone.html#section-63

        you could easily add that to your helper methods

        • Anonymous

          Haha — should have realized the implementation was simply based on the id attribute. Didn’t look because I assumed there was some other piece of internal state. Thanks!

  • Ruben Vreeken

    Hello Derrick,

    First of all, great work on Backbone.Marrionette plugin! It absolutely rocks and it’s been very interesting watching it develop over the past months (I’ve been following your progress since ~v0.4)

    These template helpers seem to be very handy, but i’m having a little trouble with them in my latest project.

    Basically, I’m trying to inject a few error messages into the template alongside the serialized model. The errors are currently stored in a property of the view object, because adding them to the model seems messy to me.
    So I’ve implemented a templateHelpers method, which is supposed to make them accessible inside the template, but so far I’m just getting errors.

    If you would be so kind to take a look at it, you can find my question on stackoverflow:
    http://stackoverflow.com/questions/11638613/backbone-marionette-templatehelpers-access-data-stored-outside-the-model

    • Ruben Vreeken

      Nevermind, I fixed it.

  • http://twitter.com/davegbeck David Beck

    Hi Derick,

    Awesome work on Marionette. Thanks.

    Wanted to share this underscore mixin that allows you to add global helper functions to underscore templates. (That is, helpers that are always available.)

    https://github.com/dgbeck/underscore-template-helpers

  • http://twitter.com/Puigcerber Pablo Villoslada

    Thanks for sharing this Derick!

  • Carl-Erik Kopseng

    This was great and solved my problem exactly.

  • http://www.coreyknowsbest.com/ Corey Rothwell

    How did i not think of this. I was adding a bunch of helper functions directly into the template files… ugh. Thanks!

  • landike

    Thanks. I’m just in progress of choosing best i18n solution for javascript:
    http://landike.blogspot.com/2013/09/x-ation-on-javascript.html
    And key point is good support of Underscore templates. I do realize that I may implement some overrides, but Isn’t it logical, that with so wide javascript tools nowadays, I can’;t find the best for me.

    Now, I’m on Requirre.js i18n and plan to implement something kinda of override of Backbone/Marionette on tempalte() or render() levels.
    So your post is and will be useful.

    If you have some ideas about i18n projects which may meet my goals, I would appreciate your comments on my blog.