Handlebar / Mustache
com.github.jknack.handlebarsMustache
Basic Usage: hash + template -> render
Variable:
- {{this}} - the context object, and no search to the parent and ancestors in the context stack
- {{name}} - HTML escaped by default;
- by default variable miss returns empty string
Unescaped: {{{name}}} or {{& name }}
Section: {{#boolean_val}} {{/boolean_val}}
- render 0, 1 or many times
- false, or empty lists : no render
- non-empty list: one or more times, context of block set for each of items
- callable object (lambdas): lambda called with (text, render) - javascript?
- non-false, not a list: the object is used as context for single rendering
Inverted Sections {{^key}} {{/key}}
- render once if key does not exist, is false, or is an empty list
Comments {{! some comment }}
Partials {{> key }}
- rendered at run time
- recursive partials are possible
- use as includes/imports/nests
- to include "key.hbs" into the place (handlebars)
- not relative path, everytime use "path1/path2/file" to refer absolute path /path1/path2/file.hbs (from class root, for example)
- inherit context
Template inheritance
- use the block / partial syntax
- {{#partial "id" }}...{{/partial}} - define a partial
- {{#block "id"}}....{{/block}} - define a block to be filled with a partial
{{> (partialNameHelper) }}
- dynamic partial (https://github.com/jknack/handlebars.java/issues/375)
- partialNameHelper shoud be a name (key, above)
- to use context, {{> (lookup . 'myVariable') }}
Set delimiter {{=<% %>=}} change {{ }} to <% %>
Handlebars
http://jknack.github.io/handlebars.java/
Handlebar has helpers to extend mustache, some built-ins are:
with {{#with story}} ... {{/with}} - switch context
each {{#each a_collection }} ... {{/each}} / can do with plain mustache {{#
if {{#if active}} ...{{else}} ... {{/if}} / or plain mustache
unless {{#unless active}}...{{/unless}} / can do with plain mustache
Helpers:
See http://jknack.github.io/handlebars.java/helpers.html
Basic helpers {{name context? [argument]* [hash]*}}
Block helpers {{#name context? [argument]* [hash]* }} ... {{/name}}
name context? [argument]* [hash]*}}
...
{{/name}}
Name, context, argument, and hash are defined as follows:
name: qid;
context: expression;
argument: expression;
hash: id '=' expression;
expression: literal | qid;
qid
:
'../' qid
| id '[' .* ']' ('.' | '/') qid
| id '[' .* ']' qid
| id ('.' | '/') qid
| 'id'
;
id: idStart (idStart | idPart)*;
idStart: [a-zA-Z_$@];
idPart: [0-9];
literal
:
stringLiteral
| boolLiteral
| numberLiteral
| 'this'
| '.'
;
stringLiteral:'"' .* '"';
boolLiteral:'true' | 'false';
numberLiteral: '0'..'9'+;
To register custom helpers
Register one at a time:
handlebars.registerHelper("blog", new Helper<Blog>() {
public CharSequence apply(Blog blog, Options options) {
return options.fn(blog);
}
});
To register multiple helpers at once, register a
Helper Source
- a class
- public method (registered as a helper)
- returning a CharSequence
- can be static
- name -> name of helper
- Context, parameters and options are all optionals
- If context is present it must be the first method argument
- If options is present it must be the last method argument
Example:
public class HelperSource {
public String blog(Blog blog, Options options) {
return options.fn(blog);
}
public static String now() {
return new Date().toString();
}
public String render(Blog context, String param0, int param1,
boolean param2, Options options) {
return ...
}
}
Register: an instance, or xx.class
Helper options:
Parameter with position, with method argument, with auto boxing:
{{blog this "arg0"}}
...
public CharSequence apply(Blog blog, Options options) {
return options.param(0);
}
{{blog this "arg0"}}
...
public CharSequence blog(Blog blog, String arg0) {
return arg0;
}
{{blog this "string" true 678}}
...
public CharSequence blog(Blog blog, Options options) {
String str = options.param(0);
boolean bool = options.param(1);
int num = options.param(2);
}
{{blog this "string" true 678}}
...
public CharSequence blog(Blog blog, String str, boolean bool, int num) {
}
Access a context stack value
{{blog this author}}
...
public CharSequence blog(Blog blog, Author author) {
return author.getName();
}
handlebars.registerHelper(Handlebars.HELPER_MISSING
Registers a default helper
Loader
http://jknack.github.io/handlebars.java/loader.html
Able to load templates from file system, class path, web context and more
APIs: (no need to implement unless using non-default source)
- TemplateLoader
- Before name of template being solved, prefix & suffix gets added.
- String DEFAULT_PREFIX = "/"; (depends on specific template loader implementation)
- DEFAULT_SUFFIX = ".hbs"; (default .hbs but can be changed)
- E.g. "hello" with prefix=/site suffix=.html -> /site/hello.html
- Exactly same rules for partials {{> hello }}
- This is done by TemplateLoader
- Before name of template being solved, prefix & suffix gets added.
- TemplateSource
ClassPathTemplateLoader
- default loader if none is set
- default prefix: / suffix .hbs
- usage: handlebars.compile("hello");
- usage with specific prefix/suffix: see doc
FileTemplateLoader: see doc
ServletContextTemplateLoader: see doc
CompositeTemplateLoader: combine two or more loaders
Context Stack
Important concept. com.github.jknack.handlebars.Context object represents the context stack.
Resolution algorithm looks like: (first try current context, if not found, try parent, until resolved or resolved to null and nothing rendered)
while (context != null && context.get("value") == null) {
context = context.parent;
}
Extending Context Stack
- Combine: Context context = Context.newBuilder(first_for_example_a_model_hash).combine("user", second_for_example_user).build(); template.apply(context)
- Value Resolver
- by default, only maps & java bean objects are supported (registered)
- FieldValueResolver.INSTANCE : resolve to private/public fields
- MethodValueResolver.INSTANCE : access to public methods
- DIY resolver - create your own
- resolvers can be combined
Typesafe Template
See doc