Chapter 1. 快速入门 Quick Start
模版 + 数据模型 = 输出 Template + data model = output
FreeMarker是基于这样一个理念的:设计人员与开发人员是不同的人,他们分别擅长不同的技能。理想情况下,应该将设计人员的关注点集中到外观展现上 – 他们创建HTML文件、图片、和web页面的其他方面。同时,开发人员来创建产生数据的系统,再由设计人员设计的页面来显示。
问题是你经常不得不在页面上(或其他文档中)显示一些在设计期未知的信息。一个典型的电子商务应用就是基于这种动态数据的 – 比如库存的实时状态,美元与日元的转换汇率,顾客的订单是否已经发送等。这些数据总是在变。遇到这种情况,你需要再你的HTML(或其他文本中放一些特殊的结构;FreeMarker的输出不仅仅限于HTML),然后FreeMarker会在需要输出页面给用户时将这些代码替换成正确的数据。现在以一个非常简单的例子开始 -- 你需要在欢迎页面显示用户的名称并提示你公司的最近产品:
<html>
<head>
<title>Welcome!</title>
</head>
<body>
<h1>Welcome ${user}!</h1>
<p>Our latest product:
<a href="${latestProduct.url}">${latestProduct.name}</a>!
</body>
</html>
上面的内容就是一个普通的HTML片段(除了其中的特殊代码${...})。这些代码是FreeMarker要发送输出到用户时,用来说明应该将实时的文本信息放到哪里去的。像这样的文件被叫做模版templates。
那么是user, latestProduct.url和latestProduct.nam从哪儿来的呢?他们是从数据模型中复制来的。数据模型是存储在计算机内存中的内容。由开发人员写的程序创建数据模型(即数据模型由程序动态生成),这就是模版提供变化信息的机制。这些信息可能来自数据库、文件甚至是由程序生成的内容都可以。但模版作者其实并不关系数据来源。他们只是使用已有的数据模型。
我们说,输出是通过将模版和数据模型相结合创建的:
Template + data model = output
数据模型可能是这样:
(root)
|
+- user = "Big Joe"
|
+- latestProduct
|
+- url = "products/greenmouse.html"
|
+- name = "green mouse"
(不要误会:数据模型并不是文本,这里只是提供一个数据模型的形象的样子)
打个比方,这就像是文件系统:root和latestProduct对应于目录,user, url和name对应于文件。url和name在目录latestProduct中。但这只是一个比喻,其实并没有真正的文件和目录。
当FreeMarker将上面的数据模型与示例模版合成时,web页面的访问者就会得到如下输出:
<html>
<head>
<title>Welcome!</title>
</head>
<body>
<h1>Welcome Big Joe!</h1>
<p>Our latest product:
<a href="products/greenmouse.html">green mouse</a>!
</body>
</html>
数据模型 The data model
正如你已经看到的,数据模型是基于树形结构的。这棵树可以任意复杂,并且不限深度:
(root)
|
+- animals
| |
| +- mouse
| | |
| | +- size = "small"
| | |
| | +- price = 50
| |
| +- elephant
| | |
| | +- size = "large"
| | |
| | +- price = 5000
| |
| +- python
| |
| +- size = "medium"
| |
| +- price = 4999
|
+- test = "It is a test"
|
+- whatnot
|
+- because = "don't know"
其中作为目录的变量(the root, animals, mouse, elephant, python, whatnot)叫做散列hashes。哈西使用查找名来存放其它子变量。
存储单个值的变量叫做标量scalars。
当你访问一个子变量时,必须使用从根root开始的、以句点.分割的路径。比如要访问老鼠mouse的价格price,从根root开始到animals,然后到mouse,再到price。所以应该这样写:animals.mouse.price。当你用特殊代码${...}包围这样的表达式,就是告诉FreeMarker输出这个节点的相应文本。比如:${ animals.mouse.price }???
还有一个重要的变量:序列sequences。序列与散列类似,都可以包含子变量,但是没有相应的查找名,而是使用序列存储子变量,可以通过数字下标访问子变量。比如,在下面的数据模型中,animals和whatnot.fruits都是序列:
(root)
|
+- animals
| |
| +- (1st)
| | |
| | +- name = "mouse"
| | |
| | +- size = "small"
| | |
| | +- price = 50
| |
| +- (2nd)
| | |
| | +- name = "elephant"
| | |
| | +- size = "large"
| | |
| | +- price = 5000
| |
| +- (3rd)
| |
| +- name = "python"
| |
| +- size = "medium"
| |
| +- price = 4999
|
+- whatnot
|
+- fruits
|
+- (1st) = "orange"
|
+- (2nd) = "banana"
要访问序列的子变量,需要使用类似于数组的下标机制,将下标放到中括号中。下标从0开始,第二个变量的下标是1,以此类推。所以要获得第一个动物的名字,使用animals[0].name。要获得whatnot.fruits中的第二个条目(这里是字符串"banana"),使用whatnot.fruits[1]。
标量可以存储不同类型的值。最常用的类型有:
- 字符串:文本、字符序列。要在FreeMarker中使用字符串,必须使用引号,如"mouse" 或 'mouse'。
- 数字:就是数值。字符串"50"与数值50是完全不同的。前者是两个字符(尽管读音与数字相同),而后者是一个可以用于数学计算的数字值。所以,不要用引号包围数字!这会使FreeMarker将该量作为字符串处理。合法的数字有:0, 4999, -273, 3.14。
总结:
- 数据模型可以视作是层次结构的 -- 就像是树。
- 标量scalar存储单值。可以存储字符串或数字(还有别的东西,稍后介绍)。
- 散列hash是用于存储其它变量的容器,并具有查找名。
- 序列sequence是一种按顺序存储其它变量的容器。可以通过数字下标进行访问。
模版 The template
最简单/常见的模版是HTML文件。当客户端访问页面时,FreeMarker就会将该HTML原封不动的发送到客户端。但如果你想获得动态效果,就会在HTML中特定位置放置一些FreeMarker可以识别的东西:
<!--[if !supportLists]-->· <!--[endif]-->占位符${...}:FreeMarker会在输出中使用括号中内容相对应的实际值来替换它。
<!--[if !supportLists]-->· <!--[endif]-->FTL标签(FreeMarker Template Language tags):FTL标签与HTML标签相似,但它们是FreeMarker的内容,并不会打印到输出中。FTL标签名以井号”#”开头,以便于HTML标签区分开来。(其实还可以用” @”代替”#”,后续章节会解释)
<!--[if !supportLists]-->· <!--[endif]-->注释Comments:与HTML注释类似,只是由<#-- 和 -->来界定(而不是<!—和 -->)。这两个界定符之间的任何内容都会被FreeMarker忽略,并且不会打印到输出中。
任何既不是FTL标签也不是占位符或注释的内容都被当作普通文本来处理,并且不会被FreeMarker解析,而直接打印到输出中。
FTL标签也称为“指令”directives。就像是HTML标签也被叫做HTML元素,比如<table>和</table>也叫做table元素。如果你感觉二者没有区别,可以把“FTL标签”和“指令”作为同义词。
指令的例子 Examples of directives
尽管FreeMarker有很多的指令,但这个小例子只使用最常用的指令中的三个。
if指令 The if directive
使用if指令可以根据条件跳过模版的一个部分。比如,假设在第一个例子very first example中,你想将你的老板于其它用户区分开来,假设你老板的名字叫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字符串“, our beloved leader”只有在变量user的是值是字符串"Big Joe"时才显示。通常,如果条件condition的值为false时,在<#if condition>与</#if>标签中的内容。
注意:这里使用了两种不同语法的元素。等号=的左边,使用了一个变量,右边使用了字符串。也可以这样写(使用基于散列样例的数据模型):
<#if animals.python.price < animals.elephant.price>
Pythons are cheaper than elephants today.
</#if>
使用<#else>标签可以制定当条件为假时要做什么:
<#if animals.python.price < animals.elephant.price>
Pythons are cheaper than elephants today.
<#else>
Pythons are not cheaper than elephants today.
</#if>
如果python的价格比大象elephant的价格低,会输出”Pythons are cheaper than elephants today.”,否则输出“Pythons are not cheaper than elephants today.”。
list指令 The list directive
当你想列举一些内容的时候,应该使用list指令。如果将这个模版与前面的序列样例数据模型合并:
|
<p>We have these animals:
<table border=1>
<tr><th>Name<th>Price
<#list animals as being>
<tr><td>${being.name}<td>${being.price} Euros
</#list>
</table>
会输出:
<p>We have these animals:
<table border=1>
<tr><th>Name<th>Price
<tr><td>mouse<td>50 Euros
<tr><td>elephant<td>5000 Euros
<tr><td>python<td>4999 Euros
</table>
list指令的通用格式是:
<#list SequenceVar as variable>repeatThis</#list>
<#list 序列变量 as 变量>重复内容</#list>
repeatThis重复内容 部分就是当在给定的序列变量SequenceVar中的每个条目被遍历(从第一个开始)时所要重复的内容。每重复一次,变量variable就会持有当前条目的值。这个变量只存在于<#list ...>与</#list>内,并且不会重写/覆盖任何位于数据模型中的同名变量。
再来一个例子,将前面数据模型中的水果列出来:
<p>And BTW we have these fruits:
<ul>
<#list whatnot.fruits as fruit>
<li>${fruit}
</#list>
<ul>
注意:你应该已经熟悉whatnot.fruits了,它是对数据模型中的变量的引用。
include指令 The include directive
使用include指令可以将外部文件导入模版(译注:与html、jsp中的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,就会在所有的页面中看到新的版权声明。
结合使用多个指令 Using directives together
你可以在一个页面中使用任意数量的指令,也可以自由的嵌套指令。比如下面的例子会列出所有的动物并将大型动物的名称以粗体显示:
<p>We have these animals:
<table border=1>
<tr><th>Name<th>Price
<#list animals as being>
<tr>
<td>
<#if being.size = "large"><b></#if>
${being.name}
<#if being.size = "large"></b></#if>
<td>${being.price} Euros
</#list>
</table>
注意:由于FreeMarker不对FTL标签外部的文本进行解析,所以占位符和注释不会将上面的<b>和</b>视为嵌套错误的标签。