Handlebars.java is a Java port of handlebars.
Handlebars provides the power necessary to let you build semantic templates effectively with no frustration.
Mustache templates are compatible with Handlebars, so you can take a Mustache template, import it into Handlebars, and start taking advantage of the extra Handlebars features.
In general, the syntax of Handlebars templates is a superset of Mustache templates. For basic syntax, check out the Mustache manpage.
Development version: 0.6.3-SNAPSHOT
Stable version: 0.6.2
<dependency>
<groupId>com.github.jknack</groupId>
<artifactId>handlebars</artifactId>
<version>${handlebars-version}</version>
</dependency>Handlebarshandlebars = newHandlebars();
Templatetemplate = handlebars.compile("Hello {{this}}!");
System.out.println(template.apply("Handlebars.java"));Output:
Hello Handlebars.java!
The handlebars.java server is small application where you can write Mustache/Handlebars template and merge them with data.
It is a useful tool for Web Designers.
Download from Maven Central:
- Go here
- Under the Download section click on jar
Maven:
<dependency>
<groupId>com.github.jknack</groupId>
<artifactId>handlebars-proto</artifactId>
<version>${current-version}</version>
</dependency>Usage:
java -jar handlebars-proto-${current-version}.jar -dir myTemplates
Example:
myTemplates/home.hbs
<ul>
{{#items}}
{{name}}
{{/items}}
</ul>
myTemplates/home.json
{
items: [
{
name: "Handlebars.java rocks!"
}
]
}or if you prefer YAML myTemplates/home.yml:
list:
- name: Handlebars.java rocks!http://localhost:6780/home.hbs
enjoy it!
- -dir: set the template directory
- -prefix: set the template's prefix, default is /
- -suffix: set the template's suffix, default is .hbs
- -context: set the context's path, default is /
- -port: set port number, default is 6780
- -content-type: set the content-type header, default is text/html
Sometimes you need or want to test multiples datasets over a single template, you can do that by setting a data parameter in the request URI.
Example:
http://localhost:6780/home.hbs?data=mytestdata
Please note you don't have to specified the extension file.
- with
- each
- if
- unless
- log
- block
- partial
- precompile
- embedded
See the built-in helper documentation.
Block and partial helpers work together to provide you Template Inheritance.
Usage:
{{#block "title"}}
...
{{/block}}
context: A string literal which define the region's name.
Usage:
{{#partial "title"}}
...
{{/partial}}
context: A string literal which define the region's name.
Precompile a Handlebars.java template to JavaScript using handlebars.js
user.hbs
Hello {{this}}!home.hbs
<scripttype="text/javascript">{{precompile"user"}}</script>Output:
<scripttype="text/javascript">(function(){vartemplate=Handlebars.template,templates=Handlebars.templates=Handlebars.templates||{};templates['user.hbs']=template(function(Handlebars,depth0,helpers,partials,data){helpers=helpers||Handlebars.helpers;varbuffer="",functionType="function",escapeExpression=this.escapeExpression;buffer+="Hi ";depth0=typeofdepth0===functionType ? depth0() : depth0;buffer+=escapeExpression(depth0)+"!";returnbuffer;});})();</script>Usage:
{{precompile "template" [wrapper="anonymous, amd or none"]}}
context: A template name. Required.
wrapper: One of "anonymous", "amd" or "none". Default is: "anonymous"
The embedded helper allow you to "embedded" a handlebars template inside a <script> HTML tag:
user.hbs
<tr><td>{{firstName}}</td><td>{{lastName}}</td></tr>home.hbs
<html>
...
{{embedded "user"}}
...
</html>Output:
<html>
...
<scriptid="user-hbs" type="text/x-handlebars"><tr><td>{{firstName}}</td><td>{{lastName}}</td></tr></script>
...
</html>Usage:
{{embedded "template"}}
context: A template name. Required.
handlebars.registerHelper("blog", newHelper<Blog>() {
publicCharSequenceapply(Blogblog, Optionsoptions) {
returnoptions.fn(blog);
}
});handlebars.registerHelper("blog-list", newHelper<List<Blog>>() {
publicCharSequenceapply(List<Blog> list, Optionsoptions) {
Stringret = "<ul>";
for (Blogblog: list) {
ret += "<li>" + options.fn(blog) + "</li>";
}
returnnewHandlebars.SafeString(ret + "</ul>");
}
});handlebars.registerHelper("blog-list", newHelper<Blog>() {
publicCharSequenceapply(List<Blog> list, Optionsoptions) {
Stringp0 = options.param(0);
assertEquals("param0", p0);
Integerp1 = options.param(1);
assertEquals(123, p1);
...
}
});
Beanbean = newBean();
bean.setParam1(123);
Templatetemplate = handlebars.compile("{{#blog-list blogs \"param0\" param1}}{{/blog-list}}");
template.apply(bean);handlebars.registerHelper("blog-list", newHelper<Blog>() {
publicCharSequenceapply(List<Blog> list, Optionsoptions) {
Stringp0 = options.param(0, "param0");
assertEquals("param0", p0);
Integerp1 = options.param(1, 123);
assertEquals(123, p1);
...
}
});
Templatetemplate = handlebars.compile("{{#blog-list blogs}}{{/blog-list}}");handlebars.registerHelper("blog-list", newHelper<Blog>() {
publicCharSequenceapply(List<Blog> list, Optionsoptions) {
Stringclass = options.hash("class");
assertEquals("blog-css", class);
...
}
});
handlebars.compile("{{#blog-list blogs class=\"blog-css\"}}{{/blog-list}}");handlebars.registerHelper("blog-list", newHelper<Blog>() {
publicCharSequenceapply(List<Blog> list, Optionsoptions) {
Stringclass = options.hash("class", "blog-css");
assertEquals("blog-css", class);
...
}
});
handlebars.compile("{{#blog-list blogs}}{{/blog-list}}");file:line:column: message
evidence
^
[at file:line:column]
Examples:
template.hbs
{{value
/templates.hbs:1:8: found 'eof', expected: 'id', 'parameter', 'hash' or '}'
{{value
^
If a partial isn't found or if has errors, a call stack is added
/deep1.hbs:1:5: The partial '/deep2.hbs' could not be found
{{> deep2
^
at /deep1.hbs:1:10
at /deep.hbs:1:10
Helper or runtime errors are similar to syntax errors, except for two thing:
- The location of the problem may (or may not) be the correct one.
- The stack-trace isn't available
Examples:
Block helper:
publicCharSequenceapply(finalObjectcontext, finalOptionsoptions) throwsIOException {
if (context == null) {
thrownewIllegalArgumentException(
"found 'null', expected 'string'");
}
if (!(contextinstanceofString)) {
thrownewIllegalArgumentException(
"found '" + context + "', expected 'string'");
}
...
}base.hbs
{{#block}} {{/block}}
Handlebars.java reports:
/base.hbs:2:4: found 'null', expected 'string'
{{#block}} ... {{/block}}
In short from a helper you can throw an Exception and Handlebars.java will add the filename, line, column and the evidence.
Let's say you need to access to the current logged-in user in every single view/page. You can publishing the current logged in user by hooking into the context-stack. See it in action:
hookContextStack(Objectmodel, Templatetemplate) {
Useruser = ....;// Get the logged-in user from somewhereMapmoreData = ...;
Contextcontext = Context
.newBuilder(model)
.combine("user", user)
.combine(moreData)
.build();
template.apply(user);
context.destroy();
}Where is the hookContextStack method? Well, that will depends on your application architecture.
By default, Handlebars.java use the JavaBean methods (i.e. public getXxx methods) and Map as value resolvers.
You can choose a different value resolver. This section describe how to do it.
Resolves a value as public method prefixed with "get"
Contextcontext = Context
.newBuilder(model)
.resolver(JavaBeanValueResolver.INSTANCE)
.build();Resolves a value as no-static field.
Contextcontext = Context
.newBuilder(model)
.resolver(FieldValueResolver.INSTANCE)
.build();It resolve values as map key.
Contextcontext = Context
.newBuilder(model)
.resolver(MapValueResolver.INSTANCE)
.build();Resolves a value as public method.
Contextcontext = Context
.newBuilder(model)
.resolver(MethodValueResolver.INSTANCE)
.build();Finally, you can merge multiples value resolvers
Contextcontext = Context
.newBuilder(model)
.resolver(
MapValueResolver.INSTANCE,
JavaBeanValueResolver.INSTANCE,
FieldValueResolver.INSTANCE)
.build();Functions like abbreviate, capitalize, join, dateFormat, yesno, etc., are available from [StringHelpers] (https://github.com/jknack/handlebars.java/blob/master/handlebars/src/main/java/com/github/jknack/handlebars/StringHelpers.java).
StringHelpers.register(handlebars);Maven:
<dependency>
<groupId>com.github.jknack</groupId>
<artifactId>handlebars-json</artifactId>
<version>${handlebars-version}</version>
</dependency>
Usage:
handlebars.registerHelper("json", newJSONHelper()); {{json context [view="foo.MyFullyQualifiedClassName"]}}
Alternative:
handlebars.registerHelper("json", newJSONHelper().viewAlias("myView",
foo.MyFullyQualifiedClassName.class); {{json context [view="myView"]}}
context: An object, may be null.
view: The name of the Jackson View. Optional.
Maven:
<dependency>
<groupId>com.github.jknack</groupId>
<artifactId>handlebars-jackson2</artifactId>
<version>${handlebars-version}</version>
</dependency>Similar to Jackson1.x, except for the name of the helper: Jackson2Helper
Maven:
<dependency>
<groupId>com.github.jknack</groupId>
<artifactId>handlebars-markdown</artifactId>
<version>${handlebars-version}</version>
</dependency>Usage:
handlebars.registerHelper("md", newMarkdownHelper()); {{md context}}
context: An object or null. Required.
Maven:
<dependency>
<groupId>com.github.jknack</groupId>
<artifactId>handlebars-humanize</artifactId>
<version>${handlebars-version}</version>
</dependency>Usage:
// Register all the humanize helpers.HumanizeHelper.register(handlebars);See the JavaDoc of the [HumanizeHelper] (https://github.com/jknack/handlebars.java/blob/develop/handlebars-humanize/src/main/java/com/github/jknack/handlebars/HumanizeHelper.java) for more information.
Maven:
<dependency>
<groupId>com.github.jknack</groupId>
<artifactId>handlebars-springmvc</artifactId>
<version>${handlebars-version}</version>
</dependency>Using value resolvers:
HandlebarsViewResolverviewResolver = ...;
viewResolver.setValueResolvers(...);In addition, the HandlebarsViewResolver add a message helper that uses the Spring MessageSource class:
{{message "code" [arg]* [default="default message"]}}
where:
- code: the message's code. Required.
- arg: the message's argument. Optional.
- default: the default's message. Optional.
Checkout the HandlebarsViewResolver.
- Handlebars.java follows the JavaScript API with some minors exceptions due to the nature of the Java language.
- The parser is built on top of [Parboiled] (https://github.com/sirthias/parboiled).
- Data is provided as primitive types (int, boolean, double, etc.), strings, maps, list or JavaBeans objects.
- Helpers are type-safe.
- Handlebars.java is thread-safe.
- Handlebars.java scope resolution follows the Mustache Spec.
Given:
{
"value": "parent",
"child": {
}
}and
Hello {{#child}}{{value}}{{/child}}will be:
Hello parentNow, the same model and template with Handlebars.js is:
Hello That is because Handlebars.js don't look in the context stack for missing attribute in the current scope (as the Mustache Spec says).
Hopefully, you can turn-off the context stack lookup in Handlebars.java by qualifying the attribute with this.:
Hello {{#child}}{{this.value}}{{/child}}- Passes 123 of 127 tests from the Mustache Spec.
- The 4 missing tests are: "Standalone Line Endings", "Standalone Without Previous Line", "Standalone Without Newline", "Standalone Indentation" all them from partials.yml.
- In short, partials works 100% if you ignore white spaces indentation.
Handlebars.java depends on:
+- org.apache.commons:commons-lang3:jar:3.1:compile
+- org.parboiled:parboiled-java:jar:1.1.3:compile
| +- org.parboiled:parboiled-core:jar:1.1.3:compile
| +- org.ow2.asm:asm:jar:4.0:compile
| +- org.ow2.asm:asm-tree:jar:4.0:compile
| +- org.ow2.asm:asm-analysis:jar:4.0:compile
| \- org.ow2.asm:asm-util:jar:4.0:compile
+- org.mozilla:rhino:jar:1.7R4:compile
+- org.slf4j:slf4j-api:jar:1.6.4:compile
- Fork the project on Github.
- Wandering what to work on? See task/bug list and pick up something you would like to work on.
- Create an issue or fix one from issues list.
- If you know the answer to a question posted to our mailing list - don't hesitate to write a reply.
- Share your ideas or ask questions on mailing list - don't hesitate to write a reply - that helps us improve javadocs/FAQ.
- If you miss a particular feature - browse or ask on the mailing list - don't hesitate to write a reply, show us a sample code and describe the problem.
- Write a blog post about how you use or extend handlebars.java.
- Please suggest changes to javadoc/exception messages when you find something unclear.
- If you have problems with documentation, find it non intuitive or hard to follow - let us know about it, we'll try to make it better according to your suggestions. Any constructive critique is greatly appreciated. Don't forget that this is an open source project developed and documented in spare time.
- Mathias: For the parboiled PEG library
- Handlebars.js
