最简单的模板通常是普通的HTML文件(或者是其他任何文本文件; FreeMarker本身不属于HTML)。当客户端访问某个页面时, FreeMarker要发送HTML代码至客户端浏览器中去显示。如果想要页面动起来 (这里指动态网页技术,译者注),那么就要在HTML中放置能被FreeMarker所解析的特殊代码片段:
-
${...}
: FreeMarker将会输出真实的值来替换大括号内的表达式,这样的表达式被称为 interpolation(插值,译者注)。 -
FTL 标签 (FreeMarker模板的语言标签): FTL标签和HTML标签有一些相似之处,但是它们是FreeMarker的指令,是不会在输出中打印的。 这些标签的名字以
#
开头。(用户自定义的FTL标签则需要使用@
来代替#
,但这属于更高级的话题了。) -
注释: 注释和HTML的注释也很相似, 但是它们使用
<#--
and-->
来标识。 不像HTML注释那样,FTL注释不会出现在输出中(不出现在访问者的页面中), 因为 FreeMarker会跳过它们。
其他任何不是FTL标签,插值或注释的内容将被视为静态文本, 这些东西不会被FreeMarker所解析;会被按照原样输出出来。
FTL标签也被称为 指令。
这些指令在HTML的标签 (比如: <table>
和
</table>
) 和HTML元素 (比如:
table
元素) 中的关系是相同的。(如果现在还没有感觉到它们的不同,
那么把“FTL标签”和“指令”看做是同义词即可。)
可以在 http://freemarker-online.kenshoo.com/ 上很方便的尝试编写模板
基本指令
这里我们仅仅来看一些非常常用的指令,当然 (指令还有很多)。
if 指令
使用 if
指令可以有条件地跳过模板的一些片段。
比如,假设在 最初的示例 中,
想向你的老板Big Joe特别地问好,可其他人不同:
<html> <head> <title>Welcome!</title> </head> <body> <h1> Welcome ${user}<#if user == "Big Joe">, our beloved leader</#if>! </h1> <p>Our latest product: <a href="${latestProduct.url}">${latestProduct.name}</a>! </body> </html>
此时,告诉 FreeMarker,当和 "Big Joe"
相同时
", our beloved leader" (我们最尊敬的领导,译者注)
才是if条件中那唯一的 user
变量的值。
通常来讲,如果 condition
是false(布尔值),那么介于 <#if
condition>
和
</#if>
标签中的内容会被略过。
我们来详细说说
condition
的使用:
==
是用来判断它两侧的值是否相等的操作符,
比较的结果是布尔值,也就是true或者false。在 ==
的左侧,是 被引用的变量,
我们很熟悉这样的语法结构;最终它会被变量的值所替代。通常来说,
在指令或插值中没有被引号标注的内容都被视为变量的引用。右侧则是指定的字符串,
在模板中的字符串 只能 放在引号内。
当价格为0时,就会打印出 "Pythons are free today!":
<#if animals.python.price == 0> Pythons are free today! </#if>
和之前示例中,字符串被直接指定相似,
但这里则是数字(0
)被直接指定了。
请注意,这里的数字 没有 放在引号内。
如果将("0"
)放在引号中,
那么FreeMarker就会将其误判为字符串了(也就是字符串0,译者注)。
当价格不为0时,则会打印出"Pythons are not free today!":
<#if animals.python.price != 0> Pythons are not free today! </#if>
你也许就会猜测了, !=
就是"不等于"。
同时,也可以这样编来写代码(使用 数据模型来描述哈希表):
<#if animals.python.price < animals.elephant.price> Pythons are cheaper than elephants today. </#if>
使用 <#else>
标签可以指定当条件为false时程序所要执行的内容。比如:
<#if animals.python.price < animals.elephant.price> Pythons are cheaper than elephants today. <#else> Pythons are not cheaper than elephants today. </#if>
这个示例中,如果蟒蛇的价格比大象的价格低的话,
程序将会打印出 "Pythons are cheaper than elephants today."。
否则会打印 "Pythons are not cheaper than elephants today."。
后面也可以使用 elseif
来完善它:
<#if animals.python.price < animals.elephant.price> Pythons are cheaper than elephants today. <#elseif animals.elephant.price < animals.python.price> Elephants are cheaper than pythons today. <#else> Elephants and pythons cost the same today. </#if>
如果变量本身就是布尔值(true/false),则可以直接让其作为
if
的 condition
(判断条件,译者注):
<#if animals.python.protected> Pythons are protected animals! </#if>
list 指令
当需要列表显示内容时,list指令是必须的。比如: 如果合并该模板到 前面描述序列的数据模型 中:
<p>We have these animals: <table border=1> <#list animals as animal> <tr><td>${animal.name}<td>${animal.price} Euros </#list> </table>
那么输出结果将会是这样的:
<p>We have these animals: <table border=1> <tr><td>mouse<td>50 Euros <tr><td>elephant<td>5000 Euros <tr><td>python<td>4999 Euros </table>
list
指令的一般格式为:
<#list sequence as
loopVariable>repeatThis</#list>
。
repeatThis
部分将会在给定的
sequence
遍历时在每一项中重复,
从第一项开始,一个接着一个。在所有的重复中,
loopVariable
将持有当前遍历项的值。
这个变量仅存在于 <#list
...>
和
</#list>
标签内。
sequence
可以是任意表达式,
比如我们可以列表显示示例数据模型中的水果,就像这样:
<ul> <#list misc.fruits as fruit> <li>${fruit} </#list> </ul>
你应该很熟悉表达式 misc.fruits
了; 它 引用了数据模型中的变量。
上面示例中的一个问题是如果我们有0个水果,它仍然会输出一个空的
<ul></ul>
,而不是什么都没有。
要避免这样的情况,可以这么来使用 list
:
<#list misc.fruits> <ul> <#items as fruit> <li>${fruit} </#items> </ul> </#list>
此时, list
指令将列表视为一个整体,
在 items
指令中的部分才会为每个水果重复。
如果我们有0个水果,那么在 list
中的所有东西都被略过了,
因此就不会有 ul
标签了。
另一个列表相关的常见任务是:使用一些分隔符来列出水果,比如逗号:
<p>Fruits: <#list misc.fruits as fruit>${fruit}<#sep>, </#list>
<p>Fruits: orange, banana
被 sep
覆盖的部分(我们也可以这么来写:
...<#sep>,
</#sep></#list>
) 只有当还有下一项时才会被执行。
因此最后一个水果后面不会有逗号。
再次回到这个话题,如果我们有0个水果,会怎么样?只是打印
"Fruits:" 也没有什么不方便。
list
指令,也像 if
指令那样,可以有
else
部分,如果列表中有0个元素时就会被执行:
<p>Fruits: <#list misc.fruits as fruit>${fruit}<#sep>, <#else>None</#list>
事实上,这个过于简单的示例可以这么来写, 但是它使用了本主题中没有介绍的语言特性:
<p>Fruits: ${fruits?join(", ", "None")}
所有的这些指令(list
,
items
, sep
,
else
)可以联合起来使用:
<#list misc.fruits> <p>Fruits: <ul> <#items as fruit> <li>${fruit}<#sep> and</#sep> </#items> </ul> <#else> <p>We have no fruits. </#list>
在 指令参考 中, 可以获取到更多关于这些指令的内容。
include 指令
使用 include
指令,
我们可以在模板中插入其他文件的内容。
假设要在一些页面中显示版权声明的信息。那么可以创建一个文件来单独包含这些版权声明,
之后在需要它的地方插入即可。比方说,我们可以将版权信息单独存放在页面文件
copyright_footer.html
中:
<hr> <i> Copyright (c) 2000 <a href="http://www.acmee.com">Acmee Inc</a>, <br> All Rights Reserved. </i>
当需要用到这个文件时,可以使用 include
指令来插入:
<html> <head> <title>Test page</title> </head> <body> <h1>Test page</h1> <p>Blah blah... <#include "/copyright_footer.html"> </body> </html>
此时,输出的内容为:
<html> <head> <title>Test page</title> </head> <body> <h1>Test page</h1> <p>Blah blah... <hr> <i> Copyright (c) 2000 <a href="http://www.acmee.com">Acmee Inc</a>, <br> All Rights Reserved. </i> </body> </html>
当修改了 copyright_footer.html
文件,
那么访问者在所有页面都会看到版权声明的新内容。
重用代码片段的一个更有力的方式是使用宏,但是只是更为高级的话题了, 将会在 后续讨论。
联合使用指令
在页面上也可以多次使用指令,而且指令间也可以很容易地相互嵌套。
比如,在 list
指令中嵌套 if
指令:
<#list animals as animal> <div<#if animal.protected> class="protected"</#if>> ${animal.name} for ${animal.price} Euros </div> </#list>
请注意,FreeMarker并不解析FTL标签以外的文本、插值和注释, 上面示例在HTML属性中使用FTL标签也不会有问题。
使用内建函数
内建函数很像子变量(如果了解Java术语的话,也可以说像方法),
它们并不是数据模型中的东西,是 FreeMarker 在数值上添加的。
为了清晰子变量是哪部分,使用 ?
(问号)代替
.
(点)来访问它们。常用内建函数的示例:
-
user?html
给出user
的HTML转义版本, 比如&
会由&
来代替。 -
user?upper_case
给出user
值的大写版本 (比如 "JOHN DOE" 来替代 "John Doe") -
animal.name?cap_first
给出animal.name
的首字母大写版本(比如 "Mouse" 来替代 "mouse") -
user?length
给出user
值中 字符的数量(对于 "John Doe" 来说就是8) -
animals?size
给出animals
序列中 项目 的个数(我们示例数据模型中是3个) -
如果在
<#list animals as animal>
和对应的</#list>
标签中:-
animal?index
给出了在animals
中基于0开始的animal
的索引值 -
animal?counter
也像index
, 但是给出的是基于1的索引值 -
animal?item_parity
基于当前计数的奇偶性,给出字符串 "odd" 或 "even"。在给不同行着色时非常有用,比如在<td class="${animal?item_parity}Row">
中。
-
一些内建函数需要参数来指定行为,比如:
-
animal.protected?string("Y", "N")
基于animal.protected
的布尔值来返回字符串 "Y" 或 "N"。 -
animal?item_cycle('lightRow','darkRow')
是之前介绍的item_parity
更为常用的变体形式。 -
fruits?join(", ")
通过连接所有项,将列表转换为字符串, 在每个项之间插入参数分隔符(比如 "orange,banana") -
user?starts_with("J")
根据user
的首字母是否是 "J" 返回布尔值true或false。
内建函数应用可以链式操作,比如user?upper_case?html
会先转换用户名到大写形式,之后再进行HTML转义。(这就像可以链式使用
.
(点)一样)
可以阅读 全部内建函数参考。
处理不存在的变量
数据模型中经常会有可选的变量(也就是说有时并不存在)。 除了一些典型的人为原因导致失误外,FreeMarker 绝不能容忍引用不存在的变量, 除非明确地告诉它当变量不存在时如何处理。这里来介绍两种典型的处理方法。
这部分对程序员而言:
一个不存在的变量和一个是 null
值的变量,
对于FreeMarker来说是一样的,所以这里所指的"丢失"包含这两种情况。
不论在哪里引用变量,都可以指定一个默认值来避免变量丢失这种情况,
通过在变量名后面跟着一个 !
(叹号,译者注)和默认值。
就像下面的这个例子,当 user
不存在于数据模型时,
模板将会将 user
的值表示为字符串
"visitor"
。(当 user
存在时,
模板就会表现出 ${user}
的值):
<h1>Welcome ${user!"visitor"}!</h1>
也可以在变量名后面通过放置 ??
来询问一个变量是否存在。将它和 if
指令合并,
那么如果 user
变量不存在的话将会忽略整个问候的代码段:
<#if user??><h1>Welcome ${user}!</h1></#if>
关于多级访问的变量,比如 animals.python.price
,
书写代码:animals.python.price!0
当且仅当 animals.python
永远存在,
而仅仅最后一个子变量 price
可能不存在时是正确的
(这种情况下我们假设价格是 0
)。
如果 animals
或 python
不存在,
那么模板处理过程将会以"未定义的变量"错误而停止。为了防止这种情况的发生,
可以如下这样来编写代码 (animals.python.price)!0
。
这种情况就是说 animals
或 python
不存在时,
表达式的结果是 0
。对于 ??
也是同样用来的处理这种逻辑的; 将 animals.python.price??
对比
(animals.python.price)??
来看。