UNPKG

iopa-template-handlebars

Version:

Handlebars Template Engine for the IOPA Stack for Node.js

284 lines (192 loc) 9.97 kB
# [![IOPA](http://iopa.io/iopa.png)](http://iopa.io)<br> iopa-template-handlebars [![Build Status](https://api.shippable.com/projects/56e32c2e9d043da07bc08f19/badge?branchName=master)](https://app.shippable.com/projects/56e32c2e9d043da07bc08f19) [![IOPA](https://img.shields.io/badge/iopa-middleware-99cc33.svg?style=flat-square)](http://iopa.io) [![limerun](https://img.shields.io/badge/limerun-certified-3399cc.svg?style=flat-square)](https://nodei.co/npm/limerun/) [![NPM](https://nodei.co/npm/iopa-template-handlebars.png?downloads=true)](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