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

Developing an Ember.js Edge special code

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>

In fiddles or short examples, this method of creating templates is common. The template is wrapped in a `script` tag with the type of `text/x-handlebars`, and the name of the template is passed as `data-template-name`.

For full applications, this method is usually superseded by server-side compilation of templates. In those cases, a server will compile handlebars templates and set the compiled content onto the `Ember.TEMPLATES` variable. That makes the following equivalent to the last example.

var App = Ember.Application.create();
Ember.TEMPLATES['index'] = Ember.Handlebars.compile("Hello Ember.js World!");

In chapter 10 and beyond, how to use server-side template compilation will be explained in detail. Server-side compilation means faster initial render time for a template, and the removal of about 50K in Handlebars JavaScript for the client.

Each of the above examples demonstrates use of the default "index" template name. The default behavior of a route is to load a template with the same name. The following sets a template for an explicitly defined route:

var App = Ember.Application.create();
App.Router.map(function(){
  this.route('about');
});
Ember.TEMPLATES['about'] = Ember.Handlebars.compile("<h1>About Us</h1>");

And this sets the template for an explicitly defined nested route:

var App = Ember.Application.create();
App.Router.map(function(){
  this.resource('cars', function(){
    this.route('transmission', {path: '/transmission/:transmission_type'});
  });
});
Ember.TEMPLATES['cars.transmission'] = \
  Ember.Handlebars.compile("Cars with a specific transmission type");

