04-1.play模板标签详解

    xiaoxiao2021-04-12  39

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

    ${'&'}       经模板解析后,会默认转义输出“& ;”。如果使用了#{verbatim}标签: #{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 {   ...}       每个想要作为标签(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/}
    转载请注明原文地址: https://ju.6miu.com/read-667501.html

    最新回复(0)