首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Play For Scala 开发指南 - 第8章 用户界面

Play For Scala 开发指南 - 第8章 用户界面

作者头像
joymufeng
发布2019-03-12 16:05:24
1.4K0
发布2019-03-12 16:05:24
举报

Twirl模板引擎介绍

Twirl 是 Play 内置的模板引擎,负责数据层展示与用户行为收集。Twirl 被设计成一个独立的模块,可以脱离 Play 环境单独使用。Twirl 采用Scala作为底层模板语言,所以你无需学习额外的语法便可以轻松上手。

Hello, Twirl

创建文件views/hello.scala.html,内容如下:

@(name: String)

<html>
 <body>
  <h1>Hello, @name!</h1>
 </body>
</html>

每个模板文件最终将会被编译成一个同名函数,所以我们也可以称模板文件为模板函数。模板函数的内容包括两部分,第一行为函数参数声明,其余部分为函数体。对于上面定义的模板文件,编译后生成的函数类型为:

(name: String) => Html

由于编译后的模板函数就是普通的 Scala 函数,所以你可以在任何地方使用模板函数:

val content = views.html.hello("play")

跟常见的模板层引擎一样,模板函数的函数体包含两部分内容,一部分是静态的HTML内容,另一部分是动态的Scala表达式。静态的HTML内容将会保持不变原样输出,而动态的 Scala 表达式部分将会插入动态生成的内容。 Twirl使用@符号区分Scala表达式和HTML文本,即以@符号开头的部分是Scala表达式,其余部分即为HTML内容。

我们可以通过@符号在函数体内引用参数:

<h1>Hello, @name!</h1>

配合(){}可以写出更复杂的语句:

<h1>Hello, @(user.firstName + user.lastName)!</h1>
<h1>Hello, @{
             customer.firstName
             customer.lastName
           }!
</h1&gt

()用于插入单行代码,插入结果为当前表达式的值;而{}用于插入多行代码,插入结果为最后一行表达式的值。

由于模板文件参与编译过程,并且是类型安全的,所以编译器会帮你拦住大部分错误。

Twirl是无状态的

JSP或是其它的第三方模板引擎都会有一个上下文(Context)的概念,上下文中保存着当前请求的状态。而在Twirl中则没有上下文的概念,模板函数仅仅是一个普通的函数,没有复杂的上下文状态存在,这种无状态的设计更加简洁并易于理解,不仅方便测试,而且大大提升了模板层的可用性,我们不仅可以在 Controller 层使用模板页面,在 Service 层一样可以使用。例如可以利用Twirl编写一个邮件模板,或者是利用Twirl生成静态Html文件等等。

大家可能觉得奇怪,没有了上下文,在模板中如何获取当前的请求呢?答案很简单:通过参数传递喽!利用Scala的隐式参数的特性,在调用模板函数时不需要显示传入,编译器会自动传入。

Twirl基本语法

下面介绍几个常用的Scala表达式,方便你快速熟悉Twirl语法。

@if表达式用于控制某部分HTML内容是否显示:

@if(user.isMale) {
  <h1>你好, @{user.name}先生</h1>
} else {
  <h1>你好, @{user.name}小姐</h1>
}

@for表达式用于重复显示HTML内容:

<ul>
@for(u <- users) {
  <li>@{user.name}</li>
}
</ul>

对于通用逻辑可以定义为可复用函数:

@display(product: Product) = {
  @product.name ($@product.price)
}

<ul>
@for(product <- products) {
  @display(product)
}
</ul>

@defining用于定义可重用的值:

@defining(user.firstName + " " + user.lastName) { fullName =>
  <div>你好 @{fullName}</div>
}

使用函数也可以实现可重用值,并且更加简洁:

@fullName = @{user.firstName + " " + user.lastName}
<div>你好 @{fullName}</div>

@import用于引入外部依赖:

@(user: User)

@import utils._
...

通过@**@可以插入一段注释:

@*********************
* This is a comment  *
*********************@

@Html用于展示原始字符串内容,避免转义,通常用于输出HTML文本或Json格式内容:

@Html(htmlContent)

页面布局

通常我们会创建一个views/main.scala.html文件用于控制页面的整体布局:

@(title: String)(content: Html)

<!DOCTYPE html>
<html>
  <head>
    <title>@title</title>
  </head>
  <body>
    <section class="content">@content</section>
  </body>
</html>

main模板接受两个参数,一个是页面标题title,另一个是页面正文content。然后我们就可以在views/index.scala.html模板中复用这个布局:

@(title: String)

@main(title) {
  <h1>欢迎光临!</h1>
}

处理表单

用户在浏览器端通过Html表单填充业务数据并提交至服务器端进行处理,与之对应的,Play 在服务器端提供了 Form 类用于处理与Html表单相关的操作:

  • 数据绑定
  • 数据校验
  • 数据抽取
  • 错误处理
  • 页面渲染

在使用 Play 的 Form 相关功能之前,需要先导入如下路径:

import play.api.data._
import play.api.data.Forms._
import play.api.data.validation.Constraints._

数据绑定

数据绑定是指将用户输入的表单数据绑定到 Form 对象的过程,例如下面定义一个用于接收用户登录邮箱和密码的 Form 实例:

val loginForm = Form(tuple("email" -> text, "password" -> text))

利用 Form.bindFromRequest() 方法可以从当前的请求体中绑定表单参数:

val bindForm = userForm.bindFromRequest() match {
  case Some(v) => println("绑定成功")
  case _       => println("绑定失败")
}

数据校验

