Handlebars.js: Client Side Templating

Last modified: 29 November 2011

We use handlebars.js as a client side templating solution for our JavaScript. 

What kind of stuff can I do in a client-side template?

Handlebars is an extension to mustache templates, so you should read about that first. Next, read the handlebars.js homepage and also the readme on Github.

Writing and using templates

To create a new template, name it with a .handlebars suffix, place it in the appropriate package directory under javascript/, and add it to the templates list in the package inside of packages.py.  At runtime, code in that JavaScript package (or other packages further down the dependency chain) will be able to access the template by referencing it using the name package.filename.

Example directory layout
javascript/
  profile-package/
    profile.js
    sometemplate.handlebars

packages.py:
javascript = {
  ...
  "profile": {
    "templates": [
      "sometemplate.handlebars",
      ...
    ],
    "javascript": [
      "profile.js"
      ...


sometemplate.handlebars:
<div>Hello, {{ name }}</div>

profile.js:
var template = Templates.get("profile.sometemplate");
var context = { name: "World!" };
$("#container").html(template(context));   // inserts "<div>Hello, World!</div>" into the container

Details of how it works

There are two modes in which the templates can be served: debug and compiled. 

Debug Mode

When running on a local dev instance, the contents of the handlebars templates will be embedded into the page in which they're needed as inline <script> tags whenever you reference the package in a jinja2 template. Check out js_css_packages/templatetags.py for details of how this works. The short story is that the server will read the contents of the template file and embed it into the page when it serves the page. However, since the templates live under javascript/, which is a directory specified for static file hosting, GAE will disallow reading from the template file under javascript/. As a result, we've created a symlink called clienttemplates/ to get around this.

The code needing to use the template need not worry about this though; it simply just uses the abstraction provided by templates.js and calls Template.get("package.name") to access the template as usual. Under the hood, templates.js will retrieve the contents of the appropriate inline <script> tag, and call Handlebars.compile on the content to convert it to a function at runtime. 

Compile Mode

At deploy time, handlebar templates are "compiled" into a simple JavaScript function so that they don't need to be compiled at runtime. The compiled JavaScript template will be included in the JS package before all other JS files in that package, and minified as normal.

Again, this is all abstracted by templates.js, so the code to user the templates will not change.

Since we don't need runtime compilation of the templates in production, we also serve a much smaller version of the handlebars library, called handlebars.vm.js. 

Unit tests & use in Python

In order to support rendering Handlebars templates server-side we use a tiny, badly supported library called PyBars (/third_party/pybars).

To ensure that Handlebars templates are rendered identically on both client and server, all templates that are used on the server are rendered using test data in both PyBars and Node.js by a (large-size) unit test. The test data sits in companion files to the .handlebars files, so if your template is in /javascript/shared_package/mytemplate.handlebars, the test data in JSON format should be in /javascript/shared_package/mytemplate.handlebars.json. If there is no test data for a template, it will not be compiled into Python and will not be renderable on the server.

A simple way to get some test data for a template that is already rendering client-side is to temporarily uncomment the line in Templates.get() in /javascript/shared-package/templates.js that writes the JSON'ified context to the console.

The unit test will catch bugs in PyBars, helpers that are implemented in JS but have no Python equivalents, and a few cases where something magically works in JS even though it shouldn't. To make sure all the templates are working, run python tools/runtests.py --max-size=large handlebars.unit_test or use the make allcheck command to run the full suite of unit tests.

Embedded within jinja2 templates

Handlebars templates can be rendered server-side using the handlebars_template jinja2 template tag, for example:
{{ handlebars_template('video', 'video-wrapper', {'video': video}) }}

Note that any helper functions used in the template must be defined in python on the server as well; see handlebars_helpers in /handlebars/render.py.

Word of warning; the Handlebars parser on the server seems to be stricter than the client-side parser, and will simply silently stop rendering a template if certain extra spacing is including inside tags, as in:
{{#if true}}I should always get displayed{{/if}}
{{#if true }}I probably won't be, if rendered server-side{{/if }}

How to deploy

See the Deploy page.
Comments