0.4 #permalink Handlebars

Toggle example guides Toggle HTML markup

Handlebars is a minimal templating language using embedded expressions as variables. The templates are precompiled into raw javascript functions to lessen the burden on the client, but keep in mind the advice in the last section that adding or modifying elements in javascript can introduce an unwanted flash. They're perfect for adding content in response to an AJAX call (see the login.hbs template and how it's used in login.bsfy) or to reduce repetition in elements that aren't visible on pageload (see bio.hbs and team.bsfy).

See the documentation for full details, but here's the gist of how it fits into our stack:

Client-side use

  • Write your template in a file in meta/www/ with an .hbs extension. For instance, suppose you write a hello.hbs file with this content:

    <h1>Hi, {{name}}</h1>
     <p>My favorite color is {{color}}</p>
    
  • Then just require the template in your page's bsfy file. The gulp javascript command contains a browserify transform that compiles hbs templates into browserify-ready components.

    var hello = require('../../v2/js/templates/hello.hbs');
    
  • Next, get a javascript object with the context the template requires. You could build it in your bsfy file as below (and as in team.bsfy), or it could just as easily be fetched with an ajax call. This is how the login section works.

    var model = {
      name: "Nick",
      color: "orange"
    }
    
  • Finally, saturating the template with the context (the template exports a function which is passed the context as an argument) results in a string containing the resulting html, so just put it in the place you want it to go in the DOM:

    helloHtml = hello(model);
    $('.js-helloDiv').html(helloHtml);
    

    This will result in the div with class js-helloDiv receiving this completed html when the javascript is loaded and processed:

    <h1>Hi, Nick</h1>
     <p>My favorite color is orange</p>
    

Server-side use

As of May 2016, you can now include handlebars files from within your freemarker files. The point here is to abstract your components' html markup into handlebars templates, which are first rendered on the server (for fast initial pageload and no flash or loading spinner), then can be updated client-side (as in the previous section) in response to user actions or ajax calls. Awesome! Previously, a function like this would have required duplicating markup in both an ftl or html file and an hbs file (or jquery/raw javascript).

This functionality is provided by a taglib, and used as follows: simply include the tag library descriptor from your ftl file:

   <#assign hbs=JspTaglibs["/WEB-INF/handlebars.tld"]>

Then place the tag where you want the template to be rendered. The tag takes one argument: the path of the template to be used, relative to the www folder, and without extension:

   <@hbs.hbs template="v2/js/templates/tile"/>

The handlebars template has access to all public getters in your action, just as freemarker does, so if you have a getTotal() method, you can access its return value from handlebars by {{total}}. In addition, the handlebars template should have access to all variables prior #assigned in your freemarker template, as well as any local scope variables defined in a loop or macro call inside which the tag is placed. In short, any variable you can refer to from freemarker by $​{foo} should be available in handlebars as {{foo}}. There may be other things you normally have access to that are unavailable to handlebars such as properties files or text processors. Feel free to add any additional context you may need in the GGHandlebars.getContext method, or ask Nick for help.

Client-server integration

The system as built only provides a consistent view layer. It's up to the developer to decide on the model implementation and the controller logic, and to ensure that they are treated consistently on initial pageload and in response to user interaction. I'm open to either a structured set of functions to simplify or abstract these matters, or a convention that we agree on for how to do this.

Handlebars Helpers

Helpers are small javascript functions that provide added functionality to the templating system. Because of the restrictive (by design) nature of the language, it sometimes saves a lot of time to write a small function to provide the missing functionality, but before jumping to this solution I suggest you consider whether you could refactor the logic you're trying to provide into the javascript code, the action, or the freemarker template. Although the java handlebars implementation is able to use Rhino to interpret helpers written in javascript, it seems unacceptably slow (in limited local testing, it seems on the order of 1s per helper). Unless anyone can figure out a way around that, I suggest we not use this function, with the possible exception of static cached pages.

Instead, you can write two versions of a helper you want to use: one in javascript and one in java (in the HelperSource class). Note however, that some shortcuts that take advantage of javascript's untyped nature of course won't work in java, so tread carefully. See the {{#compare}} and {{#math}} helpers which are implemented this way.

Handlebars partials

To my surprise, they just work (with one little caveat)! The java implementation searches for them from the root of www, just as it does to resolve the templates when using the tag library. So if you write a partial in meta/www/path/to/page/partial.hbs, you can use it within any handlebars template by `{{> path/to/page/partial}}. This means, however, that that's the name of the partial, so if you want to use it on the client side, your registration has to be a little clunky:

    Handlebars = require("hbsfy/runtime");
     var myPartial = require("./partial.hbs");                     //location relative to the file you're writing in
     Handlebars.registerPartial("path/to/page/partial",myPartial); //the name you used in the template; relative to www
Example
Markup:
Markup
Markup:
Source: styleguide/styleguide.less, line 208