This document details how to use Rythm to write template source.
Rythm Template Engine is a text generator that merges dynamic content into static template.
In order to use Rythm to generate a text file, the essential work is to create template file which write down the static part and use Rythm syntax to define the dynamic parts. It is also important to understand that a template file has one or more arguments which is the origin of the dynamic content. The Java program pass parameters to template file via arguments to generate different result.
So a template file is a text file, some parts of which have placeholders for dynamically generated content. The template’s dynamic elements are written using the Java language.
Dynamic elements are resolved during template execution. The rendered result is then sent as part of the HTTP response.
In summary, a rythm template is composed of the following types of components:
- static content, which is literally output to the render result without any changes
- expressions, which output the dynamic content to the render result. See more about expressions
- directives, which control the behavior of the template in this and that way. Please refer to directive reference
The Rythm template syntax is designed to be easy to learn and what you need to understand is one fact:
The magic @
character is used to lead all rythm element.
@
character, you need to double it. E.g.This is an email address in template: someone@@gmail.com
All static content (those text not defined in Rythm elements including script blocks) are output literally including whitespace and new lines.
one line comment start with @//
@// this is one line comment
multiple lines comment are put inside @*
and *@
block.
@*
this is a multiple line comment.
The text inside this block will be ignore by Rythm template processor
*@
When a template is rendered, it nearly always needs to pass some dynamic information to the template. This is done via template arguments. Unless rare cases you should declare arguments of a template using @args
directive:
@args User user, Order order
...
The main reason that argument declaration is needed is that Rythm is a static typed template engine, it needs to parse your template source code and convert it into a valid java source code and then call a compiler to compile the template into byte code. The benefit of this static typed processing (vs. interpreted processing as velocity, freemarker etc) is:
- It's type safe and thus compiler help you capture some errors
- It's much faster than interpreting processing
- You are not required to declare template arguments at top of the template source code. It's more like a c++ style, you just need to declare arguments before they are used.
- You can have multiple
@args
declaration in a single template source file
In some cases you can waive for template argument declaration:
- The template does not have arguments. All dynamic information is retrieved out via method calls, tag calls etc. Obviously you don't need to declare something that doesn't exists
- Your template is simple enough that you don't need to call any field or method on your template arguments, the only thing you need is the
toString()
method, in that case you can waive argument declaration and Rythm will treat those arguments as anObject
type. Note you can't have complex expression on those arguments. E.gHi @username
is okay, butHi @user.name
won't be compiled unless you declared@args User user
- You enabled the feature.type_inference configuration and you are sure that the first time running the template, all parameters are not null
For more about template argument declaration, please refer to @args directive
Output expression is the core function of Rythm template, it is used to emit dynamic content passed into the template, including emission of object fields and methods, class fields and methods:
@args User user, Foo foo
// emit instance field
@foo.bar
// emit instance method
@user.getName()
// emit class field
@Long.MAX_VALUE
@Integer.MAX_VALUE
// emit class method
@Boolean.parseBoolean("true")
The bracket ( )
can be used to compose complicated expressions or to separate an expression from other part of the template:
@(1 + 5) @// print out the result of 1 + 5
@// use ( ) to separate expression from other part of the template
@(foo.bar)_and_some_other_string
Rythm does not directly support evaluate object property following JavaBean specification. For example if you need to get bar
property of foo
object, you need to refer to the getter method foo.getBar()
unless your bar property is public. Hoever Rythm does support JavaBeans via dynamic expression evaluation by suffix the expression with another @
:
@foo.getBar() @//the normal way to get bar proeprty
@foo.bar@ @// dynamic expression evaluation, notice the "@" at the end of the expression
Note that using dynamic expression evaluation might suffer the template performance a little bit.
As shown in the above example, the instances/classes/fields/methods inside an expression could come from anyone of the followings:
- Template arguments
- Invoke another template
- Template built-ins
- Variable declared in scripting blocks
- Directly referenced classes
- Variable declared with
@assign()
directive - Variable declared with
.assign()
extension to template call
Rythm allows you to put any Java code inside a template:
@{
String fullName = client.name.toUpperCase() + " " + client.forname;
}
<h1>Client @fullName</h1>
You can use p()
function to print content to the current output inside scripting block:
@{
String fullName = client.name.toUpperCase() + " " + client.forname;
p("<h1>").p(fullName).p("</h1>");
}
But obviously this is not a goo way to go for most times.
Rythm defines a very handy function on each template, the s()
. It returns a string utility instance with a set of static methods built in. Here are some examples using the s()
function in a template source:
@args String foo = "hello world"
@s().capFirst(foo) @// use s() utility to process a string
@// the above code will produce exactly the same result of
@foo.capFirst() @// use built-in transformer to process a string
@if (s().eq(foo, bar)) {
...
}
@s().escapeHtml(foo) @// use s() to escape a string
@// the above code will produce exactly the same output of
@foo.escapeHtml() @// use built-in transformer to process a string
@s().random() @// generate a random string
@s().random(8) @// generate a random string with 8 characters
@s().i18n(foo) @// get i18n message of foo
@// the above code will produce exactly the same result of
@foo.i18n() @// use built-in transformer to get the message of foo
So you might notice a fact that in case s()
function returns a processing result of a single parameter, then there is always a corresponding transformer corresponding with it. That's true because Rythm registered all those processing method of s()
utility as built-in transformer. And normally using transfomer is easier and clean than s()
. However in case you can't use transformer, you will probably come back to s()
Rythm provides three flow control directive for template author to control the template rendering:
@if (isMobile) {
@// render mobile block
} else {
@// render pc block
}
<ul>
@for(Product product: products) {
<li>@product.getName(). Price: @product.getPrice().format("## ###,00") €</li>
}
</ul>
@if (user.hasRole("guest")) {
<p>You don't have permission to view this page</p>
@return
}
@// continue rendering the secured page
For more about the flow control please refer to the directive reference:
- Branching -
@if/else/elseif
- Looping -
@for
- Return -
@return
The capability of invoking other template from the current template is an important feature that facilitate template reuse and makes template user's life easier. Many template engine provides tools to support template reuse, e.g. the #include
and #parse
of velocity, the #include
, #macro
and #import
of freemarker, like those template engines, Rythm also provides tools to support template reuse, just in a more general and much easier way.
Suppose you have a template foo.html
and another one named bar.html
in the same directory. The following command allows you invoke bar.html
inside foo.html
:
@foo()
See following topics for more about template invocation:
- Passing arguments when invoking a template
- Handling paths
- Invoke template with different extension
- Dynamic template invocation
- Passing body with template invocation
- Further processing template invocation result
As stated above most template has arguments. Thus when calling a template with arguments you need to pass arguments to them. Again the foo and bar case. Now suppose the bar template has an argument declaration:
@** the bar.html template **@
@args String whoCallMe, boolean sayBye
Hi @whoCallMe, this is inside bar.
@if (sayBye) {
Bye @whoCallMe
}
Now the foo template needs a little bit update:
@** the foo.html template **@
@bar("foo", true)
In the above example, we pass parameters to template by parameter declaration position. But Rythm also provides a more friendly parameter passing approach when you have many parameters
@** the foo.html template **@
@bar(whoCallMe: "foo", sayBye: true)
or even with the nice javascript style
@** the foo.html template **@
@bar({
whoCallMe: "foo",
sayBye: true
})
Usually templates are organized in different folders in a non-trial project. This brings the issue of how to invoke a template that is not in the same folder of the current template. Following the idea of java package, Rythm allows it to use dot separated path to identify a template. Consider the following directory structure:
\---app
\---rythm
| tmpl1.html
|
+---Application
| | index.html
| | tmplA.html
| |
| \---bar
| tmplB.html
|
\---foo
| tmpl2.html
|
\---zee
tmpl3.html
Now inside the app/rythm/Application/index.html
, you call the other templates like follows:
@Application.tmplA() @// full path invocation
@Application.bar.tmplB() @// full path invocation
@tmplA() @// relative path invocation
@bar.tmplB() @// relative path invocation
@foo.zee.tmpl3() @// full path invocation
You can also save typing by using @import
directive:
@import foo.*
@foo.tmpl2() @// full path invocation
@foo.zee.tmpl3() @// full path invocation
@tmpl2() @// import path invocation
@zee.tmpl3() @// import path invocation
Normally you don't need to add template extension (e.g. .html
) when you invoke another template if the template been invoked has the same extension as the current template. For example, if you invoken template bar.html
from template foo.html
, simply use @bar()
is enough.
However if you want to invoke a template with a different extension, say you want to invoke a template bar.js
from template foo.html
, you need to add the extension: bar.js()
. For the moment Rythm support template source file with the following extensions:
.html
.json
.js
.css
.csv
.tag
.xml
txt
rythm
There are some cases that template being invoked can only be determined at runtime. Rythm provides dynamic template invocation to handle this situation:
@args String platform @// could be pc, iphone, ipad ...
...
@invoke("designer." + platform)
In the above code, your templates are stored in different files which corresponding to platform types, and when you are writing the template, you don't know which template exactly to invoke. The above code enable you to invoke the needed template based on a runtime variable platform
. So when the platform
value is "pc", the above code exactly has the same effect as
@designer.pc()
In Rythm it is possible to pass additional content in addition to parameters when invoking a template:
@greenscript.js() {
$(function()) {...}
}
With the introduction of callback extension, Rythm make it possible to pass in parameters when callback the body in the callee template:
@lookupRole(permission: "superuser").callback(List<Role> roleList) {
<ul>superusers
@for(Role role: roleList) {
<li>@role.getName()</li>
}
</ul>
}
and in your lookupRole.html
template:
@args String permission
@{
List<Role> roles = Role.find("permission", permission).asList()
}
@renderBody(roles)
Usually when a template is invoked, the rendering result is inserted at where the invocation is happening. Rythm makes it flexible to allow user to further processing the result before inserted.
It is possible to escape template invocation result using specific format:
@foo().escape("csv")
@escape("csv") {@foo()}
The above code makes sure all expressions output within @foo()
call will be escaped using “csv” format, while @foo().escape(“csv”)
ensure the tag invocation result itself get escaped.
One interesting tool Rythm provides is it allows you to cache the template call result when it returns and use the cached content next without calling the template again:
@bar("foo", true).cache() @// cache using default TTL, which is 1 hour
@bar("foo", true).cache("1h") @// cache invocation result for 1 hour
@bar("foo", true).cache(60 * 60) @// cache invocation result for 1 hour
The above statement invoke template bar
using parameter ["foo", true] and cache the result for one hour. Within the next one hour, the template will not be invoked, instead the cached result will be returned if the parameter passed in are still ["foo", true].
So you see Rythm is smart enough to cache template invocation against tag name and the parameter passed in. If the template invocation has a body, the body will also be taken into consideration when calculating the cache key.
In some cases where the template invocation result is not merely a function of the template, parameter and body, but also some implicit variables, where you might expect different result even you have completely the same signature of template invocation. Rythm provide way for you to take those additional implicit variable into account when calculating the cache key:
@{User user = User.current()}
@bar("foo", true).cache("1h", user.isAdmin())
The above statement shows how to pass additional parameter to cache decoration.
@cache()
directiveIn case you want to reuse a single invocation result in multiple place in the current template, you can use .assign()
extension to assign the template invocation result into a variable:
@bar("foo", false).assign(fooResult)
Now you have a template variable fooResult
contains the content of foo.html
. You can process it and output it later on:
@fooResult.toLowerCase().raw()
It is possible to chain the invocation extension functions in one statement:
@someTemplate().raw().cache("1h").assign("xResult").callback(String name) {
echo @name
}
Although template invocation is already very simple and easy to use compare to other competitors, Rythm provides an even more lightweight way for code reuse: the inline tag.
Suppose you have some repeated code structure in one template source, and you don't think it's heavy enough to isolate them out and put them into a separate template file, then inline tag is your friend. Seriously I found this feature is one of my favourite feature in my daily works.
The following code is picked up from the source code of rythm-web site project, which is what you are looking at now (unless you are reading this doc on github):
<ul class="nav-sub doc clearfix container">
@def book(String bookId, String url) {
<li class="book clearfix @bookId" data-url="@url">
<i class="icon-book" style="font-size: 32px;margin-bottom: 10px;"></i>
@{bookId = "main.book." + bookId;}
<div>@i18n(bookId)</div>
</li>
}
@book("index", "/doc/index.md")
@book("tutorial", "/doc/tutorial.md")
@book("template_guide", "/doc/template_guide.md")
@book("developer_guide", "/doc/developer_guide.md")
@book("configuration", "/doc/configuration.md")
@book("directive", "/doc/directive.md")
@book("builtin_transformer", "/doc/builtin_transformer.md")
</ul>
The code demonstrate how you define a inline tag "book" and use it immediately after definition. When I create html templates, I found the way is really a finger type saving feature.
There are certain limits you should be aware of using inline tag compare to a normal template
-
you can not use template invocation extensions (i.e. escape, cache, assign) on an inline tag call, but there are workarounds:
@myInlineTag().cache("3h") @// this is NOT okay @// the following is good @cache("3h") { @myInlineTag() } @myInlineTag().raw().assign("myResult") @// this is NOT okay @// the following is good @chain().raw().assign("myResult") { @myInlineTag() }
-
You cannot pass argument by name when invoking inline tag. ALWAYS pass parameter to inline tag by position:
@myInlineTag(varName: "value") @// this will cause compilation error @myInlineTag("value") @// this is okay
One of the cool thing is you are not limited to use inline tag to output repeated code, but also use it to define your utility functions, and yes they are translate into protected methods in the source code generated for your template:
@def boolean isMobile() {
UserAgent ua = UserAgent.current();
return ua.isMobile();
}
And later on it can be used as:
@if (isMobile()) {
@mobile.designer()
} else {
@pc.designer()
}
The following code demonstrate a subtle difference of Java code scripting inside inline tag when return type is presented or not:
@def boolean isMobileAndShowPlatform() {
UserAgent ua = UserAgent.current();
boolean isMobile = ua.isMobile();
p("The platform is ");
p(isMobile ? "mobile" : "pc");
return isMobile;
}
@def showPlatform() {
@{ UserAgent ua = UserAgent.current() }
The platform is @if (ua.isMobile()) {mobile} else {pc}
}
So the rules are
-
if inline tag return type is presented and is not
void
- Java code shall be put inside tag definition directly
- Output message shall be done via
p()
function call
-
If inline tag return type is absent or is
void
- Java code shall be put inside
@{ }
scripting block - Output message shall be put inside tag definition directly
- Java code shall be put inside
As the third code reuse mechanism, Rythm support include other template inline:
@include("foo.bar")
The above statement will put the content of template foo.bar in place. “foo.bar” is translate into file name following template invocation convention.
The difference between @include(“foo.bar”)
a tag and call the template via @foo.bar()
is the former put the content of the template into the current template inline, while the latter invoke the template specified and insert the result in place. It is some how like #include
vs. function call in c language. @include
is super fast to reuse part of template because it suppress the function invocation at runtime. It’s a inline function call if you speak c++. In other words, @include
process happen at template parsing time, while tag invocation happen at template executing time.
Things you can achieve with @include
but not template invocation:
- Reuse inline tag definition
Things you can achieve with template invocation but not @include
Because @include
are parsed at parsing time therefore it’s not possible to include template dynamically as shown below:
@// spec could be one of facebook, google
@args String spec
@include("page." + spec) @// THIS WON'T WORK
@invoke("page." + spec) @// THIS WORKS!
It is also not possible to apply invocation decorations to @include()
for the same reason.
@include("my.common.tag.lib").cache().assign("someVariable").raw() @// THIS WON'T WORK
@my.common.tag.lib().cache().assign("someVariable").raw() @// this works
@// the following also works
@chain().cache().assign("someVariable").raw() {
@include("my.common.tag.lib")
}
A good feature provided with @include
is that you can import the inline tag definition from the template being included:
Suppose you have created a template named rythm/util.html
with a set of inline tags:
@def hi (String who) {
Hi @who
}
@def bye (String who) {
Bye @who
}
Now in your other template you can import all the inline tags hi
, bye
and call them like a local function:
@include("util")
@hi("rythm")
@bye("rythm")
Macro is the forth code reuse mechanisn in Rythm. Like @include
, macro provides a way to reuse template content at parsing time, but inside a template file. Suppose you defined a macro called “myMacro”:
@macro("myMacro") {
content inside myMacro
}
Later on in the same template file you can invoke "myMacro" using the following code:
...
@exec("myMacro") @// first method to execute a macro
@myMacro() @// second method to execute a macro
which produce the following output:
content inside myMacro
At first glance this effect could be achieved using inline tag or even assignment. However they are fundamentally different in that macro executing happen at parsing time while tag call and assignment are happened at runtime, which could result in subtle differences of content been rendered. Let’s take a look at the following code:
@macro("foo") {
<foo>
<bar>abc</bar>
</foo>
}
@compact() {@exec("foo")}
---------------------------
@nocompact() {@exec("foo")}
So the above code will display “foo” content in compact and nocompact mode. If we change the implementation to:
@def foo() {
<foo>
<bar>abc</bar>
</foo>
}
@compact() {@foo()}
----------------------
@nocompact() {@foo()}
The result will not be expected. The reason is assignment will evaluate the content at runtime, and when “foo” content is assigned, the content is already produced and later on calling it in @compact
and @nocompact
block will not make any change. For the same reason the following code works neither:
@assign("foo") {
<foo>
<bar>abc</bar>
</foo>
}
@compact() {@foo.raw()}
-------------------------
@nocompact() {@foo.raw()}
@exec
, this is unlike @assign
, which is executed for only once when assignment happen.@foo()
will invoke foo
macro instead of foo
tagThe following table brief the four reuse mechanisms of Rythm:
External | Internal | |
---|---|---|
Runtime | @invoke | @def |
Parsing time | @include | @macro |
As shown in the above table, each one of the four reuse mechanims has it's characters. Here are some general guide lines to choose which method to use:
- For common code across multiple templates, use external reuse method, i.e. any one of
@invoke
and@include
- For repeating patterns appeared within current template, use internal reuse method, say one of
@def
and@macro
- If you need to pass parameters to the reuse part, use Runtime methods:
@invoke
or@def
- If you don't need parameters to the reuse part, use Parsing time methods:
@include
or@macro
Except@include
, all other three reuse mechanism support call style of @foo()
. This brings a concern of priority. Suppose you have a foo.html
template file, an inline tag with name foo
and a macro with name foo
, in case @foo()
is encountered, Rythm will decide which mechanism to use following the following rules:
The inline tag without arguments has higher priority than Macro
@def foo() {foo inside inline tag}
@macro(foo) {foo inside macro}
@foo()
Macro has higher priority than template invocation
- the template content of foo.html
foo invoked by template call
- the template content of the main template
@macro(foo) {foo inside macro}
@foo()
Template inheritance is a good way to implement template layout management.
@// this type of extended template declaration is deprecated: @extends("main.html")
@extends(main)
<h1>Some code</h1>
The main template is the layout template, you can use the doLayout tag to include the content of the declaring template:
<h1>Main template</h1>
<div id="content">
@doLayout()
</div>
render()
or renderSection()
without section name specified does the same thing with @doLayout()
You can even specify the default content if the sub template does not have any content provided:
<h1>Main template</h1>
<div id="content">
@doLayout() {
default content
}
</div>
Rythm use the same approach to look up extended template and template being invoked, for example, if you want to extend rythm/layout/foo/bar.html
, you can declare the extend statement as @extends(layout.foo.bar)
. Please refer to Handling paths for more detail.
Like Razor, Rythm provides a section concept to enable you to define sections in sub templates and output them in parent template.
In your layout template, say main.html:
<!DOCTYPE html>
@args String title = "ABC Corporate"
<html>
<head>
...
</head>
<body>
<div id="header">
<h1>@title</h1>
</div>
<div id="sidebar">
@// render "sidebar" section
@render("sidebar")
</div>
<div id="content">
@// render main content
@render() @// or doLayout()
</div>
<div id="footer">
@render("footer") {
@// here we define default content for footer section
@// if sub template failed to supply this section then
@// the default content will be output instead
<p>Site footer - © Santa Clause</p>
}
</div>
</body>
</html>
And in your working template, say ‘index.html’:
@extends(main)
@set(title="Home page")
<h2>Welcome to my site</h2>
<p>This is our home page</p>
<p>Not super exciting is it?</p>
<p>Yada, Yada, Yada</p>
@section("sidebar") { @// define "sidebar" section
<p>This sidebar has "Home Page" specific content</p>
<ul>
<li><a href="#">Link One</a></li>
<li><a href="#">Link Two</a></li>
<li><a href="#">Link Three</a></li>
</ul>
}
In the above example people with sharp eyes can notice that the footer section render in the layout template is different from the sidebar section in that we supplied a default content to the former. So if user did not define the footer section in the sub template, the default content will be output instead.