iopa-template-handlebars
Version:
Handlebars Template Engine for the IOPA Stack for Node.js
284 lines (192 loc) • 9.97 kB
Markdown
# [](http://iopa.io)<br> iopa-template-handlebars
[](https://app.shippable.com/projects/56e32c2e9d043da07bc08f19)
[](http://iopa.io)
[](https://nodei.co/npm/limerun/)
[](https://nodei.co/npm/iopa-template-handlebars/)
## About
`iopa-template-handlebars` is IOPA middleware for rendering templates using handlebars template engine
## Installation
```js
$ npm install iopa-template-handlebars
```
## Credits
This project, including documentation, was forked under BSD license from the Yahoo [ericf/express-handlebars](https://github.com/ericf/express-handlebars) with various changes throughout to map to IOPA vs Express.
To date, the raw parsing routines have not been adjusted materially from the Yahoo implementation.
## Goals and Features
After building a half-dozen Express apps, the original author developed requirements and opinions about what a Handlebars view engine should provide and how it should be implemented. The following is that list:
* Add the concept of "layout"
* Add the concept of "partials" via Handlebars' partials mechanism.
* Support a directories of partials; e.g., `{{> foo/bar}}` which exists on the file system at `views/partials/foo/bar.handlebars`, by default.
* Smart file system I/O and template caching. When in development, templates are always loaded from disk. In production, raw files and compiled templates are cached, including partials.
* All async and non-blocking. File system I/O is slow and servers should not be blocked from handling requests while reading from disk. I/O queuing is used to avoid doing unnecessary work.
* Ability to easily precompiled templates and partials for use on the client, enabling template sharing and reuse.
* Ability to use a different Handlebars module/implementation other than the Handlebars npm package.
### Package Design
This package was designed to work great for both the simple and complex use cases. We _intentionally_ made sure the full implementation is exposed and is easily overridable.
The package exports a function which can be invoked with no arguments or with a `config` object and it will return a function (closed over sane defaults) which can be registered with an IOPA app. It's an engine factory function.
This exported engine factory has four properties which expose the underlying implementation:
* (default): The simplest IOPA middleware to use in `app.user(handlebars)`;
* `IopaHandlebars()`: The constructor function which holds the internal implementation on its `prototype`. This produces instance objects which store their configuration, `compiled` and `precompiled` templates, and expose an `engine()` function which can be registered with an IOPA app.
* `engine()`: A convenience factory function for creating `IopaHandlebars` instances.
An instance-based approach is used so that multiple `IopaHandlebars` instances can be created with their own configuration, templates, partials, and helpers.
## Installation
Install using npm:
```shell
$ npm install express-handlebars
```
## Usage
This view engine uses sane defaults that leverage the "IOPA-way" of structuring an app's views. This makes it trivial to use in basic apps:
### Basic Usage
**Directory Structure:**
```
.
├── app.js
└── views
├── home.hbs
└── layouts
└── main.hbs
2 directories, 3 files
```
**app.js:**
Creates a super simple IOPA app which shows the basic way to register a Handlebars view engine using this package.
```javascript
const iopa = require('iopa'),
templates = require('iopa-templates'),
handlebars = require('iopa-template-handlebars'),
iopaConnect = require('iopa-connect'),
http = require('http'),
var app = new iopa.App();
app.use(templates);
app.engine('.hbs', handlebars({defaultLayout: 'main', views: 'test/views'}));
app.use(function(context, next) {
return context.render('home.hbs');
});
http.createServer(app.buildHttp()).listen(3000);
```
**views/layouts/main.handlebars:**
The main layout is the HTML page wrapper which can be reused for the different views of the app. `{{{body}}}` is used as a placeholder for where the main content should be rendered.
```handlebars
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Example App</title>
</head>
<body>
{{{body}}}
</body>
</html>
```
**views/home.handlebars:**
The content for the app's home view which will be rendered into the layout's `{{{body}}}`.
```handlebars
<h1>Example App: Home</h1>
```
#### Running the Example
The above example is bundled in this package's `demo.js`, where it can be run by:
```shell
$ node demo
```
### Template Caching
This view engine uses a smart template caching strategy. In development, templates will always be loaded from disk, i.e., no caching. In production, raw files and compiled Handlebars templates are aggressively cached.
### Layouts
A layout is simply a Handlebars template with a `{{{body}}}` placeholder. Usually it will be an HTML page wrapper into which views will be rendered.
This view engine supports the concept of "layout". It can be configured with a path to the layouts directory, by default it's set to `"views/layouts/"`.
The layout into which a view should be rendered can be overridden per-request by assigning a different value to the `layout` request local. The following will render the "home" view with no layout:
```javascript
app.get('/', function (context) {
context.render('home', {layout: false});
});
```
### Helpers
Helper functions, or "helpers" are functions that can be [registered with Handlebars][] and can be called within a template. Helpers can be used for transforming output, iterating over data, etc. To keep with the spirit of *logic-less* templates, helpers are the place where logic should be defined.
Handlebars ships with some [built-in helpers][], such as: `with`, `if`, `each`, etc. Most application will need to extend this set of helpers to include app-specific logic and transformations. Beyond defining global helpers on `Handlebars`, this view engine supports `IopaHandlebars` instance-level helpers via the `helpers` configuration property, and render-level helpers via `options.helpers` when calling the `render()` and `renderView()` methods.
The following example shows helpers being specified at each level:
**app.js:**
Creates a super simple IOPA app which shows the basic way to register `IopaHandlebars` instance-level helpers, and override one at the render-level.
```javascript
const iopa = require('iopa'),
templates = require('iopa-templates'),
handlebars = require('./index.js'),
http = require('http'),
iopaConnect = require('iopa-connect')
router = require('iopa-router'),
helpers = require('./test/advanced/lib/helpers');
var app = new iopa.App();
app.use(templates);
app.use(router);
app.engine('.hbs', handlebars({
defaultLayout: 'main',
views: 'test/advanced/views',
helpers : helpers,
partialsDir: [
'shared/templates/',
'views/partials/'
]}));
app.get('/', function (context) {
return context.render('home', {
title: 'Home'
});
});
app.get('/yell', function (context) {
return context.render('yell', {
title: 'Yell',
// This `message` will be transformed by our `yell()` helper.
message: 'hello world'
});
});
app.get('/exclaim', function (context) {
return context.render('yell', {
title : 'Exclaim',
message: 'hello world',
// This overrides _only_ the default `yell()` helper.
helpers: {
yell: function (msg) {
return (msg + '!!!');
}
}
});
});
var port = process.env.PORT || 3000;
http.createServer(app.buildHttp()).listen(port);
console.log('listening at:', port);
```
**views/home.handlebars:**
The app's home view which uses helper functions to help render the contents.
```handlebars
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Example App - Home</title>
</head>
<body>
<!-- Uses built-in `if` helper. -->
{{#if showTitle}}
<h1>Home</h1>
{{/if}}
<!-- Calls `foo` helper, overridden at render-level. -->
<p>{{foo}}</p>
<!-- Calls `bar` helper, defined at instance-level. -->
<p>{{bar}}</p>
</body>
</html>
```
#### More on Helpers
Refer to the [Handlebars website][] for more information on defining helpers:
* [Expression Helpers][]
* [Block Helpers][]
### Metadata
Handlebars has a data channel feature that propagates data through all scopes, including helpers and partials. Values in the data channel can be accessed via the `{{@variable}}` syntax. IOPA Handlebars provides metadata about a template it renders on a model object allowing access to things like the view name passed to `context.render()`
The following is the list of metadata that's accessible on the data object:
* `cache`: Boolean whether or not the template is cached.
* `view`: String name of the view passed to `res.render()`.
* `layout`: String name of the layout view.
* `data`: Original data object passed when rendering the template.
* `helpers`: Collection of helpers used when rendering the template.
* `partials`: Collection of partials used when rendering the template.
[registered with Handlebars]: https://github.com/wycats/handlebars.js/#registering-helpers
[built-in helpers]: http://handlebarsjs.com/#builtins
[Handlebars website]: http://handlebarsjs.com/
[Expression Helpers]: http://handlebarsjs.com/expressions.html#helpers
[Block Helpers]: http://handlebarsjs.com/block_helpers.html