A full feaured Java-based template engine for Play2
h1. japid42 - Japid for Play 2
Bing Ran ([email protected])
The latest version: see the end of this file for version information
Compatibility: Play 2.1.x, 2.2.x
Note: One must use Japid42 version 0.9.16 or later with Play 2.2.1 or later.
Note: version 0.7.4 and older is compatible with 2.0.4. Version 0.9.9.1 or earlier is compatible with Play2.1.x.
h2. About
Japid42 is a native Java based template engine for Java programmers using Play2.
It can also be used in any application that need an advanced templating solution.
h2. Features
New features since version 0.9.5:
Credit: the work is derived from Peter Hausel’s little nice project hosted here: https://github.com/pk11/play-jaxrs. The differences are explained later.
h2. Usage
h4. For a full sample application with Japid, see: http://github.com/branaway/computer-japid
Please also check out the JapidSample project in the samples directory for an example.
Basically you’ll need to do three things:
//... val appDependencies = Seq( javaCore, cache, // not needed for working with Play 2.1.x "japid42" % "japid42_2.10" % "{the latest version}" // use 0.9.9.1 if you want to use Japid routing with Play 2.1.1 or earlier ) val main = PlayProject(appName, appVersion, appDependencies).settings( resolvers += Resolver.url("My GitHub Play Repository", url("http://branaway.github.io/releases/"))(Resolver.ivyStylePatterns) ) //...
Note: adjust the version number accordingly.
public class Global extends cn.bran.play.GlobalSettingsWithJapid{}
Note:
addImport(String imp) addImport(Class> cls) addImportStatic(String imp) addImportStatic(Class> cls) setKeepJavaFiles(boolean keepJava) setLogVerbose(boolean verb) setTemplateRoot(String... root) setRefreshInterval(int i) setCacheResponse(boolean c) setPresentErrorInHtml(boolean presentErrorInHtml) setEnableJITCachePersistence(boolean enableJITCachePersistence)
Please see the @computer-japid@ (https://github.com/branaway/computer-japid) example for a real-world Global class definition.
package controllers; import play.mvc.Result; import cn.bran.play.JapidController; public class Application extends JapidController { public static Result index() { return renderJapid("cool"); } }
@(String m) Hello, $m!
h2. Releasing Your Application
When an app is ready to be distributed, you’ll need to modify the build.scala to prepare your project for distribution. There are two things needed to be done:
val main = play.Project(appName, appVersion, Seq()).settings(
...
,unmanagedResourceDirectories in Compile <+= baseDirectory( _ / "japidroot" )
)
public class Global extends GlobalSettingsWithJapid {
@Override
public void onStartJapid() {
if (_app.isDev())
setTemplateRoot("japidroot");
else
setTemplateRoot((String[])null); // scan class only
}
}
This applies to using Japid in sub-projects settings too.
h2. Using Japid in Sub-projects
In Build.scala, you may have a sub-project defined like this:
val appDependencies = Seq(
"japid42" % "japid42_2.10" % "{the version number}"
)
val foo = PlayProject("foo", "0.1", appDependencies, path = file("modules/foo"))
val main = PlayProject(appName, appVersion, appDependencies).settings(
resolvers += Resolver.url("Japid on Github", url("http://branaway.github.io/releases/"))(Resolver.ivyStylePatterns)
).dependsOn(foo)
Obviously you have a sub-project located in modules/foo. For the system to know the Japid scripts in the sub-project, you’ll need to tell Japid the location, using the JapidRenderer.setTemplateRoot(String…) method, in the global initialization method:
public class Global extends cn.bran.play.GlobalSettingsWithJapid {
@Override
public void onStartJapid() {
setTemplateRoot("japidroot", "modules/foo/japidroot");
}
}
In the above example, the first argument is the location of the Japid scripts in the parent project. The second argument is the root for the Japid scripts in the sub-project. All paths are relative to the root of the master project.
The @computer-japid@(http://github.com/branaway/computer-japid) sample application demos this feature.
h2. Use Japid Router to replace the Play2 routing mechanism (experimental but quite usable)
Note: version 0.9.5 supports Play 2.1.1 while version 0.9.6+ supports Play 2.1.2, and version 0.9.11 supports Play 2.2.x.
Every typical Play applications come with a route file in the conf directory. The conf file is compiled to multiple scala files when the app is loaded or reloaded.
Two issues with the approach:
The newly introduced Japid Router takes care of these by using some conventions with minimum Annotations.
These are the steps to take advantage of the feature:
public class Global extends GlobalSettingsWithJapid { @Override public void onStartJapid() { //... setUseJapidRouting(true); // not required since 0.9.11 } }
package controllers; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.QueryParam; import play.mvc.Result; import cn.bran.play.JapidController; @Path("") // an empty value means to use the controller name (minus the leading "controller." part) as the first segment of the URL path public class Jax extends JapidController { // takes HTTP GET. the path is "yahoo" @GET public static Result yahoo() { return ok("news!"); } @GET @Path("/glass") // explicit path public static Result g() { return ok(renderJapid("ggff")); } // take any HTTP method // explicit path following JAX-RS convention @Path("/book/{isbn:[0-9]+}/{chapter:[abc]}") public static Result getBook(@PathParam("isbn") Integer id, @PathParam("chapter") String chap, @QueryParam("note") String note) { return ok("got book: " + id + "/" + chap + ": " + note); } // take any HTTP method // auto-routing based on positional parameter mapping public static Result bo(int id, String chap, @QueryParam("note") String note) { return ok("got bo: " + id + "/" + chap + ": " + note); } }
Note: the Japid routing can actually co-exist with the traditional route file. The Japid router falls back to the default route file in case no route is matched.
The @GET, @POST, @PUT, @DELETE, @HEAD, @OPTIONS, @Path, @PathParam and @QueryParam are standard JAX-RS annotations. But the resemblance to JAX-RS ends there.
h3. The auto-routing convention
When an action method carries no JAX-RS @Path annotation, the routing takes a set of conventional rules:
package controllers;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
import play.mvc.Result;
@Path("") // the path to this controller is the name of the class minus the "controller." part
public class Jax extends Controller {
public static Result book(int id, String chap, @QueryParam("note") String note) {
return ok("got book: " + id + "/" + chap + ": " + note);
}
}
To invoke the @book@ action, the URL must be compose similar to @/Jax.book/123/chapter2?note=mynote@. The id parameter takes 123 as the value. The chap parameter takes “chapter2” as the value. The argument to the “note” parameter gets its value from the query string in the name of “note”. The convention can be summarized as
http://myhost/{app name}/{controller name without leading "controllers."}.{method name}/{parameter 1}/{parameter 2}/...?{query param 1}=...&{query param 2}=...
Note: the app name segment is defined by an instance of @ApplicationPath with the Global class.
h3. Credit
As mentioned before, the Japid router is inspired by Peter Hausel’s project at “https://github.com/pk11/play-jaxrs”. I have made a few interesting features on top of it.
h2. Use Japid in any Java applications
First of all, you grab the japid jar from the @target/scala-2.10@ folder and two other dependencies from the @libs4plainjava@ folder.
Then the following code snippet is the simplest form of using Japid in any plain Java applications:
// do this once, using true to set dev mode to reload Japid script changes, false otherwise. JapidRenderer.init(true) // now render some data to a Japid script "{app name}/japidroot/japidviews/hello.html" RenderResult rr = JapidRenderer.renderWith("japidviews/hello.html", "John"); // rr.toString() outputs what you want to see
By default the intermediary Java files derived from the Japid scripts are saved along with the scripts. Use @JapidRenderer.setKeepJavaFiles(false)@ to keep them in the memory only.
The following is a code snippet that demonstrate using Japid with a Servlet:
public class S2 extends HttpServlet { public void init(ServletConfig config) throws ServletException { String p = config.getServletContext().getRealPath("WEB-INF/japidroot"); JapidRenderer.setTemplateRoot(p); JapidRenderer.init(true); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Person p = new Person(); p.name = "Nadal"; RenderResult rr = JapidRenderer.render(p); response.getWriter().println(rr.toString()); } }
h2. Documentation
The “computer-japid” sample project contains a README file that explains most of the Japid usage. Please refer to it.
For detailed Japid grammar please refer to the Japid 1 manual before I write something formal:
- https://github.com/branaway/Japid
h2. Sample applications
h2. Hacking Japid42 in Eclipse
Note:
h2. History
2012.3.24: v0.1
2012.3.27: v0.2
2012.4.4: v0.2.1
2012.7.28: v0.2.2
2012.7.28: v0.3
2012.10.12. v0.4, beta
2012.10.17. v0.5
2012.10.23. v0.5.1
2012.10.23. v0.5.2
<code>the value is $MyController.action(...).</code>
2012.11.1. v0.5.3
2012.11.8. v0.5.4
2012.11.10. v0.5.5
2012.11.10. v0.6
2012.11.10. v0.6.1
2012.11.18. v0.7. See the JapidSample42 for examples for the new features.
2012.11.20. v0.7.1
- Annotate the form class with @cn.bran.play.AuthenticForm@
- embed $authenticityToken() in the proper position of an html form in your Japid templates. The method would generate a hidden field in the form.
2012.11.26. v0.7.2
2012.11.27. v0.7.3
2012.12.2. v0.7.4
2013.2.8. v0.8
2013.2.8. v0.8.1
// an action in a controller
public static Result dynamic() {
String template = "`(String s, int i)\nhi ${s}, are you ${i} years old?";
return ok(renderDynamic(template, "Johnny", 13));
}
2013.5.15. v0.9.1
public class Global extends cn.bran.play.GlobalSettingsWithJapid {
@Override
public void onStartJapid() {
setTemplateRoot("japidroot", "modules/foo/japidroot");
setLogVerbose(true);
setKeepJavaFiles(false); // keep the Java code derived from Japid scripts in memory only
addImport("com.foo");
}
}
@Test
public void testDynamic() throws IOException {
JapidRenderer.setKeepJavaFiles(false);
JapidRenderer.init(OpMode.dev, "japidroot", 1, null, JapidRenderer.class.getClassLoader());
String template = "`(String s, int i)\n";
template +="hi ${s}, are you ${i} years old?";
template +="\n`t japidviews._tags.taggy `-"; // calling a tag on hard drive
RenderResult rr = JapidRenderer.renderDynamic(template, "Johnny", 13);
System.out.println(rr);
rr = JapidRenderer.renderWith("japidviews/myscript.html", "hi", 123);
System.out.println(rr);
}
Pay attention to the renderDynamic(…) and renderWith(…) methods.
`(String who)
Hello ${who}!
` switch(who) { case "John":
this is John
` break; case "Mary":
this is Mary
` break; default:
who are you?
`}
2013.5.23 v0.9.2
2013.5.28 v0.9.2.3
2013.5.29 v0.9.3
2013.6.9 v0.9.3 (keep the old number)
2013.6.10 v0.9.4
2013.6.13 v0.9.4.1
2013.7.18 v0.9.5
unmanagedResourceDirectories in Compile <+= baseDirectory( _ / "japidroot" )
2013.7.23 0.9.7
2013.8.30 0.9.8
2013.10.3 0.9.9
val main = play.Project(appName, appVersion, Seq()).settings(
...
,unmanagedResourceDirectories in Compile <+= baseDirectory( _ / "japidroot" )
)
public class Global extends GlobalSettingsWithJapid {
@Override
public void onStartJapid() {
if (_app.isDev())
setTemplateRoot("japidroot", "app");
else
setTemplateRoot((String[])null); // scan class only
}
}
2013.10.3 0.9.9.1
2013.10.6 version 0.9.10
2013.10.9 version 0.9.10.1
2013.10.11 version 0.9.11
2013.10.15 version 0.9.12
2013.10.29 version 0.9.13
2013.10.31 version 0.9.13.1
2013.11.14 version 0.9.14
2013.11.24 version 0.9.16
2013.12.21 version 0.9.16.1
2014.3.11 version 0.9.17
2014.3.11 version 0.9.17.1