Play有一个高效的模板系统,它允许动态生成html、xml、json或其他文本格式的文档。Play的模板引擎使用Groovy作为表达式语言。它的标签系统允许你创建一些可以重复使用的功能。
模板默认存储在app/views目录下。
4.1. 模板语法
模板文件是一个文本文件,其中的一些占位符用于动态生成内容。模板的动态元素是用groovy语言写的。Groovy语法非常接近java的语法。
动态元素在模板执行期间被提取出来,最后以http response方式返回给客户端。
Expressions: ${…}
要创建一个动态元素,最简单的方法就是声明一个表达式。语法为${„},表达式的最终结果将被插入在表达式使用的地方。
如:
<h1>Client ${client.name}</h1>
如果不确定client对象是否为null,则可以使用如下的groovy语法:
<h1>Client ${client?.name}</h1>
如果client不为null,则显示,否则不显示。
Template decorators : #{extends /} and #{doLayout /}
Decorators(装饰?母版)提供了一个清晰的解决方案来在多个不同的模板间共享同一个页面布局(或设计)。
使用#{get} 和 #{set}标签来在模板和decorator(母版)之间共享变量。
在decorator(母版)里嵌入一个页面就是一行代码的事:
#{extends 'simpledesign.html' /}
#{set title:'A decorated page' /}
次内容将被渲染。
decorator(母版)文件simpledesign.html的代码为:
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>#{get 'title' /}</title>
<link rel="stylesheet" type="text/css" href="@{'/public/stylesheets/main.css'}" />
</head>
<body>
<h1>#{get 'title' /}</h1>
#{doLayout /}
<div class="footer">Built with the play! framework</div>
</body>
</html>
Tags: #{tagName /}
一个tag就是一个可以带参数的模板碎片,如果只有一个参数,则默认的参数名称就是arg,而且arg可以省略。
例如,如下的tag用于插入一个javascript脚本:
#{script 'jquery.js' /}
tag必须是关闭的:
#{script 'jquery.js' /}
或
#{script 'jquery.js'}
#{/script}
例如,list标签用于迭代所有的集合元素,它带有两个必须的参数items和as:
<h1>Client ${client.name}</h1>
<ul>
#{list items:client.accounts, as:'account' }
<li>${account}</li>
#{/list}
</ul>
所有的动态表达式将被模板引擎转义以避免XSS安全问题(XSS:跨站脚本攻击),因此下列的title变量将被转义:
${title} --> <h1>Title</h1>
如果不想转义,可以明确调用raw()方法:
${title.raw()} --> <h1>Title</h1>
当然,如果你想显示一个较多内容的raw html,你可以使用#{verbatim /}标签:
#{verbatim}
${title} --> <h1>Title</h1>
#{/verbatim}
**Actions: @{…} or @@{…}**
可以使用Route来反向找到URL相应的特定route配置,在模板里,可以使用@{„}语法来完成这个任务,主要用于生成URL链接。
示例:
<h1>Client ${client.name}</h1>
<p>
<a href="@{Clients.showAccounts(client.id)}">All accounts</a>
</p>
<hr />
<a href="@{Clients.index()}">Back</a>
<!--得到index()方法的链接-->
@{…} 语法与 @{…} 相似,不过它生成的绝对 URL 路径,对 email 很实用。
Messages: &{…}
&{…}语法可用于国际化支持,以显示不同语言的消息:
例如在conf/messages目录下的文件里,我们可以这样定义:
clientName=The client name is %s
在模板里使用时:
<h1>&{'clientName', client.name}</h1>
Comment: *{…}*
注释部分将被模板引擎忽略:
*{**** Display the user name ****}*
<div class="name">
${user.name}
</div>
Scripts: %{…}%
脚本是一段复杂的表达式集合,在脚本内可以声明变量和定义一些语句,play使用%{„}%来插入一段脚本。
%{
fullName = client.name.toUpperCase()+' '+client.forname;
}%
<h1>Client ${fullName}</h1>
在脚本里可以使用out对象直接输出动态内容:
%{
fullName = client.name.toUpperCase()+' '+client.forname;
out.print('<h1>'+fullName+'</h1>');
}%
在脚本里也可创建一个结构,比如用于迭代:
<h1>Client ${client.name}</h1>
<ul>
%{
for(account in client.accounts) {
}%
<li>${account}</li>
%{
}
}%
</ul>
请记住,模板不是一个放置复杂代码的好地方。因此,请使用tag代替或放置在在controller或model对象里。
4.2. Template inheritance继承
模板可以被继承,比如模板被作为另外一个模板的一部分而被包含进去。
要继承另一个模板,请使用#{extends ‘„’}:
#{extends 'main.html' /}
<h1>Some code</h1>
main.html模板是一个标准模板,它使用doLayout标签来包含本模板的内容:
<h1>Main template</h1>
<div id="content">
#{doLayout /}
</div>
4.3. 定制模板标签
一个tag就是一个模板文件,存储于app/views/tags目录下,模板文件名将用作tag名称。
例如,创建一个hello标签,只需在app/views/tags/目录下创建一个hello.html文件,其内容如下:
Hello from tag!
不需要任何配置,你就可以直接使用hello标签了:
#{hello /}
检索tag参数
tag参数默认被暴露为模板变量,变量名称带有‘_’字符前缀。
例如:
Hello ${_name} !
现在你就可以给name变量传递参数了:
#{hello name:'Bob' /}
如果你的tag只有一个参数,默认的参数名称是arg,而且可以忽略。
例如:
Hello ${_arg}!
使用更简单:
#{hello 'Bob' /}
调用标签体
如果标签支持body,使用doBody标签,你就能在标签代码里的任何位置包含它。
比如:
Hello #{doBody /}!
然后你可使用这个名字作为标签体:
#{hello}
Bob
#{/hello}
格式化特定标签
你可以为不同的内容类型创建多个tag版本,play将自动选择适当的Tag。例如,play将使用app/views/tags/hello.html来处理request.format为html的内容,使用app/views/tags/hello.xml来处理格式为xml的请求。
如果内容格式不匹配, play将返回一个app/views/tags/hello.tag的处理结果。
4.4. 定制java标签
你也可以使用java代码来定义一个定制tags。这和继承自play.templates.JavaExtensions类的JavaExtensions类相似,创建一个FastTag时,需要在类里创建一个方法,并且继承自play.templates.FastTags。每一个要当作tag来执行的方法都必须遵循如下的方法签名:
public static void _tagName(Map<?, ?> args, Closure body, PrintWriter out, ExecutableTemplate template, int fromLine)
注意:tag名称之前必须要有下划线”_”。
为了理解如何创建一个真正的tag,让我们看一下两个内建的tags。
比如,verbatim标签仅通过简单调用JavaExtensions的toString()方法来实现的,并且被传递给tag的body。
public static void _verbatim(Map<?, ?> args, Closure body, PrintWriter out, ExecutableTemplate template, int fromLine) {
out.println(JavaExtensions.toString(body));
}
tag的body一定是在一对tag之间的(一开一关),即
<verbatim>My verbatim</verbatim>
Body的值应该是
My verbatim
第二个示例是option标签,要复杂一点,因为这个标签依赖于其父标签来实现相关功能。
public static void _option(Map<?, ?> args, Closure body, PrintWriter out,
ExecutableTemplate template, int fromLine) {
Object value = args.get("arg");
Object selection = TagContext.parent("select").data.get("selected");
boolean selected = selection != null && value != null
&& selection.equals(value);
out.print("<option value="" + (value == null ? "" : value) + "" "
+ (selected ? "selected="selected"" : "")
+ "" + serialize(args, "selected", "value") + ">");
out.println(JavaExtensions.toString(body));
out.print("</option>");
}
这些代码将输出一个标准的html option标签,并依据父标签的值设置为相应的option为selected,前三行用于设置用于输出的变量。最后三行输出tag的结果。
在FastTags.java in github里有不同难度的标签示例。
标签命名空间
请检查你的tag在项目或与play标签之间没有冲突,你可以使用类级别的注释@FastTags.Namespace来设置命名空间。
因此,对hello标签来说,可以如下设置(空间名称为my.tags)
@FastTags.Namespace("my.tags")
public class MyFastTag extends FastTags {
public static void _hello (Map<?, ?> args, Closure body, PrintWriter out,
ExecutableTemplate template, int fromLine) {
...
}
}
之后在模板里用如下方式进行调用。
#{my.tags.hello/}
4.5. 在模板里的Java对象扩展
当你在模板引擎里使用一个java对象时,play为其加入了一些新的方法,这些方法在原始的java类里并不存在,而是被模板引擎动态加入到模板里的,play对一些基本的java原始类进行了扩展。
例如,为了方便在模板里对数字进行格式化,play在java.lang.Number里增加了一个format方法:
之后,我们在模板里就很容易对一个数字进行格式化:
<ul>
#{list items:products, as:'product'}
<li>${product.name}. Price: ${product.price.format('## ###,00')} €</li>
#{/list}
</ul>
同样可用于java.util.Date
具体的内容见Java extensions,它列出了一些扩展了的方法,如下:
对集合的扩展:
join(‘/’) 用/进行连接后返回
last() 返回最后一个元素
pluralize() 是否大于1个,大于则返回一个字符串:集合名+s
pluralize(plural) 是否大于1个,大于则返回一个字符串:集合名+plural指定的字符串,如es
pluralize(singular,plural) 为1则返回集合名+singular指定的字符串,不为1则返回集合名+plural指定的字符串
对Date的扩展:
Format(foramt) 返回格式化后的日期,如new Date().format(‘dd MMMM yyyy))
Format(format,language) 带语言,如new Date().format(‘dd MM yy’,’en’)
Since()
Since(condition)
另外还对Long、Map、Number、对象、String、String数组等类型进行扩展。
创建定制扩展
只需要创建一个继承了play.templates.JavaExtensions的类即可。
例如,创建一个定制数字格式化的方法:
package ext;
import play.templates.JavaExtensions;
public class CurrencyExtensions extends JavaExtensions {
public static String ccyAmount(Number number, String currencySymbol) {
String format = "'"+currencySymbol + "'#####.##";
return new DecimalFormat(format).format(number);
}
}
每个扩展方法都是一个static方法,而且要返回一个java.lang.String结果,以便于在页面里显示。第一个参数将用于保存扩展后的对象。
使用方法如下:
<em>Price: ${123456.324234.ccyAmount()}</em>
模板扩展类会被play自动加载,你只需要重启你的应用程序即可。
4.6. 模板里可以使用的保留对象
所有添加到renderArgs作用域的对象都将直接注入成模板变量。
例如,从controller向模板注入一个user bean:
renderArgs.put("user", user );
在一个action里渲染模板里时,框架会自动添加以下保留对象:
Variable | Description | API documentation | See also |
---|---|---|---|
errors | Validation errors | play.data.validation.Validation.errors() | Validating HTTP data |
flash | Flash scope | play.mvc.Scope.Flash | Controllers – Session & Flash Scope |
lang | The current language | play.i18n.Lang | Setting up I18N – Define languages |
messages | The messages map | play.i18n.Messages | Setting up I18N – Externalize messages |
out | The output stream writer | java.io.PrintWriter | |
params | Current parameters | play.mvc.Scope.Params | Controllers – HTTP parameters |
play | Main framework class | play.Play | |
request | The current HTTP request | play.mvc.Http.Request | |
session | Session scope | play.mvc.Scope.Session | Controllers – Session & Flash Scope |
以上列出的名称和owner/delegate/it在Groovy里是默认的保留字,在创建变量时要注意回避。