Handpipe.js is a new & different take on handlebars templates. It uses ES6 generators to retrieve data asynchronously & Node's stream abstraction to compile & process templates memory & time efficiently.
Getting started
Example 1
Given a template in the DOM:
<!doctype html>
<div id="output"></div>
<script id="template" type="text/x-handpipe-template">
<div>
<h1>{{title}}</h1>
<p>{{desc}}</p>
</div>
</script>
<script src="bundle.js"></script>
Read, process and pipe to DOM element with ID "output":
// main.js
var innerhtml = require("innerhtml")
, hp = require("handpipe")
, template = document.getElementById("template")
, output = document.getElementById("output")
var compiler = hp({
title: function (next, cb) {
// Some async operation to get data for "title"
setTimeout(function () { cb(null, "foobar") }, 1000)
},
desc: "Lorem ipsum dolor sit"
})
innerhtml.createReadStream(template)
.pipe(compiler)
.pipe(innerhtml.createWriteStream(output))
Browserify this file to use in the browser:
npm install -g browserify
npm install innerhtml handpipe
browserify main.js > bundle.js
Example 2
Given template.html:
<!doctype html>
<div>
<h1>{{title}}</h1>
<p>{{desc}}</p>
</div>
Read, process and pipe to output.html:
var fs = require("fs")
, hp = require("handpipe")
var compiler = hp({
title: function (next, cb) {
// Some async operation to get data for "title"
setTimeout(function () { cb(null, "foobar") }, 1000)
},
desc: "Lorem ipsum dolor sit"
})
fs.createReadStream("template.html")
.pipe(compiler)
.pipe(fs.createWriteStream("output.html"))
To use this file:
- Install node >= 0.11
- Start node using
--harmony
flag.
API
handpipe([ data ])
Create a transform stream to compile a template and apply a data context to it. Pipe Handlebars in, and HTML/Markdown/whatever out. Shortcut for handpipe.compile().pipe(handpipe.apply([ data ]))
data
The data
object gets values for the template. Properties that require async processing are functions with the signature function (next, cb) {}
.
next
is an object that has a key
property. This is the name of the variable / function your code should invoke to retrieve the value. In loops, the next object will also contain an index
and an iterable
property.
Invoke cb
when the value has been retrieved. Pass the value as the second argument (error as the first if one occurred).
handpipe.compile()
Create a new handpipe compiler. The compiler is a transform stream you can pipe templates into and compiled template JS out from.
handpipe.apply([ data ])
Applies the data
to the compiled template. Returns a transform stream that you can pipe compiled template JS into and HTML/Markdown/whatever out from.
Template syntax
Template syntax is a subset of handlebars.
Variable output
Output variables using double curly braces:
{{varname}}
handpipe supports simple paths so you can output content below the current context:
{{foo.bar}} or {{foo/bar}}
Nested paths can also include ../
segments, which evaluate their paths against a parent context.
<h1>Comments</h1>
<div id="comments">
{{#each comments}}
<h2><a href="/posts/{{../permalink}}#{{id}}">{{title}}</a></h2>
<div>{{body}}</div>
{{/each}}
</div>
Loops
Use the each
block helper to iterate over arrays:
<ul>
{{#each sprockets}}
<li>{{name}} ({{teeth}})</li>
{{/each}}
</ul>
You might populate this template with the following JS:
hp({
sprockets: function (next, cb) {
db.sprockets.find({}).toArray(cb)
}
})
If sprocket objects do not have "name" and/or "teeth" properties then the data
object passed to handpipe will be queried so that a value can be provided. next.key
will be "name" or "teeth" (depending on which property is currently being evaluated), next.context
will be the current sprocket object.
Conditionals
Use the if
block helper to create conditionally rendered template blocks:
{{#if title}}
<h1>{{title}}</h1>
{{/if}}
With optional alternative:
{{#if title}}
<h1>{{title}}</h1>
{{else}}
<h1>Unnamed</h1>
{{/if}}
HTML escaping
handpipe HTML-escapes values returned by a {{expression}}
. If you don't want handpipe to escape a value, use the "triple-stash", {{{
.
<div class="entry">
<h1>{{title}}</h1>
<div class="body">
{{{body}}}
</div>
</div>
Comments
Use {{! comment }}
or {{!-- comment --}}
to create comments that don't appear in output HTML. Any comments that must contain {{
or }}
should use the {{!-- --}}
syntax.
Context re-binding
Use the with
block helper to alter the context for a template block:
<div class="entry">
<h1>{{title}}</h1>
{{#with author}}
<h2>By {{firstName}} {{lastName}}</h2>
{{/with}}
</div>