handlebars-layouts

Handlebars helpers which implement layout blocks similar to Jade, Jinja, Swig, and Twig.

handlebars-layouts

Handlebars helpers which implement layout blocks similar to Jade, Jinja, Swig, and Twig.

With Node.js:

$ npm install handlebars-layouts

With Bower:

$ bower install shannonmoeller/handlebars-layouts

Helpers are generated by passing in your instance of Handlebars. This allows you to selectively register the helpers on various instances of Handlebars.

  • handlebars Handlebars - An instance of Handlebars.

Generates an object containing the layout helpers suitible for passing into registerHelper.

var handlebars = require('handlebars'),
    layouts = require('handlebars-layouts');
 
handlebars.registerHelper(layouts(handlebars));
  • handlebars Handlebars - An instance of Handlebars.

Both generates an object containing the layout helpers and registers them with Handlebars automatically.

var handlebars = require('handlebars'),
    layouts = require('handlebars-layouts');
 
layouts.register(handlebars);
  • partial String - Name of partial to render.
  • context Object (Optional) - A custom context for the partial.
  • attributes Object (Optional) - Arbitrary values that will be added to the partial data context.

Loads a layout partial of a given name and defines block content.

{{#extend "layout" foo="bar"}}
    {{#content "title" mode="prepend"}}Example - {{/content}}
{{/extend}}

The {{#extend}} helper allows you to reason about your layouts as you would class extension where the above is equivalent to the following psuedo code:

class Page extends Layout {
    constructor() {
        this.foo = 'bar';
    }
 
    title() {
        return 'Example - ' + super();
    }
}
  • partial String - Name of partial to render.
  • context Object (Optional) - A custom context for the partial.
  • attributes Object (Optional) - Arbitrary values that will be added to the partial data context.

Allows you to load a partial which itself extends from a layout. Blocks defined in embedded partials will not conflict with those in the primary layout.

{{#extend "layout"}}
 
    {{#content "body"}}
        {{#embed "gallery"}}
            {{#content "body"}}
                <img src="1.png" alt="" />
                <img src="2.png" alt="" />
            {{/content}}
        {{/embed}}
 
        {{#embed "modal" foo="bar" name=user.fullName}}
            {{#content "title" mode="prepend"}}Image 1 - {{/content}}
            {{#content "body"}}<img src="1.png" alt="" />{{/content}}
        {{/embed}}
    {{/content}}
 
{{/extend}}

The {{#embed}} helper allows you to reason about your partials as you would class instantiation where the above is equivalent to the following psuedo code:

class Page extends Layout {
    body() {
        var gallery = new Gallery();
 
        gallery.replaceBody('<img src="1.png" alt="" />\n<img src="2.png" alt="" />');
 
        var modal = new Modal({
            foo: 'bar',
            name: this.user.fullName
        });
 
        modal.prependTitle('Image 1 - ');
        modal.replaceBody('<img src="1.png" alt="" />');
 
        return gallery.toString() + modal.toString();
    }
}
  • name String - Block identifier.

Defines a named block, with optional default content. Blocks may have content appended, prepended, or replaced entirely when extending or embedding. You may append and prepend to the same block multiple times.

{{#block "header"}}
    <h1>Hello World</h1>
{{/block}}
 
{{#block "main"}}
    <p>Lorem ipsum...</p>
{{/block}}
 
{{#block "footer"}}
    <p>&copy; 1970</p>
{{/block}}
  • name String - Identifier of the block to modify.
  • mode String (Optional) - Means of providing block content. Default: replace.

Sets block content, optionally appending or prepending using the mode attribute.

Layout:

<html>
    ...
    <body>
        {{#block "header"}}
            <h1>Hello World</h1>
        {{/block}}
 
        {{#block "main"}}
            <p>Lorem ipsum.</p>
        {{/block}}
 
        {{#block "footer"}}
            <p>&copy; 1999</p>
        {{/block}}
    </body>
</html>

Page:

{{#extend "layout"}}
 
    {{#content "header"}}
        <h1>Goodnight Moon</h1>
    {{/content}}
 
    {{#content "main" mode="append"}}
        <p>Dolor sit amet.</p>
    {{/content}}
 
    {{#content "footer" mode="prepend"}}
        <p>MIT License</p>
    {{/content}}
 
{{/extend}}

Output:

<html>
    ...
    <body>
        <h1>Goodnight Moon</h1>
 
        <p>Lorem ipsum.</p>
        <p>Dolor sit amet.</p>
 
        <p>MIT License</p>
        <p>&copy; 1999</p>
    </body>
</html>

There are times where you need to wrap a block with an element or use a different class depending on whether content has been provided for a block. For this purpose, the content helper may be called as a subexpression to check whether content has been provided for a block.

For example, you may wish to have an optional column in a grid layout:

{{!-- layout.hbs --}}
<div class="grid">
    <div class="grid-col {{#if (content "right")}}grid-col_2of3{{else}}grid-col_full{{/if}}">
        {{{block "left"}}}
    </div>
    {{#if (content "right")}}
        <div class="grid-col grid-col_1of3">
            {{{block "right"}}}
        </div>
    {{/if}}
</div>

For a page that only needs a left column, you may omit defining content for the right block:

{{!-- page.html --}}
{{#extend "layout"}}
 
    {{#content "left"}}
        <p>Left</p>
    {{/content}}
 
{{/extend}}

Resulting in:

<div class="grid">
    <div class="grid-col grid-col_full">
        <p>Left</p>
    </div>
</div>

For a page with two columns, simply define content for both blocks:

{{!-- page.html --}}
{{#extend "layout"}}
 
    {{#content "left"}}
        <p>Left</p>
    {{/content}}
 
    {{#content "right"}}
        <p>Right</p>
    {{/content}}
 
{{/extend}}

Resulting in:

<div class="grid">
    <div class="grid-col grid-col_2of3">
        <p>Left</p>
    </div>
    <div class="grid-col grid-col_1of3">
        <p>Right</p>
    </div>
</div>
<!doctype html>
<html lang="en-us">
<head>
    {{#block "head"}}
        <title>{{title}}</title>
 
        <link rel="stylesheet" href="assets/css/screen.css" />
    {{/block}}
</head>
<body>
    <div class="site">
        <div class="site-hd" role="banner">
            {{#block "header"}}
                <h1>{{title}}</h1>
            {{/block}}
        </div>
 
        <div class="site-bd" role="main">
            {{#block "body"}}
                <h2>Hello World</h2>
            {{/block}}
        </div>
 
        <div class="site-ft" role="contentinfo">
            {{#block "footer"}}
                <small>&copy; 2013</small>
            {{/block}}
        </div>
    </div>
 
    {{#block "foot"}}
        <script src="assets/js/controllers/home.js"></script> 
    {{/block}}
</body>
</html>
{{#extend "layout"}}
    {{#content "head" mode="append"}}
        <link rel="stylesheet" href="assets/css/home.css" />
    {{/content}}
 
    {{#content "body"}}
        <h2>Welcome Home</h2>
 
        <ul>
            {{#items}}
                <li>{{.}}</li>
            {{/items}}
        </ul>
    {{/content}}
 
    {{#content "foot" mode="prepend"}}
        <script src="assets/js/analytics.js"></script>
    {{/content}}
{{/extend}}
var handlebars = require('handlebars');
var layouts = require('handlebars-layouts');
 
// Register helpers 
handlebars.registerHelper(layouts(handlebars));
 
// Register partials 
handlebars.registerPartial('layout', fs.readFileSync('layout.hbs', 'utf8'));
 
// Compile template 
var template = handlebars.compile(fs.readFileSync('page.html', 'utf8'));
 
// Render template 
var output = template({
    title: 'Layout Test',
    items: [
        'apple',
        'orange',
        'banana'
    ]
});
 
console.log(output);
<!doctype html>
<html lang="en-us">
<head>
    <title>Layout Test</title>
 
    <link rel="stylesheet" href="assets/css/screen.css" />
    <link rel="stylesheet" href="assets/css/home.css" />
</head>
<body>
    <div class="site">
        <div class="site-hd" role="banner">
            <h1>Layout Test</h1>
        </div>
 
        <div class="site-bd" role="main">
            <h2>Welcome Home</h2>
            <ul>
                <li>apple</li>
                <li>orange</li>
                <li>banana</li>
            </ul>
        </div>
 
        <div class="site-ft" role="contentinfo">
            <small>&copy; 2013</small>
        </div>
    </div>
 
    <script src="assets/js/analytics.js"></script> 
    <script src="assets/js/controllers/home.js"></script> 
</body>
</html>

Standards for this project, including tests, code coverage, and semantics are enforced with a build tool. Pull requests must include passing tests with 100% code coverage and no linting errors.

$ npm test

© 2015 Shannon Moeller me@shannonmoeller.com

Licensed under MIT