1. FTL标签
1.1. 开始标签: <#directivename parameters>。
1.2. 结束标签: </#directivename>。
1.3. 除了标签以#开头外, 其他都和html、xml的语法很相似。
1.4. 如果标签没有嵌套内容(在开始标签和结束标签之间的内容), 那么可以只使用开始标签。例如: <#include something />指令没有可嵌套的内容。
2. 指令分为预定义指令(FreeMarker定义的指令, 例如: if, list, include)和用户自定义指令。
3. 用户自定义的指令使用@来代替#。比如: <@mydirective parameters>...</@mydirective>。
4. if指令
4.1. 使用if指令可以有条件地跳过模板的一些片段。如果condition是false(布尔值), 那么介于<#if condition>和</#if>标签中的内容会被略过。
<#-- 如果萝卜比白菜便宜输出内容, 反之不输出。 -->
<#if radish.price < cabbage.price>
萝卜比白菜便宜。
</#if>
4.2. 使用<#else>标签可以指定当条件为false时程序所要执行的内容。
<#-- 如果萝卜比白菜便宜输出: 萝卜比白菜便宜; 反之输出: 白菜比萝卜便宜。 -->
<#if radish.price < cabbage.price>
萝卜比白菜便宜。
<#else>
白菜比萝卜便宜。
</#if>
4.3. if后面使用elseif增加判断条件。
<#-- 如果萝卜比白菜便宜输出: 萝卜比白菜便宜; 如果白菜比萝卜便宜输出: 白菜比萝卜便宜; 反之输出: 萝卜和白菜一样贵。 -->
<#if radish.price < cabbage.price>
萝卜比白菜便宜。
<#elseif cabbage.price < radish.price>
白菜比萝卜便宜。
<#else>
萝卜和白菜一样贵。
</#if>
4.4. elseif和else必须出现在if内部(也就是, 在if的开始标签和结束标签之间)。
4.5. if中可以包含任意数量的elseif(包括0个); 0个或1个else。
5. list指令
5.1. 当需要列表显示内容时, list指令是必须的。
5.2. list指令的一般格式为: <#list sequence as loopVariable>repeatThis</#list>。repeatThis部分将会在给定的sequence遍历时在每一项中重复, 从第一项开始, 一个接着一个。在所有的重复中, loopVariable将持有当前遍历项的值。这个变量仅存在于<#list ...>和</#list>标签内。
<ul>
<#list misc.fruits as fruit>
<li>${fruit}</li>
</#list>
</ul>
5.3. 上面示例中的一个问题是如果我们有0个水果, 它仍然会输出一个空的<ul></ul>, 而不是什么都没有。要避免这样的情况, 可以这么来使用list:
<#list misc.fruits as fruit>
<ul>
<li>${fruit}</li>
</ul>
</#list>
5.4. 此时, list指令将列表视为一个整体, 如果我们有0个水果, 那么在list中的所有东西都被略过了, 因此就不会有ul标签了。
5.5. 另一个列表相关的常见任务是: 使用一些分隔符来列出水果, 比如逗号:
<p>Fruits:</p> <#list misc.fruits as fruit>${fruit}<#sep>, </#sep></#list>
5.6. 被sep覆盖的部分只有当还有下一项时才会被执行。因此最后一个水果后面不会有逗号。
5.7. 再次回到这个话题, 如果我们有0个水果, 会怎么样?只是打印"Fruits:"也没有什么不方便。list指令, 也像if指令那样, 可以有else部分, 如果列表中有0个元素时就会被执行:
<p>Fruits:</p> <#list misc.fruits as fruit>${fruit}<#sep>, </#sep><#else>None</#list>
5.8. 请注意, 循环变量(fruit)在else标签和list结束标签中间不存在, 因为那部分不是循环中的部分。
5.9. 如果不得不在第一列表项之前或在最后一个列表项之后打印一些东西, 那么就要使用items指令, 但至少要有一项。
5.10. 所有的这些指令(list, items, sep, else)可以联合起来使用:
<#list misc.fruits>
<p>Fruits:</p>
<ul>
<#items as fruit>
<li>${fruit}<#sep>, </#sep></li>
</#items>
</ul>
<#else>
<p>We have no fruits.</p>
</#list>
5.11. break指令在迭代的任意点退出。通常来说, break将仅存在于为每个迭代项调用的指令体中, 而且只能存在于这样的指令中。不能在list的else部分使用break, 除非list内嵌到了其它可以break的指令中。如果list使用items指令迭代, 那么break指令必须放在items内部。
6. include指令
6.1. 使用include指令, 我们可以在模板中插入其他文件的内容。
6.2. 假设要在一些页面中显示版权声明的信息。那么可以创建一个文件来单独包含这些版权声明, 之后在需要它的地方插入即可。比方说, 我们可以将版权信息单独存放在页面文件 copyright_footer.html中:
<i>© 1999–2015 The FreeMarker Project. All rights reserved.</i>
6.3. 当需要用到这个文件时, 可以使用include指令来插入:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>主页</title>
</head>
<body>
<h1>欢迎来到我的个人主页。</h1>
<#include "/copyright_footer.html">
</body>
</html>
6.4. 此时, 输出的内容为:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>主页</title>
</head>
<body>
<h1>欢迎来到我的个人主页。</h1>
<i>© 1999–2015 The FreeMarker Project. All rights reserved.</i>
</body>
</html>
6.5. 当修改了copyright_footer.html文件, 那么访问者在所有页面都会看到版权声明的新内容。
6.6. include指令还可以包含以下参数
6.6.1. parse: 如果它为true, 那么被包含的文件将会当作FTL来解析, 否则整个文件将被视为简单文本。如果你忽略了这个选项, 那么它默认是true。
6.6.2. encoding: 被包含文件从包含它的文件继承的编码方式(实际就是字符集), 除非你用这个选项来指定编码方式。合法的名字有: ISO-8859-2, UTF-8, Shift_JIS, Big5, EUC-KR, GB2312。
6.6.3. ignore_missing: 当为true, 模板引用为空时压制错误, 而<#include ...>不会输出任何东西。当为false时, 如果模板不存在, 那么模板处理就会发生错误并停止。如果忽略这个选项, 那么它的默认值是false。
6.6.4. 实例: <#include "/foo.ftl" parse=true encoding="UTF-8" ignore_missing=false>
7. 例子
7.1. 新建一个名为FMDirective的动态Web工程, 同时添加相关jar包。
7.2. 编写FMFactory.java
package com.fm.util;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import freemarker.template.Configuration;
import freemarker.template.TemplateExceptionHandler;
public class FMFactory {
private final static FMFactory instance = new FMFactory();
private FMFactory() {}
public static FMFactory getInstance() {
return instance;
}
private Map<String, Configuration> map = new ConcurrentHashMap<String, Configuration>();
// 创建单个Configuration实例
public synchronized Configuration getCfg(Object servletContext, String path) {
if(null != map.get(path)) {
return map.get(path);
}
Configuration cfg = new Configuration(Configuration.VERSION_2_3_22);
cfg.setServletContextForTemplateLoading(servletContext, path);
cfg.setDefaultEncoding("utf-8");
cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
map.put(path, cfg);
return cfg;
}
}
7.3. 编写Vegetable.java
package com.fm.model;
import java.io.Serializable;
public class Vegetable implements Serializable {
private static final long serialVersionUID = 1L;
private Float price; // 动物售价
public Vegetable() {
}
public Vegetable(Float price) {
this.price = price;
}
public Float getPrice() {
return price;
}
public void setPrice(Float price) {
this.price = price;
}
}
7.4. 编写BaseDirective.java
package com.fm.action;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.fm.model.Vegetable;
import com.fm.util.FMFactory;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
public class BaseDirective extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Configuration cfg = FMFactory.getInstance().getCfg(req.getServletContext(), "/WEB-INF/templates");
Vegetable radish = new Vegetable(6F);
Vegetable cabbage = new Vegetable(1F);
Map<String, Object> root = new HashMap<String, Object>();
root.put("radish", radish);
root.put("cabbage", cabbage);
List<String> fruits = new ArrayList<String>();
fruits.add("香蕉");
fruits.add("苹果");
fruits.add("梨子");
root.put("fruits", fruits);
// 获取模板
Template temp = cfg.getTemplate("vegetable.html");
Writer out = new OutputStreamWriter(resp.getOutputStream());
try {
// 合并模板和数据模型
temp.process(root, out);
} catch (TemplateException e) {
e.printStackTrace();
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
7.5. 修改web.xml
7.6. 在/WEB-INF/templates目录下编写vegetable.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>基本指令</title>
</head>
<body>
<#-- 如果萝卜比白菜便宜输出内容, 反之不输出。 -->
<#if radish.price < cabbage.price>
萝卜比白菜便宜。
</#if>
萝卜: ¥${radish.price}<br />
白菜: ¥${cabbage.price}
<hr />
<#-- 如果萝卜比白菜便宜输出: 萝卜比白菜便宜; 反之输出: 白菜比萝卜便宜。 -->
<#if radish.price < cabbage.price>
萝卜比白菜便宜。
<#else>
白菜比萝卜便宜。
</#if>
<hr />
<#-- 如果萝卜比白菜便宜输出: 萝卜比白菜便宜; 如果白菜比萝卜便宜输出: 白菜比萝卜便宜; 反之输出: 萝卜和白菜一样贵。 -->
<#if radish.price < cabbage.price>
萝卜比白菜便宜。
<#elseif cabbage.price < radish.price>
白菜比萝卜便宜。
<#else>
萝卜和白菜一样贵。
</#if>
<hr style="background-color: red; height:1px; border:none;" />
<ul>
<#list fruits as fruit>
<li>${fruit}</li>
</#list>
</ul>
<hr />
<#list fruits>
<ul>
<#items as fruit>
<li>${fruit}</li>
</#items>
</ul>
</#list>
<hr />
<#list fruits>
<p>Fruits:</p>
<ul>
<#items as fruit>
<li>${fruit}<#sep> ,</#sep></li>
</#items>
</ul>
<#else>
<p>我们没有库存水果了。</p>
</#list>
<hr style="background-color: red; height:1px; border:none;" />
<#include "/copyright_footer.html" />
</body>
</html>
7.7. 在/WEB-INF/templates目录下编写copyright_footer.html
<i>© 1999–2015 The FreeMarker Project. All rights reserved.</i>
7.8. 运行项目