自定义指令
自定义指令可以使用 macro
指令来定义,
这是模板设计者所关心的内容。
Java程序员若不想在模板中实现定义指令,而是在Java语言中实现指令的定义,
这时可以使用 freemarker.template.TemplateDirectiveModel
类来扩展
(请参考 后续章节)。
基本内容
宏是有一个变量名的模板片段。可以在模板中使用宏作为自定义指令, 这样就能进行重复性的工作。例如,创建一个宏变量来输出大字号的''Hello Joe!'':
<#macro greet> <font size="+2">Hello Joe!</font> </#macro>
macro
指令自身不输出任何内容,
它只是用来创建宏变量,所以就会有一个名为 greet
的变量。在 <#macro greet>
和
</#macro>
之间的内容
(称为 宏定义体)
将会在使用该变量作为指令时执行。可以在FTL标记中通过
@
代替#
来使用自定义指令。
使用变量名作为指令名。而且,自定义指令的 结束标记 也是需要的。那么,
就可以这样来使用 greet
:
<@greet></@greet>
因为
<anything></anything>
和
<anything/>
是相同的,
也可以使用单标记形式(如果你了解 XML,那么就应该很熟悉了):
<@greet/>
将会输出:
<font size="+2">Hello Joe!</font>
宏能做的事情还有很多,因为在
<#macro ...>
和 </#macro>
之间的东西是模板片段,也就是说它可以包含插值
(${...}
) 和FTL标签
(如 <#if
...>...</#if>
)。
程序员通常将使用
<@...>
这称为
宏 调用。
参数
我们来改进 greet
宏使之可以使用任意的名字,
而不仅仅是''Joe''。为了实现这个目的,就要使用到
参数。在 macro
指令中,宏名称的后面位置是用来定义参数的。这里我们仅在
greet
宏中定义一个参数,person
:
<#macro greet person> <font size="+2">Hello ${person}!</font> </#macro>
那么就可以这样来使用这个宏:
<@greet person="Fred"/> and <@greet person="Batman"/>
这和HTML的语法是很相似的,将会输出:
<font size="+2">Hello Fred!</font> and <font size="+2">Hello Batman!</font>
那么我们就看到了,宏参数的真实值是可以作为变量
(person
)放在宏定义体中的。使用 预定义指令时,参数的值
(=
号后边的值)可以是 FTL 表达式。
那么,不像HTML,"Fred"
和 "Batman"
引号就可以不用要了。
<@greet person=Fred/>
也意味着使用变量的值
Fred
作为 person
参数,
而不是字符串"Fred"
。当然参数值并不一定是字符串类型,
也可以是数字,布尔值,哈希表,序列等。也可以在 =
号左边使用复杂表达式(比如 someParam=(price + 50)*1.25
)。
自定义指令可以有多个参数。如下所示,再添加一个新的参数 color
:
<#macro greet person color> <font size="+2" color="${color}">Hello ${person}!</font> </#macro>
那么,这个宏就可以这样来使用:
<@greet person="Fred" color="black"/>
参数的顺序不重要,下面的这个和上面的含义也是相同的:
<@greet color="black" person="Fred"/>
当调用这个宏的时候,只能使用在 macro
指令中定义的参数(本例中是:person
和 color
)。
那么当你尝试 <@greet person="Fred" color="black" background="green"/>
的时候就会发生错误,因为并没有在 <#macro...>
中提及参数 background
。
同时也必须给出在宏中定义所有参数的值。如果尝试
<@greet person="Fred"/>
时也会发生错误,
因为忘记指定 color
的值了。
很多情况下需要给一个参数指定一个相同的值,所以我们仅仅想在这个值发生变化后重新赋给变量。
那么要达到这个目的,在macro
指令中必须这么来指定变量:
param_name=usual_value
。
例如,当没有特定值的时候,我们想要给 color
赋值为 "black"
,那么 greet
指令就要这么来写:
<#macro greet person color="black"> <font size="+2" color="${color}">Hello ${person}!</font> </#macro>
现在,我们这么使用宏就可以了:
<@greet person="Fred"/>
,因为它和
<@greet person="Fred" color="black"/>
是相等的,
这样参数 color
的值就是已知的了。
如果想给 color
设置为 "red"
,
那么就写成: <@greet person="Fred" color="red"/>
,
这时 macro
指令就会使用这个值来覆盖之前设置的通用值,
参数 color
的值就会是 "red"
了。
根据已知的 FTL 表达式规则,
明白下面这一点是至关重要的。someParam=foo
和 someParam="${foo}"
是不同的。第一种情况,
是把变量 foo
的值作为参数的值来使用。第二种情况则是使用 插值形式的字符串,
那么参数值就是字符串了,这个时候, foo
的值呈现为文本,
而不管 foo
是什么类型的(数字,日期等)。看下面这个例子:
someParam=3/4
和 someParam="${3/4}"
是不同的。
如果指令需要 someParam
是一个数字值,
那么就不要用第二种方式。切记不要改变这些。
宏参数的另外一个重要的方面是它们是局部变量。 更多局部变量的信息可以阅读:在模板中定义变量
嵌套内容
自定义指令可以嵌套内容,和预定义指令相似:<#if
...>nested
content</#if>
。
例如,下面这个例子中是创建了一个可以为嵌套的内容画出边框的宏:
<#macro border> <table border=4 cellspacing=0 cellpadding=4><tr><td> <#nested> </tr></td></table> </#macro>
<#nested>
指令执行位于开始和结束标记指令之间的模板代码段。
如果这样写:
<@border>The bordered text</@border>
将会输出:
<table border=4 cellspacing=0 cellpadding=4><tr><td> The bordered text </td></tr></table>
nested
指令也可以多次被调用,例如:
<#macro do_thrice> <#nested> <#nested> <#nested> </#macro> <@do_thrice> Anything. </@do_thrice>
将会输出:
Anything. Anything. Anything.
如果不使用 nested
指令,
那么嵌套的内容就不会被执行,如果不小心将
greet
指令写成了这样:
<@greet person="Joe"> Anything. </@greet>
FreeMarker 不会把它视为错误,只是输出:
<font size="+2">Hello Joe!</font>
嵌套的内容被忽略了,因为 greet
宏没有使用 nested
指令。
嵌套的内容可以是任意有效的FTL,包含其他的用户自定义指令,这样也是对的:
<@border> <ul> <@do_thrice> <li><@greet person="Joe"/> </@do_thrice> </ul> </@border>
将会输出:
<table border=4 cellspacing=0 cellpadding=4><tr><td> <ul> <li><font size="+2">Hello Joe!</font> <li><font size="+2">Hello Joe!</font> <li><font size="+2">Hello Joe!</font> </ul> </tr></td></table>
在嵌套的内容中,宏的 局部变量 是不可见的。为了说明这点,我们来看:
<#macro repeat count> <#local y = "test"> <#list 1..count as x> ${y} ${count}/${x}: <#nested> </#list> </#macro> <@repeat count=3>${y!"?"} ${x!"?"} ${count!"?"}</@repeat>
将会输出:
test 3/1: ? ? ? test 3/2: ? ? ? test 3/3: ? ? ?
因为 y
, x
和 count
是宏的局部(私有)变量,从宏外部定义是不可见的。
此外不同的局部变量的设置是为每个宏自己调用的,所以不会导致混乱:
<#macro test foo>${foo} (<#nested>) ${foo}</#macro> <@test foo="A"><@test foo="B"><@test foo="C"/></@test></@test>
将会输出:
A (B (C () C) B) A
宏和循环变量
像 list
这样的预定义指令可以使用循环变量;
可以阅读 在模板中定义变量 来理解循环变量。
自定义指令也可以有循环变量。比如我们来扩展先前例子中的
do_thrice
指令,就可以拿到当前的循环变量的值。
而对于预定义指令(如list
),当调用指令时,循环变量的
name是给定的(比如 <#list foos as
foo>...</#list>
中的 foo
),变量 value
的设置是由指令本身完成的。
<#macro do_thrice> <#nested 1> <#nested 2> <#nested 3> </#macro> <@do_thrice ; x> <#-- user-defined directive uses ";" instead of "as" --> ${x} Anything. </@do_thrice>
将会输出:
1 Anything. 2 Anything. 3 Anything.
语法规则是给确定"循环"的循环变量传递真实值(比如重复嵌套内容)来作为
nested
指令的参数(当然参数可以是任意的表达式)。
循环变量的名称是在自定义指令的开始标记(<@...>
)
的参数后面通过分号确定的。
一个宏可以使用多个循环变量(变量的顺序是很重要的):
<#macro repeat count> <#list 1..count as x> <#nested x, x/2, x==count> </#list> </#macro> <@repeat count=4 ; c, halfc, last> ${c}. ${halfc}<#if last> Last!</#if> </@repeat>
将会输出:
1. 0.5 2. 1 3. 1.5 4. 2 Last!
在自定义指令的开始标签(分号之后)为循环变量指定不同的数字是没有问题的,
而不能在 nested
指令上使用。如果在分号之后指定的循环变量少,
那么就看不到 nested
指令提供的最后的值,
因为没有循环变量来存储这些值,下面的这些都是可以的:
<@repeat count=4 ; c, halfc, last> ${c}. ${halfc}<#if last> Last!</#if> </@repeat> <@repeat count=4 ; c, halfc> ${c}. ${halfc} </@repeat> <@repeat count=4> Just repeat it... </@repeat>
如果在分号后面指定了比 nested
指令还多的变量,
那么最后的循环变量将不会被创建(在嵌套内容中不会被定义)。
自定义指令和宏进阶
现在你也许已经阅读过 FreeMarker 参考手册的相关部分了:
也可以在FTL中定义方法,参见 function
指令。
也许你对命名空间感兴趣: 命名空间。 命名空间可以帮助你组织和重用经常使用的宏。