本章节将介绍 FreeMaker 的 <#list> 以及与它相关的指令。本节主要内容如下:
<#list> 指令用于迭代显示列表/集合(如:List、Array数组、Set集合)。语法如下:
形式1:
<#list sequence as item> Part repeated for each item <#else> Part executed when there are 0 items </#list>
参数说明:
<#else>:是可选的,而且仅仅从 FreeMarker 2.3.23 版本开始支持。
sequence:将我们想要迭代的项,算作是序列或集合的表达式
item:循环变量的名称 (不是表达式)
注意:在<#list><#else>标签之间、或<#else></#list>标签之间可以是任意的FTL指令 (包括嵌套的 list 指令)。
形式2 (从 FreeMarker 2.3.23 版本开始):
<#list sequence> Part executed once if we have more than 0 items <#items as item> Part repeated for each item </#items> Part executed once if we have more than 0 items <#else> Part executed when there are 0 items </#list>
参数说明:
<#else>:是可选的,而且仅仅从 FreeMarker 2.3.23 版本开始支持。
sequence:将我们想要迭代的项,算作是序列或集合的表达式
<#items>:该标签的作用是替代 <list> 的 “as item”部分
item:循环变量 的名称 (不是表达式)
假设 users 包含 ['Joe', 'Kate', 'Fred'] 序列:
<#list users as user> <p>${user}</p> </#list>
输出结果:
<p>Joe</p> <p>Kate</p> <p>Fred</p>
list 指令执行在 list 开始标签 <#list> 和 list 结束标签</#list> ( list 中间的部分) 之间的代码, 对于在序列 (或集合) 中每个值指定为它的第一个参数。 对于每次迭代,循环变量 (本例中的 user) 将会存储当前项的值。
循环变量 (user) 仅仅存在于 list 标签体内。 而且从循环中调用的宏/函数不会看到它 (就像它只是局部变量一样)。
注意:在 list 中的 else 仅从 FreeMarker 2.3.23 版本开始支持。
当没有迭代项时(即 users 为空列表),才使用 else 指令, 可以输出一些特殊的内容而不只是空在那里:
<#list users as user> <p>${user}</p> <#else> <p>No users</p> </#list>
该输出和之前示例是相同的,除了当 users 包含0项时:
<p>No users</p>
请注意,循环变量 (user) 在 <#else> 标签和 </#list> 标签中间不存在, 因为那部分不是循环中的部分。
<#else> 必须是真的在 (也就是在源代码中) list 指令体内部。也就是说,不能将它移出到宏或包含的模板中。
注意:<#items> 从 FreeMarker 2.3.23 版本开始存在
如果不得不在第一列表项之前或在最后一个列表项之后打印一些东西, 那么就要使用 items 指令,但至少要有一项。典型的示例为:
<#list users> <ul> <#items as user> <li>${user}</li> </#items> </ul> </#list>
输出结果:
<ul> <li>Joe</li> <li>Kate</li> <li>Fred</li> </ul>
如果没有迭代项,那么上面的代码不会输出任何内容, 因此不用以空的 <ul></ul> 来结束。
也就是说,当 <#list> 指令没有 as item 参数,如果只有一个迭代项,指令体中的代码仅仅执行一次,否则就不执行。内嵌的 <#items> 指令必须对每个迭代项执行,那么 <#items> 指令需要使用 as item 定义循环变量,而不是 list。
有 <#items> 的 list 指令也可以有 else 指令:
<#list users> <ul> <#items as user> <li>${user}</li> </#items> </ul> <#else> <p>No users</p> </#list>
更多细节:
解析器会检查没有 as item 参数的 list 通常会有嵌入的 items 指令,该 items 指令通常会有一个包围的 list,它没有 as item 参数。当模板解析时就会检查,而不是当模板执行的时候。因此,这些规则也适用于FTL源代码本身, 所以不能将 items 移出到宏或者被包含的模板中。
list 可以有多个 items 指令, 但是只有一个允许执行(直到不离开或重新进入包围的 list 指令);之后试图调用 items 会发生错误。所以多个 items 可以用于不同的 if-else 分支中去,但不能迭代两次。
items 指令不能有它自己的嵌入 else 指令,只能被包含的 list 可以有。
循环变量 (user) 仅仅存在于 items 指令体内部。
注意:sep 从 FreeMarker 2.3.23 版本开始存在。
当不得不显示介于每个迭代项 (但不能在第一项之前或最后一项之后) 之间的一些内容时,可以使用 sep。例如:
<#list users as user>${user}<#sep>, </#list>
输出结果:
Joe, Kate, Fred
上面的 <#sep>, </#list> 是 <#sep>, </#sep></#list> 的简写; 如果将它放到被包含的指令关闭的位置时,sep 结束标签可以忽略。下面的示例中,就不能使用该简写 (HTML标签不会结束任何代码,它们只是 FreeMarker 输出的原生文本):
<#list users as user> <div> ${user}<#sep>, </#sep> </div> </#list>
sep 是编写 <#if item?has_next>...</#if> 的方便形式,有 list 或 items 循环变量时,它就可以使用,并且不限次数。而且, 也可以有任意的 FTL 作嵌入的内容。
解析器会检查在 list ... as item 内部使用的 sep 或者 items 指令,所以不能将 sep 从重复的部分移出到宏或被包含的模板中。
可以使用 break 指令在迭代的任意点退出。例如:
<#list 1..10 as x> ${x} <#if x == 3> <#break> </#if> </#list>
输出结果:
1 2 3
注意:
(1)如果使用的是“<#list users as user>...</#list>”格式的 list 指令,则 <#break> 指令可以放到 list 中的任意位置。
(2)如果使用的是“<#list users><#items as user>...</#items></#list>”格式的 list 指令,则 <#break> 只能放到 <#items> 指令中;如果你将 <#break> 指令放在 <#items> 外面,<#list> 内,将抛出如下错误信息:
Exception in thread "main" freemarker.core.ParseException: Syntax error in template "template5.ftl" in line 31, column 5: <#break> must be nested inside a directive that supports it: #list with "as", #items, #switch (or the deprecated #foreach)
(3)和 <#else> 和 <#items>指令类似, <#break> 指令只能在指令体内部使用,而不能移出到宏或被包含的模板中。
FreeMarker 从 2.3.23 版本开始,循环变量内建函数是访问当前迭代(如:list指令)状态的最佳方式。例如,这里我们使用 counter 和 item_parity 循环变量内建函数 (更多循环变量内建函数,请参考):
<#list users> <table> <#items as user> <tr class="${user?item_parity}Row"> <td>${user?counter} <td>${user} </#items> </table> </#list>
输出结果:
<table> <tr class="oddRow"> <td>1 <td>Joe <tr class="evenRow"> <td>2 <td>Kate <tr class="oddRow"> <td>3 <td>Fred </table>
其中:
counter 表示当前迭代是 list 的第几次迭代,从 1 开始,可以用来显示顺序号。可以将 ${user?counter} 替换为 ${user_index + 1}。
item_parity:表示当前正在进行的迭代奇数还是偶数行,odd(奇) even(偶)。可以使用如下代码实现:
<#if book?counter % 2 = 0> even <#else> odd </#if>
在 2.3.22 和之前的版本中,有两个额外的循环变量来获得迭代状态 (出于向后兼容考虑,它们仍然存在):
item_index (已废弃,由 item?index 代替): 循环中当前项的索引 (从0开始的数字)。
item_has_next (已废弃,由 item?has_next 代替): 辨别当前项是否是序列的最后一项的布尔值。
<#list> 和 <#if> 语句类似,是允许进行多层嵌套。如下:
<#list 1..2 as i> <#list 1..3 as j> i = ${i}, j = ${j} </#list> </#list>
输出结果:
i = 1, j = 1 i = 1, j = 2 i = 1, j = 3 i = 2, j = 1 i = 2, j = 2 i = 2, j = 3
<#list> 指令允许使用冲突的循环变量名称(下面实例将外层和内层 list 指令的循环变量名称都定义为 i),比如:
<#list 1..2 as i> Outer: ${i} <#list 10..12 as i> Inner: ${i} </#list> Outer again: ${i} </#list>
输出结果:
Outer: 1 Inner: 10 Inner: 11 Inner: 12 Outer again: 1 Outer: 2 Inner: 10 Inner: 11 Inner: 12 Outer again: 2
【Java程序员请注意】
如果经典兼容模式下 <#list> 接受标量,并将它视为单元素序列。
如果传递包装了 java.util.Iterator 的集合到 <#list> 中,那么只能迭代其中的元素一次,因为 Iterator 是它们天然的一次性对象。 当视图再次去迭代这样的集合变量时,会发生错误并中止模板处理。