Handlebars - Helper

handlebars

What is a helper?

A helper (or a helper function) is a JavaScript function that are free to implement whatever behavior they like.

How can we use a helper?

Inside the template, the helper is used by the following convention: {{nameOfHelper nameOfToken1 nameOfToken2 ...}}:

Helper:
Handlebars.registerHelper('fullName', function(person) {
  return person.firstName + " " + person.lastName;
});

Template:
<div class="post">
  <h1>By {{fullName author}}</h1>
  <div class="body">{{body}}</div>

  <h1>Comments</h1>

  {{#each comments}}
  <h2>By {{fullName author}}</h2>
  <div class="body">{{body}}</div>
  {{/each}}
</div>

Context:
var context = {
  author: {firstName: "Alan", lastName: "Johnson"},
  body: "I Love Handlebars",
  comments: [{
    author: {firstName: "Yehuda", lastName: "Katz"},
    body: "Me too!"
  }]
};

In the above code, we register a helper function under the name 'fullName', and the author attribute is read from the context object and is passed onto the helper function.

A Handlebars' helper call is a simple identifier, followed by zero or more parameters (separated by space). Each parameter is a Handlebars expression.

{{{link story}}}

In this case, link is the name of a Handlebars helper, and story is read from the context object and is passed as a parameter to the helper.

How many arguments can a helper function accept?

The helper function take any number of argument.

How does Handlebars handle name conflict?

In the case of name conflicts, helpers are given priority. If we register a helper named X, and the context object and the template also contain a token name X, Handlebars will pick the helper.

Why should we use Handlebars.SafeString when returning HTML from inside a helper?

Handlebars.registerHelper('link', function(object) {
  var url = Handlebars.escapeExpression(object.url),
      text = Handlebars.escapeExpression(object.text);

  return new Handlebars.SafeString(
    "<a href='" + url + "'>" + text + "</a>"
  );
});

When returning HTML from a helper, you should return a Handlebars SafeString if you don't want it to be escaped by default. When using SafeString all unknown or unsafe data should be manually escaped with the escapeExpression method.

How can we pass simple literal values to a helper?

You can also pass a simple String, number, or boolean as a parameter to Handlebars helpers.

{{{link "See more..." story.url}}}

In this case, Handlebars will pass the link helper two parameters: the string "See more…" and the result of evaluating story.url in the current context.

How can we pass a hash argument to a helper?

Handlebars helpers can also receive an optional sequence of key-value pairs as their final parameter (referred to as hash arguments in the documentation):

{{{link "See more..." href=story.url class="story"}}}

In the above code, we pass a simple literal string "See more…", and the other parameters (which contains the = sign) are automatically lumped into one hash. The keys in hash arguments must each be simple identifiers, and the values are Handlebars expressions. This means that values can be simple identifiers, paths, or strings.

Handlebars.registerHelper('link', function(text, options) {
  var attrs = [];

  for (var prop in options.hash) {
    attrs.push(
        Handlebars.escapeExpression(prop) + '="'
        + Handlebars.escapeExpression(options.hash[prop]) + '"');
  }

  return new Handlebars.SafeString(
    "<a " + attrs.join(" ") + ">" + Handlebars.escapeExpression(text) + "</a>"
  );
});

Handlebars provides the final hash as options.hash. This makes it easier to accept a variable number of parameters, while also accepting an optional Hash. If the template provides no hash arguments, Handlebars will automatically pass an empty object ({}), so you don't need to check for the existence of hash arguments.

What is a block helper and how does it work?

Handlebars' block helper is a mechanism for invoking a helper with a block of the template. Block helpers can then invoke that block zero or more times with any context it chooses.

A block helper is not much different from a normal helper. It is a helper. It can do everything that a normal JavaScript function can do.

A block helper is registered and used in the same way as the normal helper (see above). However, like a block, a block helper also start with a # and have a construct similar to a block: {{#helperName parameterOneName parameterTwoName ...}} ... {{/helperName}}

Handlebars.registerHelper('list', function(items, options) {
  var out = "<ul>";

  for(var i=0, l=items.length; i<l; i++) {
    out = out + "<li>" + options.fn(items[i]) + "</li>";
  }

  return out + "</ul>";
});

Template:
{{#list people}}{{firstName}} {{lastName}}{{/list}}

Context:
{
  people: [
    {firstName: "Yehuda", lastName: "Katz"},
    {firstName: "Carl", lastName: "Lerche"},
    {firstName: "Alan", lastName: "Johnson"}
  ]
}

Output:
<ul>
  <li>Yehuda Katz</li>
  <li>Carl Lerche</li>
  <li>Alan Johnson</li>
</ul>

The helper receives the people as its first parameter (items), and an options hash as its second parameter. The options hash contains a property named fn, which you can invoke with a context just as you would invoke a normal Handlebars template. The fn function, when invoked, evaluate the block's template. In the above code, when we invoke the fn function, we pass it the object that will be used as the context object. Specifically, we pass it each item of the array.

Since the contents of a block helper are escaped when you call options.fn(context), Handlebars does not escape the results of a block helper. If it did, inner content would be double-escaped!

When a helper is used with a block, Handlebars will pass the contents of the block compiled into a function to the helper. In other words, the fn function is the compiled template function for the block's template.

Handlebars always invokes helpers with the current context as this, so you can invoke the block with this to evaluate the block in the current context.

Handlebars.registerHelper("studyStatus", function(data, options){
  var len = data.length;
  var returnData="";
  for(var i=0;i<len;i++){
    // change the value of the passingYear to
    // passed/not passed based on the conditions.
    data[i].passingYear=(data[i].passingYear < 2015) ? "passed" : "not passed";

    // here options.fn(data[i]) temporarily changes the
    // scope of the whole studyStatus helper
    // block to data[i]. So {{name}}=data[i].name
    // in the template.
    returnData = returnData + options.fn(data[i]);

  }

  return returnData;
});

Template:
{{#studyStatus students}}
  {{name}} has {{passingYear}}
{{/studyStatus}}

Context:
var context = {
  "students":[
    {"name" : "John", "passingYear" : 2013},
    {"name" : "Doe" , "passingYear" : 2016}
  ]
}

Output:
John has passed.
Doe has not passed.

How will Handlebars behave if the block's template contains an {{else}} block?

If an else expression is found in the block Handlebars will also pass the contents of the else block to the helper as well.

Here’s an example block helper that iterates through an array, letting the contents know whether it’s an even or odd row. The helper takes the array to iterate over, the css class name for even rows, and the css class name for odd rows as arguments. You’ll also notice the compiled template function fn for the contents of the block and the compiled else block function, elseFn are arguments to the helper function. The helper simply adds a property named stripeClass to each item in the array as we iterate over it so that we can output that class name within the block. If the array given is falsy or empty the helper just returns the contents of the else block.

Helper:
Handlebars.registerHelper("stripes", function(array, even, odd, fn, elseFn) {
  if (array && array.length > 0) {
    var buffer = "";
    for (var i = 0, j = array.length; i < j; i++) {
      var item = array[i];

      // we'll just put the appropriate stripe class name onto the item for now
      item.stripeClass = (i % 2 == 0 ? even : odd);

      // show the inside of the block
      buffer += fn(item);
    }

    // return the finished buffer
    return buffer;
  } else {
    return elseFn();
  }
});

Template:
{{#stripes myArray "even" "odd"}}
  <div class="{{stripeClass}}">
    ... code for the row ...
  </div>
{{else}}
  <em>There aren't any people.</em>
{{/stripes}}

In the above code, notice that there is an {{else}} but there is no {{if}}.

Does Mustache have helper?

No, not explicitly, but the view / context object can contain function which get invoked when Mustache encounter an expression that evaluated to a function. More on this below.

How is Handlebars' helper different from Mustache's view function?

In Mustache, the view or context object can contain function.

Mustache's "view functions" can be used in at least two different ways.

When the value is a function, it is called. Mustache requires a function that returns the function to be used. The returned function will be called with the section's literal block of text, un-rendered, as its first argument. The {{tags}} will not have been expanded - the function should do that on its own. In this way you can implement filters or caching. The second argument is a special rendering function that uses the current view as its view argument. It is called in the context of the current view object.

Mustache template:
{{#wrapped}}
  {{name}} is awesome.
{{/wrapped}}

Mustache view:
{
  "name": "Willy",
  "wrapped": function() {
    return function(text, render) {
      return "<b>" + render(text) + "</b>"
    }
  }
}

In the above code, 'wrapped' defines the section / block, but it is a function in the view object. Whatever that function returns will be the result of that section.

When used as above, it does not involve a list or array. Another similar example:

{
  "name": "Tater",
  "bold": function () {
    return function (text, render) {
      return "<b>" + render(text) + "</b>";
    }
  }
}

{{#bold}}Hi {{name}}.{{/bold}}

<b>Hi Tater.</b>

In the above template, "bold" defines the section / block, but it is a function in the view object.

The below example demonstrate how Mustache's "view functions" can be used to iterate over a list:

View:
{
  "beatles": [
    { "firstName": "John", "lastName": "Lennon" },
    { "firstName": "Paul", "lastName": "McCartney" },
    { "firstName": "George", "lastName": "Harrison" },
    { "firstName": "Ringo", "lastName": "Starr" }
  ],
  "name": function () {
    return this.firstName + " " + this.lastName;
  }
}

Template:
{{#beatles}}
* {{name}}
{{/beatles}}

Output:
* John Lennon
* Paul McCartney
* George Harrison
* Ringo Starr

In the above template, "beatles" define the section block, and it is an array, not a function, In this case, the "function tag" ({{name}}) appears inside the body of the block. The {{name}} function will be invoke once for each item in the list. It will be called in the context of the current item in the list on each iteration.

With Handlebars, it is true that block helper can iterate over a list. However, we must explicitly register the helper, and the name of the helper is used to define the block, so it is more like the first scenario with Mustache (the "helper / function" is used to define the block), and in order to use the block helper to iterate over a list, the list is passed as a parameter to the helper:

Handlebars.registerHelper('list', function(items, options) {
  var out = "<ul>";

  for(var i=0, l=items.length; i<l; i++) {
    out = out + "<li>" + options.fn(items[i]) + "</li>";
  }

  return out + "</ul>";
});

Template:
{{#list people}}{{firstName}} {{lastName}}{{/list}}

Context:
{
  people: [
    {firstName: "Yehuda", lastName: "Katz"},
    {firstName: "Carl", lastName: "Lerche"},
    {firstName: "Alan", lastName: "Johnson"}
  ]
}

Output:
<ul>
  <li>Yehuda Katz</li>
  <li>Carl Lerche</li>
  <li>Alan Johnson</li>
</ul>

In the above code, 'list' is the name of the helper, and it is used to define the block, and people is read from the context object and is passed as a parameter to the helper function.

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License