playframework- 5.3.1 内置标签

5.3.1 内置标签#

      由于Play采用的是MVC模型,这就要求表现层从请求处理和数据存储中分离开来。但是静态页面的功能毕竟是有限的,不能满足复杂的显示的需要,Play对一些常用的模板标签进行了封装,不仅实现了表现层和控制层的业务分离,还大大提升了模板功能的重用性。

 

1. #{a}

      Play需要通过路由器(逆向)生成URL,以此来调用指定的Action。框架封装了HTML中的超链接标签<a>,可以方便地调用控制器中的Action,如:

#{a @Application.index()} 首页 #{/a}

      该实例使用了内置标签#{a},可以生成指向控制器Application中index方法的HTML链接元素。在模板执行时,以上代码会被会解析为如下HTML代码:

<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}标签,将标签体内的日期进行缓存。还可以为#{cache}标签添加for参数,设置缓存的过期时间:

#{cache 'currentTime', for:'3s'}
   ${new java.util.Date()}
#{/cache}

      上例为#{cache}标签添加了for参数,设置缓存的过期时间为3秒。

4.#{doLayout /}

      Play引入了模板继承机制,假设定义装饰模板main.html,代码如下:

<!-- common header here -->
<h1>Main template</h1>
 
<div id="content">
    #{doLayout /}
</div>
<!-- common footer here -->

      #{doLayout /}标签可以包含自身的页面内容,即表示在标签处可以插入子模板的内容。

5.#{extends}

      子模板可以通过#{extends}标签继承已经定义好的装饰模板。

#{extends 'main.html'/}

<h1>Some code</h1>

      当子模板使用#{extends}标签继承了main.html,就会包含父模板的内容。需要注意的是:在指定模板中别漏掉 #{doLayout /}标签声明。

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}

7.#{else},#{elseif}

      #{else}标签通常与#{if}标签配合使用:

#{if user}
    Connected user is ${user}
#{/if}
#{else}
    Please log in
#{/else}

      在Play的模板中,#{else}标签也可以与#{list}标签一起使用,当使用#{list }标签进行迭代的集合为空时,可以执行#{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>

      由于很多应用中表单的奇偶行的颜色是不同的,使用#{errors}标签的error_parity参数就可以很方便的解决这个问题,这样有利于用户查看表单,用户体验性更强。

 

 

 

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}标签以POST方式提交时,自带了令牌保护机制,也就是说#{form}标签包含了#{authenticityToken /}的功能:

#{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>

      上例代码中,直接在当前模板中导入了tree.html模板。

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>

      有关Ajax的具体用法,会在第10.12节进行详细介绍。

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}标签的具体使用方法如下:

#{option user.id} ${user.name} #{/option}

      在模板执行时,以上代码会被会解析为如下HTML代码:

<option value="42">jto</option>

      #{option}标签一般与#{select}标签配合使用。

 

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:'china@oopsplay.org'},就设置了模板变量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' /}

      #{script}标签有以下几类参数:

  • src(必须):设置脚本文件的地址,无需在地址前加上默认父目录/public/javascripts。
  • id(可选):设置生成的<script>元素的id。
  • charset(可选):设置脚本文件的字符编码,默认为UTF-8。

      如果只需要在#{script}标签中设置src的参数值,则可以省略src参数名,直接写上需要引入的JavaScript文件即可:

#{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转义功能。如果直接按照如下方式在模板中输出表达式:

${'&amp;'} 

      经模板解析后,会默认转义输出“&amp ;”。如果使用了#{verbatim}标签:

#{verbatim}${'&amp;'}#{/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 {
   ...
}

      每个想要作为标签(tag)运行的方法都具有固定的声明方式,具体如下:

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

      第二个Play内置标签例子是#{option},它要稍微复杂一点,因为它的实现依赖于父标签#{select}(Play也封装了#{select}标签,在play/framework/templates/tags/select.tag文件中可以查看具体定义)。

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/}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值