Handlebars in Node and Express


About handlebars view engine used in Node

1 How to setup in express?

Notice, we here use the Express.js 4.

Be default, Express will require the engine based on the file extension. For example jade, Express incokes the following inernall, and caches the require() on subsequent calls to increase performance.

app.engine('jade', require('jade').__express);

Use this method for engines that do not provide .__express out of box, if you wish to ‘map’ a different extension to the template engine. For example, ejs, we can do this:

app.engine('html', require('ejs').renderFile);

So for handlebars, if we use the hbs package, then we can use either of these ways:

app.set('view engine', 'hbs');

// must set the view engine to html, since express set the jade as default engine
app.set('view engine', 'html');
// here, we use the hbs package, there are still lots of other package to use, like handlebars, express-handlebars
app.engine('html', require('hbs').__express);

2 Handlebars Syntax

The handlebars is a template engine, which inherits from Mustache.

Variables

{{title}}

Iteration(each)

each is one of the built-in helpers, it allows you to iterate through objects and arrays. Inside the block, we use the @key for the former(objects), and the @index for the later(arrays). In addition, each item is referred to as this. When an item is an object itself, this can be omitted and just the property name is used to reference the value of that property.

<div>
    \{\{#each languages}}
    <p>\{\{@index}}. \{\{this}}</p>
    \{\{/each}}
</div>
{language: ['a', 'b', 'c']}

Unescaped Output

By default, handlebars escapes values. If you don’t want it to escape a value, use the triple cruly brace. }

Conditions(if)

if is another built-in helper invoked via #.

\{\{#if user.admin}}
  <button class='lanch'>Lanch</button>
  \{\{else}}
  <button class='login'>Login</button>
  \{\{/if}}

Unless

Equal to if not. An another helper.

\{\{#unless user.admin}}
\{\{else}}
\{\{/unless}}

With

In case there’s an object with nested properties, use the with can solve this problem.

\{\{#with user}}
  <p>\{\{name}}</p>
    \{\{#with contact}}
      <span>Twitter: @\{\{twitter}}</span>
    \{\{/with}}
\{\{/with}}

Comments

To output comments, just use the regular html <!-- -->. To hide comments in the final output, use or.

Customer helper

Customer Handlebars helps are similar to built-in helper blocks and jade mixins. To use customer helpers, we need to create them as a javascript function and register them with the handlebars instance.

Handlebars.registerHelper('table', function(data) {
    var str='<table>';
    for(var i = 0; i < data.length; i++) {
        str += '<tr>';
        for(var key in data[i]) {
            str += '<td>' + data[i][key] + '</td>';
        };
        str += '</tr>';
    };
    str += '</table>';
    return new Handlerbars.SafeString(str);
});
{
    node:[
        {name: 'express', url: 'http://expressjs.com/'},
        {name: 'hapi', url: 'http://spumko.github.io/'},
        {name: 'compound', url: 'http://compoundjs.com/'},
        {name: 'derby', url: 'http://derbyjs.com/'}
    ]
}
#### Block
The context is changing. Like this:

```html
<ul>
    \{\{#each tours}}
        \{\{! I'm in a new block...and the context has changed }}
        <li>
            \{\{name}} - \{\{price}}
            \{\{#if ../currencies}}
                (\{\{../../currency.abbrev}})
            \{\{/if}}
        </li>
    \{\{/each}}
</ul>
\{\{#unless currencies}}
    <p>All prices in \{\{currency.name}}.</p>
\{\{/unless}}
\{\{#if specialsUrl}}
    \{\{! I'm in a new block...but the context hasn't changed (sortof) }}
    <p>Check out our <a href="">specials!</p>
\{\{else}}
    <p>Please check back often for specials.</p>
\{\{/if}}
<p>
    \{\{#each currencies}}
        <a href="#" class="currency">\{\{.}}</a>
    \{\{else}}
        Unfortunately, we currently only accept \{\{currency.name}}.
    \{\{/each}}
</p>

The data:
{
    currency: {
    name: 'United States dollars',
    abbrev: 'USD',
},
    tours: [
        { name: 'Hood River', price: '$99.95' },
        { name: 'Oregon Coast', price, '$159.95' },
    ],
    specialsUrl: '/january-specials',
    currencies: [ 'USD', 'GBP', 'BTC' ],
}

Notice here when inside the tours object, the context has changed to be the tours, so if you want to access the currency property, we need use ../currency.

Each helper in handlebars will change the context. For if block, it will create a new context which is a copy of the parent context. That is to say, in if/else block, the new context will be the same with it previous context( parent context). However, if there is a if in the each, we need use the ../../. to access the currency in the example. So be careful, it is complicated, so we would better not use if in each block.

{{.}} refers to the current context.

Includes(Partials)

Includes or partials templates in Handlebars are interpreted by the `expression. Partials are akin to helpers and are registered withHandlebars.registerPartial(name, source)`, where name is a string and source is a Handlebars template code for the partial.

var hbs = require('hbs');
hbs.registerPartials(__dirname + '/views/partials'); // access the partial files in the partials folder

3 StandAlone Handlebar Usage

Pratical NodeJS Chapter4 The handlebars-example.js and handlebars-example.html code.

4 Weired part of hbs used in express

  • Using the partials
app.set('view engine', 'html');
app.engine('html', require('hbs').__express);
var hbs = require('hbs');
hbs.registerPartials(__dirname + '/views/partials');
<div>
    <p>This is example</p>
    \{\{title}}
    \{\{> part1}} <!-- refer to the part1.html in partials folder -->
</div>
  • Using the data
router.get('/', function(req, res, next) {
  res.locals.title = 'express';
  res.render('index');
});

Unlike the ejs, we use the title to access it not the locals.title

<div>
    <p>This is example</p>
    \{\{title}}
    \{\{> part1}} <!-- refer to the part1.html in partials folder -->
</div>
  • Using the layout

If we pass nothing, and the views folder has a file called layout.html, then it will be taken as the layout.

router.get('/', function(req, res, next) {
  res.locals.title = 'express';
  res.render('index'); // will use the layout.html as the layout
  //res.render('index', {layout: null}); // set the layout to null
});
  • Using the layout and data at the same time
router.get('/', function(req, res, next) {
  res.locals.title = 'express';
  res.render('index', {layout: 'main', title: 'express'}); // will use the layout.html as the layout
});

Here, we will use the main.html as the layout file, and pass the title data.