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.
The Handlebars.java blog is a good place for getting started too.
<dependency>
<groupId>com.github.jknack</groupId>
<artifactId>handlebars</artifactId>
<version>${handlebars-version}</version>
</dependency>
SNAPSHOT versions are NOT synchronized to Central. If you want to use a snapshot version you need to add the https://oss.sonatype.org/content/repositories/snapshots/ repository to your pom.xml.
Handlebars handlebars = new Handlebars();
Template template = handlebars.compileInline("Hello {{this}}!");
System.out.println(template.apply("Handlebars.java"));
Output:
Hello Handlebars.java!
Templates are loaded using the TemplateLoader
class. Handlebars.java provides three implementations of a TemplateLodaer
:
- ClassPathTemplateLoader (default)
- FileTemplateLoader
- SpringTemplateLoader (see the handlebars-springmvc module)
This example load mytemplate.hbs
from the root of the classpath:
mytemplate.hbs:
Hello {{this}}!
Handlebars handlebars = new Handlebars();
Template template = handlebars.compile("mytemplate");
System.out.println(template.apply("Handlebars.java"));
Output:
Hello Handlebars.java!
You can specicy a different TemplateLoader
by:
TemplateLoader loader = ...;
Handlebars handlebars = new Handlebars(loader);
A TemplateLoader
provides two important properties:
prefix
: useful for setting a default prefix where templates are stored.suffix
: useful for setting a default suffix or file extension for your templates. Default is:.hbs
Example:
TemplateLoader loader = new ClassPathTemplateLoader();
loader.setPrefix("/templates");
loader.setSuffix(".html");
Handlebars handlebars = new Handlebars(loader);
Template template = handlebars.compile("mytemplate");
System.out.println(template.apply("Handlebars.java"));
Handlebars.java will resolve mytemplate
to /templates/mytemplate.html
and load it.
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
- i18n and i18nJs
- string helpers
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
<script type="text/javascript">
{{precompile "user"}}
</script>
Output:
<script type="text/javascript">
(function() {
var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
templates['user'] = template(function (Handlebars,depth0,helpers,partials,data) {
helpers = helpers || Handlebars.helpers;
var buffer = "", functionType="function", escapeExpression=this.escapeExpression;
buffer += "Hi ";
depth0 = typeof depth0 === functionType ? depth0() : depth0;
buffer += escapeExpression(depth0) + "!";
return buffer;});
})();
</script>
You can access to the precompiled template by:
var template = Handlebars.templates['user']
For more information have a look at Precompiling Templates documentation.
Usage:
{{precompile "template" [wrapper="anonymous, amd or none"]}}
context: A template name. Required.
wrapper: One of "anonymous", "amd" or "none". Default is: "anonymous"
There is a maven plugin available too.
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>
...
<script id="user-hbs" type="text/x-handlebars">
<tr>
<td>{{firstName}}</td>
<td>{{lastName}}</td>
</tr>
</script>
...
</html>
Usage:
{{embedded "template"}}
context: A template name. Required.
A helper built on top of a {@link ResourceBundle}. A {@link ResourceBundle} is the most well known mechanism for internationalization (i18n) in Java.
Usage:
{{i18n "hello"}}
This require a messages.properties
in the root of classpath.
Using a locale:
{{i18n "hello" locale="es_AR"}}
This require a messages_es_AR.properties
in the root of classpath.
Using a different bundle:
{{i18n "hello" bundle="myMessages"}}
This require a myMessages.properties
in the root of classpath.
Using a message format:
{{i18n "hello" "Handlebars.java"}}
Where hello
is Hola {0}!
, results in Hola Handlebars.java!
.
Translate a ResourceBundle
into JavaScript code. The generated code assume you have the I18n in your application.
Usage:
{{i18nJs [locale] [bundle=messages]}}
If locale argument is present it will translate that locale to JavaScript. Otherwise, the default locale.
The generated code looks like:
<script type="text/javascript">
I18n.defaultLocale = 'es_AR';
I18n.locale = 'es_AR';
I18n.translations = I18n.translations || {};
// Spanish (Argentina)
I18n.translations['es_AR'] = {
"hello": "Hi {{arg0}}!"
}
</script>
Finally, it converts message patterns like: Hi {0}
into Hi {{arg0}}
. This make possible to the I18n JS library to interpolate variables.
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/helper/StringHelpers.java).
TypeSafe templates are created by extending the TypeSafeTemplate
interface. For example:
// 1
public static interface UserTemplate extends TypeSafeTemplate<User> {
// 2
public UserTemplate setAge(int age);
public UserTemplate setRole(String role);
}
// 3
UserTemplate userTmpl = handlebars.compileInline("{{name}} is {{age}} years old!")
.as(UserTemplate.class);
userTmpl.setAge(32);
assertEquals("Edgar is 32 years old!", userTmpl.apply(new User("Edgar")));
- You extend the
TypeSafeTemplate
interface. - You add all the set method you need. The set method can returns
void
orTypeSafeTemplate
object. - You create a new type safe template using the:
as()
method.
There are two ways of registering helpers.
handlebars.registerHelper("blog", new Helper<Blog>() {
public CharSequence apply(Blog blog, Options options) {
return options.fn(blog);
}
});
handlebars.registerHelper("blog-list", new Helper<List<Blog>>() {
public CharSequence apply(List<Blog> list, Options options) {
String ret = "<ul>";
for (Blog blog: list) {
ret += "<li>" + options.fn(blog) + "</li>";
}
return new Handlebars.SafeString(ret + "</ul>");
}
});
A helper source is any class with public methods returning an instance of a CharSequence
.
public static? CharSequence methodName(context?, parameter*, options?) {
}
Where:
- A method can/can't be static
- The method's name became the helper's name
- Context, parameters and options are all optionals
- If context and options are present they must be the first and last arguments of the method
All these are valid definitions of helper methods:
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 ...
}
}
...
handlebars.registerHelpers(new HelperSource());
Or, if you prefer static methods only:
handlebars.registerHelpers(HelperSource.class);
handlebars.registerHelper("blog-list", new Helper<Blog>() {
public CharSequence apply(List<Blog> list, Options options) {
String p0 = options.param(0);
assertEquals("param0", p0);
Integer p1 = options.param(1);
assertEquals(123, p1);
...
}
});
Bean bean = new Bean();
bean.setParam1(123);
Template template = handlebars.compileInline("{{#blog-list blogs \"param0\" param1}}{{/blog-list}}");
template.apply(bean);
handlebars.registerHelper("blog-list", new Helper<Blog>() {
public CharSequence apply(List<Blog> list, Options options) {
String p0 = options.param(0, "param0");
assertEquals("param0", p0);
Integer p1 = options.param(1, 123);
assertEquals(123, p1);
...
}
});
Template template = handlebars.compileInline("{{#blog-list blogs}}{{/blog-list}}");
handlebars.registerHelper("blog-list", new Helper<Blog>() {
public CharSequence apply(List<Blog> list, Options options) {
String class = options.hash("class");
assertEquals("blog-css", class);
...
}
});
handlebars.compileInline("{{#blog-list blogs class=\"blog-css\"}}{{/blog-list}}");
handlebars.registerHelper("blog-list", new Helper<Blog>() {
public CharSequence apply(List<Blog> list, Options options) {
String class = options.hash("class", "blog-css");
assertEquals("blog-css", class);
...
}
});
handlebars.compileInline("{{#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:
public CharSequence apply(final Object context, final Options options) throws IOException {
if (context == null) {
throw new IllegalArgumentException(
"found 'null', expected 'string'");
}
if (!(context instanceof String)) {
throw new IllegalArgumentException(
"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(Object model, Template template) {
User user = ....;// Get the logged-in user from somewhere
Map moreData = ...;
Context context = Context
.newBuilder(model)
.combine("user", user)
.combine(moreData)
.build();
template.apply(user);
context.destroy();
}
Where is the hookContextStack
method? Well, it 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 values from public methods prefixed with "get/is"
Context context = Context
.newBuilder(model)
.resolver(JavaBeanValueResolver.INSTANCE)
.build();
Resolves values from no-static fields.
Context context = Context
.newBuilder(model)
.resolver(FieldValueResolver.INSTANCE)
.build();
Resolves values from a java.util.Map
objects.
Context context = Context
.newBuilder(model)
.resolver(MapValueResolver.INSTANCE)
.build();
Resolves values from public methods.
Context context = Context
.newBuilder(model)
.resolver(MethodValueResolver.INSTANCE)
.build();
Resolves values from JsonNode
objects.
Context context = Context
.newBuilder(model)
.resolver(JsonNodeValueResolver.INSTANCE)
.build();
Available in Jackson 1.x and Jackson 2.x modules.
Context context = Context
.newBuilder(model)
.resolver(
MapValueResolver.INSTANCE,
JavaBeanValueResolver.INSTANCE,
FieldValueResolver.INSTANCE
).build();
The cache system is designed to provide scalability and flexibility. Here is a quick view of the TemplateCache
system:
public interface TemplateCache {
/**
* Remove all mappings from the cache.
*/
void clear();
/**
* Evict the mapping for this source from this cache if it is present.
*
* @param source the source whose mapping is to be removed from the cache
*/
void evict(TemplateSource source);
/**
* Return the value to which this cache maps the specified key.
*
* @param source source whose associated template is to be returned.
* @param parser The Handlebars parser.
* @return A template.
* @throws IOException If input can't be parsed.
*/
Template get(TemplateSource source, Parser parser) throws IOException;
}
As you can see, there isn't a put
method. All the hard work is done in the get
method, which is basically the core of the cache system.
By default, Handlebars.java use a null
cache implementation (a.k.a. no cache at all) which looks like:
Template get(TemplateSource source, Parser parser) throws IOException {
return parser.parse(source);
}
Beside the null
cache Handlebars.java provides three more implementations:
-
ConcurrentMapTemplateCache
: a template cache implementation built on top of aConcurrentMap
that detects changes in files automatically. This implementation works very well in general, but there is a small window where two or more threads can compile the same template. This isn't a huge problem with Handlebars.java because the compiler is very very fast. But if for some reason you don't want this, you can use theHighConcurrencyTemplateCache
template cache. -
HighConcurrencyTemplateCache
: a template cache implementation built on top ofConcurrentMap
that detects changes in files automatically. This cache implementation eliminate the window created byConcurrentMapTemplateCache
tozero
. It follows the patterns described in Java Concurrency in Practice and ensure that a template will be compiled just one time regardless of the number of threads. -
GuavaTemplateCache
: a template cache implementation built on top of Google Guava. Available in handlebars-guava-cache module
You can configure Handlebars.java to use a cache by:
Handlebars hbs = new Handlebars()
.with(new MyCache());
A MissingValueResolver
let you use default values for {{variable}}
expressions resolved to null
.
MissingValueResolver missingValueResolver = new MissingValueResolver() {
public String resolve(Object context, String name) {
//return a default value or throw an exception
...;
}
};
Handlebars handlebars = new Handlebars().with(missingValueResolver);
By default, Handlebars.java throws an java.lang.IllegalArgumentException()
if a helper cannot be resolved.
You can override the default behaviour by providing a helperMissing
helper. Example:
handlebars.registerHelper(Handlebars.HELPER_MISSING, new Helper<Object>() {
@Override
public CharSequence apply(final Object context, final Options options) throws IOException {
return options.fn.text();
}
});
You can access to a parameter name if you set the: stringParams: true
. Example:
{{sayHi this edgar}}
Handlebars handlebars = new Handlebars()
.stringParams(true);
handlebars.registerHelper("sayHi", new Helper<Object>() {
public Object apply(Object context, Options options) {
return "Hello " + options.param(0) + "!";
}
});
results in:
Hello edgar!
How it works? stringParams: true
instruct Handlebars.java to resolve a parameter
to his name if the value isn't present in the context stack.
By default, Handlebars.java don't allow a partial to call him self (directly or indirectly).
You can change this by setting the: Handlebars.inifiteLoops(true)
, just avoid StackOverflowError
.
The Mustache Spec has some rules for removing spaces and new lines, by default, this feature is off.
You can turn this on by setting the: Handlebars.prettyPrint(true)
.
Maven:
<dependency>
<groupId>com.github.jknack</groupId>
<artifactId>handlebars-json</artifactId>
<version>${handlebars-version}</version>
</dependency>
Usage:
handlebars.registerHelper("json", JacksonHelper.INSTANCE);
{{json context [view="foo.MyFullyQualifiedClassName"] [escapeHTML=false] [pretty=false]}}
Alternative:
handlebars.registerHelper("json", new JacksonHelper().viewAlias("myView",
foo.MyFullyQualifiedClassName.class);
{{json context [view="myView"] [escapeHTML=false] [pretty=false]}}
context: An object, may be null.
view: The name of the Jackson View. Optional.
escapeHTML: True, if the JSON content contains HTML chars and you need to escaped them. Default is: false.
pretty: True, if the JSON content must be formatted. Default is: false.
Maven:
<dependency>
<groupId>com.github.jknack</groupId>
<artifactId>handlebars-jackson2</artifactId>
<version>${handlebars-version}</version>
</dependency>
Same as 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", new MarkdownHelper());
{{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/master/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:
HandlebarsViewResolver viewResolver = ...;
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 [ANTLR v4] (http://www.antlr.org/).
- 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. For example:
Given:
{
"value": "parent",
"child": {
}
}
and
Hello {{#child}}{{value}}{{/child}}
will be:
Hello parent
Now, 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}}
- Handlebars.java throws a
java.io.FileNotFoundException
if a partial cannot be loaded.
- Passes the 123 tests from the Mustache Spec.
- Tests can be found here comments.yml, delimiters.yml, interpolation.yml, inverted.yml, lambdas.yml, partials.yml, sections.yml
- Passes all the Handlebars.js tests
- Tests can be found here basic context, string literals, inverted sections, blocks, block helper missing, helper hash, partials
+- org.apache.commons:commons-lang3:jar:3.1
+- org.antlr:antlr4-runtime:jar:4.0
+- org.mozilla:rhino:jar:1.7R4
+- org.slf4j:slf4j-api:jar:1.6.4
- 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.
[Edgar Espina] (https://twitter.com/edgarespina)