Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at the jQuery Conference 2010 (Boston, MA) with:

Using Underscore.js Templates To Render HTML Partials

Posted by Ben Nadel

I've never used Underscore.js before. But, we're going to start using some Backbone.js at InVision; and, since Underscore.js is a hard dependency of Backbone.js, we decided to use Underscore's template() method as our client-side HTML partial rendering engine.

All client-side template engines attempt to answer the same question: How do I merge this data into this block of HTML? Solutions can range from the complex - creating a DSL (Domain Specific Language) like the jQuery Template Markup Language (jtml) - to the simple - embedding raw JavaScript directives within your template. The Underscore.js template() method takes the latter approach, providing simple hooks for executing JavaScript in and around parts of your template's HTML.

This low-level approach can lead to some strange looking template markup; but, it definitely provides for the most power and flexibility. As you'll see in the demo below, my template can make full use of all JavaScript functionality, including the underscore library itself.

By default, the template() method uses JavaScript's "with(..)" directive to help locate unscoped variable references. This is considered an anti-pattern; and, it is something that is deprecated in future versions of JavaScript. As such, Underscore.js also allows you to define a scope variable in which you will make your top-level variable references. Since we use the "rc" - Request Collection / Request Context - variable on the server side, I decided to update the root Underscore.js template settings to use "rc" on the client-side:

  • _.templateSettings.variable = "rc";

Now, all top-level variable references in our Underscore.js templates have to be referenced off of, "rc."

You can also override the ERB (Embedded Ruby) style markup, if you want to use braces (ala Mustache or Handlebars) instead of angle-brackets. Both approaches kind of look equally funny to me, however, so I just stuck with the defaults.

That said, let's take a look at my fist use of the Underscore.js template engine:

  • <!doctype html>
  • <html>
  • <head>
  • <meta charset="utf-8" />
  • <title>Looking At Underscore.js Templates</title>
  • </head>
  • <body>
  •  
  • <h1>
  • Looking At Underscore.js Templates
  • </h1>
  •  
  •  
  • <!-- BEGIN: Underscore Template Definition. -->
  • <script type="text/template" class="template">
  •  
  • <h2>
  • <%- rc.listTitle %>
  • </h2>
  •  
  • <ul>
  • <% _.each( rc.listItems, function( listItem ){ %>
  •  
  • <li>
  •  
  • <%- listItem.name %>
  •  
  • <% if ( listItem.hasOlympicGold ){ %>
  • <em>*</em>
  • <% } %>
  •  
  • </li>
  •  
  • <% }); %>
  • </ul>
  •  
  •  
  • <% var showFootnote = _.any(
  • _.pluck( rc.listItems, "hasOlympicGold" )
  • ); %>
  •  
  •  
  • <% if ( showFootnote ){ %>
  •  
  • <p style="font-size: 12px ;">
  •  
  • <em>* Olympic gold medalist</em>
  •  
  • </p>
  •  
  • <% } %>
  •  
  • </script>
  • <!-- END: Underscore Template Definition. -->
  •  
  •  
  • <!-- Include and run scripts. -->
  • <script type="text/javascript" src="../jquery-1.8.0.js"></script>
  • <script type="text/javascript" src="../underscore.js"></script>
  • <script type="text/javascript">
  •  
  •  
  • // When rending an underscore template, we want top-level
  • // variables to be referenced as part of an object. For
  • // technical reasons (scope-chain search), this speeds up
  • // rendering; however, more importantly, this also allows our
  • // templates to look / feel more like our server-side
  • // templates that use the rc (Request Context / Colletion) in
  • // order to render their markup.
  • _.templateSettings.variable = "rc";
  •  
  • // Grab the HTML out of our template tag and pre-compile it.
  • var template = _.template(
  • $( "script.template" ).html()
  • );
  •  
  • // Define our render data (to be put into the "rc" variable).
  • var templateData = {
  • listTitle: "Olympic Volleyball Players",
  • listItems: [
  • {
  • name: "Misty May-Treanor",
  • hasOlympicGold: true
  • },
  • {
  • name: "Kerri Walsh Jennings",
  • hasOlympicGold: true
  • },
  • {
  • name: "Jennifer Kessy",
  • hasOlympicGold: false
  • },
  • {
  • name: "April Ross",
  • hasOlympicGold: false
  • }
  • ]
  • };
  •  
  • // Render the underscore template and inject it after the H1
  • // in our current DOM.
  • $( "h1" ).after(
  • template( templateData )
  • );
  •  
  •  
  • </script>
  • </body>
  • </html>

