Handlebars is a simple template language inspired by Mustache. This component is a YUI port of the original Handlebars project.
This guide covers the YUI port of Handlebars, which differs from the original Handlebars in only a few minor ways and is kept closely in sync with the original. The official Handlebars documentation can be found at handlebarsjs.com.
To include the source files for Handlebars and its dependencies, first load the YUI seed file if you haven't already loaded it.
<script src="http://yui.yahooapis.com/3.18.1/build/yui/yui-min.js"></script>
Next, create a new YUI instance for your application and populate it with the
modules you need by specifying them as arguments to the YUI().use()
method.
YUI will automatically load any dependencies required by the modules you
specify.
<script> // Create a new YUI instance and populate it with the required modules. YUI().use('handlebars', function (Y) { // Handlebars is available and ready for use. Add implementation // code here. }); </script>
For more information on creating YUI instances and on the
use()
method, see the
documentation for the YUI Global Object.
A Handlebars template is just some text that contains Handlebars expressions like {{foo}}
. Although most people use Handlebars to generate HTML, it can easily generate JSON, XML, YAML, or any other plain text format.
When you render a template, Handlebars evaluates expressions and replaces them with data. Block expressions can be used for iteration, simple if/else branching, and executing helper functions, but otherwise Handlebars is completely logic-less. This makes it ideal for separating content from functionality.
First, you need to define a template string somewhere. Most template strings are long and contain lots of newlines and indentation, which makes them hard to read and maintain if you store them directly in JavaScript. A common pattern in apps that use Handlebars is to use micro-templating, which means embedding template strings in a static HTML document, wrapped in a <script>
element with the attribute type="text/x-handlebars-template"
. Thanks to the type
attribute, the browser won't try to execute the script, and you can fetch the template string whenever you need it using Y.one()
.
Here's a simple micro-template that creates a list of links:
<script id="list-template" type="text/x-handlebars-template"> <p>YUI is brought to you by:</p> <ul> {{#items}} <li><a href="{{url}}">{{name}}</a></li> {{/items}} </ul> </script>
Once you have a template string, the next step is to compile that string into a Handlebars template function. You can then render the template by passing a data object (also known as a context) into that function.
<script> YUI().use('handlebars', 'node-base', function (Y) { // Extract the template string and compile it into a reusable function. var source = Y.one('#list-template').getHTML(), template = Y.Handlebars.compile(source), html; // Render the template to HTML using the specified data. html = template({ items: [ {name: 'pie', url: 'http://pieisgood.org/'}, {name: 'mountain dew', url: 'http://www.mountaindew.com/'}, {name: 'kittens', url: 'http://www.flickr.com/search/?q=kittens'}, {name: 'rainbows', url: 'http://www.youtube.com/watch?v=OQSNhk5ICTI'} ] }); // Append the rendered template to the page. Y.one('body').append(html); }); </script>
After it's rendered and appended to the page, the output looks like this:
YUI is brought to you by:
You can re-render the template at any time simply by executing the stored template
function again and passing in new data. Templates only need to be parsed once, which makes rendering a template multiple times very fast.
// Re-render the template with new data. No need to parse the template again. var html = template({ items: [ {name: 'caffeine', url: 'http://en.wikipedia.org/wiki/Caffeine'}, {name: 'git', url: 'http://git-scm.com/'}, {name: 'numberwang', url: 'http://www.youtube.com/watch?v=qjOZtWZ56lc'} ] });
Alternatively, if you only need to render something once, you can use the convenient render()
method to parse and render a template in a single step.
// Parse and render a template in a single step. var html = Y.Handlebars.render(source, { // ... data ... });
A basic Handlebars expression starts with {{
, contains some text, and ends with }}
. These delimiters are often referred to as "mustaches", since they look a little bit like fancy mustaches if you turn your head sideways, squint a bit, and use your imagination. When a template is rendered, Handlebars will replace expressions with rendered data.
A simple Handlebars expression looks like this:
<h1>{{title}}</h1>
This tells Handlebars:
If there's a helper function named "title", execute it and insert its return value here.
Otherwise, if there exists a title
property in the current context, and that property is not falsy or an empty array, insert its value here.
Otherwise, insert an empty string.
By default, content rendered using a double-mustache expression like {{foo}}
will automatically be HTML-escaped for safety. To render unescaped HTML output, use a triple-mustache expression like {{{foo}}}
. Only use a triple-mustache expression for content you trust! Never use it to render unfiltered user input.
Handlebars also supports an alternative {{&foo}}
syntax to render unescaped content, but this syntax is less commonly used. That said, some people find it preferable to the triple-mustache syntax since it's easier to spot at a glance.
All expressions are evaluated relative to the current context. The default context for a template is the data object passed in when the template is rendered.
Here's the default context used for the examples in the rest of this section:
{ site: { title: 'AwesomeBlog', url: 'http://blog.example.com' }, article: { id: 1, title: 'My blog is awesome' } }
You can change the context using a block expression. Inside the block, the context will be set to the value referenced in the block's opening tag.
<div class="header"> {{#site}} <h1><a href="{{url}}">{{title}}</a></h1> {{/site}} </div> <div class="content"> {{#article}} <h2>{{title}}</h2> {{/article}} </div>
Certain block helpers also change the context within the block. For example, when iterating over an array of items using {{#each items}} ... {{/each}}
or {{#items}} ... {{/items}}
, the context inside the block will be set to the current item.
The special expression {{.}}
always evaluates to the current context, sort of like this
in JavaScript. In fact, {{.}}
and {{this}}
do exactly the same thing! This is especially useful when iterating over an array of strings, since it allows you to output each string value.
Using blocks to change the context is optional. Expressions can reference deeply-nested properties using dot notation:
<div class="content"> <h2>{{article.title}}</h2> </div>
The special expression ../
references the current context's parent scope.
<div class="content"> {{#article}} <h2><a href="{{../site.url}}/article/{{id}}">{{title}}</a></h2> {{/article}} </div>
Note that ../
references the parent scope, but not necessarily the previous level in the context hierarchy. Block helpers can invoke a block with any context, so a purely hierarchical reference wouldn't make much sense.
Block expressions are used to change the current context or to pass a block of content to a helper function. A block expression starts with an opening tag prefixed by #
, contains some content, and ends with a closing tag prefixed by /
, similar to an HTML element.
The following example uses the built-in each
helper to iterate over an array named gadgets
and render the block's content in the context of each item in the array.
Template Source | Data | Output |
---|---|---|
<ul> {{#each gadgets}} <li>{{name}}</li> {{/each}} </ul> |
{ gadgets: [ {name: 'iPhone'}, {name: 'Android'}, {name: 'Windows Phone'} ] } |
<ul> <li>iPhone</li> <li>Android</li> <li>Windows Phone</li> </ul> |
The each
helper is so useful that Handlebars will actually use it as the default helper if you create a block expression with an identifier that points to an array value. So we could also write the template above like this and get the same result:
<ul> {{#gadgets}} <li>{{name}}</li> {{/gadgets}} </ul>
If a block expression refers to an empty array, a falsy value, or a value that doesn't exist in the current context, the block's contents won't be rendered.
A block expression that refers to a non-array value such as an object or string will change the context to that value inside the block. See Contexts & Paths for an example of this.
Handlebars provides several built-in block helpers that are always available to templates.
The each
helper iterates over an array. The block will be rendered once for each item, and its context will be set to the current item.
Template Source | Data | Output |
---|---|---|
<h1>Bands Ryan likes</h1> <ul> {{#each bands}} <li>{{.}}</li> {{/each}} </ul> |
{ bands: [ 'The Dandy Warhols', 'The Brian Jonestown Massacre', 'The Black Keys', 'Black Rebel Motorcycle Club' ] } |
<h1>Bands Ryan likes</h1> <ul> <li>The Dandy Warhols</li> <li>The Brian Jonestown Massacre</li> <li>The Black Keys</li> <li>Black Rebel Motorcycle Club</li> </ul> |
If you create a block expression with an identifier that points to an array value, Handlebars will automatically use the each
helper to iterate over that array, so we could rewrite the template above like this and get the same result:
<h1>Bands Ryan likes</h1> <ul> {{#bands}} <li>{{.}}</li> {{/bands}} </ul>
The with
block helper renders the given block in a different context. This can save you some typing in a template that will render a lot of namespaced data.
Template Source | Data | Output |
---|---|---|
<p class="author"> {{#with author}} {{firstName}} {{lastName}} {{/with}} </p> |
{ author: { firstName: 'Ryan', lastName: 'Grove' } } |
<p class="author"> Ryan Grove </p> |
If you create an expression with an identifier that points to an object value, Handlebars will automatically use the with
helper to render the block in the context of that object, so we could rewrite the template above like this and get the same result:
<p class="author"> {{#author}} {{firstName}} {{lastName}} {{/author}} </p>
The if
block helper accepts a single parameter, which may be either a literal value or a reference to a value. If the value is truthy and not an empty array, the contents of the block will be rendered. If an optional else
block is provided, its contents will be rendered if the value is falsy or an empty array. Inside an if
or else
block, the context remains the same as it was outside the block.
In order to prevent templates from becoming bogged down with logic that should be implemented in JavaScript or in a helper function, the if
helper only supports simple true/false logic based on a single value. It doesn't support comparisons or logical operators like &&
and ||
.
Template Source | Data | Output |
---|---|---|
<h1>Currently online</h1> {{#if users}} <ul> {{#users}} <li>{{.}}</li> {{/users}} </ul> {{/if}} |
{ users: [ 'Ryan Grove', 'Eric Ferraiuolo', 'Lucas Smith', 'Dav Glass' ] } |
<h1>Currently online</h1> <ul> <li>Ryan Grove</li> <li>Eric Ferraiuolo</li> <li>Lucas Smith</li> <li>Dav Glass</li> </ul> |
If we empty the users
array and re-render the template, the list won't be included in the output.
Template Source | Data | Output |
---|---|---|
<h1>Currently online</h1> {{#if users}} <ul> {{#users}} <li>{{.}}</li> {{/users}} </ul> {{/if}} |
{ users: [] } |
<h1>Currently online</h1> |
We can add an else
block to display an informative message when no users are online.
Template Source | Data | Output |
---|---|---|
<h1>Currently online</h1> {{#if users}} <ul> {{#users}} <li>{{.}}</li> {{/users}} </ul> {{else}} <p>Nobody's here!</p> {{/if}} |
{ users: [] } |
<h1>Currently online</h1> <p>Nobody's here!</p> |
The unless
block helper does exactly the opposite of the if
helper: it renders the contents of the block if the provided value is falsy or an empty array.
Template Source | Data | Output |
---|---|---|
<h1>Currently online</h1> {{#unless users}} <p>Nobody's here!</p> {{else}} <ul> {{#users}} <li>{{.}}</li> {{/users}} </ul> {{/unless}} |
{ users: [ 'Ryan Grove', 'Eric Ferraiuolo', 'Lucas Smith', 'Dav Glass' ] } |
<h1>Currently online</h1> <ul> <li>Ryan Grove</li> <li>Eric Ferraiuolo</li> <li>Lucas Smith</li> <li>Dav Glass</li> </ul> |
An expression beginning with {{!
is treated as a comment and won't show up in the rendered output.
{{! I'm a Handlebars comment! I won't show up in rendered output. }} <!-- I'm an HTML comment! I will show up in rendered output. -->
Multi-line comments work too.
{{! I'm a multi-line comment! }}
Unfortunately, Handlebars doesn't ignore expressions inside comments, so you can't actually comment out an expression.
<!-- This won't work (it'll leave a trailing "}}" behind) --> {{! {{expression}} }}
An expression like {{> partialName}}
will render the named partial template at that position in the output, inheriting the current data context. A partial is a reusable template that's registered using the registerPartial()
method.
See the Partials section of this guide for more details on creating and using partials.
Helper functions (often referred to simply as "helpers") are functions that are registered with the Handlebars runtime using the registerHelper()
method and can then be called from within a template at render-time. Helper functions can transform text, iterate over arbitrary data, implement branching logic, and more.
A helper function can accept parameters and even entire blocks of template content. Here's how you might call a simple inline helper to render an HTML link, passing in the link text and URL:
{{link "Handlebars docs" "http://handlebarsjs.com"}}
Block helpers have a slightly different syntax, using an opening and closing tag to delimit an entire block of template content. The helper name is specified in the opening and closing tags of the block:
{{#list contents}} {{link text url}} {{/list}}
Many of Handlebars' own features are implemented as Built-in Block Helpers.
Helper functions are executed when a template is rendered, not when it's compiled. This allows helper functions to take advantage of the state of the rendering environment (since a template may have been pre-compiled somewhere else), and to return different content even when the same template is rendered multiple times.
To define a custom helper function, call the registerHelper()
method and provide a name and a function to execute when the helper is called. The function may accept any number of arguments from the template, which may be provided either as literal values or as references to data properties. The final argument passed to the function will always be an options
object (more on this later).
Here's a simple helper that takes two parameters and spits out an HTML link.
// Register a {{{link}}} helper for creating HTML links. Y.Handlebars.registerHelper('link', function (text, url) { return '<a href="' + url + '">' + text + '</a>'; });
We can use this helper to render the following template:
Template Source
<ul> <li>{{{link "Pie" "http://pieisgood.org/"}}}</li> <li>{{{link kittens.text kittens.url}}}</li> </ul>
Data
{ kittens: { text: "Kittens", url : "http://www.flickr.com/search/?q=kittens" } }
Output
<ul> <li><a href="http://pieisgood.org/">Pie</a></li> <li><a href="http://www.flickr.com/search/?q=kittens">Kittens</a></li> </ul>
Notice the use of the triple-mustache for the {{{link}}}
expressions? That was necessary in order to prevent the helper's return value from being HTML-escaped. As an alternative to using a triple-mustache, we could modify the helper to return an instance of Y.Handlebars.SafeString
, which will bypass HTML escaping even when used with a double-mustache expression.
// Register a {{link}} helper for creating HTML links. The return value of this // helper will never be automatically HTML-escaped, so the helper does its own // internal escaping. Y.Handlebars.registerHelper('link', function (text, url) { text = Y.Escape.html(text); url = Y.Escape.html(url); return new Y.Handlebars.SafeString('<a href="' + url + '">' + text + '</a>'); });
Now we can simply use {{link "Text" "http://example.com/"}}
to call the helper, and we don't need to worry about escaping in the template itself since we know the helper will take care of it.
In the example above, we created a helper that accepts two unnamed arguments. Unnamed arguments work fine for simple helpers, but you can also choose to supply a helper function with a hash of named parameters. This may be more intuitive if your helper accepts a lot of options.
The final argument passed to a helper function is always a special options
object. The options.hash
property is an object hash that contains any named parameters that were passed from the template.
// Register a {{link}} helper that accepts a hash of named parameters instead // of individual arguments. Y.Handlebars.registerHelper('link', function (options) { var text = Y.Escape.html(options.hash.text), url = Y.Escape.html(options.hash.url); return new Y.Handlebars.SafeString('<a href="' + url + '">' + text + '</a>'); });
In the template, pass hash arguments as key/value pairs delimited by =
and separated by a space. Keys must be simple identifiers. Values may be literal values (strings, bools, integers) or references to data properties.
<ul> <li>{{link text="Pie" url="http://pieisgood.org/"}}</li> <li>{{link text=kittens.text url=kittens.url}}</li> </ul>
A helper function can even accept a combination of standard arguments and hash arguments.
// Register a {{link}} helper that accepts a combination of standard args and // hash arguments. Y.Handlebars.registerHelper('link', function (text, options) { var url = Y.Escape.html(options.hash.url); text = Y.Escape.html(text); return new Y.Handlebars.SafeString('<a href="' + url + '">' + text + '</a>'); });
<ul> <li>{{link "Pie" url="http://pieisgood.org/"}}</li> <li>{{link kittens.text url=kittens.url}}</li> </ul>
A block helper works similarly to a basic helper, with a couple of differences:
The options
object passed to a block helper function contains an fn
property, which is a function that accepts a context as an argument and returns a string containing the rendered contents of the block.
The options
object passed to a block helper function also contains an inverse
property. This is a function that accepts a context just like the fn
property, except that it renders the block following an else
statement. If there isn't an else
statement, the inverse
function will be a noop.
The return value of a block helper is not automatically HTML-escaped, but the return value of options.fn()
is automatically escaped. This ensures that block helpers don't return double-escaped content.
Here's a simple block helper that wraps its contents in a <p>
element:
Y.Handlebars.registerHelper('gamera', function (options) { return '<p class="' + this.animal + '">' + options.fn(this) + '</p>'; });
Template Source | Data | Output |
---|---|---|
<p>Gamera is really neat!</p> {{#gamera}} He is made of {{animal}} meat! {{/gamera}} |
{ animal: 'turtle' } |
<p>Gamera is a really neat!</p> <p class="turtle"> He is made of turtle meat! </p> |
Here's how Handlebars implements its own if
block helper. The options.inverse()
function makes it possible to render the contents of an else
block if one is provided.
Y.Handlebars.registerHelper('if', function (condition, options) { if (condition) { return options.fn(this); } else { return options.inverse(this); } });
A partial is like a mini-template that can be called from a larger template. Partials are often used to render frequently-used chunks of content, such as a header, footer, or a common view of some data.
Partials are registered with Handlebars through the registerPartial()
method. Once registered, a partial can be referenced from a template using an expression like {{> partialName }}
. Partials may be registered as string templates or as compiled template functions.
A partial will inherit the current context of the position at which it's included.
// Register a couple of reusable partials. Y.Handlebars.registerPartial('header', '<h1>{{title}}</h1>'); Y.Handlebars.registerPartial('footer', '<p>Copyright (c) 2012 by Me.</p>');
Template Source | Data | Output |
---|---|---|
<div> {{> header}} <p>Mustaches are awesome!</p> {{> footer}} </div> |
{ title: 'My Page About Mustaches' } |
<div> <h1>My Page About Mustaches</h1> <p>Mustaches are awesome!</p> <p>Copyright (c) 2012 by Me.</p> </div> |
Handlebars templates must be compiled before they can be rendered. One benefit of this is that a template only needs to be compiled once, and it can then be rendered multiple times without being recompiled. Templates can even be precompiled on the server or on the command line and then rendered on the client for optimal performance.
To compile a template string, pass it to the Y.Handlebars.compile()
method. You'll get back a function.
// Compile a template into a reusable function. var template = Y.Handlebars.compile('My favorite food is {{food}}.');
When you're ready to render the template, execute the function and pass in some data. You'll get back a rendered string.
// Render a previously compiled template. var output = template({food: 'pie'}); // => "My favorite food is pie.";
You can re-render the template at any time just by calling the function again. You can even pass in completely different data.
// Re-render a previously compiled template. output = template({food: 'cheesecake'}); // => "My favorite food is cheesecake."
If you don't plan to use a template more than once, you can compile and render it in a single step with Y.Handlebars.render()
.
// Compile and render a template in a single step. output = Y.Handlebars.render('My favorite food is {{food}}.', {food: 'pie'}); // => "My favorite food is pie."
Since Handlebars templates can be compiled and rendered in separate steps, it's possible to precompile a template for use later. You can precompile a template into raw JavaScript on the server or even on the command line, serve this precompiled JavaScript template to the client, and then render it on the client using any data the client has at its disposal.
The main benefit of precompilation is performance. Not only does the client not need to go through the compile step, you don't even have to load the Handlebars compiler on the client! All the client needs in order to render a precompiled template is a very small (about 1KB minified and gzipped) piece of JavaScript provided by the handlebars-base
module.
To precompile Handlebars templates on the server using Node.js, first install the YUI npm module by running the following in a terminal from the directory that contains your server application (this assumes you already have Node and npm installed):
$ npm install yui
This will install the yui
npm module in the current directory and make it available to your application.
Next, in your application code, call the precompile()
method to precompile a Handlebars template. It will return a string containing JavaScript code.
// Load the YUI Handlebars module. var Handlebars = require('yui/handlebars').Handlebars; // Precompile a template string (pass any string you like here). var precompiled = Handlebars.precompile('My favorite food is {{food}}.');
The precompiled
variable will contain a string of JavaScript code that looks something like this:
function (Handlebars,depth0,helpers,partials,data) {\n helpers = helpers || Handlebars.helpers; data = data || {};\n var buffer = "", stack1, foundHelper, functionType="function", escapeExpression=this.escapeExpression;\n\n\n buffer += "My favorite food is ";\n foundHelper = helpers.food;\n if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{},data:data}); }\n else { stack1 = depth0.food; stack1 = typeof stack1 === functionType ? stack1.apply(depth0) : stack1; }\n buffer += escapeExpression(stack1) + ".";\n return buffer;}
The precompile()
method differs from the compile()
method in a couple of important ways:
The precompile()
method returns a string of JavaScript code that's meant to be parsed and executed later, whereas compile()
returns a live JavaScript function.
The code returned by the precompile()
method contains no references to any outside objects. Once it's evaluated, the resulting precompiled function must be passed to the Y.Handlebars.template()
method, which will "revive" it and make the functionality of the current page's Handlebars class available to it.
You can now serve this precompiled JS to the client in whatever way makes the most sense for your application. On the client, load the handlebars-base
YUI module and pass the precompiled template to the Y.Handlebars.template()
method to convert it into a renderable template function.
Here's a simple Express app that precompiles a template on the server and renders it on the client:
#!/usr/bin/env node var Handlebars = require('yui/handlebars').Handlebars, app = require('express').createServer(), precompiled = Handlebars.precompile('My favorite food is {{food}}.'); app.get('/', function (req, res) { res.send( '<html><body>' + '<script src="http://yui.yahooapis.com/3.18.1/build/yui/yui-min.js"></script>' + '<script>' + 'YUI().use("handlebars-base", "node", function (Y) {' + 'var template = Y.Handlebars.template(' + precompiled + ');' + 'Y.one("body").append(template({food: "pie"}));' + '});' + '</script>' + '</body></html>' ); }); app.listen(7000);
To see this simple server in action, save it to a file, install Express and YUI by running npm i express yui
, then execute the file with Node.js and browse to http://localhost:7000/.
The original Handlebars project provides a Node.js-based Handlebars command-line application that can be installed via npm and used to precompile Handlebars template files. Since the precompiled templates produced by the original Handlebars are compatible with YUI Handlebars, this is a great way to precompile your Handlebars templates manually or as part of a build process.
First, you'll need to install Node.js and npm if you haven't already. See their respective websites for instructions.
Next, install the Handlebars npm module. Note that this program is maintained by the maintainers of the original Handlebars project, so there's a chance it could change or break compatibility with YUI Handlebars without notice.
$ npm install -g handlebars
Now you can run the handlebars
executable to precompile a template into JavaScript code.
$ handlebars my-template.handlebars -f precompiled-template.js
This will compile a template to a JavaScript file which you can load on your page. You could render it like this:
<!DOCTYPE html> <meta charset="utf-8"> <title>My Favorite Food</title> <body> <div id="content"></div> <script src="http://yui.yahooapis.com/3.18.1/build/yui/yui-min.js"></script> <script> YUI().use('handlebars-base', 'get', 'node', function (Y) { // Create a global Handlebars variable that points to Y.Handlebars. This is // necessary for compatibility with precompiled templates generated by the // original Handlebars project. window.Handlebars = Y.Handlebars; // Load the precompiled template JS onto the page. Y.Get.js('precompiled-template.js', function (err) { if (err) { Y.error('Template failed to load: ' + err); return; } // Render the template and insert its output into the page. var output = Y.Handlebars.templates['my-template']({food: 'pie'}); Y.one('#content').append(output); }); }); </script> </body>
The YUI Handlebars component is mostly just a YUI wrapper around the original Handlebars code. It's kept closely in sync with the latest code from the upstream Handlebars project, and our intent is to ensure that YUI Handlebars and original Handlebars can be used interchangeably to render the same templates. To see what upstream Handlebars version YUI uses, inspect Y.Handlebars.VERSION
.
YUI Handlebars differs from the original Handlebars in the following minor ways:
YUI Handlebars is a first-class YUI module intended to be loaded via YUI().use()
or as a dependency of a module created via YUI.add()
. Like all YUI modules, it adds its API to the Y
namespace, so the YUI Handlebars object is Y.Handlebars
instead of the global Handlebars
namespace used by the original Handlebars.
The Y.Handlebars.log()
function and the log
helper call Y.log()
under the hood. The log implementation in original Handlebars is a noop that's meant to be overridden.
YUI Handlebars appends a "-yui" suffix to the Y.Handlebars.VERSION
property.