- 模板引擎
Unmi 注:因为是 Web 框架,所以展示层必不可少,也就是接下来两章,我们进入到 Play2.0 中的模板引擎的世界,不同与Play 1.x 让你眼花缭乱的 ${}, #{}, @{}, @@{}, &{}, %{}% 和 {} 如此众多的标签样式,而在 Play 2.0 中只有一个魔幻的 @ 符号。这就叫化繁为简,Play 1.x 的模板是基于 Groovy 的,而 Play 2.0 是基于 Scala 的,它在效率上也改善不少。
基于 Scala 的类型安全的模板引擎
Play 2.0 带来了崭新而真正强大的基于 Scala 的模板引擎. 这一新引擎的设计灵感来自于 ASP.NET 的 Razor. 具体表现在:
- 简洁, 富有表现力, 且流畅: 它最小化了一个文件所需的字符和按键的数量, 并且开启了一个快速而流畅的代码流. 不像多数的模板语法, 你不必中断你的代码去显式的告诉你的 HTML 说这儿有一个服务端代码块. 解析器足够的聪明,能依据你的代码进行推断(Unmi 注: 推断出哪块代码应该由服务端来执行). 这使得简洁而富有表现力的语法显得更干净, 并且输入变得快速而有乐趣.
- 易学: 源于其最小化的概念,而让你快速而富有成效的掌握它. 只需用到你现有的 Scala 语言和 HTML 知识.
- 并非新的语言: 我们尽量选择不去创建一门新语言. 相应的,我们希望能让开发者使用它们现有的 Scala 语言技能。也就是要在选择你自己语言的情况下,提供一套模板标记语法,从而让你书写出令人敬畏的 HTML 结构流.
- 可用任何文本编辑器: 无须特别的工具,在任何纯文本编辑器中都能保持高效.
模板是需要经过编译的, 因此将你会在浏览器中看到任何的错误:
概述
Play 的 Scala 模板是一个简单的文本文件, 它包含都着小块的 Scala 代码. 它们能产生出任何的文本格式的内容, 如 HTML, XML 或 CSV.
模板系统设计的让你选择它处理起 HTML 来感觉很舒适, 也让 Web 设计人员很容易用它.
模板会被遵循一个简单的命名约定而编译为标准的 Scala 函数: 假如你创建一个 views/Application/index.scala.html 模板文件, 它将生成一个 views.html.Application.index 函数.
例如, 这儿是一个简单的模板:
| 1 2 3 4 5 6 7 8 9 | @(customer: Customer, orders: Seq[Order]) <h1>Welcome @customer.name!</h1> <ul>  @orders.map { order =>   <li>@order.title</li> }  </ul> | 
然后你可以在任何 Scala 代码中像调用一个函数那样调用它:
| 1 | val html = views.html.Application.index(customer, orders) | 
语法: 神奇的 ‘@’ 字符
Scala 模板仅使用 @ 作为单一的特殊字符. 每次遇到这个字符时, 它就标示着一个 Scala 表达式的开始. 它无须你显式的关闭代码块 - 这会从你的代码进行推断:
| 1 2 3 | Hello @customer.name!        ^^^^^^^^^^^^^         Scala code | 
Unmi 注: 看到上面的 ^^^^^^^^^^^^^,可别认为是有些语言编译器在告诉你这行的语法错误,这里只是用来圈划出是一行 Scala 代码。
因为模板引擎能通过分析你的代码来侦测出你的代码块会在哪里结束, 所以这样只能写简单的表达式. 如果你想插入一个有多个 token (multi-token Unmi 注: token 是编译原理里的概念,没有一个合适的翻译,只要理解到下面表达式如果不加括号,只会解析出 @customer.firstName 的值,其他原样输出) 的表达式, 那么需要显式的加括号来标记:
| 1 2 3 | Hello @(customer.firstName + customer.lastName)!        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^                      Scala Code | 
你也可以使用花括号, 就像在 Scala 代码中那样来写多个表达式的语句块:
| 1 2 3 | Hello @{val name = customer.firstName + customer.lastName; name}!        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^                              Scala Code | 
由于 @ 是一个特殊字符, 你有时候需要转译它. 这就要用到 @@:
| 1 | My email is bob@@example.com | 
模板参数
模板就是个函数, 所以它会有参数, 模板参数必须声明在模板文件的第一行:
| 1 | @(customer: models.Customer, orders: Seq[models.Order]) | 
Unmi 注: 在 Play 1.x 中自定义的标签中,所使用的以下划线开头的变量(如 _name) 即是要传入的参数。
你还能为参数指定默认值:
| 1 | @(title: String = "Home") | 
或甚至可以多个参数组:
| 1 | @(title:String)(body: Html) | 
Unmi 注: 看到上面的写法就是柯里化的参数。
而且还能用隐式参数:
| 1 | @(title: String)(body: Html)(implicit request: RequestHeader) | 
迭代
你可以使用 Scala 的 for 语句, 依循标准的方式. 但需加说明的是模板编译器会在你的语句块结束前添加一个 yield 关键字:
| 1 2 3 4 5 | <ul> @for(p <- products) {   <li>@p.name ($@p.price)</li> }  </ul> | 
你大约知道, 下面这种 for 语句的用法只是遍历传统 Map 的语法糖:
| 1 2 3 4 5 | <ul> @products.map { p =>   <li>@p.name ($@p.price)</li> }  </ul> | 
If 块
If 块没什么特别的. 使用 Scala 的 if 语句标语的方式就行:
| 1 2 3 4 5 | @if(items.isEmpty) {   <h1>Nothing to display</h1> } else {   <h1>@items.size items!</h1> } | 
模式匹配
你还能在模板中使用模式匹配:
| 1 2 3 4 5 6 7 8 9 10 11 | @connected match {   case models.Admin(name) => {     <span class="admin">Connected as admin (@name)</span>   }   case models.User(name) => {     <span>Connected as @name</span>   } } | 
Unmi 注: 模式匹配在语法上当然也是和 Scala 代码一致的,模式匹配在 Scala 是一个比较不容易理解的内容,可能是因为在 Java 中找不到相对应的功能吧。
声明可重要块
你可以创建可重用的代码块: Unmi 注: 其实就是在模板中定义的函数。
| 1 2 3 4 5 6 7 8 9 | @display(product: models.Product) = {   @product.name ($@product.price) } <ul> @products.map { p =>   @display(product = p) }  </ul> | 
注: 你还可以声明可重用的纯 Scala 块: Unmi 注: 区别是在方法体前面是否用 “@” 符号,纯 Scala 块中可写纯粹的 Scala 代码,无须再用 "@" 符号了。
| 1 2 3 4 5 | @title(text: String) = @{   text.split(' ').map(_.capitalize).mkString(" ") } <h1>@title("hello world")</h1> | 
注: 声明 Scala 块这种方式在模板中有时候很有用, 但是请记住,模板不是一个写复杂逻辑代码的好地方. 通常更好的方式是把这些代码写在一个 Scala 源文件中去(如果你愿意的话,可把这类 Scala 源文件存在
views/包下).
依据约定, 一个可重用的块名定义是以 implicit 开始的,它就会被标记为隐式的 (implicit):
| 1 | @implicitFieldConstructor = @{ MyFieldConstructor() } | 
Unmi 注: 这样上面的 @implicitFieldConstructor 就可以应用到隐式参数上去。
声明可重用的值
你可用 defining 帮助方法来定义作用域内的值:
| 1 2 3 | @defining(user.firstName + " " + user.lastName) { fullName =>   <div>Hello @fullName</div> } | 
Unmi 注: 上面 fullName 就是用 @defining() 定义的 user.firstName +" " + user.lastName 的值,以后用 fullName 变量就行。
Import 语句
你可以在模板(或子模板)开始处引入你想要内容:
| 1 2 3 4 5 | @(customer: models.Customer, orders: Seq[models.Order]) @import utils._ ... | 
注释
你可以用 @* *@ 来写服务端的注释块:
| 1 2 3 | @*********************  * This is a comment *  *********************@ | 
你在第一行加上的注释会把当前模板记录到 Scala API 文档中去:
| 1 2 3 4 5 6 7 8 | @*************************************  * Home page.                        *  *                                   *  * @param msg The message to display *  *************************************@ @(msg: String) <h1>@msg</h1> | 
转义(Escaping)
默认时, 动态内容会按照模板类型(如 HTML 或 XML) 规则进行转义. 假如你想输出为原始内容, 就用模板的内容类型包裹一下.
例如要输出原始的 HTML:
| 1 2 3 | <p>   @Html(article.content)     </p> | 
Unmi 注: 比如说,如果 article.content 的内容为 "<div style="color:red">Hello</div>",直接写成 @article.content 将直接输出为:
<div style="color:red">Hello</div>, 也就是它内部把 < 转义成了 < > 变成了 >,即 HTML 源文件是 "< style="color:red">Hello</div>
而写成 @Html(article.content),输出的 HTML 源文件中还是 <div style="color:red">Hello</div>,页面中看到的效果将是:
Hello

