6.3 使用HTML标签
与通用标签不同,HTML标签不过多提供控制结构或逻辑。而是着重于如何使用action类、value stack或者Data Tags中的数据,并且在HTML中呈现出来。如果说普通标签只是简单地做些输出结果(如果有内容),HTML标签的输出则是因模板(template)而异,它们常常组合在一起作为一个主题(theme),做实际的渲染输出HTML的工作。关于模板和主题将在以后的章节详细介绍。
Struts2竭力支持开发者所偏爱的技术,这也是Struts2没有绑定于某一种特定的模板语言的原因。Struts2支持几乎所有应用广泛的模板语言甚至还为新语言提供了接口。默认情况下,几乎每一个标签都支持JSP、Velocity和FreeMarker。
注意
Struts2中默认的模板语言是FreeMarker,而不像其前身WebWork中默认的是Velocity。关于两者的优劣有很多评论,一般的说法是FreeMarker提供的错误信息更多,更加容易调试。
6.3.1 模板和主题
Struts提供的html标签的核心是主题(theme)和模板(template)。首先明确一下3个概念:
1)tag(标签):小段代码,在JSP、FreeMarker或者Velocity里执行。
2)template(模板):一个文件,通常使用JSP或者FreeMarker编写,能被特定的标签(HTML tags)输出。
3)theme(主题):一系列templates打包在一起,提供通用的功能。
Struts2提供了4种主题可以选择:
1)simple主题:一个最小的主题,包含了最少的辅助功能。使用simple主题的缺点就是它不支持其他主题那么多的属性。例如label属性在simple主题里没有任何用处。类似地,simple主题提供的功能也远远少于xhtml和ajax主题,自动显示错误信息就不被支持。
2)xhtml主题:使用了通用的HTML实践的默认主题。
3)css_xhtml主题:使用严格的CSS布局对xhtml主题重新实现。
4)ajax主题:一个基于xhtml主题的主题,提供了高级AJAX特性。
关于主题有两项配置,它们定义在struts.properties文件中,如表6.18所示。
表6.18 主题的配置
注意
使用simple主题的缺点就是它不支持其他主题那么多的属性。例如,label属性在simple主题里没有任何用处。类似地,simple主题提供的功能也远远少于xhtml和ajax主题提供的:自动显示错误信息就不被支持。
6.3.2 通用属性
每个UI标签都有一些共有的通用属性。除了那些处理label的属性,其他属性都可以在simple和xhtml主题上使用。表6.19列出了UI标签所有的通用属性。
表6.19 UI标签所有的通用属性
如果没有设定ID属性,那么Struts2表单标签会自动设置一个ID。form标签的ID就是其action的名字,如“updatePerson”。对于Form内的表单元素ID被设定为所在form的ID+"_"+元素的name属性,如“updatePerson_username”。
除了表6.19中的通用属性之外,所有的UI标签也支持通常设置的JavaScript事件。这允许简单的JavaScript集成并让表单更具有交互性,见表6.20。
表6.20 UI标签支持的JavaScript事件
所有的UI标签还有一项特性很有趣,也很有用,那就是“name-value联动”。通常在构建一个表单时输入数据的字段也是从它获取数据的字段,并会把它显示在表单中(通过value属性赋值)。常见的就是在修改属性的表单中,如下代码:
<s:form action="updateUser"> <s:textfield name="user.firstName" value="%{user.firstName}"/> </s:form>
Struts2提供一个简单的方式,可以自动设置value的值,无须以显式的方式给value赋值了。如下代码所示:
<s:form action="updateUser"> <s:textfield name="user.firstName“/> </s:form>
当然,有时不需要默认的值在表单中作为这个字段的值,可以给value属性赋其他值。
6.3.3 表单标签介绍
在HTML中的表单元素在Struts2中都提供了对应的标签来支持,见表6.21所示。
表6.21 表单标签成员列表
表单标签成员众多,但使用方法都比较类似。本书只介绍几种典型的标签。
1. form标签
form标签是所有UI标签中独一无二的,因为它担当了容器的角色。它有一个起点(<s:form>)和一个终点(</s:form>)。在simple主题里,它只输出html的form元素,而在xhtml主题中,它除了form元素之外还输出周围的表格。form标签的属性如表6.22所示。
表6.22 form标签属性表
要注意最重要的两个属性是action和namespace。它们组合起来,用来把form链接到一个特殊的action。例如,如下代码会被提交到/secure/updateProfile.action:
<s:form action="updateProfile" namespace="/secure">
2. textfield标签
textfield标签是最常用的一个UI标签,输出一个text类型的HTML input元素,在前面的章节中已经多次使用。textfield标签的属性如表6.23所示。
表6.23 textfield标签属性表
3. textarea标签
textarea标签用来填写比textfield标签更大数量的文本(包括换行符),不像textfield和password标签那样输出HTML<input>标记,这个标签输出HTML<textarea>标记。textarea标签的参数如表6.24所示。
表6.24 textarea标签参数表
4. checkbox标签
不像其他标签,checkbox标签不把字段的value当成字符串类型。也就是说,复选框必须有一个字段为布尔量来求值或者能被转化为布尔类型。
<s:checkbox label=" BOX" name=user.address.poBox fieldValue="true'>
注意
复选框和其他的标签有点不同。HTML的规范方式要求只有当复选框被选中的时候才会提交对应的值。如果复选框没有被选中,那么提交的参数中就根本不包含这个项目。因此接受参数的model设计时,应该将默认值定为false,很可能是model此字段没有被赋值。这样做可以保证表单提交时不论复选框状态为何,都会得到期望的值。
5. select标签
创建一个HTML Select列表组件。select标签的参数如表6.25所示。
表6.25 select标签参数表
最重要的属性是list,它告诉标签要选择的选项从哪里来。在最简单的表单里,列表呈现给用户,选择的值会被提交到表单并被映射到指定的字段,也是与名字对应的属性。如果一个值被预先设置,那么列表里这个字段具有相同值的选项就会自动被选中。
<s:select label="State" name="user.address.state" list="{'江西','湖北'}">
在这个例子中,表达式user.address.state的值会被设置为江西或者湖北。如果填入的值是这两个值中的一个,select标签就会设置表单。输出的HTML可能是
<select> <option>'江西'<option> <option>'湖北'<option> </select>
很多时候可能会希望option的显示值和实际值是不同的。比如:
<select> <option value=1>'江西'<option> <option value=2>'湖北'<option> </select>
这需要给select标签设置listKey和listValue属性就可以。listKey和listValue特性是通过迭代这个列表并在循环中把对象放到stack顶部的方式进行的。这也是iterator标签使用的相同的方式。如果使用的是一个map作为select标签进行迭代的列表,对象会通过Map.Entry进行迭代,就像iterator标签一样。因为Map.Entry提供了getKey()和getValue()方法,这些通常被用来作为listKey和listValue的值。使用基于Map的方法来重新编写state下拉框:
<s:select label="State" name="user.address.state" list="#{1:'江西', 2:'湖北'}">
6.3.4 非表单标签
非表单标签与表单标签相反,是为输出一些表单以外的HTML元素设计的,主要用于显示而非提交参数。它们也会带来很多方便,如table自动排序等。非表单标签成员如表6.26所示。
表6.26 非表单标签成员列表
需要解释一下的是component标签,使用特定的模板输出一个自定义的UI widget(组件)。附加的对象可以通过param标签传递给模板,设置的对象可以在模板里面通过 $parameters.paramname获取。如在component(组件)内部,它们可以通过$parameters.get('key1') 和 $parameters.get('key2') 的方式被访问,也允许通过$parameters.key1和$parameters.key2来引用它们。
component标签提供了一种建立自定义UI标签的方式。例如,假设想要一个包含3项的复选框来表示on/off/none,可以使用HTML来编写一个,用JavaScript和一些定制的图像来代表3个状态。
6.3.5 标签实例
首先看一下需要表现的页面,是一个用户信息修改提交页面,如图6.11所示。
图6.11 标签举例界面图
如果用普通JSP表现,如实例6-14所示,在firstname字段后面有关于错误信息的处理,如果有错误信息,就在这个字段后面打印错误日志。
【实例6-14】标签实例:updateProfile.jsp
01 <%@ page 02 import="org.hibernate.auction.model.User,org.hibernate.auction.model.Address, java.util.Map,java.util.Collections"%> 03 <% 04 User user = (User) request.getAttribute("user"); 05 Map fieldErrors = (Map) request.getAttribute("fieldErrors"); 06 if (fieldErrors == null) { 07 fieldErrors = Collections.EMPTY_MAP; 08 } 09 %> 10 <html> 11 <head> 12 <title><s:text name="title" />UpdateProfile</title> 13 </head> 14 <body> 15 <form action="updateProfile.action" method="post"> 16 <table> 17 <% if (fieldErrors.containsKey("user.firstname")) {%> 18 <tr> 19 <td align="center" valign="top" colspan="2"> 2 0 <span class="errorMessage"> <%= fieldErrors.get("user.firstname")%> 21 </span> 22 </td> 23 </tr> 24 <% }%> 25 <tr> 26 <td align="right"> 27 <label> 28 First name£o 29 </label> 30 </td> 31 <td> 32 <input type="text" name="user.firstname" 33 value="<%= user.getFirstname() %>" /> 34 </td> 35 </tr> 36 <tr> 37 <td align="right"> 38 <label> 39 Last name£o 40 </label> 41 </td> 42 <td> 43 <input type="text" name="user.lastname" 44 value="<%= user.getLastname() %>" /> 45 </td> 46 </tr> 47 <tr> 48 <td align="right"> 49 <label> 50 Email£o 51 </label> 52 </td> 53 <td> 54 <input type="text" name="user.email" 55 value="<%= user.getEmail() %>" /> 56 </td> 57 </tr> 58 <tr> 59 <td align="right"> 60 <label> 61 Gender£o 62 </label> 63 </td> 64 <td> 6 5 <input type="radio" name="user.gender" value="0" id="user.gender0" 6 6 <% if (user.getGender() == 0) { %> checked="checked" <% } %> /> 67 <label for="user.gender0"> 68 Male 69 </label> 7 0 <input type="radio" name="user.gender" value="1" id="user.gender1" 7 1 <% if (user.getGender() == 1) { %> checked="checked" <% } %> /> 72 <label for="user.gender1"> 73 Female 74 </label> 75 </td> 76 </tr> 77 <% 78 Address address = user.getAddress(); 79 boolean nullAddress = address == null; 80 %> 81 <tr> 82 <td align="right"> 83 <label> 84 Street Address: 85 </label> 86 </td> 87 <td> 88 <input type="text" name="user.address.street" 89 value="<%= !nullAddress ? address.getStreet() £o ""%>" /> 90 </td> 91 </tr> 92 <tr> 93 <td align="right"> 94 <label> 95 Zip Code£o 96 </label> 97 </td> 98 <td> 99 <input type="text" name="user.address.zipcode" 100 value="<%= !nullAddress ?address.getZipcode() £o ""%>" /> 101 </td> 102 </tr> 103 <tr> 104 <td align="right"> 105 <label> 106 City£o 107 </label> 108 </td> 109 <td> 110 <input type="text" name="user.address.city" 111 value="<%= !nullAddress ?address.getCity() £o ""%>" /> 112 </td> 113 </tr> 114 <tr> 115 <td align="right"> 116 <label> 117 State£o 118 </label> 119 </td> 120 <td> 121 <select name="user.address.state"> 122 <option value="Californa" 123 <% if (!nullAddress && 124 "California".equals(address.getState())) { %> 125 selected="selected" <% } %>> 126 Californa 127 </option> 128 <option value="Oregon" 129 <% if (!nullAddress && 130 "Oregon".equals(address.getState())) { %> 131 selected="selected" <% } %>> 132 Oregon 133 </option> 134 </select> 135 </td> 136 </tr> 137 <tr> 138 <td align="right"> 139 <label> 140 Country£o 141 </label> 142 </td> 143 <td> 144 <select name="user.address.country"> 145 <option value="USA" 146 <% if (!nullAddress && "USA".equals(address.getCountry())) { %> 147 selected="selected" <% } %>> 148 USA 149 </option> 150 <option value="Canada" 151 <% if (!nullAddress &&"Canada".equals(address.getCountry())) { %> 152 selected="selected" <% } %>> 153 Canada 154 </option> 155 <option value="Mexico" 156 <% if (!nullAddress &&"Mexico".equals(address.getCountry())) { %> 157 selected="selected" <% } %>> 158 Mexico 159 </option> 160 <option value="Other" 161 <% if (!nullAddress &&"Other".equals(address.getCountry())) { %> 162 selected="selected" <% } %>> 163 Other 164 </option> 165 </select> 166 </td> 167 </tr> 168 <tr> 169 <td colspan="2"> 170 <table> 171 <tr> 172 <td valign="middle"> 173 <input type="checkbox" name="user.address.poBox" value="true" 174 <% if (!nullAddress && address.isPoBox()) { %> 175 checked="checked" <% } %> /> 176 </td> 177 <td valign="middle" style="width:100%"> 178 <label class="checkboxLabel"> 179 P.O. Box 180 </label> 181 </td> 182 </tr> 183 </table> 184 </td> 185 </tr> 186 <tr> 187 <td colspan="2"> 188 <div align="'right'"> 189 <input value="Update Profile" type="submit" /> 190 </div> 191 </td> 192 </tr> 193 </table> 194 </form> 195 </body> 196 </html>
【代码剖析】在上述代码中,如果改为由Struts2的标签实现,那么该代码里190多行的代码只剩下30行就可以完成,Strus2的标签威力可见一斑。
通过使用Struts2标签不仅输入与Java值对象的关系是自动完成的,而且控件的布局(在xhtml主题中对form及其中的控件都使用table做了自动布局等渲染工作,在sample主题中不包含自动布局功能。请参考6.5.3节中的介绍)、错误信息的处理都是由标签完成,用户无须再干预,具体内容如实例6-15所示。
【实例6-15】标签实例:updateProfile1.jsp
01 <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> 02 <%@ page import="java.util.Map,java.util.Collections"%> 03 <%@ taglib prefix="s" uri="/struts-tags"%> 04 <html> 05 <head> 06 <title>UpdateProfile</title> 07 <s:head /> 08 </head> 09 <body> 10 <!--使用标签实现JSP--> 11 <s:form action="updateProfile" method="post"> 12 <s:textfield label="First name" name="user.firstname" 13 cssStyle="float:left; color:red" /> 14 <s:textfield label="Last name" name="user.lastname" /> 15 <s:textfield label="Email" name="user.email" /> 16 <s:radio label="Gender" name="user.gender" 17 list="#{0 : 'Male', 1 : 'Female'}" /> 18 <s:textfield label="Street" name="user.address.street" /> 19 <s:textfield label="Zip Code" name="user.address.zipcode" /> 20 <s:textfield label="City" name="user.address.city" /> 21 <s:select label="State" name="user.address.state" 22 list="{'Californa', 'Oregon'}" /> 23 <s:select label="Country" name="user.address.country" 24 list="{'USA', 'Canada', 'Mexico', 'Other'}" /> 25 <s:checkbox label="P.O. Box" name="user.address.poBox" 26 fieldValue="true" /> 27 <s:submit value="Update Profile" /> 28 </s:form> 29 <s:set name="user" value="user" scope="request" /> 30 <s:set name="fieldErrors" value="fieldErrors" scope="request" /> 31 </body> 32 </html>
【代码剖析】由于Struts2会自动增加控件的样式所有,如果想自己定义空间的样式不能再使用class属性,要使用Struts2单独提供的cssClass、cssStyle属性。这两个属性在通用属性中已经有过介绍,是所有控件都具有的属性。
图6.12 OGNL对象范围