Smart Tech for a better Web

Precompiling hogan.js/mustache templates on a Java server with Struts 2

von

Hogan.js is some kind of a wrapper around Mustache (for JavaScript). Mustache is a template mechanism I wanted to use, and Hogan.js provided me a nice interface for it. This means, when I now refer to "Hogan" templates in fact they are "Mustache" templates. In this post I will describe how you can compile your Templates with Java (Struts 2) on the server side and deliver the JavaScript render functions to the client. I do this because compiling templates waste the most of the time in JavaScript. On the server I can do it one time (probably at server start) and cache it there or make use of the browser cache. I have not made a speed measurement to verify this is actually quicker. If you have one at hand, let me know.

Prepare Struts 2 for Hogan.js

First I created an action:

<action name="templates" class="de.grobmeier.webapp.services.JSTemplateService">
   <result>/jsp/templates.jsp</result>
</action>

This will call JSTemplateService when I request for localhost/templates.action (ending of this "file" is to be configured in your struts application). It uses then a JSP file on success to render the output.

Let's look at the JSTemplateService.java file. I leave out all setters/getters and class definition stuff. Basically this is my execute() method, which is called once the request arrives.

ScriptEngineManager engineMgr = new ScriptEngineManager();
ScriptEngine engine = engineMgr.getEngineByName("ECMAScript");

engine.put("compiled", compiled);

InputStream hoganInput =
   getClass().getResourceAsStream("hogan-1.0.3.min.js");
InputStream templatesInput =
   getClass().getResourceAsStream("templates.js");

First, I need to create a ScriptEngine which runs ECMAScript (lets say JavaScript from now on). I put a reference of Mapcompiled to the engine. Now JavaScript has access to my Java object and can put values in it.

Then I open streams to hogan.js and templates.js. The second file does is the place where my templates reside. We will look at it later.

Reader hoganReader = null;
Reader templatesReader = null;

// try ... catch
hoganReader = new InputStreamReader(hoganInput);
templatesReader = new InputStreamReader(templatesInput);

engine.eval(hoganReader);
engine.eval(templatesReader);

We need two readers to make it work with the ScriptEngine. Then the fun begins: both JavaScript files will be evaluated. I can tell in advance, that the "compiled" map will be filled by the templates.js file.

builder.append("var Templates = {};\n");
for (String key : compiled.keySet()) {
    builder.append("Templates.");
    builder.append(key);
    builder.append(" = ");
    builder.append(compiled.get(key));
    builder.append("\n");
}
result = builder.toString();

With this snipped I iterate over my "compiled" Map. Each value is a String containing a JavaScript function which is in fact the "render"-Function of the Hogan-Template. I assign everything to an Templates object, finally I should be able to access the "hello" Template with "Templates.hello". After all the complete String will be stored in the "result" member.

So far so good. Finally we need a JSP file which outputs our JavaScript.

<%@taglib prefix="s" uri="/struts-tags" %>
<s:property value="result" escapeHtml="false" />

Pretty easy. That's all from the server side: a request to "templates.action" will now execute hogan.js and templates.js and create a JavaScript string which will be then returned.

Make sure Hogan.js is available for Struts, which is is usually in /src/main/resources/packagename.

Another note, please take care not do decorate your request with for example Sitemesh. If you use sitemesh open decorators.xml and exclude templates.action from the decorations:

<excludes>
   <pattern>/templates.action*</pattern>
</excludes>

(this is for Sitemesh 2)

I have used the compilation as a Struts 2-Action which means, you always compile the templates this action is called. The file can be stored in the Browser cache, which will reduce compilations. If you want to do compilation one time only, then you probably should create a Spring bean in singleton scope or have a similar mechanism.

Lets finally look at our templates.js file.

var hello = 'Hello ';
compiled.put("hello", Hogan.compile(hello, {asString: true}));

First line is my mustache syntax. I put this into the Hogan.compile method and the asString parameter to it. Hogan.compile will then return the render function as a String. This is then put into the already mentioned Java Collection namely "compiled" under the name "hello".

Finally we need to use the generated JavaScript. Here is the HTML file.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
   <script src="http://twitter.github.com/hogan.js/builds/1.0.3/hogan.js"></script>
   <script src="http://localhost:8080/templates.action"></script>
</head>
<body>
   <script type="text/javascript">
    var template = new HoganTemplate();
    template.r = Templates.hello;
    console.log( template.render({name: "Christian" }) );
   </script>
</body>
</html>

First, you need to include hogan.js and then our templates. Once done, you can create a HoganTemplate object, which I store in the template variable. Here you need to put your rendered function into the templates.r property. This is the property into which Hogan.compile would put it for you too, if using it at browser side. Finally you can render your template with calling it with the params to your likings. In my case "Hello Christian" will be shown.

Tags: #Apache Struts #Java #JavaScript

Newsletter

ABMELDEN

BLOG-POST TEILEN