由於Play採用的是MVC模型,這就要求表現層從請求處理和數據存儲中分離開來。但是靜態頁面的功能畢竟是有限的,不能滿足複雜的顯示的需要,Play對一些常用的模板標籤進行了封裝,不僅實現了表現層和控制層的業務分離,還大大提升了模板功能的重用性。
1. #{a}
Play需要通過路由器(逆向)生成URL,以此來調用指定的Action。框架封裝了HTML中的超鏈接標籤<a>,可以方便地調用控制器中的Action,如:
#{a @Application.index()} 首頁 #{/a}
<a href="/application/index ">首頁</a>
2.#{authenticityToken /}
使用#{authenticityToken /}標籤可以防止跨站請求僞造,同時也能消除刷新提交和後退提交所帶來的困擾。#{authenticityToken /}首先會爲服務器和客戶端表單生成相同的隨機令牌,並以隱藏的input輸入域的形式嵌入表單。當用戶點擊submit按鈕提交時,令牌會伴隨着表單信息一併發送至控制器中的Action,在Action對提交的表單進行處理前,會先進行令牌的比對,只有令牌信息一致時,才進行後續的表單處理操作。在模板中可以直接使用#{authenticityToken /}標籤:
#{authenticityToken /}
當爲form表單添加了#{authenticityToken /}標籤後,HTML代碼將會被解析爲:
<input type="hidden" name="authenticityToken"
value="1c6d92fed96200347f06b7c5e1a3a28fa258ef7c">
3.#{cache}
Play的設計理念是無狀態架構,不建議開發人員記錄用戶的狀態。緩存是很好的解決方案,在開發中會被大量的用到,但緩存也有它自身的缺點,因爲緩存只是存在於內存中,適用於存放暫時性的數據,時間一到就會過期。框架對緩存進行了封裝,可以在模板中以標籤的形式對緩存進行操作:
#{cache 'startTime'}
${new java.util.Date()}
#{/cache}
#{cache 'currentTime', for:'3s'}
${new java.util.Date()}
#{/cache}
4.#{doLayout /}
Play引入了模板繼承機制,假設定義裝飾模板main.html,代碼如下:
<!-- common header here -->
<h1>Main template</h1>
<div id="content">
#{doLayout /}
</div>
<!-- common footer here -->
5.#{extends}
子模板可以通過#{extends}標籤繼承已經定義好的裝飾模板。
#{extends 'main.html'/}
<h1>Some code</h1>
6.#{if},#{ifnot},#{ifError},#{ifErrors}
在模板中使用#{if}標籤可以進行條件判斷:
#{if user.countryCode == 'en' }
Connected user is ${user}
#{/if}
同時,#{if}標籤還支持複雜的條件判斷:
#{if ( request.actionMethod == 'administer' && user.isAdmin() ) }
You are admin, allowed to administer.
#{/if}
#{ifnot}標籤顧名思義,就是ifnot的意思,其執行時具體的作用與#{if !condition}等價:
#{ifnot tasks}
No tasks today
#{/ifnot}
可能經常會遇到這種情況,經過控制器中Action的處理,向模板中注入驗證錯誤的提示消息。#{ifError}標籤可以判斷指定的作用域中是否有驗證錯誤的信息:
#{ifError 'user.name'}
<p>
User name is invalid:
#{error 'user.name' /}
<p>
#{/ifError}
只要模板中有任何的驗證錯誤信息,#{ifErrors}標籤都可以將其輸出:
#{ifErrors}
<p>Error(s) found!</p>
#{/ifErrors}
#{else}標籤通常與#{if}標籤配合使用:
#{if user}
Connected user is ${user}
#{/if}
#{else}
Please log in
#{/else}
#{list items:task, as:'task'}
<li>${task}</li>
#{/list}
#{else}
Nothing to do...
#{/else}
#{elseif}標籤的作用同樣是進行條件判斷,用法與#{else}標籤非常類似,也可以與#{list}標籤配合使用:
#{if tasks.size() > 1}
Busy tasklist
#{/if}
#{elseif tasks}
One task on the list
#{/elseif}
#{else}
Nothing to do
#{/else}
8. #{error},#{errorClass},#{errors}
#{error}標籤的作用是輸出驗證後的錯誤消息。如果指定字段的錯誤消息確實存在,則直接將其打印出來:
#{error 'user.name'/}
#{errorClass}標籤的作用是如果存在指定的驗證錯誤消息,則標籤在解析時將被替換爲文本hasError,以此可以用於設置出現錯誤後的HTML元素的樣式。
<input name="name" class="#{errorClass 'name'/}">
在模板執行時,以上代碼將會按照如下規則解析:
<input name="name" class="${errors.forKey('name') ? 'hasError' : ''}">
#{errors}標籤可以遍歷當前的驗證錯誤對象集合,其使用方式與 #{list}非常類似,循環體中使用的對象變量名稱是${error}:
<ul>
#{errors}
<li>${error}</li>
#{/errors}
</ul>
#{errors}的標籤體內參數變量有以下幾種:
- error:表示error對象。
- error_index:表示當前錯誤在集合中的序號。
- error_isLast:表示是否是集合中的最後一個錯誤。
- error_isFirst:表示是否是集合中的第一個錯誤。
- error_parity:表示當前錯誤在集合中序號的奇偶值,可能是even或odd。
<table>
<tr>
<th>#</th>
<th>Error</th>
</tr>
#{errors}
<tr class="${error_parity}">
<td>${error_index}</td>
<td>${error}</td>
</tr>
#{/errors}
</table>
9.#{field}
#{field}標籤的作用非常大,本着Play的“簡約”理念,#{field}標籤可以幫助開發者減少同一變量名的重複使用:
<p>
<label>&{'user.name'}</label>
<input type="text" id="user_name" name="user.name" value="${user.name}" class="${errors.forKey('user.name') ? 'has_error' : ''}">
<span class="error">${errors.forKey('user.name')}</span>
</p>
以上實例中,表單input輸入中需要大量用到user.name。大量重複操作不僅給開發人員帶來了不便,也使得頁面的代碼顯得非常混亂。使用#{field}標籤很大程度上可以緩解這個問題:
#{field 'user.name'}
<p>
<label>&{field.name}</label>
<input type="text" id="${field.id}" name="${field.name}" value="${field.value}" class="${field.errorClass}">
<span class="error">${field.error}</span>
</p>
#{/}
Play提倡在頁面開發中儘量使用#{field}標籤,而不是重複地編寫變量名。
10.#{form}
Play的#{form}標籤封裝了HTML中的<form>元素,在模板中插入#{form}標籤就等價於插入了表單元素。當在模板中使用了#{form}標籤時,Play會從路由配置中自動匹配已經定義好的HTTP方法:如果在路由配置中沒有定義HTTP方法類型,則默認以POST方式請求;如果在路由配置中GET與POST都定義了,則默認以最先定義的HTTP方法執行。#{form}標籤中有以下三種參數:
- method(可選):定義表單提交的HTTP方法類型,可以是POST或 GET。
- id(可選):用於定義表單的id屬性。
- enctype(可選):設置表單數據編碼方式,默認的編碼方式爲application/x-www-form-urlencoded。
Play中字符編碼方式只能是utf-8。
在Play模板中使用#{form}標籤的具體示例如下:
#{form @Client.details(), method:'GET', id:'detailsForm'}
...
#{/form}
在模板執行時,以上代碼會被會解析爲如下HTML代碼:
<form action="/client/details" id="detailsForm" method="GET"
accept-charset="utf-8">
...
</form>
也可以指定目標實體,作爲Action方法的一部分:
#{form @Client.details(client.id)}
...
#{/form}
假設在控制器中,按照如下定義details方法。該方法中聲明瞭String類型的參數,參數名爲clientId。之後HTTP請求參數的名稱會從聲明的Action方法中匹配:
public static void details(String clientId){
// ...
}
在模板代碼解析時,Play會自動生成帶clientId參數的URL地址:
<form action="/client/details?clientId=3442" method="GET"
accept-charset="utf-8">
...
</form>
#{form @Client.create(), method:'POST', id:'creationForm',
enctype:'multipart/form-data' }
...
#{/form}
在模板執行時,以上代碼會被會解析爲如下HTML代碼:
<form action="/client/create" id="creationForm" method="POST"
accept-charset="utf-8" enctype="multipart/form-data">
<input type="hidden" name="authenticityToken"
value="1c6d92fed96200347f06b7c5e1a3a28fa258ef7c">
...
</form>
11.#{i18n}
i18n在模板中的用法比較特殊,不僅可以直接以標籤的形式用於模板語言,也可以直接在JavaScript中調用。可以在JavaScript代碼中使用i18n()方法來訪問本地化消息文件,如i18n('app_title')。
首先,需要在conf/messages中進行國際化定義:
hello_world=Hello, World !
hello_someone=Hello %s !
之後,在模板中就可以插入#{i18n /}標籤來開啓國際化支持了:
#{i18n /}
在JavaScript中可以調用i18n()方法來進行國際化操作,通過conf/messages中的key獲取當前key的國際化值:
alert(i18n('hello_world'));
alert(i18n('hello_someone', 'John'));
還可以通過設置key的值來限制允許使用的國際化值,並且允許使用通配符來設置範圍:
#{i18n keys:['title', 'menu.*'] /}
12.#{include}
#{include}標籤的作用是在當前模板中導入另一個模板,並且當前模板中的所有變量對導入的模板透明。其具體使用方法如下:
<div id="tree">
#{include 'tree.html' /}
</div>
13.#{render}
#{render}標籤的作用是指定模板進行渲染。#{render}標籤的參數是指定渲染模板的路徑,可以是絕對路徑,也可以是相對於/app/views的路徑:
#{render 'Application/other.html'/}
14.#{jsAction}
#{jsAction}標籤會生成一個返回服務端Action的URL地址的JavaScript函數,它不會自動執行Ajax請求,需要通過返回的URL地址手動執行。具體的應用示例如下:
首先在conf/routes文件中定義路由:
GET /users/{id} Users.show
之後,就可以在客戶端導入定義好的路由:
<script type="text/javascript">
var showUserAction = #{jsAction @Users.show(':id') /}
var displayUserDetail = function(userId) {
$('userDetail').load( showUserAction({id: userId}) )
}
</script>
15.#{list}
#{list}標籤提供非常簡便的操作遍歷對象集合:
<ul>
#{list items:products, as:'product'}
<li>${product}</li>
#{/list}
</ul>
其中#{list items:products, as:'product'}用來遍歷對象集合products,循環體中的每個對象用變量product來引用。在循環體的代碼中可以引用一些預定義的變量,這些變量有固定的名稱,但是會以循環體中的對象變量名作爲前綴,在上面的例子中就是product_Xxx。#{list}標籤的預定義的變量有以下幾種:
- note_index:表示當前對象在集合中的序號。
- note_isFirst:表示是否是集合中的第一個對象。
- note_isLast:表示是否是集合中的最後一個對象。
- note_parity:表示當前對象在集合中序號的奇偶值,可能是even或odd。
<ul>
#{list items:products, as:'product'}
<span class="${product_parity}">${product_index}. ${product}</span>
${product_isLast ? '' : '-'}
#{/list}
</ul>
#{list}標籤中的items參數是可選的,也可以無需聲明參數類型直接寫參數,這樣可以簡化#{list}標籤的用法:
#{list products, as:'product'}
<li>${product}</li>
#{/list}
在#{list}標籤中可以使用Groovy中的range對象,用法與for循環非常類似:
#{list items:0..10, as:'i'}
${i}
#{/list}
#{list items:'a'..'z', as:'letter'}
${letter} ${letter_isLast ? '' : '|' }
#{/list}
#{list}標籤中as參數也不是必須的,可以用約定的下劃線符“ _ ”作爲默認的變量名:
#{list users}
<li>${_}</li>
#{/list}
16.#{option}
#{option}標籤是HTML中<option>的封裝,其作用是在當前模板位置中插入<option>元素。#{option}標籤的參數只有一個:
- value:選項的值。
#{option user.id} ${user.name} #{/option}
在模板執行時,以上代碼會被會解析爲如下HTML代碼:
<option value="42">jto</option>
17.#{select}
#{select}標籤是HTML中<select>的封裝,其作用是在當前模板位置中插入<select>元素。#{select}標籤有一個必選的參數:
- name(必須):設置select元素的name屬性。
其具體使用方法如下:
#{select 'booking.beds', value:2, id:'select1'}
#{option 1}One king-size bed#{/option}
#{option 2}Two double beds#{/option}
#{option 3}Three beds#{/option}
#{/select}
在模板執行時,以上代碼會被會解析爲如下HTML代碼:
<select name="booking.beds" size="1" id="select1" >
<option value="1">One king-size bed</option>
<option value="2" selected="selected">Two double beds</option>
<option value="3">Three beds</option>
</select>
在#{select}標籤中使用items屬性可以自動生成所需的option選擇項:
- items(可選):設置集合對象。
- value(可選):設置默認選中項。
- labelProperty(可選):設置每一項的lable值對應對象的哪個屬性。
- valueProperty(可選):設置每一項的value值對應對象的哪個屬性,默認爲對象的id屬性。
labelProperty和valueProperty屬性值不支持Java基本類型,需使用封裝類型如Integer或Long來代替int或long類型 。
以下代碼爲#{select}標籤中使用items屬性的例子。首先由控制器Action向頁面模板注入用戶集合List<User> users,且每一個用戶user都有name屬性,id是User的主鍵:
#{select 'users', items:users, valueProperty:'id', labelProperty:'name', value:5, class:'test', id:'select2' /}
在模板執行時,以上代碼會被會解析爲如下HTML代碼:
<select name="users" size="1" class="test" id="select2" >
<option value="0" >User-0</option>
<option value="1" >User-1</option>
<option value="2" >User-2</option>
<option value="3" >User-3</option>
<option value="4" >User-4</option>
<option value="5" selected="selected">User-5</option>
</select>
18.#{set},#{get}
#{set}標籤用於設置可以在模板中使用的變量。如#{set email:'[email protected]'},就設置了模板變量email的值,可以通過 #{get 'email'} 來獲取。#{set}標籤的定義中可以引用其他變量:
#{set title:'Profile of ' + user.login /}
還可以在#{set}標籤體內定義變量的值:
#{set 'title'}
Profile of ${user.login}
#{/set}
#{get}標籤的作用是獲取由#{set}標籤定義的值,在Play的模板中,通過get/set機制,可以使父模板和子模板間進行通信。
<head>
<title>#{get 'title' /}
</head>
可以在#{get}標籤體內設置當變量不存在時的缺省值,如下例當title變量不存在時,會顯示Homepage。
<head>
<title>#{get 'title'}Homepage #{/}
</head>
19.#{script}
#{script}標籤用於生成一個<script>元素,可以引入/public/javascripts目錄下的JavaScript文件。如 #{script 'jquery.js'}就引用了/public/javascripts/jquery.js 文件。
#{script src:'jquery-1.5.1.min.js ', id:'jquery' , charset:'utf-8' /}
- src(必須):設置腳本文件的地址,無需在地址前加上默認父目錄/public/javascripts。
- id(可選):設置生成的<script>元素的id。
- charset(可選):設置腳本文件的字符編碼,默認爲UTF-8。
#{script 'jquery-1.5.1.min.js' /}
20.#{stylesheet}
#{stylesheet}標籤的使用方法與#{script}類似,不同的是#{stylesheet}標籤使用<link>元素來引入/public/stylesheets目錄下的CSS文件。如#{stylesheet id:'main', media:'print', src:'print.css', title:'Print stylesheet' /}就引用了/public/stylesheets/print.js 文件。
#{stylesheet}標籤有以下幾類參數:
- src(必須):設置樣式文件的地址,無需在地址前加上默認父目錄/public/stylesheets。
- id(可選):設置生成的<link>標籤的id。
- media(可選):設置 media 屬性: screen,print,aural,projection……
- title(可選):設置標籤title屬性。
如果只需要在#{stylesheet}標籤中設置src的參數值,則可以省略src參數名,直接寫上需要引入的CSS文件即可:
#{stylesheet 'default.css' /}
21.#{verbatim}
使用#{verbatim}標籤可以禁用模板中的HTML轉義功能。如果直接按照如下方式在模板中輸出表達式:
${'&'}
#{verbatim}${'&'}#{/verbatim}
5.3.2 自定義tag#
Play提供了模板標籤的自定義功能,可以非常方便地爲應用創建特定的標籤。這不僅提高了代碼重用性,也使模板中的代碼更加整潔。在這裏提到的標籤,指的就是簡單的模板文件(存放在app/views/tags目錄中),在使用時標籤的名稱需要與模板的文件名保持一致。具體使用方法如下。在app/views/tags目錄中新建hello.html文件,即成功創建了hello標籤,之後編輯hello.html中的內容:
Hello from tag!
並沒有進行任何配置,就完成了標籤的自定義,之後就可以在其他模板中直接使用該標籤了:
#{hello /}
獲取標籤參數
在標籤中可以包含參數,框架會自動將參數傳遞給模板變量。在使用中,標籤的參數名與模板變量名並不是完全一致的,模板變量聲明時需要增加下劃線_。例如,在hello.html標籤模板文件中進行如下聲明:
Hello ${_name}!
在其他模板中,就可以通過如下語句將name參數傳遞給標籤了:
#{hello name:'Bob' /}
如果標籤中只有一個參數,就可以採用默認的參數名稱arg。由於該名稱是內置的,在使用時可以不顯式地寫出參數名。例如,在hello.html標籤模板文件中聲明:
Hello ${_arg}!
在其他模板中,簡單地使用如下語句就可以將Bob傳遞給標籤:
#{hello 'Bob' /}
調用標籤體
起始標籤和結束標籤之間包含的內容稱爲標籤體,標籤體可以是文本,HTML等。在Play中調用#{doBody /}標籤可以很方便地完成以上內容。例如,在hello.html標籤模板文件中聲明:
Hello #{doBody /}!
之後Ben將會作爲標籤體進行傳遞:
#{hello}
Ben
#{/hello}
自動選擇最適標籤
根據content type設置,不同的頁面可能會由不同的格式進行渲染(HTML,XML,JSON等)。這一點在Play中完全不用擔心,框架會選取最適合的形式來顯示。例如,當請求希望以HTML格式渲染時,Play會自動調用app/views/tags/hello.html模板;當請求希望以XML格式渲染時,Play則會選擇app/views/tags/hello.xml模板。
在Play中可以定義後綴爲.tag的通用模板標籤,但他的優先級是最低的。只有當規定的模板標籤格式是無效時,纔會調用它。例如,在程序中只定義了兩個模板標籤,分別是app/views/tags/hello.xml和app/views/tags/hello.tag,當請求希望以HTML格式渲染時,框架會優先尋找hello.html模板,當發現該文件不存在時,纔會調用hello.tag模板。
5.3.3 自定義Play標籤#
也可以在Java代碼中自定義標籤,封裝一些更爲複雜的功能。如果希望自定義Java標籤,需要先創建繼承於play.templates.FastTags的類。package utils;
public class MyFastTag extends FastTags {
...
}
public static void _tagName(Map<?, ?> args, Closure body, PrintWriter out,
ExecutableTemplate template, int fromLine)
別漏了標籤名前面的下劃線。
爲了進一步瞭解如何創建標籤,先來看看兩個Play內置標籤的例子(Play封裝了這兩個標籤,在play/framework/src/play/templates/FastTags.java文件中可以查看)。
#{verbatim}標籤只是簡單的調用了從JavaExtensions傳遞過來的toString()方法,直接在模板上打印了標籤體中的內容:
public static void _verbatim(Map<?, ?> args, Closure body, PrintWriter out,
ExecutableTemplate template, int fromLine) {
out.println(JavaExtensions.toString(body));
}
該標籤的具體使用方法如下,從標籤的開始到結束都是標籤體內容:
#{verbatim}My verbatim #{/verbatim}
模板中將會打印出標籤體內容:
My verbatim
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>");
}
該Java標籤的原理是封裝了HTML的<option>標籤,用於實現下拉列表實例,並且從父標籤#{select}中獲取被默認選中(selected)的值。方法體最開始的三行用於設定輸出的變量,最後的三行則輸出了標籤的結果。該標籤的具體使用方法如下:
#{select name:"country",value:"中國"}
#{option arg:"英國"}英國#{/option}
#{option arg:"中國"}中國#{/option}
#{option arg:"美國"}美國#{/option}
#{option arg:"日本"}日本#{/option}
#{/select}
這裏需要對父標籤#{select}的使用做一些簡單說明:country是#{select}標籤的name屬性,是必選的;中國是#{select}標籤的value屬性,用於標識selected元素,是可選的。
標籤的命名空間
實際項目中可能會自定義大量的標籤,標籤命名不規範可能會造成一些不必要的麻煩(比如與已定義的標籤重名,或是與Play框架的核心標籤衝突)。爲了解決這個問題,Play可以使用@FastTags.Namespace註解來設定命名空間。對於my.tags命名空間中的#{hello}標籤需要做以下工作:
@FastTags.Namespace("my.tags")
public class MyFastTag extends FastTags {
public static void _hello (Map<?, ?> args, Closure body, PrintWriter out,
ExecutableTemplate template, int fromLine) {
...
}
}
然後在模板中可以通過以下方式來調用#{hello}標籤:
#{my.tags.hello/}