Twirl模板引擎介紹html
Twirl 是 Play 內置的模板引擎,負責數據層展現與用戶行爲收集。Twirl 被設計成一個獨立的模塊,能夠脫離 Play 環境單獨使用。Twirl 採用Scala做爲底層模板語言,因此你無需學習額外的語法即可以輕鬆上手。前端
Hello, Twirljava
建立文件views/hello.scala.html
,內容以下:git
@(name: String) <html> <body> <h1>Hello, @name!</h1> </body> </html>
每一個模板文件最終將會被編譯成一個同名函數,因此咱們也能夠稱模板文件爲模板函數。模板函數的內容包括兩部分,第一行爲函數參數聲明,其他部分爲函數體。對於上面定義的模板文件,編譯後生成的函數類型爲:github
(name: String) => Html
因爲編譯後的模板函數就是普通的 Scala 函數,因此你能夠在任何地方使用模板函數:sql
val content = views.html.hello("play")
跟常見的模板層引擎同樣,模板函數的函數體包含兩部份內容,一部分是靜態的HTML內容,另外一部分是動態的Scala表達式。靜態的HTML內容將會保持不變原樣輸出,而動態的 Scala 表達式部分將會插入動態生成的內容。 Twirl使用@
符號區分Scala表達式和HTML文本,即以@
符號開頭的部分是Scala表達式,其他部分即爲HTML內容。編程
咱們能夠經過@符號在函數體內引用參數:後端
<h1>Hello, @name!</h1>
配合()
和{}
能夠寫出更復雜的語句:api
<h1>Hello, @(user.firstName + user.lastName)!</h1> <h1>Hello, @{ customer.firstName customer.lastName }! </h1>
()
用於插入單行代碼,插入結果爲當前表達式的值;而{}
用於插入多行代碼,插入結果爲最後一行表達式的值。瀏覽器
因爲模板文件參與編譯過程,而且是類型安全的,因此編譯器會幫你攔住大部分錯誤。
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.
date
, sqlDate
: 映射爲 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