freeMarker(二)——模板开发指南之入门
学习笔记,选自freeMarker中文文档,译自 Email: ddekany at users.sourceforge.net
模板开发指南-入门
1.模板+数据模型=输出
假设在一个在线商店的应用系统中需要一个HTML页面,和下面这个页面类似:
OUTPUT:
<html> <head> <title>Welcome!</title> </head> <body> <h1>Welcome John Doe!</h1> <p>Our latest product: <a href="products/greenmouse.html">green mouse</a>! </body> </html>
这里的用户名(上面的"Big Joe"),应该是登录这个网页的访问者的名字, 并且最新产品的数据应该来自于数据库,这样它才能随时更新。那么不能直接在HTML页面中输入它们, 不能使用静态的HTML代码。此时,可以使用要求输出的模板。 模板和静态HTML是相同的,只是它会包含一些 FreeMarker 将它们变成动态内容的指令:
TEMPLATE:
<html> <head> <title>Welcome!</title> </head> <body> <h1>Welcome ${user}!</h1> <p>Our latest product: <a href="${latestProduct.url}">${latestProduct.name}</a>! </body> </html>
模板文件存放在Web服务器上,就像通常存放静态HTML页面那样。当有人来访问这个页面, FreeMarker将会介入执行,然后动态转换模板,用最新的数据内容替换模板中 ${...} 的部分, 之后将结果发送到访问者的Web浏览器中。访问者的Web浏览器就会接收到例如第一个HTML示例那样的内容 (也就是没有FreeMarker指令的HTML代码),访问者也不会察觉到服务器端使用的FreeMarker。 (当然,存储在Web服务器端的模板文件是不会被修改的;替换也仅仅出现在Web服务器的响应中。)
请注意,模板并没有包含程序逻辑来查找当前的访问者是谁,或者去查询数据库获取最新的产品。 显示的数据是在 FreeMarker 之外准备的,通常是一些 "真正的" 编程语言(比如Java) 所编写的代码。模板作者无需知道这些值是如何计算出的。事实上,这些值的计算方式可以完全被修改, 而模板可以保持不变,而且页面的样式也可以完全被修改而无需改动模板。 当模板作者(设计师)和程序员不是同一人时,显示逻辑和业务逻辑相分离的做法是非常有用的, 即便模板作者和程序员是一个人,这么来做也会帮助管理应用程序的复杂性。 保证模板专注于显示问题(视觉设计,布局和格式化)是高效使用模板引擎的关键。
为模板准备的数据整体被称作为数据模型。 模板作者要关心的是,数据模型是树形结构(就像硬盘上的文件夹和文件),在视觉效果上, 数据模型可以是:
(root)
|
+- user = "Big Joe"
|
+- latestProduct
|
+- url = "products/greenmouse.html"
|
+- name = "green mouse"
注意:
上面只是一个形象化显示;数据模型不是文本格式,它来自于Java对象。 对于Java程序员来说,root就像一个有 getUser() 和 getLatestProduct() 方法的Java对象, 也可以有 "user" 和 "latestProducts" 键值的Java Map对象。相似地,latestProduct 就像是有 getUrl() 和 getName() 方法的Java对象。
早期版本中,可以从数据模型中选取这些值,使用 user 和 latestProduct.name 表达式即可。如果我们继续类推, 数据模型就像一个文件系统,那么 "(root)" 和 latestProduct 就对应着目录(文件夹),而 user, url 和 name 就是这些目录中的文件。
总的来说,模板和数据模型是FreeMarker来生成输出(比如第一个展示的HTML)所必须的:
模板 + 数据模型 = 输出
2.数据模型一览
正如已经看到的,数据模型的基本结构是树状的。 这棵树可以很复杂,并且可以有很大的深度,比如
(root)
|
+- animals
| |
| +- mouse
| | |
| | +- size = "small"
| | |
| | +- price = 50
| |
| +- elephant
| | |
| | +- size = "large"
| | |
| | +- price = 5000
| |
| +- python
| |
| +- size = "medium"
| |
| +- price = 4999
|
+- message = "It is a test"
|
+- misc
|
+- foo = "Something"
上图中的变量扮演目录的角色(比如 root, animals, mouse, elephant, python, misc) 被称为hashes(哈希表或哈希,译者注)。哈希表存储其他变量(被称为子变量), 它们可以通过名称来查找(比如 "animals", "mouse" 或 "price")。
存储单值的变量 (size, price, message 和 foo) 称为 scalars (标量,译者注)。
如果要在模板中使用子变量, 那应该从根root开始指定它的路径,每级之间用点来分隔开。要访问 mouse 的 price ,要从root开始,首先进入到 animals ,之后访问 mouse ,最后访问 price 。就可以这样来写 animals.mouse.price。
另外一种很重要的变量是 sequences (序列,译者注)。 它们像哈希表那样存储子变量,但是子变量没有名字,它们只是列表中的项。 比如,在下面这个数据模型中, animals 和 misc.fruits 就是序列:
(root)
|
+- animals
| |
| +- (1st)
| | |
| | +- name = "mouse"
| | |
| | +- size = "small"
| | |
| | +- price = 50
| |
| +- (2nd)
| | |
| | +- name = "elephant"
| | |
| | +- size = "large"
| | |
| | +- price = 5000
| |
| +- (3rd)
| |
| +- name = "python"
| |
| +- size = "medium"
| |
| +- price = 4999
|
+- misc
|
+- fruits
|
+- (1st) = "orange"
|
+- (2nd) = "banana"
要访问序列的子变量,可以使用方括号形式的数字索引下标。 索引下标从0开始(从0开始也是程序员的传统),那么第一项的索引就是0, 第二项的索引就是1等等。要得到第一个动物的名称的话,可以这么来写代码 animals[0].name。要得到 misc.fruits 中的第二项(字符串"banana")可以这么来写 misc.fruits[1]。(实践中,通常按顺序遍历序列,而不用关心索引, 这点会在 后续介绍。)
标量类型可以分为如下的类别:
- 字符串:就是文本,也就是任意的字符序列,比如上面提到的 ''m'', ''o'', ''u'', ''s'', ''e''。比如 name 和 size 也是字符串。
- 数字:这是数值类型,就像上面的 price。 在FreeMarker中,字符串 "50" 和数字 50 是两种完全不同的东西。前者是两个字符的序列 (这恰好是人们可以读的一个数字),而后者则是可以在数学运算中直接被使用的数值。
- 日期/时间: 可以是日期-时间格式(存储某一天的日期和时间), 或者是日期(只有日期,没有时间),或者是时间(只有时间,没有日期)。
- 布尔值:对应着对/错(是/否,开/关等值)类似的值。 比如动物可以有一个 protected (受保护的,译者注) 的子变量, 该变量存储这个动物是否被保护起来的值。
总结:
- 数据模型可以被看成是树形结构。
- 标量用于存储单一的值。这种类型的值可以是字符串,数字,日期/时间或者是布尔值。
- 哈希表是一种存储变量及其相关且有唯一标识名称的容器。
- 序列是存储有序变量的容器。存储的变量可以通过数字索引来检索,索引通常从0开始。
注意:
还有一些其它更为高级的类型,在这里我们并没有涉及到,比如方法和指令。
3.模板一览
最简单的模板通常是普通的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标签”和“指令”看做是同义词即可。)
3.1 基本指令
这里我们仅仅来看一些非常常用的指令,当然 (指令还有很多)。
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!":
> 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 文件, 那么访问者在所有页面都会看到版权声明的新内容。
注意:
重用代码片段的一个更有力的方式是使用宏,但是只是更为高级的话题了, 将会在 后续讨论。
3.2 联合使用指令
在页面上也可以多次使用指令,而且指令间也可以很容易地相互嵌套。 比如,在 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标签也不会有问题。
3.3 使用内建函数
内建函数很像子变量(如果了解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转义。(这就像可以链式使用 .(点)一样)
可以阅读 全部内建函数参考。
3.4 处理不存在的变量
数据模型中经常会有可选的变量(也就是说有时并不存在)。 除了一些典型的人为原因导致失误外,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)??来看。
译自 Email: ddekany at users.sourceforge.net
freeMarker(二)——模板开发指南之入门的更多相关文章
- FreeMarker:模板开发指南
ylbtech-FreeMarker:模板开发指南 1.返回顶部 1. Section Contents 入门 模板 + 数据模型 = 输出 数据模型一览 模板一览 数值,类型 基本内容 类型 模板 ...
- FreeMarker模板开发指南知识点梳理
freemarker是什么? 有什么用? 怎么用? (问得好,这些都是我想知道的问题) freemarker是什么? FreeMarker 是一款 模板引擎: 即一种基于模板和要改变的数据, 并用来生 ...
- freeMarker(五)——模板开发指南补充知识
学习笔记,选自freeMarker中文文档,译自 Email: ddekany at users.sourceforge.net 模板开发指南补充知识 1. 自定义指令 自定义指令可以使用 macro ...
- freeMarker(四)——模板开发指南之模板
学习笔记,选自freeMarker中文文档,译自 Email: ddekany at users.sourceforge.net 模板开发指南之模板 1. 总体结构 实际上用程序语言编写的程序就是模板 ...
- freeMarker(三)——模板开发指南之数值、类型
学习笔记,选自freeMarker中文文档,译自 Email: ddekany at users.sourceforge.net 模板开发指南——数值.类型 1.基本内容 1.1 什么是数值? 正如你 ...
- ECSHOP二次开发指南
ECSHOP二次开发指南 发布时间:2013-05-28 12:47:00 来源: 评论:0 点击: 次 [字号:大 中 小] QQ空间新浪微博腾讯微博人人网豆瓣网百度空间百度搜藏开心网复制更 ...
- [PC]PHPCMS二次开发指南(上)
------------------------------------------------------------------------------------- PHPCMS本身功能已经很完 ...
- ECMALL模板解析机制.MVC架构分析及文件目录说明.二次开发指南手册(转)
ECMALL模板解析语法与机制 http://www.nowamagic.net/architecture/archt_TemplateSyntaxAndAnalysis.php ECMALL模块开发 ...
- ECShop二次开发指南(一)
ECSHOP是一套完整的网络商店解决方案,包括前台的商品展示.购物流程和强大易用的后台管理.由于 ecshop简单易用,使用者几乎可以在3几分钟简单的设置一下就可以拥有一个网上商店系统,所以很多的B2 ...
随机推荐
- getMeasuredHeight(),getScrollY(),getHeight()的区别和联系
前言:关于控件的高度有两种获取方式,一个是获得控件的实际大小(getMeasuredHeight),就是包含显示部分和已显示的部分: 而getHeight是获得控件的显示的大小,如果控件大小超出的屏幕 ...
- POJ3468_A Simple Problem with Integers(线段树/成段更新)
解题报告 题意: 略 思路: 线段树成段更新,区间求和. #include <iostream> #include <cstring> #include <cstdio& ...
- List和Set排序的实现
List.Set.Map的区别 List和Set继承了Collection接口. List以特定索引来存取元素,可以有重复元素.Set不能存放重复元素(用对象的equals()方法来区分元素是否重复) ...
- 【BZOJ1125】[POI2008]Poc hash+map+SBT
[BZOJ1125][POI2008]Poc Description n列火车,每条有l节车厢.每节车厢有一种颜色(用小写字母表示).有m次车厢交换操作.求:对于每列火车,在交换车厢的某个时刻,与其颜 ...
- 九度OJ 1002:Grading
时间限制:1 秒 内存限制:32 兆 特殊判题:否 提交:18410 解决:4753 题目描述: Grading hundreds of thousands of Graduate Entrance ...
- 2017-2018-1 20179209《Linux内核原理与分析》第九周作业
理解进程调度时机 进程调度时机 中断处理过程(包括时钟中断.I/O中断.系统调用和异常)中,直接调用schedule(),或者返回用户态时根据need_resched标记调用schedule(): 内 ...
- [转]How Hash Algorithms Work
来看看SHA-1到底是如何工作的 http://www.metamorphosite.com/one-way-hash-encryption-sha1-data-software
- linux 字符驱动
1 结构体说明: struct cdev { struct kobject kobj; // 每一个 cdev 都是一个 kobject st ...
- Maven项目启动报错
错误信息如下: 六月 , :: 下午 org.apache.tomcat.util.digester.SetPropertiesRule begin 警告: [SetPropertiesRule]{S ...
- ARDUINO W5100 WebClient 测试
基础工作:W5100扩展板插在ARDUINO上.用网线把W5100和自己家的路由器连接.插上网线能看到侧面网口指示灯变亮.路由器开启DHCP服务(一般都是开启的). 1.打开官方例程里面的Ethern ...