As you can see, the template markup is a bit funny to read. But, if you look at it slowly, you can see that it's nothing more than raw JavaScript markup mixed in with HTML markup (which all gets compiled down to a render Function and a print buffer behind the scenes). It is important to understand that the rendering executes as a function so that you'll remember to "var" your local variables. In this case, you can see that I created an intermediary, template-local variable, "showFootnote", in order to determine if my view needs to render a particular portion of the template.

Right now, my template is embedded within a non-JavaScript Script tag located within my main page. This is easy to do for the demo; but it is not required. Your template could easily (and probably should be) placed in an external HTML file that is subsequently loaded with something like RequireJS.

Anyway, that's my first look at Underscore.js and it's template() method. Pretty cool stuff. I can see why people rave about Underscore. It has a boat-load of useful functions.




Reader Comments

@Ralph,

I believe that all of the template engines work the same way (more or less) as the micro-templating by Resig. From the ones I've looked at, they use an array behind the scenes, build it up, and then join() it and return the string. I assume they all have roughly the same performance.

Underscore.js allows you to use a scope variable for references; which, is faster than using with(). So, that's a performance boost.

Reply to this Comment

One thing to be aware of is that underscore's template, unlike other (such as Handlebars), does not do any HTML escaping for you. You can do it manually using _.escape. This is obviously important when it comes to preventing cross-sites-scripting vulnerabilities.

Reply to this Comment

@Russ,

Very cool. I'm working on moving to ColdFusion 10; but, I'm waiting for a license to come through (supposedly processing). I know you can probably use this stuff pre-CF10; but, using the function expressions probably makes life much easier!

@Stephen,

I think they may have updated it to include escaping. It looks like you have to use a slightly different variation in the ERB notation to allow for escaping:

  • <%= ... interpolation ... %>
  • <%- ... escaped ... %>
  • <% ... execute ... %>

Notice the use of "-" rather than "=". The docs say that this will HTML escape it.

Reply to this Comment

@Ben,

Wow, you're right, and it's been that way for a while. I guess the stuff I'd read previously about it is just very out of date. Sorry for the misinformation.

Reply to this Comment

Hey Ben! If ya dig Underscore/Backbone you may dig Lo-Dash (http://lodash.com), a drop-in replacement for Underscore that's customizable. If ya only want the `_.template` method you can do `lodash include=template` to create a build with just `_.template`, or if you want to use Lo-Dash with Backbone and a different template lib you can do `lodash backbone` to create a build of Lo-Dash with only the methods used by Backbone. Lo-Dash also has optimizations to automatically avoid with-statements for templates that don't use "evaluate" delimiters and sourceURL's baked into each template to help debug them in development builds.

Reply to this Comment

@Stephen,

No problem at all! Since this is my first time looking into it, I had completely fresh eyes. I know how it is - with ColdFusion, I've been through versions 4.5 -> 10. Sometimes, I'll get through 2,3,4 versions without even realizing that a method was added :D

@John-David,

I've heard good / funny things about lo-dash. I forget what Pod cast it was (JavaScript Jabber? Ruby Rouges?) where they were saying that you and the guy behind Underscore have some sort of ongoing rivalry where you keep fixing his bugs - that's too funny :D I'll definitely check it out.

Reply to this Comment

Although I like Handlebars.js, we used Mustache.cfc with Mustache.js successfully on a large project. It was wonderful having both ColdFusion and JavaScript use the same templates...

Reply to this Comment

@James,

It's funny you mention that; a couple of times, I have starting an effort to create a JavaScript parser for a small set of ColdFusion tags (so that you could essentially use the same template in both places). Then I give up :) But, it would be something to keep trying again.

Reply to this Comment

This is good stuff. However, I have yet to find a good example of how to nest templates (templates that call other templates). Any ideas on that?

Thanks for the article!

Reply to this Comment

thanks for the useful article - it help me understand templating with backbone - not sure what the rage is about regarding data binding and javascript this looks like classic asp or echoing out some php

Reply to this Comment

Hello Ben,

Nice article. I have indeed got my application to run using partial rendering of views. However i came to a roadblock.

By using

  • <script type="text/template" id="templateid">
  • <!-- Template content goes here -->
  • </script>

the code works well.

However if i put the template as an external file like

  • <script type="text/template" id="templateid" src="template.js"></script>

this wont work.

What is the difference between the above two methods and also how can i get around this issue? Or am i missing something here which maybe obvious?

Reply to this Comment

Rally thank you for your article, I applied it my webserver http://avtopazl.by/ (in search criterea, and it works really super!!!)

Guys, you do super deal!!!

Reply to this Comment

Post A Comment

?
You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.