English |  Español |  Français |  Italiano |  Português |  Русский |  Shqip

Developing an Ember.js Edge

5. Ember.Handlebars

Handlebars is a template engine implemented in JavaScript and allowing only a minimal expression of inline logic. Released in 2010, Handlebars was inspired partially by the Moustache template engine. Handlebars was created by Yehuda Katz, and Moustache by Chris Wanstrath.

Handlebars on its own has several useful features:

  • Objects are referenced by path. ticket.event.name, for example.
  • Templates can express only minimal logic, keeping complexity low.
  • Handlebars encourages you to write helpers for more complex logic.
  • Helpers can be inline, as with {{underscore event.name}} or in block form as with {{#each tickets}}{{event.name}}{{/each}}.
  • Handlebars compiles to an AST that other template languages, like Emblem, can build upon.
  • Template precompilation is supported by Handlebars, which saves tens of kilobytes in JavaScript in production and betters performance.

Ember builds upon Handlebars with powerful data bindings and helpers, while staying true to its logicless principles. We’ll explore some of Ember’s extensions shortly, but first let’s see what Handlebars offers out of the box.

Pure Handlebars

Note that you can try these pure Handlebars examples at http://tryhandlebarsjs.com.

Handlebars templates are strings compiled into template functions, which can then be executed with a context. The context is passed to the compiled template as an argument.


var template = Handlebars.compile('<p>{{person.name}}</p>'),
context = { person: { name: 'Yehuda Katz' } };

template(context); // => '<p>Yehuda Katz</p>'

To render a property from our context we wrap its path in a pair of double mustaches as seen above in {{person.name}}. As a matter of course, Handlebars automatically escapes any property before it’s rendered. If we want to output the raw property we use triple mustaches as seen below:


var template = Handlebars.compile('{{foo}} vs. {{{foo}}}'),
context = { foo: '<p>bar</p>' };

template(context); // => '<p>bar<p> vs. <p>bar</p>'

To render the context of the template directly we use this, i.e.


var template = Handlebars.compile('{{this}}'),
context = 'This!';

template(context); // => 'This!'

We can think of paths as implicitly calling this, as in this.person.name.

As well as rendering properties, we can also call registered helper functions from within a Handlebars template. Say we had a helper called upcase, we would use it like this: {{upcase person.name}}. We’ll explore the arguments helpers can take in a moment.

Note that helpers get priority over property paths, so if we need to access a property with the same name as a helper, we use the explicit path syntax: {{./property}}.

Handlebars also supports block helpers. These contain a chunk of template which is passed into the helper, like this:


{{#myBlockHelper}}
<p>This template is passed to the helper function.</p>
{
{/myBlockHelper}}

This feature of Handlebars has many potential uses, a few of which can be seen at work in the built-in block helpers that follow.

Each

Give each an array and it’ll render the block for each item it finds.


var template = Handlebars.compile('{{#each this}} <p>{{this}}</p> {{/each}}'),
context = ['One', 'Two', 'Three'];

template(context); // => ' <p>One</p> <p>Two</p> <p>Three</p> '

Handlebars’ each helper has another trick up its sleeve: we can use the {{else}} statement within the block to specify what should be rendered when the array is empty:


<ul>
{{#each items}}
<li>{{this}}</li>
{{else}}
<li>No items :(</li>
{{/end}}
</ul>

If, Else, Unless

Conditional statements will look familiar, but there notable restrictions:


{{#if isRead}}
<p>Read</p>
{{else}}
<button>Mark as read</button>
{{/if}}

{{#unless isRead}}
<button>Mark as read</button>
{{else}}
<p>Read</p>
{{/unless}}

First, the condition is really an argument to the if helper and therefore no logical operations may be performed. The logic of calculating this value should be performed elsewhere.

Second, we only get an else block; else if conditions are not supported. The reasons for this lay in part with Handlebars’ implementation, and in part with its design as a logicless language.

With

The with block helper changes the context of the template to whatever path it’s given. This provides some convenience and saves repetition when accessing many properties of one object:


{{person.name}}
{{person.age}}
{{person.nickname}}

Becomes:


{{#with person}}
{{name}}
{{age}}
{{nickname}}
{{/with}}

Custom Helpers

Now we’ve seen Handlebars’ built-in helpers in action we can try creating our own. Let’s start with that upcase helper we mentioned earlier:


Handlebars.registerHelper('upcase', function(string) {
return string.toUpperCase();
});

var template = Handlebars.compile('{{upcase foo}}'),
context = { foo: 'bar' };

template(context); // => 'BAR'

Now let’s consider the implementation of the with helper:


Handlebars.registerHelper('with', function(context, options) {
return options.fn(context);
});

Notice the second argument, options? This is passed to all helpers and contains a few useful bits of information. As seen above, options.fn contains the template function for the contents of the block. It’s a normal template like any other and we can call it however we like. In this case we simply return the result of calling it with context.

Options also has a property named hash which contains options passed into the helper in a format that looks somewhat like HTML attributes:


Handlebars.registerHelper('dl', function(options) {
var result = '<dl>';
for (var key in options.hash) {
result += '<dt>' + key + '</dt>';
result += '<dd>' + options.hash[key] + '</dd>';
}
result += '</dl>';
return new Handlebars.SafeString(result);
});

var template = Handlebars.compile('{{dl one="uno" two="dos"}}');

template();
// <dl>
// <dt>one</dt>
// <dd>uno</dd>
// <dt>two</dt>
// <dd>dos</dd>
// </dl>

As we can see, the attributes passed to the helper are available as options.hash.

More information on helper options can be found in the Handlebars documentation.

The Handlebars.SafeString constructor was slipped into this last example. Returning a Handlebars.SafeString tells Handlebars not to perform any escaping. This is to be used with care, and we should manually escape any strings that may potentially leak unwanted markup into the output using Handlebars.Utils.escapeExpression.

The final feature of Handlebars helpers we will mention is this. In a Handlebars helper, this refers to the current context the helper is being executed in. We’ve used context to mean a few different things, so let’s look at a concrete example:


Handlebars.registerHelper('upcase', function() {
return this.toUpperCase();
});

var template = Handlebars.compile('{{upcase}}'),
context = 'foo';

template(context); // => 'FOO';

This can be helpful if the helper needs to know a little about what’s going on around it.

We’ve discussed the key features of pure Handlebars in some detail, so now let’s move on to considerably more powerful feature set added by Ember.Handlebars.

Handlebars in Ember

In Ember, Handlebars plays a defined role in the larger framework. Templates will rarely need to be explicitly compiled or executed.

In development, Handlebars templates can be added in one of two places. The first is with the HTML of an application. This is a runnable Ember application:

<body>
<script type="text/javascript">
var App = Ember.Application.create();
</script>
<script type="text/x-handlebars" data-template-name="index">
Hello Ember.js World!
</script>
</body>

There has been error in communication with Booktype server. Not sure right now where is the problem.

You should refresh this page.