Templates compiled with Ember.Handlebars have several additional features over those compiled in pure handlebars. Among them are bound values (a cornerstone of Ember's two-way binding), actions, helpers for rendering other templates, and outlets.

Bound Expressions

When the Ember.Handlebars runtime evaluates an {{expression}}, it creates a special view bound to whatever property lives at the given path. This bound view knows to re-render when the property changes. Ember does not re-render the whole template when a single property changes, so how does the view know what part of the DOM to update?

Ember includes a micro library called Metamorph. Metamorph, in the words of its authors, “allows you to create a string of HTML, insert it into the DOM, and update the contents later." If we inspect the DOM of a running Ember application, we’ll see numerous tags demarcating the start and end of rendered properties or helpers.

<script id="metamorph-1-start" type="text/x-placeholder"></script>
Some <em>arbitrary</em> bit of text in the DOM.
<script id="metamorph-1-end" type="text/x-placeholder"></script>

Script tags are not rendered by browsers, and cannot be styled. They are largely impactless upon site CSS (with the exception of  `:first-child`-type selectors, using `:nth-of-type()` is the suggested alternative). Metamorph keeps track of where a value is rendered using these tags, and uses regexes or the Range API to update them without changing other parts of a page.

Handlebars, Metamorph, and Ember's property binding and aliasing give us the building blocks to bind data to the DOM. By only updating values that change, and avoiding re-rendering of entire templates, Ember can update the UI of a web application very quickly. Of course, these implementation details are abstracted away by Ember, so you rarely need to consider them when building a real world application. Nevertheless, it’s useful to know that the seemingly “magic” data bindings are actually built from simple, well defined underlying libraries.

Bound Attributes

It probably seems logical that if we can use Handlebars to dynamically update arbitrary bits of the DOM, we should be able to bind element attributes in a similar manner.

<div id="{{post.id}}">...</div>

However, we quickly find there’s a problem with this approach. Metamorph has no way of knowing whether or not the string it generates will be inside an attribute, and the result is a broken tag soup:

<div id="<script id="metamorph-1-start" 
type="text/x-placeholder"></script>someId<script id="metamorph-1-end" 
type="text/x-placeholder"></script>">...</div>

At this point, we accept the small compromise of not being able to use bound expressions directly for attributes, and in return we get the powerful bind-attr helper.

<div {{bind-attr id="person.id"}}>...</div>

Under the hood, bind-attr adds an extra data-bindattr-uid attribute to the element. This plays the same role as metamorph’s <script> markers, allowing Ember to find and update the attribute at a later point.

<div id="someId" data-bindattr-1="1">...</div>

Ember has a few neat tricks for working with bound attributes. The first concerns boolean properties. When an attribute is bound to a boolean property, the attribute will be added or removed depending on the value of the boolean:

<input {{bind-attr type="text" disabled="isDisabled"}}>

When the isDisabled is true:

<input type="text" disabled>

Otherwise:

<input type="text">

The class attribute can be bound like other attributes, but has some special behavior of its own. In the simplest case, if the value of the referenced property is a string then it is used directly, just like other attributes:

<p {{bind-attr class="priority"}}>...</p>

If the value of priority is 'high', the result will be:

<p class="high">...</p>

Boolean values behave in a similar manner to other attributes, except that rather that adding or removing the attribute, they add or remove a dasherized version of the property name. To borrow an example from the Ember guides:

<div {{bind-attr class="isUrgent"}}>...</div>

If isUrgent is true, we get:

<div class="is-urgent">...</div>

If isUrgent is false, no class name is added:

<div>...</div>

This can be further controlled with a micro-syntax somewhat like javaScript’s ternary operator:

<div {{bind-attr class="isUrgent:urgent"}}>...</div>
<div {{bind-attr class="isUrgent:urgent:not-urgent"}}>...</div>
<div {{bind-attr class="isUrgent::not-urgent"}}>...</div>

This can be read as pathToProperty:value-when-true:value-when-false. Note that either value can be omitted and if both are omitted we get the dasherized result as seen above.

Literal class names can also be added by prefixing them with a colon:

<div {{bind-attr class=":always-important"}}>...</div>

Resulting in:

<div class="always-important">...</div>

The class binding is also unique in that it accepts multiple properties. To extend our earlier examples:

<div {{bind-attr class="priority isUrgent:urgent:not-urgent :always-important"}}>...</div&gt

;

Given the value of priority is 'high' and the value of isUrgent is false, we get:

<div class="high not-urgent always-important">...</div>

Bound Statements

Ember.Handlebars makes our trusty {{each}}, {{if}} and {{unless}} helpers data-binding aware. As with bound expressions, for all practical purposes their usage is identical to pure Handlebars, except that now they automatically update when the underlying value changes.

The simplest use of Ember's `#each` helper sets the context of a block for each value.

{{#each client}}
  {{name}}
{{/each}}

However, changing the context of a template can lead to confusion. The `#each` helper is better used with a syntax that naming the current item:

{{#each client in clients}}
  {{client.name}}
{{/each}}

`#each` also allows the view and controller for each iteration to be configured:

{{#each client in clients
    itemController="client"
    itemView="client"}}
  {{client.name}}
{{/each}}

Views and controllers are shortly to be discussed in more detail. This pattern provides you with a way to handle events for each rendered item, and have a decorator class for each item.

Action Helpers

Ember.Handlebars provides two helpers for managing DOM events. The first, `link-to`, is used in place of standard HTML links. Instead of passing a URL to `link-to`, the helper expects a route path (the `.` segmented strings introduced in the chapter on routes).

var App = Ember.Application.create();
App.Router.map(function(){
 this.resource('records', function(){
    this.route('edit', { path: ':record_id/edit' });
  });
});
 
Ember.TEMPLATES['index'] = Ember.Handlebars.compile(
  '{{#link-to "records"}}View all records{{/link-to}}'
);
Ember.TEMPLATES['records.index'] = Ember.Handlebars.compile(
  '{{#each record in controller}}' +
  '  {{#link-to "records.edit" record}}Edit {{record.name}}{{/link-to}}' +
  '{{/each}}'
);

`link-to` accepts several arguments, but only a route name is required. Like the `transitionTo` route method and `transitionToRoute` controller method, additional arguments to `link-to` are models passed to the route objects. In the "records.index" template above, a record is being passed to the edit route.

When several arguments are passed, each is set as a model on a deeper nested resource. This is an example lifted from the Ember guides:

var App = Ember.Application.create();
App.Router.map(function(){
 this.resource("photos", function(){
    this.resource("photo", { path: "/:photo_id" }, function(){
      this.route("comment", { path: "/comments/:comment_id" });
    });
  });
});
 
Ember.TEMPLATES = Ember.Handlebars.compile(
  '{{#link-to "photo.comment" nextPhoto primaryComment}}' +
  '  Start at the next photo's comment' +
  '{{/link-to}}'
);

link-to only transitions to between routes. To execute an arbitrary action, the second helper for dealing with DOM events must be used. The aptly named action helper emits messages to controllers and routes, and can do so when any kind of DOM event has fired.

In use, it looks like this:

var App = Ember.Application.create();
App.IndexRoute = Ember.Route.extend({
  actions: {
    notify: function(message){
      window.alert("Template says: "+message);
    }
  }
});
Ember.TEMPLATES['index'] = Ember.Handlebars.compile(
  "<button {{action "notify" "clicked!"}}>Alert me</button>"
);

Actions default to handling click events for a DOM node. When the above button is clicked, the message "clicked!" will appear in an alert. A string can be passed to `{{action` as an argument, or an unquoted path will be passed as its value.

Actions can handle other events as well. For instance, the `submit` event on a form can trigger an action.

var App = Ember.Application.create();
App.IndexRoute = Ember.Route.extend({
  model: function(){ return new Ember.Object(); },
  actions: {
    save: function(client){
      window.alert("Saved: "+client.get('name'));
    }
  }
});
Ember.TEMPLATES['index'] = Ember.Handlebars.compile(
  '<form {{action "save" model on="submit"}}>' +
    '{{input value=model.name}}' +
    '<input type="submit" value="Save" />' +
  '</form>'
);

The model hook on `IndexRoute` specifies an initial model. That model is passed to the `index` template, which binds its name to an input. As the input is changed, the bound model's `name` property is updated. When "Save" is clicked the form is submitted, and the action executes. The action's argument (the model) is passed to the action handler.

Understanding actions is essential to successfully building a complex Ember.js application. Actions "bubble" through an Ember application in the same way that events bubble through the DOM. They have a default target of the template's context (a controller). If unhandled there, the action looks through each parent templates' controller, and finally the active routes.

If nothing handles the action, an unhandled event error will be raised.

Chapter 7 will cover actions and how they bubble in more detail.

Template Helpers

The helpers partial and render both render other templates into the current context but their behaviour and use cases are somewhat different.

partial renders the named template in the current context.

{{partial "my_partial"}}

Partials can begin with an underscore or simply match the path passed to the helper. We’ll cover exactly how to register templates with Ember in a bit, but the simplest way is in index.html:

<script type="text/x-handlebars" id="my_template">
  <p>This is my template.</p>
</script>

<script type="text/x-handlebars" id="_my_partial">
  <p>This is my partial.</p>
</script>

The template will be rendered in the same context as the helper, and will have access to whatever properties are available.

render is a little bit different, and much more powerful. Say we wanted to render a browser for an archive of posts in the sidebar of our app. We’d write something like this in our application template:

<script type="text/x-handlebars">
  <section role="main">
    {{outlet}}
  </section>
  <aside role="navigation">
    {{render "posts"}}
  </aside>
</script>

This will render the 'posts' template backed by the singleton instance of App.PostsController and an instance of App.PostsView, or dynamically generated equivalents if either is undefined. This means we can include complex chunks of our app in any template we like. Because it’s the singleton PostController, we have access to any application state the controller is aware of, meaning any time we render posts we can reproduce the current state of that part of the app.

When a model object is passed, a unique instance of the appropriate controller will be created:

{{render "posts" unreadPosts}}

This time around, we’d get a brand new instance of PostsController with whatever unreadPosts equates to as its content property.

Chapter 7 will discuss controllers in more detail, including the difference between singleton and instance controllers.

Outlets

In chapter 4, the outlet helper was quickly explained. This helper comes into play when rendering an application’s routes. Essentially, {{outlet}} tells Ember where the rendered template from a child route should be inserted.

Outlets can optionally be given names, allowing us to render templates into multiple outlets:

var App = Ember.Application.create();
App.IndexRoute = Ember.Route.extend({
 renderTemplate: function(){
    this.render('unreadPosts', { outlet: 'main' });
    this.render('favoritePosts', { outlet: 'sidebar' });
  }
});
Ember.TEMPLATES['index'] = Ember.Handlebars.compile(
  '<section role="main">' +
    '{{outlet "main"}}' +
  '</section>' +
  '<section role="navigation">' +
    '{{outlet "sidebar"}}' +
  '</section>'
);

Outlets give structure to an application's DOM, and decide how that structure maps to routes.

Other Helpers Worth Knowing About

Ember.Handlebars comes with two incredibly useful helpers for debugging in templates: debugger and log. For anyone familiar with browser debugging tools such as WebKit’s console, the utility of these two will be immediately clear. They look like this in action:

{{debugger}} {{! execution will be paused here }}
{{log someProperty}} {{! the value of someProperty will be sent to the console log }}

Another helper that may prove useful on occasion is unbound. It can be used to render unbound expression and unbound results of helpers. This would be used in a situation where we only wanted a particular part of the template to render once, in spite of subsequent changes in underlying data.

{{unbound upcase name}}

Writing Your Own Helpers

Registering custom helpers is very similar to pure handlebars, though we use the Ember.Handlebars.helper method rather than Handlebars.registerHelper:

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

This registers a bound upcase helper that will automatically update when the value of string changes.

If a helper makes use of properties of the passed-in value, we must specify explicitly these dependencies in order for data bindings to behave correctly:

Ember.Handlebars.helper('fullName', function(person) {
  return person.get('firstName') + ' ' + person.get('lastName');
}, 'firstName', 'lastName);

The Ember.Handlebars.helper method also allows us to register shorthand helper methods for rendering views:

var App = Ember.Application.create();
App.RevealView = Ember.View.extend({
  isVisible: false,
  showView: function(){
    Ember.run.later(this, function(){
      this.set('isVisible', true);
    }, this.get('delay'));
  }.on('didInsertElement')
});
Ember.Handlebars.helper('reveal', App.RevealView);
Ember.TEMPLATES['index'] = Ember.Handlebars.compile(
  '{{#reveal delay=1000}}Initially hidden content{{/reveal}}'
);

By passing a view to the `helper` method, an instance of that view will be attached to each helper block. Views are reviewed in detail in chapter 6.

The Future

As a final note for those who are curious, a successor to Handlebars is in its early stages. Christened "HTMLBars," it aims to support the same syntax as Handlebars, but produce actual DOM nodes, rather than a string which is subsequently turned into DOM. If it works, this will remove the need for bind-attr and possibly even Metamorph. At the time of writing, HTMLBars has seen a slowdown in development, but it does suggest that a next-generation templating solution is not so far down the line. In the meantime, we have the ever-dependable Handlebars.

Wrapping Up

We’ve covered Handlebars in some depth and hopefully demonstrated its power and flexibility, and also how Handlebars and Ember complement each other. Ember is not strictly dependent upon Handlebars, but it’s fair to say that it would take a lot of work to support the same capabilities with another templating language. That said, other frameworks have opted for different approaches, and it’s always worth surveying the landscape to see what other solutions exist for similar problems.

Ember renders handlebars templates via views. Views manage DOM events from their templates and manage the lifecycle of the HTML. In chapter 6, we will discuss them in detail.

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

You should refresh this page.