4.1 自定义指令
4.1.1 简介
自定义指令可以使用macro指令来定义。Java程序员若不想在模板中实现定义指令,而是在Java语言中实现指令的定义,这时可以使用freemarker.template.TemplateDirectiveModel类来扩展,后边会讲。
4.1.2 基本内容
宏是有一个变量名的模板片段。你可以在模板中使用宏作为自定义指令,这样就能进行重复性的工作。例如,创建一个宏变量来打印大号的”hello Joe!“;
<#macro greet> Hello Joe!
可以在FTL标记中通过@代替#来使用自定义指令。
<@greet>
4.1.3 参数
我们仅在greet宏中定义一个变量,person;
<#macro greet person> Hello ${person}!
那么可以这样使用宏:
<@greet person="Fred"/>and<@greet person="Batman"/>
可以去掉双引号, <@greet person=Fred/>
多个参数时,可以设置默认值,要不然的话使用的时候必须指定对应的多个参数:
<#macro greet person color="black"> Hello ${person}!
那么就可以这样使用了
<@greet person="Fred"/>
实例
/FreeMarker-hello-web/src/main/webapp/WEB-INF/ftl/4/macro.ftl
没有参数
<#macro greet> Hello Joe!
<@greet/>有参数
<#macro greet person> Hello ${person}!
<@greet person="Fred"/> and <@greet person="Bob"/>多个参数时,可以设定默认值
<#macro greet person color="black"> Hello ${person}!
<@greet person="Fred"/> and <@greet person="Bob" color="red"/>
输出:
4.1.4 嵌套内容
自定义指令可以嵌套内容,和预定义指令相似:<#if ...>nested content</#if>
。比如,下面这个例子中是创建了一个可以为嵌套的内容画出边框;
<#macro border>
<#nested> |
<#nested>标签执行位于开始和结束标签之间的模板代码块,例如:
<@border>The bordered text
输出:
The bordered text |
<#nested>指令可以被执行多次;
<#macro do_thrice> <#nested> <#nested> <#nested>
使用时:
<@do_thrice>anything
输出:
anythinganythinganything
如果不使用nested指令,那么嵌套的内容就会被执行,如果不小心写成这样:
<@greet person="Joe"> Anything.
输出是:
Hello Joe!
嵌套的内容被忽略了,因为greet没有使用nested指令;
嵌套内容可以是其他FTL指令,包含其他自定义指令也是可以的。
<@border>
- <@do_thrice>
- <@greet person="Joe">
在嵌套的内容中,宏的局部变量是不可见的;
<#macro repeat count> <#local y="test"> <#list 1..count as x> ${y} ${count}/${x} : <#nested> <@repeat count=3>${y!"?"} ${x!"?"} ${count!"?"}
将会输出:
test 3/1: ? ? ?test 3/2: ? ? ?test 3/3: ? ? ?
不同的局部变量的设置是为每个宏自己调用的,所以不会导致混乱;
<#macro test foo>${foo} (<#nested>) ${foo} <@test foo="A"><@test foo="B"><@test foo="C"/>
输出:
A (B (C () C) B) A
4.1.5 宏与循环变量
循环变量的名字是已经给定了,变量值的设置由指令本身完成。
<#macro do_thrice> <#nested 1> <#nested 2> <#nested 3><#--自定义循环变量需要用;代替as--> <@do_thrice ; x> do_something : ${x}
输出:
do_something : 1 do_something : 2 do_something : 3
例子:/FreeMarker-hello-web/src/main/webapp/WEB-INF/ftl/4/nested.ftl
宏与循环变量
<#macro do_thrice> <#nested 1> <#nested 2> <#nested 3><#--自定义循环变量需要用;代替as--> <@do_thrice ; x> do_something : ${x}
一个宏可以使用多个循环变量(变量的顺序是很重要的):
<#macro repeat count> <#list 1..count as x> <#nested x, x/2, x==count>
使用的时候:
<@repeat count=4 ; c, halfc, last> ${c}.${halfc}<#if last> Last!
那么将会输出:
\1. 0.5\2. 1\3. 1.5\4. 2 Last!
例子:/FreeMarker-hello-web/src/main/webapp/WEB-INF/ftl/4/nested.ftl
一个宏可以使用多个循环变量
<#macro repeat count> <#list 1..count as x> <#nested x, x/2, x==count><@repeat count=4 ; c, half, last> ${c}. ${half} <#if last> Last!
如果分号后指定的变量少了或多了,多出的会忽略掉;
@repeat count=4 ; c, half> ${c}. ${half}
4.1.6 自定义指令和宏进阶
你也可以在FTL中定义方法,参见function指令;
也许你对命名空间感兴趣。命名空间可以帮助你组织和重用你经常使用的宏;
4.2 在模板中定义变量
可以访问一个在模板里定义的变量,就像是访问数据模型根上的变量一样。这个变量比定义在数据模型中的同名参数具有更高的优先级。如果你恰巧定义了一个名为“foo”的变量,而在数据模型中也有一个名为“foo”的变量,那么模板中的变量就会将数据模型根上的变量隐藏(而不是覆盖!)。
在模板中可以定义三种类型的变量:
- 简单变量:它能从模板中的任何位置访问,或者从使用
include
指令引入的模板访问。可以使用assign
或macro
指令来创建或替换这些变量。 - 局部变量:它们只能被设置在宏定义体内,而且只在宏内可见。一个局部变量的生命周期只是宏的调用过程。可以使用
local
来创建或替换局部变量。 - 循环变量:循环变量是由指令(如
list
)自动创建的,而且它们只在指令的开始和结束标记内有效。宏的参数是局部变量而不是循环变量。
使用assign
创建和替换变量;
/FreeMarker-hello-web/src/main/webapp/WEB-INF/ftl/4/variable.ftl
简单变量
<#assign x=1> <#--创建变量x--> ${x}
<#assign x=x+3> <#--替换变量x--> ${x}
输出:
简单变量14
局部变量也会隐藏(而不是)同名的简单变量。循环变量也会隐藏(不是覆盖)同名的局部变量和简单变量。
实例:/FreeMarker-hello-web/src/main/webapp/WEB-INF/ftl/4/variable.ftl
<#macro test> 2. ${x} <#local x="local"> 3. ${x} <#list ["loop"] as x> 4. ${x} 5. ${x}<#assign x="plain"> 1. ${x}
<@test/> 6. ${x} <#list ["loop"] as x> 7. ${x} <#assign x="plain2"> <#--在这里替换了简单变量x--> 8. ${x} 9. ${x}
输出:
1. plain2. plain3. local4. loop5. local6. plain7. loop8. loop9. plain2
内部循环可以隐藏外部循环的变量;
实例:/FreeMarker-hello-web/src/main/webapp/WEB-INF/ftl/4/variable.ftl
<#list ["loop 1"] as x> ${x} <#list ["loop 2"] as x> ${x} <#list ["loop 3"] as x> ${x} ${x} ${x}
输出:
loop 1loop 2loop 3loop 2loop 1
有时发生一个变量隐藏数据模型中的同名变量,但是如果想访问数据模型中的变量,就可以使用特殊变量globals
。
实例:/FreeMarker-hello-web/src/main/webapp/WEB-INF/ftl/4/variable.ftl
<#assign user="Cindy"> ${user}, ${.globals.user}
/FreeMarker-hello-web/src/main/java/org/yejq/fre/service/Exercises.java
public void testVariable(Model model){ model.addAttribute("user", "lucy"); }
测试:,输出结果:
Cindy, lucy
4.3 命名空间
4.3.1 简介
如果想创建可以重复使用的宏、函数和其他变量的集合,通常用术语来说就是引用library库,使用命名空间是必然的;
4.3.2 创建一个库
实例:/FreeMarker-hello-web/src/main/webapp/WEB-INF/ftl/lib/my_test.ftl
<#macro copyright date>Copyright (C) ${date} Julia Smith. All rights reserved.
<#assign ">
/FreeMarker-hello-web/src/main/webapp/WEB-INF/ftl/4/namespace.ftl
使用import导入
<#import "../lib/my_test.ftl" as my> <@my.copyright date="2014-2016"/> ${my.mail}
输出:
使用import导入Copyright (C) 2014-2016 Julia Smith. All rights reserved.jsmith@acme.com
测试不同的命名空间;
/FreeMarker-hello-web/src/main/webapp/WEB-INF/ftl/lib/my_test.ftl
<#macro copyright date>Copyright (C) ${date} Julia Smith. All rights reserved.
Mail: ${mail} <#assign ">
/FreeMarker-hello-web/src/main/webapp/WEB-INF/ftl/4/namespace.ftl
演示不同的命名空间
<#import "../lib/my_test.ftl" as my> <#assign "> <@my.copyright date="2014-2016"/> ${my.mail}
${mail}
测试:
4.3.3 在引入的命名空间上编写变量
/FreeMarker-hello-web/src/main/webapp/WEB-INF/ftl/4/namespace.ftl
<#import "../lib/my_test.ftl" as my> ${my.mail} <#assign " in my> ${my.mail}
,输出:
jsmith@acme.comjsmith@other.com
4.3.4 命名空间和数据模型
数据模型中的变量在任何位置都是可见的。如果在数据模型中有一个名为user的变量,那么lib/my_test.ftl
也能访问TA。
/FreeMarker-hello-web/src/main/webapp/WEB-INF/ftl/lib/my_test.ftl
<#macro copyright date>Copyright (C) ${date} ${user} Julia Smith. All rights reserved.
Mail: ${mail} <#assign ">
/FreeMarker-hello-web/src/main/java/org/yejq/fre/service/Exercises.java
public void testNamespace(Model model){ model.addAttribute("user", "lucy"); }
/FreeMarker-hello-web/src/main/webapp/WEB-INF/ftl/4/namespace.ftl
数据模型中的变量在任何位置都是可见的
<#import "../lib/my_test.ftl" as my> <@my.copyright date="2014-2015"/>
测试:,输出结果:
Copyright (C) 2014-2015 lucy Julia Smith. All rights reserved. Mail: lucy@acme.com
4.3.5 命名空间的生命周期
命名空间由使用的import
指令中所写的路径来识别。如果想多次import
这个路径,那么只会为第一次的import
引用创建命名空间执行模板。后边相同的路径的import
只是创建一个哈希表当做访问相同命名空间的“门”。
/FreeMarker-hello-web/src/main/webapp/WEB-INF/ftl/4/namespace.ftl
命名空间的声明周期
<#import "/lib/my_test.ftl" as my> <#import "/lib/my_test.ftl" as foo> <#import "/lib/my_test.ftl" as bar> <#--只会在第一次创建命名空间执行模板,之后相同路径的import只是创建哈希表当做访问相同命名空间的"门"--> ${my.mail}, ${foo.mail}, ${bar.mail}
<#assign " in my> ${my.mail}, ${foo.mail}, ${bar.mail} <#--其中一个命名空间的值修改了,所有都相应修改,就像java里边的对象引用修改一样--> <#assign " in foo> ${my.mail}, ${foo.mail}, ${bar.mail}
访问:,输出:
命名空间的声明周期jsmith@acme.com, jsmith@acme.com, jsmith@acme.comjsmith@other.com, jsmith@other.com, jsmith@other.comjsmith@foo.com, jsmith@foo.com, jsmith@foo.com
还要注意命名空间是不分层次的,它们相互之间是独立存在的。那么,如果在命名空间N1中import
命名空N2,那N2也不在N1中,N1只是通过哈希表访问N2。这和主命名空间中import
N2,然后直接访问命名空间N2是一样的过程。
每一次模板的执行过程,它都有一个私有的命名空间的集合。每一次模板执行工作都是一个分离且有序的过程,它们仅仅存在一段很短的时间,同时页面用以呈现内容,然后就和所有填充过的命名空间一起消失了。
4.3.6 为他人编写库
标准库路径的格式:
/lib/yourcompany.com/your_library.ftl
如果你的公司的主页是www.example.com,那么;
/lib/example.com/widget.ftl/lib/example.com/commons/string.ftl
一个重要的规则是路径不应该包含大写字母,winForm改成win_form;
4.4 空白处理
4.4.1 简介
来看看这个模板;
按照Freemarker规则输出后是:
这么多多余的空白是很令人头疼,而且增加处理后的HTML文件大小也是没必要的;
FreeMarker提供了以下工具来处理这个问题:
- 忽略某些文件的空白的工具(解析阶段空白就被移除了)
- 剥离空白:这个特性会自动忽略FTL标签周围的空白。这个特性可以通过模板来随时使用和禁用;
- 微调指令:
t
,rt
和lt
,使用这些指令可以明确告诉FreeMarker去忽略某些空白; - FTL参数
strip_text
:这将从模板中删除所有顶级文本。对模板来说很有用,它包含某些定义的宏,因为它可以移除宏定义和其他顶级指令中的换行符,这样可以提高模板的可读性;
- 从输出移除空白的工具(移除临近的空白)
compress
指令
4.4.2 剥离空白
它会自动忽略两种典型的空白:
- 缩进空白和行末尾的尾部空白;如果这行上包含
<#if ...>x
,那么空白不会忽略,因为x不是标签。而一行上有<#if ...> <#list ...>
,这样也不会忽略空白,因为标签之间的空格是嵌入空白; - 加在这些指令之间的空白会被忽略:macro,function,assign,global,local,ftl,import;
使用剥离空白之后,上面的例子输出是:
剥离空白功能可以在ftl指令在模板中开启或关闭。默认开启。开启剥离空白不会降低模板执行的效率,剥离空白的操作在模板加载时就已经完成了。
剥离空白可以为单独的一行关闭,就是使用nt指令;
4.4.3 使用compress指令
和剥离空白相反,这个工作是直接基于生产的输出内容,而不是对模板进行的。它会强势地移除缩进,空行和重复的空格/制表符;
对于下边这段代码:
/FreeMarker-hello-web/src/main/webapp/WEB-INF/ftl/4/compress.ftl
使用compress指令,在输出的内容中移除缩进,空行和空格/制表符
<#compress> <#assign users = [{"name":"Joe", "hidden":false}, {"name":"James Bond", "hidden":true}, {"name":"Julia", "hidden":false}]> list of users:
<#list users as user> <#if !user.hidden> - ${user.name}
输出:
使用compress指令,在输出的内容中移除缩进,空行和空格/制表符
list of users:
- Joe- Julia
由于向下兼容性,名称为compress的用户自定义指令是存在的,有个single_line的属性,如果设置为true,那么会移除其中的换行符;
@compress的属性single_line
<@compress single_line=true> <#assign users = [{"name":"Joe", "hidden":false}, {"name":"James Bond", "hidden":true}, {"name":"Julia", "hidden":false}]> list of users:
<#list users as user> <#if !user.hidden> - ${user.name}
貌似不起作用,反而#compress是single_line;
4.5 替换(方括号)语法
版本2.3.4之后才有;
在指令和注释中使用[]
代替<>
;
[#list ...]...[/list][@mymacro .../][#-- the comment --]
为了使用这种语法从而代替默认语法,那么就需要用[#ftl]来开始模板。
/FreeMarker-hello-web/src/main/webapp/WEB-INF/ftl/4/ftl.ftl
[#ftl]Document 替换语法
[#assign users = [{"name":"Joe", "hidden":false}, {"name":"James Bond", "hidden":true}, {"name":"Julia", "hidden":false}]] list of users:
[#list users as user] [#if !user.hidden] - ${user.name} [/#if] [/#list]
测试:
项目
- P1:。
- P2: