# `handlebars-layouts`

[![NPM version][npm-img]][npm-url] [![Downloads][downloads-img]][npm-url] [![Build Status][travis-img]][travis-url] [![Coverage Status][coveralls-img]][coveralls-url] [![Tip][amazon-img]][amazon-url]

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

## Install

With [Node.js](http://nodejs.org):

    $ npm install handlebars-layouts

With [Bower](http://bower.io):

    $ bower install shannonmoeller/handlebars-layouts

## API

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

### `layouts(handlebars) : Object`

- `handlebars` `Handlebars` - An instance of Handlebars.

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

```js
var handlebars = require('handlebars'),
    layouts = require('handlebars-layouts');

handlebars.registerHelper(layouts(handlebars));
```

### `layouts.register(handlebars) : Object`

- `handlebars` `Handlebars` - An instance of Handlebars.

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

```js
var handlebars = require('handlebars'),
    layouts = require('handlebars-layouts');

layouts.register(handlebars);
```

## Helpers

### `{{#extend [partial] [context] [key=value ...]}}`

- `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.

```handlebars
{{#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:

```js
class Page extends Layout {
    constructor() {
        this.foo = 'bar';
    }

    title() {
        return 'Example - ' + super();
    }
}
```

### `{{#embed [partial] [context] [key=value ...]}}`

- `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.

```handlebars
{{#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:

```js
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();
    }
}
```

### `{{#block [name]}}`

- `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.

```handlebars
{{#block "header"}}
    <h1>Hello World</h1>
{{/block}}

{{#block "main"}}
    <p>Lorem ipsum...</p>
{{/block}}

{{#block "footer"}}
    <p>&copy; 1970</p>
{{/block}}
```

### `{{#content [name] mode="(append|prepend|replace)"}}`

- `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:

```handlebars
<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:

```handlebars
{{#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:

```handlebars
<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>
```

### Conditional Blocks

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](http://handlebarsjs.com/expressions.html#subexpressions) to check whether content has been provided for a block.

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

```handlebars
{{!-- 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:

```handlebars
{{!-- page.html --}}
{{#extend "layout"}}

    {{#content "left"}}
        <p>Left</p>
    {{/content}}

{{/extend}}
```

Resulting in:

```html
<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:

```handlebars
{{!-- page.html --}}
{{#extend "layout"}}

    {{#content "left"}}
        <p>Left</p>
    {{/content}}

    {{#content "right"}}
        <p>Right</p>
    {{/content}}

{{/extend}}
```

Resulting in:

```html
<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>
```

## Example

### layout.hbs

```handlebars
<!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>
```

### page.html

```handlebars
{{#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}}
```

### Putting Them Together

```js
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);
```

### Output (prettified for readability)

```handlebars
<!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>
```

## Contribute

[![Tasks][waffle-img]][waffle-url]

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.

## Test

    $ npm test

----

© 2015 Shannon Moeller <me@shannonmoeller.com>

Licensed under [MIT](http://shannonmoeller.com/mit.txt)

[amazon-img]:    https://img.shields.io/badge/tip-jar-yellow.svg?style=flat-square
[amazon-url]:    https://www.amazon.com/gp/registry/wishlist/1VQM9ID04YPC5?sort=universal-price
[coveralls-img]: http://img.shields.io/coveralls/shannonmoeller/handlebars-layouts/master.svg?style=flat-square
[coveralls-url]: https://coveralls.io/r/shannonmoeller/handlebars-layouts
[downloads-img]: http://img.shields.io/npm/dm/handlebars-layouts.svg?style=flat-square
[npm-img]:       http://img.shields.io/npm/v/handlebars-layouts.svg?style=flat-square
[npm-url]:       https://npmjs.org/package/handlebars-layouts
[travis-img]:    http://img.shields.io/travis/shannonmoeller/handlebars-layouts.svg?style=flat-square
[travis-url]:    https://travis-ci.org/shannonmoeller/handlebars-layouts
[waffle-img]:    http://img.shields.io/github/issues/shannonmoeller/handlebars-layouts.svg?style=flat-square
[waffle-url]:    http://waffle.io/shannonmoeller/handlebars-layouts
