This chapter documents Rythm directives and their usage.
Directive syntax
- Directives should start with
@
, followed by directive name and then followed by()
, optionally followed by{
and}
block.
Declare template arguments and their types.
Style 1: declare all arguments in one line:
@args String foo, int bar, Map<String, Object> myMap, ...
Style 2: declare arguments in separate lines:
@// the comma at the line end is optional
@args() {
String foo
int bar,
Map<String, Object> myMap
...
}
Note
- There can be multiple
@args
statement in one template - A template variable must be declared before using it
- You cannot declare the same template argument twice. By same it means the template variable name is the same.
assign a block of rendering result into one local variable.
@// assign something into local variable foo
@assign(foo) {
foo's full content
and blah blah...
}
@// output foo content
@foo
@// use foo in other places
@for (foo.toString().split("\n")) {
@_index: @_
}
Note
- The local variable been assigned to is of type
Object
, which means - The local variable is declared while assignment happen, meaning there cannot be other variable (including template argument) with the same name been declared
- You can't treat it as a
String
, and use String method directly - Do not use quotation mark to enclose the variable name like
foo
in the sample
Break out from within a loop
@for(int i : [1 .. 9]) {
@if (i > 5) {@break}
@i
}
Note
- You can omit the
()
after@break
becausebreak
is a Java keyword
As writting a if condition to break is a common practice, Rythm enable you to do it in a simple way:
@for(int i : [1 .. 9]) {
@breakIf(i > 5) @// the same effect as @if (i > 5) {@break}
@i
}
- @cache() - cache render part
- code type - code type switch
- comment - inline or multi-line comments
- @compact() - force compact render part
- @continue - continue to next loop
The @cache()
directive caches render part:
@cache() {
<ul id="news-list">
@for(msg: news) {
<li>
<div class="caption">@msg.caption</div>
<div class="detail">@msg.detail</div>
</li>
}
</ul>
}
So the above code cache news list for the configured cache timeout (seconds), which is 60*60 (1 hour) if you haven't configured the item.
You can pass in timeout to @cache()
directive as parameter:
@// cache for 60 seconds
@cache(60) {
...
}
@// cache for 1 minute
@cache("1mn") {
...
}
@// cache for 2 hours
@cache("2h") {
...
}
When timeout parameter is specified in int
type, it means in seconds. If value timeout value is specified in a String, then it's parsed by duration parser and converted into seconds. The default duration parser regonize the following timeout strings:
1d
: 1 day3h
: 3 hours8mn
or8min
: 8 minutes23s
: 23 seconds
There is no explicit @codeType()
directive. The code type context switch is done implicitly when smart escape is enabled.
@// under default code type (html)
@content
<script type="text/javascript">
@// enter js code type implicitly
alert("@content");
</script>
@// exit js code type implicitly
Right now Rythm support Javascript and CSS code type via <script></script>
and <style></style>
tag
Comment directive enable you to add comment to Rythm template source without output them in the render result:
@// a single line comment
@*
A multiple line
comment
*@
@**
* And it can be format
* into good shape
*@
Force compact a render part without regarding to the compact mode configration.
@compact(){
abc ddd
1
3
4
}
Continue to the next loop iteration without executing the rest loop code
@for(int i : [1 .. 9]) {
@if (i < 5) {@continue}
@i
}
Note
- You can omit the
()
after@break
becausebreak
is a Java keyword
Like [@break]#break directive, you can write the if condition inside the @continueIf()
directive:
@for(int i : [1 .. 9]) {
@continueIf(i < 5) @// the same effect as @if (i < 5) {@continue}
@i
}
- @debug - print out debug info from within template
- @def - define inline tag
- @doLayout - alias of @render
Print debug info from with template
@if (order.closed()) {
@debug("The order[%s] is closed", order.id)
}
The above code will print out a log message to console if the order is closed.
Define a function (or you can call it inline tag) which can be called in the same template. There are 2 kind of functions can be defined with @def
:
Define a function without return value:
@def sayHelloTo(String who) {
Hello @who!
}
@sayHelloTo("Rythm")
@sayHelloTo("Velocity")
Define a function with return value:
@def String absoluteUrl(String path){
return "http://rythmengine.org/" + path;
}
@absoluteUrl("/doc/directive")
@absoluteUrl("/doc/index")
A interesting fact about @def
with and without return value:
When there is no return type or return type is void
, the source code inside the block is supposed to be template output. If you want to write Java code you need to enclose them inside the @{
and }
scripting block:
@def sayHello(String who) {
@{
// pure java code inside the @def without return type
// needs to be put inside scripting block
User user = User.current();
}
Hello @who (by @user.getFullName())
}
When return type is presented, the source code inside the block is treated as Java code, if you want to output something you need to write pe(...)
of the template base method:
@def boolean ok() {
int i = new Random().nextInt(100);
boolean ok = i % 2 == 0;
if (ok) {
pe("success!");
} else {
pe("fail!");
}
return ok;
}
@if (ok()) {
nice :)
} else {
OMG :(
}
pe(...)
to print out stuff inside a @def()
function with return type is not encouraged, as it brings side effect and is not a good design pratice.
See also:
This is an alias of @render.
switch escape scheme for render part.
foo in default context: @foo
bar in default context: @bar
@escape("js") {
foo in JS context: @foo
bar in JS context: @bar
}
@escape("csv") {
foo in CSV context: @foo
bar in CSV context: @bar
}
Escape accept any Object type parameters that evaluated to the following strings (case insensitive):
RAW
– do not escape. Same effect with @raw()CSV
- escape scheme set to csvHTML
- escape scheme set to htmlJS
|JAVASCRIPT
- escape scheme set to javascriptJSON
- escape scheme set to JavaXML
- escape scheme set to XML
Note
- The parameter is case incensitive
- If the parameter evaluated to
null
or empty string or no parameter has been passed in, then Rythm will find out the escape scheme based on the current code type context. - You cannot pass a literal parameter without quotation mark like
@escape(JS)
, in that case the engine will treatJS
as a variable name. This new logic in 1.0-b9 is different from old implementation.
Execute a macro.
@exec(terms_and_conditions)
Note
- A macro can be executed multiple times in a template
- You don't have to use
@exec
to execute a macro, rather, you can execute it by name:
@terms_and_conditions()
Indicate the parent template (ie. layout template) of this template.
@extends(myLayout)
You can pass parameters to the layout template in the extends statement.
Suppose your parent template is defined as:
@args String pageId, String theme
...
<body class="@pageId @theme">
...
@doLayout()
...
@extends(myLayout, pageId: "document", theme: "dark")
or pass parameter by position
@extends(myLayout, "document", "dark")
Note
- Rythm allow unlimited level of template inheritence
Try @extends
by yourself on rythm fiddle
A powerful iteration tool with Java based syntax and a couple of useful enhancements. This section covers the following parts about @for()
loop:
- Two for loop types
- Loop variables
- Join loop output
- Loop variable type inference
- More loop styles
- Smart loop expression
There are two type of @for
loop:
<ul>
@for(int i = 0; i < products.size(); ++i) {
<li><a href="/product/@product.getId()">@product.getName()</a></li>
}
</ul>
<ul>
@for (Product product: products) {
<li><a href="/product/@product.getId()">@product.getName()</a></li>
} else {
<div class="alert">No product found</div>
}
</ul>
You can omit loop variable type in most cases when Rythm has type information on the iterable, meaning the for
statement in the above sample could be simpled as:
@for (product: products) {
You can even omit the variable name in which case rythm use the implicit loop variable _
:
<ul>
@for (products) {
<li><a href="/product/@_.getId()">@_.getName()</a></li>
} else {
<div class="alert">No product found</div>
}
</ul>
There are several useful loop utility variables defined when you are using type II loop, the variables are prefixed with <varname>_
, or _
if you are using the implicit loop variable _
<varname>_index
: the loop index, start from1
<varname>_isFirst
:true
for the first loop<varname>_isLast
:true
for the last loop<varname>_parity
: alternates betweenodd
andeven
<varname>_isOdd
:true
for loop index isodd
in sequence<varname>_size
: size of the loop
<ul>
@for (Product product: products) {
<li class="@product_parity">@product_index. @product</li>
}
</ul>
You can extend the both type I and type II loop with a .join()
:
@for (products).join(","){
{
"name": "@_.getName()",
"category": "@_.getCategory()",
"price": @_.getPrice()
}
}
The above code join the loop output by ,
which makes it very easy to generate the JSON output. And actually you can omit the ",
" in the join()
as it is the default separator.
In case Rythm has the type information of the iterable, the variable type can be omitted:
@args List<User> users
...
@for(user: users) {
...
}
In the above sample code, Rythm knows the users
is of type List<User>
and it infer the type of the loop variable user
is User
, so you don't need to declare the loop variable type. Rythm also support deduct the loop variable type from Map
iterables
@args Map<String, Integer> myMap
...
@for(key: myMap.keySet()) {...} @// key type is String
@for(val: myMap.values()) {...} @// val type is Integer
Limit of loop variable type inference:
- The iterable must be declared with @args statement
- The iterable cannot be an expression, e.g.
@for(v: foo.bar()){...}
For type II loop, besides the Java loop style, Rythm also support JavaScript and Scala style:
classic Java style
@for(s : list) {...}
JavaScript style
@for(s in list) {...}
Scala style
@for(s <- list) {...}
Rythm support several loop expression variations to save template author's typings
String spliting
@for("a, b, c").join(){@_} @// output a,b,c
@for("a : b:c").join(){@_} @// output a,b,c
@for("a; b;c").join(){@_} @// output a,b,c
@for("a - b - c").join(){@_} @// output a,b,c
@for("a_b_c").join(){@_} @// output a,b,c
Number ranges
@for (1 .. 5).join() {@_} @// output: 1,2,3,4
@for ([1 .. 5]).join() {@_} @// output: 1,2,3,4,5
Try @for
by yourself on rythm fiddle
Retrieve template attribute which is set previously and print it out.
@set("pageTitle": "orders")
...
@get("pageTitle")
Note the @set
and @get
is not to be used inside a single template because the above code could be simply replaced by
@{String pageTitle = "orders";}
...
@pageTitle
The initial purpose of @set/@get
pair is to pass data from a sub template to parent/layout template:
Sub template:
@extends(layout)
@set(pageTitle: "orders")
Layout template:
<title>@get("pageTitle")</title>
However there is an new approach to passing data from sub template to layout template:
Sub template:
extends(layout, pageTitle: "orders")
Layout template:
@args String pageTitle
<title>@pageTitle</title>
See also
- @i18n - internationalization a string
- @if - if/else flow control
- @import - declare packages to import
- @include - include another template content in place
- @inherited - render layout template default section content in place
- @init - specify the code to be executed before rendering processs start
- @invoke - call another template
Translate the supplied message code and optional parameters using current locale:
@args String x
// -- locale: default
@x: @i18n(x)
// -- locale: en
@locale("en"){
@x: @i18n(x)
}
// -- locale: zh_CN
@locale("zh_CN") {
@x: @i18n(x)
}
@i18n()
support complex message code translation with parameters:
// -- complex msg with params
@i18n('msg', "planet", 7, new Date())
// -- complex msg with params in Chinese
@locale("zh_CN") {
@i18n('msg', "planet", 7, new Date())
}
// -- relevant resource keys
@verbatim(){
x=foo
planet=Mars
msg=At {2,time,short} on {2,date,long}, we detected {1,number,integer} spaceships on the planet {0}.
}
Note
Literally you always use the i18n transformer to replace the @i18n()
directive or vice versa.
The branch flow control. It simply use the Java syntax:
@if(age<8) {
@age < 8
} else if(age<16) {
@age < 16
} else {
@age >= 16
}
To make it easy and fun, Rythm support smart evaluation in the if
statement:
@args String name, int money, List<String> names
---- smart evaluate: String ----
@if (name) {
name is true
} else {
name is false
}
---- smart evaluate: Number ----
@if (money) {
money is non-zero
} else {
money is zero
}
---- smart evalute: collections ---
@if(names) {
names is not empty
} else {
names is empty
}
Below is the smart evaluation rule table
expression type | rule |
---|---|
byte b | b != 0 |
char c | c != 0 |
boolean b | b |
int n | n != 0 |
long l | l != 0L |
float f | Math.abs(f) > 0.00000001 |
double d | Math.abs(d) > 0.00000001 |
String s |
if (S.isEmpty(s)) return false; if ("false".equalsIgnoreCase(s)) return false; if ("no".equalsIgnoreCase(s)) return false; return true; |
Collection c | null != c && !c.isEmpty() |
Map m | null != m && !m.isEmpty() |
Byte b | null != b && eval(b.byteValue()) |
Boolean b | null != b && b |
Character c | null != c && eval(c.charValue()) |
Float f | null != f && eval(f.floatValue()) |
Double d | null != d && eval(d.doubleValue()) |
Number n | null == n ? false : eval(n.intValue()) |
Object condition |
if (condition == null) { return false; } else if (condition instanceof String) { return eval((String) condition); } else if (condition instanceof Boolean) { return (Boolean) condition; } else if (condition instanceof Collection) { return eval((Collection) condition); } else if (condition instanceof Map) { return eval((Map) condition); } else if (condition instanceof Double) { return eval((Double) condition); } else if (condition instanceof Float) { return eval((Float) condition); } else if (condition instanceof Long) { return eval((Long) condition); } else if (condition.getClass().isArray()) { return Array.getLength(condition) > 0; } else if (condition instanceof Number) { return eval((Number) condition); } return true; |
Declare java package to be imported. This is exactly the same as import
keyword in Java except a few enhancement:
- You can
@import
multiple package in the same line - You can use
@import
in any place of your template
Sample 1: import a package and use class in it without full qualification
@import java.text.*
@//now you can use the imported package content without full qualification
@{
Date today = new Date();
DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
String formatted = df.format(today);
}
Today is @formatted
@(new Date().format("yyyy-MM-dd"))
is okay
Sample 2: import a class methods/fields statically and use them without full qualification
@import static java.util.Locale.*
@locale(GERMAN){
...
}
Sample 3: import multiple packages in the same line:
@import java.text.*, java.util.regex.*
When you have many packages that is not easy to read in one line, you can use the second style of @import
directive and organize them into multiple lines:
@import(){
java.text.*
java.util.regex.*
static java.util.Locale.*
}
Or mix the both styles together:
@import() {
java.text.*,java.util.regex.*
static java.util.Locale.*
}
Rythm will import the following packages implicity even you haven't declared them with @import
directive:
- java.util.*
- java.io.* (not imported when running in sandbox mode)
- org.rythmengine.template.TemplateBase
Include content from another template in place. If you have a certain part of template code that can be used in multiple templates, then you can separate them into a single template source file, and then use @include
directive to load the content of that file in place of the current template. The process happen at parsing time, it' mostly like a @exec
directive which load a macro defined in the same template file. Samples:
Suppose you have defined a template dialog
:
<div id="dia-global" class="modal hide fade">
<div class="modal-header">
<h1 data-bind="text: title"></h1>
</div>
<div class="modal-body" data-bind="html: body">
</div>
<div class="modal-footer">
<a class="btn btn-primary" data-bind="click: callback(), text: btnText"></a>
<a class="btn close" data-dismiss="modal" data-bind="text: btn2Text(), visible: btn2Text()"></a>
</div>
</div>
You can include the template in any other templates using @include()
@include(dialog)
See Locate template(TBD) to understand how Rythm locate dialog
template path.
Alias of @renderInherited.
Include layout template section default content in place. This directive can only be used within @section() {} context.
Suppose the layout template defines a section footer with default content:
<div id="footer">
@render(footer) {
copyright(c) 2001 XYZ co ltd.
}
</div>
In the child template you can output the default content along with specific footer:
@section(footer) {
The specific page of XYZ @inherited
}
And it renders the following output:
<div id="footer">
The specific page of XYZ
copyright(c) 2001 XYZ co ltd.
</div>
Specify a code segment to run before executing template build method. This is mainly to setup certain state which is only evaluated at runtime but must be setup before any render output issued. The following sample comes from rythm website
@init() {
if (page.startsWith("feature")) super.__setRenderArg("curPage", "feature");
else super.__setRenderArg("curPage", "doc");
}
So the above code says, if the page variable (String type) starts with "feature
" then set the template argument "curPage
" to "feature
" in the layout template which is specified with "super
", otherwise set "curPage
" to "doc
". this impact the top menu bar's highlight part. The status is only evaluated at runtime when rythm get the value of "page
" template argument; and it must be computed before building the output of the template.
@init(){}
block is Java code, not template outputs.
Invoke/call another template. Like @include, @invoke
reuse another external template source file, however @invoke
is more powerful:
- passing parameters to another template
- passing body
- further processing the invocation result
- chain different invocation result processing together
- dynamic invocation
You don't need to use @invoke()
to call a template, instead you use the template name directly. So the following two styles of template invocation call are exactly the same effects:
Style 1: call template with @invoke
directive
@invoke("foo", 1, 2, 3)
Style 2: call template with template name
@foo(1, 2, 3)
@invoke
process happens at runtime, which allows you to pass runtime parameters to the template being called. You can pass parameters by position or by name as shown below:
@hello("Rythm") @// by position
@hello(who: "Rythm") @// by argument name
@foo("param1", 2, 3.0) @// by position
@foo(p1: "param1", p2: 2, p3: 3.0) @// by name
You can even use the JS style:
@foo({
p1: "param1",
p2: 2,
p3: 3.0
})
Rythm automatically pass the caller template's argument values to callee template if they were the same name and same type. Thus you don't need to explicitly pass parameters when invoking the callee template from caller template if they share the same arguments. Click "Try " to view the sample in rythm fiddle to understand this concept
@args String who
@foo()
You can pass in a render part enclosed with { }
pair when calling a template:
<form>
@fieldSet("order form"){
<select name="product.id">...</select>
<input type="text" name="customer.name"></input>
...
}
</form>
So in the above template call, @fieldSet
is another template which accept a body when you call it. And the template might looks like:
@args String label
<fieldset>
<legend>@label</legend>
@// use @renderBody() to output the passed in body (the form)
@renderBody()
</fieldset>
You can declare arguments in the body to be passed to another template and let that template to call the body with parameters.
The code to call another template with body with arguments
@lookupRole(permission: "superuser").callback(List<Role> roleList) {
<ul>superusers
@for(Role role: roleList) {
<li>role.getName()</li>
}
</ul>
}
The lookupRole.html
template:
@args String permission
@{
List<Role> roles = Role.find("permission", permission).asList()
}
renderBody(roles)
The result of template invocation is output as raw data. You can dictate rythm to escape a template invocation result if needed:
@foo().escapeJSON()
@foo().escape()
@foo().escape("CSV")
You can cache a template invocation result by .cache()
extension:
@foo(1, 2, "3").cache() @// cache using default TTL
@foo(1, 2, "3").cache("1h") @// cache for 1 hour
@foo(1, 2, "3").cache(60 * 60) @// cache for 1 hour
The above statement invoke template foo
using parameter [1, 2, “3”] 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 [1, 2, “3”].
So you see Rythm is smart enough to cache invocation against template name and the parameter passed in. If the template invocation has body passed in, the body will also be taken into consideration when calculating the cache key.
In some cases where the tag invocation result is not merely a function of the template name, 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 variables into account when calculating the cache key:
@{User user = User.current()}
@foo(1, 2, "3").cache("1h", user.isAdmin())
The above statement shows how to pass additional parameter to cache the template invocation.
In case you want to refer the invocation result in more than one places in the current template, you can assign the invocation result to a local variable (which shall not be the same name with any template argument or other local variables), and use @
expression to refer to the variable instead of invoking the template multiple times:
@countrySelect().assign("selCountries")
...
@selCountries @// output country select here
...
@selCountries @// output country select there
You can chain different invocation processings together:
@foo().escapeJS().cache("1h").assign("xResult").callback(String name) {
alert('@name')
}
Dynamic invocation means the template to be invoked is determined by a runtime variable, in this case, you must use the @invoke
directive:
@args String platform @// could be pc, iphone, ipad ...
...
@invoke("designer." + platform)
So when platform is iphone, the above case has the same effect as calling @designer.iphone()
.
Usually when a template been invoked cannot be found, Rythm will report an error. Sometimes it is expected that certain template does not exists, in which case one can use .ignoreNonExistsTag()
extension with invoke keyword:
@invoke("designer." + platform).ignoreNonExistsTag()
- @locale - set locale for a render part
- @macro - define macro
- @nocompact - specify not the a render part that whitespace shall not be compact
locale setting impact the output of @format()
transformer and i18n()
directive/transformer. Usually the locale of a template is set before rendering process started. But you can use @locale()
to set the locale for a specific template part if the page is designed to display multiple languages:
@locale("zh-cn") {
...
}
Use @macro
to define a macro which can be executed with @exec()
or invoke directly by name:
@macro("terms_and_conditions") {
<h1>Terms and Conditions</h1>
...
}
Note, unlike inline function defined by @def
or normal template, macro doesn't accept parameters.
Specify not to compact a render part without regard to the current compact setting:
@nocompact(){
<pre>
-----------------
@title
------------------
Name: @name
Email: @email
</pre>
}
- @raw - output raw content
- @render - render layout content
- @renderBody - render tag enclosing body
- @renderInherited - render layout template default section content
Specify a render part that expression out shall not be escaped:
@args Component component
<div id="@component.id" class="@component.class">
@raw() {
@component.body
}
</div>
Used in layout(parent) template to output sub template main content or sections:
<html>
<head>
...
</head>
<body>
<div id="header">
@// output the "header" section
@render(header) {
default header
}
</div>
<div id="main">
@render() @// output sub template content that are not in named sections
</div>
<div id="footer">
@// output the "footer section
@render(footer) {
default footer
}
</div>
</body>
</html>
Render the calling template enclosing body. See here
Alias of @inherited
Stop the template execution immediately:
@args User user
@extends(main, "Order Manager")
@if (!user.is("order-manager")) {
<div class="alert">
You don't have the right to access this page
</div>
@return
}
Note you don't have to type ()
after @return
because return
is a Java reserved word.
In simple case you can pass an expression to @return()
to save the @if
statement:
@returnIf(!user.is("order-manager"))
@// the above is exactly the same as:
@if(!user.is("order-manager")) {
@return
}
You can write any java code in template source with scripting block:
@{
String s = "foo";
int l = s.length();
...
}@
You can actually save the last @
of the scripting block:
@{
// write any java source code here
}
If you have different sections defined by @render()
in the layout template, you can define the content of the sections in the sub template via @section
directive. Corresponding to the layout template defined in the sample of @render, here is the sub template:
@args List<Order> orders
@extends(layout)
@section(header) {
<h1>Order Manager</h1>
}
@for (orders) {
<div class="order">
...
</div>
}
Pair with @get @set
directive is used to pass value from a sub template to layout template. However it is deprecated now. See more on @get
Output current timestamp:
@ts()
Specify part of the template that shall be output literally:
A sample rythm template
@verbatim() {
<pre><code>
@args String who
Hello @who!
</code></pre>
}