Now I can easely inject HandlebarsApi in any component:
@Inject
private HandlebarsApi handlebarsApi;
And compile the template just with one line
Content page = handlebarsApi.html("page", data);
Configuration
First of all, I extracted the configuration. I like the include statement, so I easily created the handlebars settings.
application.conf
# configure Handlebars API
handlebars{
include "handlebars.conf"
}
handlebars.conf in the same directory as the application.conf
directory: "/templates"
extension: ".hbs"
In the code I can read handlebars properties as easy as
configuration.getString("handlebars.directory")
Module
The creation of module in Play is simple.
Create a class. I used @Singleton because I want to initialize handlebars only ones and utilize the cache system.
package handlebars;
@Singleton
public class HandlebarsApi {
...
}
Extend the play.api.inject.Module class; bind your class.
package handlebars;
import play.api.Configuration;
import play.api.Environment;
import play.api.inject.Binding;
import scala.collection.Seq;
public class Module extends play.api.inject.Module {
@Override
public Seq<Binding<?>> bindings(final Environment environment, final Configuration configuration) {
return seq(bind(HandlebarsApi.class).toSelf());
}
}
The last step - enable the module in the application.conf
# Bind Handlebars API
play.modules.enabled += "handlebars.Module"
Cache
Handlebars has a good cache system. There is no need to build your own. I selected the Guava cache.
Add dependency to the build.sbt
libraryDependencies += "com.github.jknack" % "handlebars-guava-cache" % "4.0.4"
Initialize handlebars with the cache
...
import java.util.concurrent.TimeUnit;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
...
// Initialize the cache. Could be builded from configuration as well
// For example: CacheBuilder.from(config.getString("hbs.cache")).build()
final Cache cache = CacheBuilder.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.maximumSize(1000)
.build();
// Initialize the engine with the cache
handlebars = new Handlebars(loader)
.with(new GuavaTemplateCache(cache));
...
MessagesApi
I use MessagesApi in the handlebars helper. Helpers are registering only once a time, fortunately MessagesApi is a singleton, so I put it in to the Helper's constructor.
Part of the Helpers class.
public final class Helpers {
final MessagesApi messagesApi;
public Helpers(final MessagesApi messagesApi){
this.messagesApi = messagesApi;
}
...
public CharSequence message(final String key, final Options options) {
// Get the current language.
final Lang lang = Context.current().lang();
// Retrieve the message, internally formatted by MessageFormat.
return messagesApi.get(lang, key, options.params);
}
}
Initialization of the Handlebars with the Helpers class.
...
@Inject
public HandlebarsApi(... final MessagesApi messagesApi) {
...
// Add helpers. MessagesApi is a singleton so we can use it in the helpers.
Helpers helpers = new Helpers(messagesApi);
handlebars.registerHelpers(helpers);
...
}
...
Content
Trivial result of the template processing in the Play is the object of play.twirl.api.Content type. So I created a simple implementation of this class for the HTML template.
HTML Content wrapper:
package handlebars;
import play.twirl.api.Content;
class HtmlContent implements Content {
private String body;
HtmlContent(final String body){
this.body = body;
}
@Override
public String body() {
return body;
}
@Override
public String contentType() {
return "text/html";
}
}
Wrapping the handlebars result:
public String render(final String templateName, final Object data) throws Exception {
return handlebars
.compile(templateName)
.apply(data);
}
public Content html(final String templateName, final Object data) throws Exception {
return new HtmlContent(render(templateName, data));
}
Mocking the Http.Context
In the process of testing templates I spend a little time to beat the nasty error
java.lang.RuntimeException: There is no HTTP Context available from here.
So do not forget to mockup the Http.Context. It's easy to do.
Here is an example from the project:
// Initialize application
Application application = new GuiceApplicationBuilder().build();
// Setup an HTTP Context
Http.Context context = mock(Http.Context.class);
// Setup the language and messages
Lang langRequest = Lang.forCode(requestLang);
Lang langSession = Lang.forCode(sessionLang);
MessagesApi messagesApi = application.injector().instanceOf(MessagesApi.class);
Messages messages = new Messages(langRequest, messagesApi);
// Train the Context
when(context.lang()).thenReturn(langSession);
when(context.messages()).thenReturn(messages);
//Http.Context.current.set(context);
// Get the handlebars API
HandlebarsApi handlebarsApi = application.injector().instanceOf(HandlebarsApi.class);
Project on the Github
You can find this simple project with the Handlebars module on the github: https://github.com/andriykuba/playframework-handlebars-example