转自KaolaFed 原文https://github.com/kaola-fed/blog/issues/84
作者:xiechuanlong
1.前言
工程中,由于ftl模板是由前端人员写的,因此在需求开发中经常会接触ftl模板,所以有必要对ftl模板进行学习。我们的标题是FreeMarker,而与我们的ftl有什么关系呢,freeMarker其实是一个java模板引擎,用来编译模板,是将ftl模板与数据结合生成我们想要的html等静态文件。就像我们运用regular开发一样,动态模板和数据,经过regular内置的模板引擎编译成我们需要的Living-DOM一样。因此它的工作模式可以用下图简单表示
本文只是一些FreeMarker重要的且常用到的知识点的一个梳理总结(结合自己的理解),非常适合新接触FreeMarker的人快速的理解并在项目中使用它,若想进一步了解并学习高级用法可以参考相应文档和书籍。所以我们接下来关注的是ftl模板是如何运用的。本文内容如下图:
2. 主要内容
2.1 ftl模板
ftl模板主要有以下四部分的内容:文本、插值、注释和指令。
- 文本 与html类似,直接输出
- 插值: ${},中间是动态数据填充
- 注释: <#--....-->
- 指令: 一般是以<# 和<@开头的标签
其中插值和指令是核心部分将详细讲解。
2.1.1 插值
插值是用来插入具体值然后转换为文本(字符串)。插值仅仅可以在两种位置使用:
a. 文本区(如<h1>Hello ${name}!</h1>)
b. 字符串表达式(如<#include "/footer/${company}.html">)中。
注1:不能在其地方使用插值:比如<#if ${isBig}>Wow!</#if>、且<#if "${isBig}">Wow! </#if>也是错误的,因为这样参数就是字符串类型了,但是if指令的参数要求是布尔值,所以运行时就会发生错误。
注2:插值可以是表达式,但表达式结果必须是字符串,数字或日期类型的,因为只有数字和日期类型可以自动转换为字符串类型,其他类型的值(如布尔,序列)只能手动转换为字符串类型,否则就会发生错误导致模板执行中止。
数字插值
会自动转换为文本,但是可以通过指令setting来为所有的数字插值设置转换格式,或者运用内建函数为某一个插值来重写默认数值格式。
日期/时间插值(java中含有日期类型):
会自动转换为文本,与数字插值一样可通过指令setting来设置转换格式,或者运用内建函数为某一个插值来重写默认数值格式。
插值为布尔值:
不会自动转换为文本,可以使用内建函数string来转换成文本字形式。
比如:
<div>${a == 2}</div>
这样是会报错中断执行的,使用内建函数如下,假设a是一个布尔值,可以这么使用:
<div>${a?string("yes","no")}</div>
a为true时会输出yes,为false时输出no。
注:1.setting指令可以参看下文的指令部分;2.内建函数可以参看下文的第三部分第三点。
2.1.2 指令
指令有两种类型:预定义指令和用户自定义指令,对于用户自定义指令一般用@来代替#.
常用的预定义指令
if指令
if指可以有条件的跳过模板的一部分,这和程序语言中的if是相识的:
<#if condition>
do something
</#if>
当condition的值为ture时,里面的部分会执行,同样可以运用if else 指令
<#if condition>
do something1
<#else>
do something2
</#if>
也可以多层判断
<#if condition>
do something1
<#elseif condition 2>
do something2
<#else>
do something3
</#if>
list指令
list指令是用来遍历集合的,它的一般格式为:
<#list list as item>repeat</#list>
repeat是会在遍历时重复执行的部分,item是遍历时集合中的每一项值,它的作用域只在<#list …>和</#list>标签之间。
include指令
include指令可以在当前模板中导入其他文件,即插入其他文件的内容。
比如a.html文件:
<hr>
<div>this is file a</div>
b.ftl引入a.html文件(假设在同一个目录)
<div>
<h1>this is file b</h1>
<#include "./a.html">
</div>
则a文件的内容会被插入在b文件中,开发中我们一般会将一些公用内容写在一个ftl文件中然后,在其他页面用过include指令引入。
assign指令
使用assign指令可以创建一个或多个新的变量,或者替换一个或多个已经存在的变量。但仅仅顶级变量可以被创建/替换(也就是说不能创建/替换 obj1.name变量)。
<#assign name="ccc">
则在assign指令后面的ftl中都可以使用变量name.
escape指令
在ftl文件中,我们会看到内容都被这个标签所包裹起来,这个指令的作用是在模板文件中转义<#escape>和</#escape>之间的插值。
例如如下模板:
<#assign m="<h1>hello</h1>">
${m}
<#escape x as x?html>
${m}
</#escape>
输出的html文件应为:
<h1>hello</h1>
<h1>test</h1>
浏览器解析后的结果为:
noescape指令
noescape指令的作用是不转义,ftl模板默认不转义的,但是运用的时候会用到escape指令转义,我们可能有时候在escape指令包含的区域有一小部分不需要转义,这时候noescape就起作用了,用来抵消escape的指令,所以一般看到noescape指令都是运用在escape中的。
仍以上述例子描述:
<#assign m="<h1>hello</h1>">
${m}
<#escape x as x?html>
${m}
<#noescape>${m}</#noescape>
</#escape>
输出的html文件应为:
<h1>hello</h1>
<h1>test</h1>
<h1>hello</h1>
浏览器解析后的结果为:
setting指令
setting是为模板设置默认的初始值(并不是我们所理解的默认变量的初始值,而是设置一些默认的行为,比如日期以哪种格式显示),可设置的值有很多:location, number_format, data_format等
如下我们对数字进行格式化设置
<#assign x=42>
<div>正常输出:${x}</div>
<#setting number_format="currency">
<div>格式化为货币形式输出:${x}</div>
浏览器中输出为:
用户自定义指令
macro指令
macro宏指令是用来用户灵活定义自己的指令,在模板中使用宏作为自定义指令,这样就能进行重复性的工作。
- 基础自定义指令:
<#macro greet>
<div>Hello world!</div>
</#macro>
这样就定义了一个指令greet,将宏引入其他文件就可以直接使用了,为了区别预定义指令,用户自定义指令以@开头,可以用以下两种方法:
<@greet><//@greet>
<@greet />
- 含有参数的自定义指令(可以有一个或多个):
<#macro greet person>
<div>Hello ${person}!</div>
</#macro>
可以用如下方法使用宏
<@greet person="Peter Sun">
<@greet person="Peter Yang"/>
- 自定义指令嵌套内容:
<#macro border>
<table border=4 cellspacing=0 cellpadding=4>
<tr>
<td>
<#nested>
</td>
</tr>
</table>
</#macro>
<#nested>编译时就会替换为嵌套的内容:
<@border>The bordered text</@border>
输出即为
<table border=4 cellspacing=0 cellpadding=4><tr><td>
The bordered text
</td></tr></table>
注: nested指令可以被多次调用
3.一些值得注意的内容
处理不存在的变量
当freemarker试图访问值为null的变量和不存在的变量时会报错,并中断执行。(很多脚本语言和模板语言都能容忍不存在的变量,通常它们将这些变量视为空字符串或0,还有逻辑false)
<#if hasContent> do something </#if>
这是我们经常会用的判断语句,但是当hasContent未定义或者为null时就会报错,在freemarker中应该怎样处理这种情况呢。
- 在if指令中我们可以用双问号??,若变量缺失或为null则表示为false.
<#if hasContent??> do something </#if>
- 在插值中我们可以运用默认值的方法(!和默认值):
<div>${user!"Anonymous"}</div>
若user未定义或为null,,模板将会将user的值表示为字符串”Anonymous”,若user并没有丢失,那么模板就会表现出”Anonymous”不存在一样。
多级访问的变量
对于多级变量,我们必须要确保前面几级的变量都存在,否之用上面两种方法也会报错中断的。比如:
<div>${animale.python.price!0}</div>
仅当animals.python存在而仅仅最后一个子变量price可能不存在才有效,animal或python不存在也会报错。此时我们可以添加括号()。
${(animale.python.price)!0}
<#if (animale.python.price)??>
这样对于任何animale,python或price不存在时就可以走上面两种方法的逻辑了。
?的运用
?运用在变量或常量后面,可以紧接一个关键字或者内建函数
- 后面紧接一个关键字如(较为常用的exists, has_content等)
exists也可以处理if条件判断中变量不存在的情况
<#if name?exists>
- 后面紧接一个内建函数,不同的类型变量后面可以接的不同的内建函数(我们可以理解为与regular中的内建过滤器一样的功能)。
-比如对于字符串的内建函数
${"hello world"?cap_first}
cap_first的内建函数作用是字符串首字母大写。
更多内建函数可参考:http://www.zheng-hang.com/chm/freemarker2_3_24/ref_builtins_string.html
需要了解更多有关freemarker的内容可前往:
http://www.zheng-hang.com/chm/freemarker2_3_24/ref.html
http://freemarker.org/