# 6.4 Directives
The Enjoy Template Engine consistently adheres to a minimalist design. With just seven core directives—#if
, #for
, #switch
, #set
, #include
, #define
, #(...)
—it achieves nearly all the functionalities of traditional template engines. The learning curve is almost non-existent for users with a foundation in any programming language.
If the built-in directives do not meet your needs, you can easily extend them at the template language level. Under the com.jfinal.template.ext.directive
package, there are five extended directives. The Active Record SQL module also extends three directives specifically for SQL management. By studying the code for these extended directives, you can easily understand how to create your own, making the process extremely simple.
Note that the extension of Enjoy template engine directives occurs at the lexical and syntactic analysis levels. This is entirely different from the tag-based extensions of traditional template engines. The former allows for comprehensive and flexible use of the engine's basic infrastructure, enabling versatile functionalities through very simple and straightforward code. To understand its power and convenience, refer to the SQL management module of Active Record.
# 1. Output Directive #(...)
Unlike almost all Java template engines, Enjoy Template Engine eliminates the independent concept of interpolation directives, treating them as one among other directives. This directive uses parentheses as delimiters, just like any other directive, avoiding the need for extra delimiters like curly braces.
Using the output directive #(...)
is very simple; just pass any expression described in Section 6.4. The directive will output the evaluation result of these expressions. Specifically, if the expression evaluates to null, there will be no output and no exceptions will be thrown. For example:
#(value)
#(object.field)
#(object.field ??)
#(a > b ? x : y)
#(seoTitle ?? "JFinal Club")
#(object.method(), null)
2
3
4
5
6
As shown above, just pass an expression to the output directive. Note that in the first line, the value
parameter can be null. If object
is null in the second line, an exception will be thrown. To avoid this, use the null-safe access operator object.field ??
.
Additionally, note the last line in the example. The directive argument is a comma expression, and the directive will only output the last expression in the comma sequence. If the last expression evaluates to null, no output will be generated.
The output directive can be customized by extending the OutputDirectiveFactory
class and overriding its getOutputDirective
method. Then you can switch to your custom output directive using me.setOutputDirectiveFactory(...)
in the configEngine(Engine me)
method.
# 2. #if
Directive
Example:
#if(cond)
...
#end
2
3
As shown, the #if
directive requires a cond
expression and ends with #end
. The cond
can be any expression introduced in Section 6.3. When cond
evaluates to true, the code inside the if
block is executed.
The #if
directive naturally supports #else if
and #else
blocks. Example:
#if(c1)
...
#else if(c2)
...
#else if(c3)
...
#else
...
#end
2
3
4
5
6
7
8
9
The usage of #else if
and #else
is identical to Java syntax. (Note: In versions prior to JFinal 3.3, you must write #elseif
without any space between else
and if
, otherwise an exception will be thrown.)
# 3. #for
Directive
The Enjoy Template Engine offers an extremely user-friendly extension to the #for
directive, allowing iteration over any type of data, including null values. Code example:
// Iterating over List, array, Set structures
#for(x : list)
#(x.field)
#end
// Iterating over a Map
#for(x : map)
#(x.key)
#(x.value)
#end
2
3
4
5
6
7
8
9
10
The first #for
directive iterates over a list, and the usage is identical to Java syntax.
The second #for
directive iterates over a map, using item.key
and item.value
to access the elements. This is an enhancement by Enjoy to reduce code verbosity. You can also use the traditional Java map iteration method: #for(x : map.entrySet()) #(x.key) #(x.value) #end
Note: When the target of iteration is null, you don't need to check for null values; the #for
directive will automatically skip the iteration, avoiding the need for if
checks and thereby improving efficiency.
The #for
directive also allows you to access its state. Code example:
#for(x : listAaa)
#(for.index)
#(x.field)
#for(x : listBbb)
#(for.outer.index)
#(for.index)
#(x.field)
#end
#end
2
3
4
5
6
7
8
9
10
In the code above, #(for.index)
and #(for.outer.index)
are used to access the current state of the #for
directive. The former gets the current iteration index (starting from 0), and the latter allows an inner #for
directive to access the state of an outer #for
directive.
Note: When #for
directives are nested, each has its own variable scope, consistent with Java language rules. For example, the two instances of #(x.field)
in the example above are in different #for
directive scopes and will correctly access the variable values in their respective scopes.
The #for
directive supports the following states:
#for(x : listAaa)
#(for.size) Size of the iterated object
#(for.index) Index starting from 0
#(for.count) Count starting from 1
#(for.first) Is it the first iteration?
#(for.last) Is it the last iteration?
#(for.odd) Is it an odd iteration?
#(for.even) Is it an even iteration?
#(for.outer) References the state of the outer #for directive
#(for.outer.size) Size of the iterated object in the outer #for directive
#end
2
3
4
5
6
7
8
9
10
11
12
The specific usage is explained in Chinese in the code above.
Apart from Map
and List
, the #for
directive also supports Collection
, Iterator
, array, Iterable
, Enumeration
, and null value iteration. The usage is identical in form to the earlier List
iteration, all being #for(id : target)
. For null values, the #for
directive will directly skip the iteration.
Additionally, the #for
directive supports iteration over any type of object, simply iterating over it once. Example:
#for(x : article)
#(x.title)
#end
2
3
In the example above, article
is just a regular Java object, not a collection. The #for
loop will iterate over this object just once. The x
in the #for
expression is the article
object itself, so you can use #(x.title)
for output
The #for
directive also supports the #else
branch. When the number of iterations is zero, the #else
block is executed. Example:
#for(blog : blogList)
#(blog.title)
#else
You haven't written any blogs yet. Click here <a href="/blog/add">to start blogging</a>.
#end
2
3
4
5
6
7
In the code above, when blogList.size()
is 0 or blogList
is null, meaning the number of iterations is zero, the #else
branch will execute. This scenario is very common in web projects.
Lastly, in addition to the #for
directive iteration methods mentioned above, it also supports a more conventional for
loop syntax. Code example:
#for(i = 0; i < 100; i++)
#(i)
#end
2
3
The syntax is almost identical to Java, with the only difference being that variable declarations don't need types; direct assignment statements suffice. Variables in the Enjoy Template Engine are dynamically typed.
Note: This form of for
loop lacks for.size
and for.last
states, and only supports the following states: for.index
, for.count
, for.first
, for.odd
, for.even
, for.outer
.
The #for
directive also supports #continue
and #break
commands, and their usage is entirely consistent with Java. Further details are not provided here.
# 4. #switch
Directive (Added in version 3.6)
The #switch
directive is aligned with Java's switch
statement, offering similar basic usage but with some improvements for a better user experience. Usage is as follows:
#switch (month)
#case (1, 3, 5, 7, 8, 10, 12)
#(month) months have 31 days
#case (2)
#(month) has 28 days in a common year and 29 days in a leap year
#default
Invalid month: #(month ?? "null")
#end
2
3
4
5
6
7
8
As shown in the code above, the #case
directive supports multiple arguments separated by commas, effectively eliminating the need for a #break
directive. Therefore, the Enjoy Template Engine does not require the use of #break
.
Parameters for the #case
directive can be any expression, such as:
#case (a, b, x + y, "abc", "123")
In the code above, the comma-separated expressions are first evaluated, and then each is compared to the value in #switch(value)
. If any of these values match, that #case
branch is executed.
The support for multiple parameters in #case
, without the need for #break
, not only reduces code but also eliminates potential errors from forgetting to include #break
. Also, unlike Java syntax, neither #case
nor #default
uses a colon character.
# 5. #set
Directive
The #set
directive is used for variable declaration and assignment. It accepts only assignment expressions or a list of comma-separated assignment expressions. Code example:
#set(x = 123)
#set(a = 1, b = 2, c = a + b)
#set(array[0] = 123)
#set(map["key"] = 456)
#(x) #(c) #(array[0]) #(map.key) #(map["key"])
2
3
4
5
6
In the above code, the first line simply assigns 123 to x. The second line is a list of assignment expressions executed from left to right. If the right-hand side of the equation has an expression, it will be evaluated before assignment. The last line outputs the values of the variables, and other directives can also access these variables like the output directive.
Note that the #for
, #include
, and #define
directives create new variable scopes. The #set
directive first looks for the variable in the current scope; if found, it operates on it, otherwise, it continues to the upper scope. If still not found, it defines the variable in the top-level scope. This design is very beneficial for variable value transmission within templates.
To explicitly specify assignment in the current scope, you can use the #setLocal
directive, which has the same parameters and usage as #set
but operates only in the current scope. #setLocal
is often used within #define
and #include
to avoid naming conflicts.
Important: Since assignment expressions are essentially expressions, and other directives inherently support any expression, the #set
directive is not mandatory for assignments. For example, assignments can be done in the #()
output directive:
#(x = 123, y = "abc", array = [1, "a", true], map = {k1:v1}, null)
The code above uses multiple assignment expressions in the output directive to achieve the functionality of #set
, ending with a null value to avoid any output. Similarly, other directives can also use assignment expressions.
# 6. #include
Directive
The #include
directive is used to include external template content, which is parsed as part of the current template. Code example:
#include("sidebar.html")
The first parameter of the #include
directive must be a String constant. If it starts with /
, it will look for the file relative to baseTemplatePath
; otherwise, it will look for the file relative to the current template's path.
baseTemplatePath
can be configured in configEngine(Engine me)
through me.setBaseTemplatePath(...)
.
Additionally, the #include
directive supports an unlimited number of assignment expressions, which is very beneficial for modularization. For example, the template file named _hot_list.html
is used to display hot projects, hot news, etc.:
<div class="hot-list">
<h3>#(title)</h3>
<ul>
#for(x : list)
<li>
<a href="#(url)/#(x.id)">#(x.title)</a>
</li>
#end
</ul>
</div>
2
3
4
5
6
7
8
9
10
The variables title
, list
, and url
in the HTML fragment are needed for rendering "Hot Projects" and "Hot News":
#include("_hot_list.html", title="Hot Projects", list=projectList, url="/project")
#include("_hot_list.html", title="Hot News", list=newsList, url="/news")
2
In the two lines above, different values for title
, list
, and url
are passed to _hot_list.html
, enabling modular reuse of _hot_list.html
.
# 7. #render
Directive
The #render
directive is nearly identical to #include
in usage, supporting an unlimited number of assignment expressions. There are two main differences:
#render
supports dynamic template parameters, e.g.,#render(temp)
, wheretemp
can be any expression. In contrast,#include
can only use string constants like#include("abc.html")
.Functions defined in
#render
using#define
are only effective in its sub-template and are not valid in the parent template. This design is very beneficial for modularization.
The core purpose of introducing the #render
directive is to support dynamic template parameters.
# 8. #define
Directive
The #define
directive is one of the main ways to extend the template engine. It allows you to define template functions (Template Functions). With #define
, you can define reusable template segments as individual template functions, which can be customized by passing in parameters when called.
Here's how to use #define
to implement a layout feature. First, create a layout.html
file with the following code:
#define layout()
<html>
<head>
<title>JFinal Club</title>
</head>
<body>
#@content()
</body>
</html>
#end
2
3
4
5
6
7
8
9
10
In the code above, a template function named layout
is defined using #define layout()
. It ends with #end
. Inside it, #@content()
refers to another template function named content
.
Note: When calling a template function, an additional @
symbol is used to differentiate it from directive calls.
Next, create another template file as follows:
#include("layout.html")
#@layout()
#define content()
<div>
This is the template content, similar to the 'nested' part of traditional template engines.
</div>
#end
2
3
4
5
6
7
8
The first line includes the previously created layout.html
. The second line calls the layout
template function defined in layout.html
, which in turn calls the content
function defined in the current file. Simply understand this as function definition and function invocation.
To make a layout template reusable across many templates, you can set all the template functions in that file as shared by using me.addSharedFunction("layout.html")
in configEngine(Engine me)
. This eliminates the need for #include
, reducing code redundancy and improving reusability.
The Enjoy Template Engine eliminates boring concepts like layout
, nested
, and macro
, thus reducing the learning curve and greatly enhancing extensibility.
Template functions support formal parameters, similar to Java but without specifying the parameter type. Here's a code example:
#define test(a, b, c)
#(a)
#(b)
#(c)
#end
2
3
4
5
To call this function, you would use:
#@test(123, "abc", user.name)
The number of formal and actual parameters should match. If you need to pass additional, optional parameters, you can use the #set
directive beforehand.
# 9. Template Function Calls and #call
Directive
To call a template function defined by #define
, use #@name(p1, p2…, pn)
. Note the additional @
symbol to differentiate it from directive calls.
Template functions also support safe invocation, denoted by #@name?(p1, p2…, pn)
. Safe calls do nothing if the template function is not defined, making them suitable for optional template content. For example:
#define layout()
<html>
<head>
<link rel="stylesheet" type="text/css" href="/assets/css/jfinal.css">
#@css?()
</head>
<body>
<div class="content">
#@main()
</div>
<script type="text/javascript" src="/assets/js/jfinal.js"></script>
#@js?()
</body>
</html>
#end
2
3
4
5
6
7
8
9
10
11
12
13
14
15
You can then define additional CSS or JS content through #define css()
and #define js()
in templates that require them.
The #call
directive is new in jfinal 3.6 and allows for dynamic specification of both the template function name and parameters. The usage is as follows:
#call(funcName, p1, p2, ..., pn)
To ignore the call if the function doesn't exist, add a constant value true
as the first parameter:
#call(true, funcName, p1, p2, ..., pn)
This makes the template function calling mechanism much more flexible.
# 10. #date
Directive
The #date
directive is used for formatting the output of date-type data, including all objects that inherit from the Java Date class, such as Date and Timestamp. The usage is incredibly straightforward:
#date(account.createAt)
#date(account.createAt, "yyyy-MM-dd HH:mm:ss")
2
In the first line of code, only one parameter is used, so the date is formatted using the default date pattern, which is "yyyy-MM-dd HH:mm". In the second line, the date is formatted according to the pattern specified in the second parameter.
If you want to change the default output format, you can configure it using engine.setDatePattern()
.
# keepPara Issue
When a date-type field is submitted to the backend, and the backend uses the Controller's keepPara()
method, it converts this date-type data into a String type. In such cases, using #date(...)
to output this String will throw an exception. To handle this, you can 'keep' the type as shown below:
// keepPara() keeps all form submission data, converting them to String types.
keepPara();
// Use parameterized keepPara again to specify that the 'createAt' field should be kept as a Date type.
keepPara(Date.class, "createAt");
2
3
4
5
As shown above, the second line of code uses the Date.class
parameter to specifically keep the 'createAt' field as a Date type. This way, the #date(createAt)
directive on the page will not throw an exception. Methods like keepModel(...)
and keepBean(...)
will maintain the original types, so no additional handling is needed.
# 11. #number
Directive
The #number
directive is used to format the output of numerical data, including all objects that inherit from the Java Number class, such as Double, Float, Integer, Long, and BigDecimal. The usage is exceedingly simple:
#number(3.1415926, "#.##")
#number(0.9518, "#.##%")
#number(123456789, ",###")
#number(300000, "The speed of light is ### km per second.")
2
3
4
In the above examples, the first parameter for the #number
directive is of a numerical type, and the second parameter is a String pattern. The pattern parameter works the same way as the pattern used in Java's DecimalFormat class. If you're not sure how to use the pattern, you can search for "DecimalFormat" in a search engine to find plenty of resources.
The two parameters for the #number
directive can be variables or complex expressions. The use of constants in the above examples is merely for demonstration purposes.
# 12. #escape
Directive
The #escape
directive is used for HTML-safe output, helping to mitigate XSS attacks. It escapes characters like '<' and '>' in HTML-formatted data. For example, it would convert '<' to <
and a space to
.
Used in a similar way to the output command:
#escape(blog.content)
# 13. Directive Extension
Thanks to the original DKFF and DLRD algorithms, the Enjoy Template Engine allows for extremely convenient extensions of directives at the language level. The amount of code required is minimal, and the learning curve is virtually non-existent. Here's a code example:
public class NowDirective extends Directive {
public void exec(Env env, Scope scope, Writer writer) {
write(writer, new Date().toString());
}
}
2
3
4
5
In the above code, by extending the Directive
class and implementing the exec
method, a new #now
directive is created with just three lines of code. This directive outputs the current date into the template. To use it, you only need to add it to the template engine using me.addDirective("now", NowDirective.class)
. Here's how to use this directive in a template:
The current date is: #now()
In addition to supporting directives without an #end
block (i.e., directives without a body), the Enjoy Template Engine also directly supports directives that include an #end
and a body. Here's an example:
public class Demo extends Directive {
// ExprList represents the list of parameter expressions for the directive
public void setExprList(ExprList exprList) {
// Custom control of exprList can be done here
super.setExprList(exprList);
}
public void exec(Env env, Scope scope, Writer writer) {
write(writer, "Before executing body");
stat.exec(env, scope, writer); // Execute body
write(writer, "After executing body");
}
public boolean hasEnd() {
return true; // Returning true means this directive has an #end ending tag
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
In the example above, the Demo
class extends Directive
and overrides the hasEnd
method, returning true
. This indicates that the extended directive has an #end
ending tag. The exec
method contains three lines of code. The stat.exec(...)
line executes the code within the directive's body. The lines before and after stat.exec(...)
output a string, and the final output is a combination of all these.
Additionally the parameters of the directive can be controlled by overriding the setExprList(...) method of the parent class, which is not required.
After adding to the engine via me.addDirective("demo", Demo.class), it can be used as in the following code example: `` #demo() Here is the content of the demo body #end
The final output looks like this:
body Before execution Here is the content of the demo body After body
The #demo directive body in the above example contains a string of characters that will be executed by stat.exec(...) in the Demo.exec(...) method, and write(...) before and after stat.exec(...). The results produced by the two method calls and the results produced by body generate the final result.
**Important**: Properties declared in directives are globally shared, so they must be thread-safe. For instance, consider a snippet from `com.jfinal.template.ext.directive.DateDirective`:
```java
public class DateDirective extends Directive {
private Expr valueExpr;
private Expr datePatternExpr;
private int paraNum;
...
}
2
3
4
5
6
7
8
9
10
11
12
Here, there are three properties of types Expr
, Expr
, and int
. Expr
is thread-safe. Although int paraNum
may not seem thread-safe, it is only written to during initialization in the constructor, and read everywhere else. Thus, this int
property is also thread-safe in this context.
# 14. Common Mistakes
The most common mistake when using the Enjoy Template Engine is confusing "expressions" and "non-expressions." Expressions refer to everything within the parentheses when calling a directive or template function:
#directiveName(everything here is an expression)
#@functionName(everything here is an expression)
2
For example, the correct usage is:
#directiveName(user.name)
A common mistake would be:
#directiveName( #(user.name) )
Simply put, this mistake involves using a directive where an expression should be used. Never use the '#' character within an expression; just use Java expressions directly.