定义自己的指令
Page Contents
就模板作者而言,可以使用macro
指令定义用户定义的指令。想要以 Java 语言而不是模板来实现指令的 Java 程序员应该使用freemarker.template.TemplateDirectiveModel
(请参阅more here...)。
Basics
宏是与变量关联的模板片段。您可以在模板中将该变量用作用户定义的指令,因此它有助于执行重复的任务。例如,这将创建一个宏变量,该变量打印一个大的“ Hello Joe!”:
<#macro greet>
<font size="+2">Hello Joe!</font>
</#macro>
macro
指令本身不显示任何内容;它只是创建宏变量,所以会有一个名为greet
的变量。仅当您将变量用作指令时,才会执行<#macro greet>
和</#macro>
之间的操作(称为 宏定义主体 )。您可以通过在 FTL 标记中写入@
而不是#
来使用用户定义的指令。使用变量名称作为指令名称。此外,用户定义指令的end-tag是强制性的。因此,您使用greet
是这样的:
<@greet></@greet>
但由于<anything></anything>
与<anything/>
等价,因此您应使用以下较短的格式(如果您知道XML,则对您很熟悉):
<@greet/>
这将打印:
<font size="+2">Hello Joe!</font>
但是宏可以做更多的事情,因为<#macro ...>
和</#macro>
之间的内容是模板片段,因此它可以包含插值(${...}
)和 FTL 标签(例如<#if ...>...</#if>
)。
Note:
程序员会在<@...>
上说您“调用”宏。
Parameters
让我们改进greet
宏,以便它可以使用任意名称,而不仅是“ Joe”。为此,您可以使用 parameters 。您可以在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
)访问。与predefined directives一样,参数的值(=
的右侧)是FTL expression。因此,与 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"/>
参数的 Sequences 并不重要,因此与前面的命令等效:
<@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
,则可以实现此目的。例如,如果在使用greet
指令时未为该参数指定值,则想将"black"
用于color
:
<#macro greet person color="black">
<font size="+2" color="${color}">Hello ${person}!</font>
</#macro>
现在<@greet person="Fred"/>
是确定的,因为它等效于<@greet person="Fred" color="black"/>
,因此color
参数的值是已知的。如果要_5 表示"red"
,则编写<@greet person="Fred" color="red"/>
,并且该值将覆盖macro
指令指定的常规值,因此color
参数的值将为"red"
。
同样,重要的是要认识到-根据已经说明的FTL 表达规则-someParam=foo
和someParam="${foo}"
是非常不同的。在第一种情况下,将变量foo
的值用作参数的值。在第二种情况下,您使用带插值的字符串 Literals,因此参数的值将是一个字符串-在这种情况下,foo
的值将呈现为文本-而不考虑foo
的类型(如数字,日期等)。 。或者,另一个示例:someParam=3/4
和someParam="${3/4}"
不同。如果该指令想要someParam
的数值,它将不喜欢第二种变化。不要交换这些。
宏参数的一个非常重要的方面是它们是局部变量。有关局部变量的更多信息,请阅读:在模板中定义变量
Nested content
自定义指令可以具有嵌套内容,类似于<#if ...>nested content</#if>
之类的 sched 义指令。例如,这将创建一个宏,以在其嵌套内容周围绘制边框:
<#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>
will print:
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>
宏的local variables在嵌套内容中不可见。说这个:
<#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
之类的 sched 义指令可以使用所谓的循环变量。您应该阅读在模板中定义变量以了解循环变量。
用户定义的指令也可以具有循环变量。例如,让我们扩展前面示例的do_thrice
指令,以便将当前的重复编号公开为循环变量。与 sched 义的指令(如list
)一样,循环变量的名称(在<#list foos as foo>...</#list>
中为foo
)在调用指令时给出,而变量的值(*)由指令本身设置。
<#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
指令的参数(当然,该参数可以通过任意表达式表示)传递。循环变量的名称在参数和分号后的用户定义的指令打开标记(<@...>
)中指定。
宏可以使用更多的一个循环变量(变量的 Sequences 很重要):
<#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 中定义方法,请参见功能指令。
另外,您可能对命名空间Namespaces感兴趣。命名空间可帮助您组织和重用常用的宏。
Java 程序员可能想知道指令(宏是指令)和方法(类似函数的东西)也可以分别通过实现 TemplateDirectiveModel或TemplateMethodModelEx
接口用 Java 语言编写。然后,您可以将 Java 实现插入<#assign foo = "com.example.FooDirective"?new()>
或<#assign foo = "com.example.FooMethod"?new()>
之类的模板中,否则将放置<#macro foo ...>
或<#function foo ...>
。