下面我们为表单参数添加如下约束:

  • email参数必填,且格式必须为邮箱
  • password参数必填,且内容必须为非空
val loginForm = Form(tuple("email" -> email, "password" -> nonEmptyText))

此时在使用 Form.bindFromRequest() 方法从当前的请求体中绑定表单参数时,只有当所有的表单参数均满足约束条件才能绑定成功,否则绑定失败:

val bindForm = userForm.bindFromRequest() match {
  case Some(v) => println("绑定成功")
  case _       => println("绑定失败")
}

常用的约束如下:

  • text: 映射为 scala.String 类型, 可以使用 minLength 和 maxLength 参数限定长度。
  • nonEmptyText: 映射为非空的 scala.String 类型, 可以使用 minLength 和 maxLength 参数限定长度。
  • number: 映射为 scala.Int 类型,可选参数: min, max, 和 strict。
  • longNumber: 映射为 scala.Long 类型, 可选参数: min, max, 和 strict。
  • bigDecimal: 映射为 scala.math.BigDecimal 类型,可选参数:precision 和 scale.
  • datesqlDate: 映射为 java.util.Date, java.sql.Date 类型,可选参数:pattern 和 timeZone.
  • email: 映射为邮箱格式的 scala.String 类型。
  • boolean: 映射为 scala.Boolean。
  • checked: 映射为 scala.Boolean。
  • optional: 映射为 scala.Option。

除了上面的内置约束,我们可以针对每个表单项编写更精确的自定义约束,例如:

val userForm = Form(
  tuple(
    "email" -> text.verifying(_ == "user@playscala.cn"), 
    "name" -> text.verifying(_ == "user")
  )
)

我们也可以针对整个 Form 编写自定义约束:

  val userForm = Form(
    tuple(
      "email" -> email,
      "name" -> nonEmptyText
    ) verifying("邮箱名和用户名不匹配!", t => t._1.contains(t._2))
  )

数据抽取

当执行了数据绑定,并且成功地通过了数据校验,我们就可以从 Form 中抽取业务数据了:

loginForm.bindFromRequest().fold(
  formWithErrors => {
    //绑定失败,formWithErrors 包含了详细的错误信息
    BadRequest(views.html.login(formWithErrors))
  }, tuple => {
    //利用模式匹配取出业务数据
    val (email, password) = tuple
    Redirect(routes.Application.home(email))
  }
)

在上面的示例中,我们从 Form 中抽取的结果类型为Tuple,但是当表单项比较多时使用Tuple类型就不太合适了。针对上面的示例,我们稍作改动便可以将抽取的结果类型变为 Case Class:

case class UserData(email: String, name: String)
  
val userForm = Form(
  mapping(
    "email" -> email,
    "name" -> nonEmptyText
  )(UserData.apply)(UserData.unapply)
)

错误处理

当数据校验未通过时,我们将会得到一个包含错误信息的 formWithErrors 对象,通过调用 Form.errors 方法可以获取所有错误列表:

val allErrors: Seq[FormError] = formWithErrors.errors

每个 FormError 包含如下信息:

  • key 如果key为空则为全局错误,否则为表单字段错误且和表单字段同名。
  • message 错误消息提示或错误消息对应的key。
  • args 用于填充错误消息的参数。

Form.globalErrors包含在Form.errors中,其key值为空,无对应的表单项。通常为 Form 级的自定义校验错误。

如果表单校验发生错误,我们可以直接把错误信息以Json格式写回客户端:

loginForm.bindFromRequest().fold(
  formWithErrors => {
    //绑定失败,写回错误信息
    Ok(Json.obj("status" -> 1, "errors" -> formWithErrors.errorsAsJson))
  }, tuple => {
    //绑定成功
    Ok(Json.obj("status" -> 0))
  }
)

页面渲染

我们可以直接将 Form 对象作为模板参数传递到模板层,Play 专门为模板层提供了一个工具包(views.html.helper._)用于处理表单操作。除了上文的 formWithErrors 对象,  我们也可以将业务数据填充到 Form 实例中,然后传递给模板页面进行渲染:

val userForm = Form(tuple("email" -> email, "name" -> nonEmptyText))
Ok(views.html.editUser(userForm.fill(("user@playscala.cn", "user"))))

在editUser.scala.html 模板文件中,我们可以很方便地将 userForm 中的数据渲染成 HTML 表单:

@(userForm: Form[(String, String)])

@helper.form(action = routes.Application.doEditUser()) {
  @helper.inputText(userForm("email"))
  @helper.inputText(userForm("name"))
}

利用 helper 工具包在模板层渲染表单时,对前端页面设计有较强的侵入性,严重影响了前后端分离开发,所以在实际开发中不建议使用 helper 工具包,而是直接编写 Html 代码:

@(userForm: Form[(String, String)])

<form action="@routes.Application.doEditUser()" method="Post">
  <input name="email" value="@userForm("email").value">
  <input name="name" value="@userForm("name").value">
</form>

更进一步,模板层参数中也不应该出现 Form 类型参数,前端通过异步方式获取表单校验或提交的结果。当用户再次提交模板层渲染出的表单时,表单参数传至服务器端,重新执行校验、绑定和抽取等步骤,整个处理过程形成了一个闭环。

关于模板层 helper 的详细内容请参考官方文档

小结

Twirl 模板引擎使用 Scala 编程语言作为其底层的模板语法,利用无状态的函数式设计,为开发者带来了非常不错的开发体验。由于 Twirl 优秀的设计,即使在前后端分离的主流开发形势下,仍然发挥着不可替代的作用。

转载请注明 joymufeng

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018/10/04 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档