从2.0版本开始,如果使用JSP和Spring Web MVC的话,Spring提供了一套支持数据绑定的标签集合,用于处理表单元素。 每个标签所支持的属性集合和与其对应的HTML标签相同,这就让这些标签看起来很熟悉,而且用起来很直观。 由这些标签库生成的HTML页面符合HTML 4.01/XHTML 1.0标准。
与其它表单/输入标签库不同,Spring的表单标签库与Spring Web MVC集成在一起, 使标签可以使用命令对象(command object)和你的控制器处理的参考数据(reference data)。 就像下面这些例子展示的一样,表单标签让JSP更易于开发、阅读和维护。
让我们进入表单标签的领域,并通过一个例子研究每个标签如何使用。 当某些标签需要更进一步的解释时,我们已经把所生成的HTML片段也一起列了进来。
Spring的表单标签库包含在spring.jar
中。 这个库的描述符(descriptor)叫做spring-form.tld
。
要使用这个库中的标签,在JSP页面的开头加入下面声明:
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
在上面的声明中,form
就是为想要使用的这个库中的标签所确定的标签命名前缀。
这个标签会生成一个HTML的“form”标签,同时为内部标签的绑定暴露了一个绑定路径(binding path)。 它把命令对象(command object)放在PageContext
中,这样内部的标签就可以访问这个命令对象了。 这个库中的其他标签都是form
标签的嵌套标签。
假设我们有一个叫做User
的领域对象,它是一个JavaBean, 拥有诸如firstName
和lastName
这样的属性。 我们将把它当作返回form.jsp
的表单控制器的表单支持对象(form backing object)。 下面是一个form.jsp
可能的样子的例子:
<form:form> <table> <tr> <td>First Name:</td> <td><form:input path="firstName" /></td> </tr> <tr> <td>Last Name:</td> <td><form:input path="lastName" /></td> </tr> <tr> <td colspan="2"> <input type="submit" value="Save Changes" /> </td> </tr> </table> </form:form>
上面firstName
和lastName
的值是从由页面控制器放置在PageContext
中的命令对象中得到的。 请继续读下去来看几个关于如何使用form
标签的内部标签的例子。
生成的HTML看起来就是一个标准的form:
<form method="POST"> <table> <tr> <td>First Name:</td> <td><input name="firstName" type="text" value="Harry"/></td> </tr> <tr> <td>Last Name:</td> <td><input name="lastName" type="text" value="Potter"/></td> </tr> <tr> <td colspan="2"> <input type="submit" value="Save Changes" /> </td> </tr> </table> </form>
上面的JSP有一个预设前提,就是表单支持对象(form backing)的变量名是“command
”。 如果你将这个表单支持对象用其他名称加以定义(这可算是一种最佳实践),你就可以将这个命名变量绑定到表单上,如下例所示:
<form:form commandName="user">
<table>
<tr>
<td>First Name:</td>
<td><form:input path="firstName" /></td>
</tr>
<tr>
<td>Last Name:</td>
<td><form:input path="lastName" /></td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="Save Changes" />
</td>
</tr>
</table>
</form:form>
这个标签生成一个“text”类型的HTML“input”标签。 关于这个标签的示例,请见第 13.9.2 节 “form
标签”。
这个标签生成一个“checkbox”类型的HTML“input”标签。
让我们假设我们的User
有比如新闻订阅和其他一组业余爱好这样的偏好。 下面就是一个Preferences
类的例子:
public class Preferences { private boolean receiveNewsletter; private String[] interests; private String favouriteWord; public boolean isReceiveNewsletter() { return receiveNewsletter; } public void setReceiveNewsletter(boolean receiveNewsletter) { this.receiveNewsletter = receiveNewsletter; } public String[] getInterests() { return interests; } public void setInterests(String[] interests) { this.interests = interests; } public String getFavouriteWord() { return favouriteWord; } public void setFavouriteWord(String favouriteWord) { this.favouriteWord = favouriteWord; } }
form.jsp
如下:
<form:form> <table> <tr> <td>Subscribe to newsletter?:</td> <%-- Approach 1: Property is of typejava.lang.Boolean
--%> <td><form:checkbox path="preferences.receiveNewsletter"/></td> </tr> <tr> <td>Interests:</td> <td> <%-- Approach 2: Property is of an array or of typejava.util.Collection
--%> Quidditch: <form:checkbox path="preferences.interests" value="Quidditch"/> Herbology: <form:checkbox path="preferences.interests" value="Herbology"/> Defence Against the Dark Arts: <form:checkbox path="preferences.interests" value="Defence Against the Dark Arts"/> </td> </tr> <tr> <td>Favourite Word:</td> <td> <%-- Approach 3: Property is of typejava.lang.Object
--%> Magic: <form:checkbox path="preferences.favouriteWord" value="Magic"/> </td> </tr> </table> </form:form>
有三种checkbox
标签的使用方法满足你对checkbox的需求。
第一种用法:若绑定值是java.lang.Boolean
类型,则值为true
时,input(checkbox)
就标记为选中。其value
属性对应于setValue(Object)
值的属性的解析值。
第二种用法:若绑定值是array
或java.util.Collection
类型,则当设定的setValue(Object)
值出现在绑定的Collection
中时,input(checkbox)
就标记为选中。
第三种用法:若绑定值为其他类型,则当设定的setValue(Object)
与其绑定值相等时,input(checkbox)
才标记为选中。
注意,不管使用哪种方法,生成的HTML结构都是相同的。下面是包含一些checkbox的HTML片段:
<tr> <td>Interests:</td> <td> Quidditch: <input name="preferences.interests" type="checkbox" value="Quidditch"/> <input type="hidden" value="1" name="_preferences.interests"/> Herbology: <input name="preferences.interests" type="checkbox" value="Herbology"/> <input type="hidden" value="1" name="_preferences.interests"/> Defence Against the Dark Arts: <input name="preferences.interests" type="checkbox" value="Defence Against the Dark Arts"/> <input type="hidden" value="1" name="_preferences.interests"/> </td> </tr>
也许没有注意到的是在每个checkbox背后还隐藏了其他字段(field)。 当一个HTML页面中的checkbox没有被选中时,它的值不会在表单提交时作为HTTP请求参数的一部分发送到服务器端, 因此我们需要给这个HTML的奇怪动作想出一个变通方案,来让Spring的表单数据绑定可以工作。 checkbox
标签遵循了Spring现有的惯例,就是对于每个checkbox都包含了一个下划线("_"),再跟上一个隐藏参数。 这样一来,就相当于告诉Spring“ 这个checkbox在表单中是可见的,并且希望表单数据将要被绑定到的对象能够反映出任意的checkbox的状态”。
这个标签生成多个“checkbox”类型的HTML“input”标签。
这一节建立在上一节checkbox
标签的例子之上。 有时倾向于并不在JSP页面中列出全部可能的业余爱好,而是想在运行时提供一个可用选项的清单,并把它传递给相应标签。 这就是checkboxes
标签的目标。 传入一个Array
、List
,或者Map
, 并把可用选项包含在“items”属性中。典型的情况是,这个绑定的属性是一个集合,这样它才能持有用户选择的多个值。 下面是使用了这个标签的JSP的一个例子:
<form:form>
<table>
<tr>
<td>Interests:</td>
<td>
<%-- Property is of an array or of type java.util.Collection
--%>
<form:checkboxes path="preferences.interests" items="${interestList}"/>
</td>
</tr>
</table>
</form:form>
这个例子假定了“interestList”是一个List
,作为模型属性它包含了用于被选择的字符串的值。 而在使用一个Map
的情况下,map条目的键被用作值,map条目的值被用作显示的文本标记。 也可以使用一个定制的对象,提供“itemValue”属性存放值,“itemLabel”属性存放文本标记。
这个标签生成一个“radio”类型的HTML“input”标签。
一个典型用法是把多个标签实例绑定到同一属性上,但它们有不同的值。
<tr> <td>Sex:</td> <td>Male: <form:radiobutton path="sex" value="M"/> <br/> Female: <form:radiobutton path="sex" value="F"/> </td> </tr>
这个标签生成多个“radio”类型的HTML“input”标签。
正像上面提到的checkboxes
标签,你可能希望传入一个运行时的变量作为可用的选项。 这种用法下就需要使用radiobuttons
标签。 传入一个Array
、List
,或者Map
, 并把可用选项包含在“items”属性中。在使用Map
的情况下,map条目的键被用作值, map条目的值被用作显示的文本标记。也可以使用一个定制的对象,提供“itemValue”属性存放值,“itemLabel”属性存放文本标记。
<tr> <td>Sex:</td> <td><form:radiobuttons path="sex" items="${sexOptions}"/></td> </tr>
这个标签生成一个“password”类型的HTML“input”标签,并赋以绑定的值。
<tr> <td>Password:</td> <td> <form:password path="password" /> </td> </tr>
请注意默认情况下,口令的值不会被显示出来。 如果一定要显示口令的值,就把“showPassword
”属性的值置为true。
<tr> <td>Password:</td> <td> <form:password path="password" value="^76525bvHGq" showPassword="true" /> </td> </tr>
这个标签生成一个HTML“select”元素。它支持被选中的选项的数据绑定, 也支持使用嵌套的option
和options
标签。
我们假设有一个User
拥有多项专长。
<tr> <td>Skills:</td> <td><form:select path="skills" items="${skills}"/></td> </tr>
如果这个User
的专长是草药学,那么“Skills”这一行的HTML源代码就如下所示:
<tr> <td>Skills:</td> <td><select name="skills" multiple="true"> <option value="Potions">Potions</option> <option value="Herbology" selected="selected">Herbology</option> <option value="Quidditch">Quidditch</option></select> </td> </tr>
这个标签生成一个HTML的“option”。根据绑定的值,它会恰当的设置“selected”属性。
<tr> <td>House:</td> <td> <form:select path="house"> <form:option value="Gryffindor"/> <form:option value="Hufflepuff"/> <form:option value="Ravenclaw"/> <form:option value="Slytherin"/> </form:select> </td> </tr>
如果这个User
的宿舍在Gryffindor(Gryffindor是哈利·波特在Hogwarts的宿舍——译者注), 那么“House”这一行的HTML源代码就如下所示:
<tr> <td>House:</td> <td> <select name="house"> <option value="Gryffindor" selected="selected">Gryffindor</option> <option value="Hufflepuff">Hufflepuff</option> <option value="Ravenclaw">Ravenclaw</option> <option value="Slytherin">Slytherin</option> </select> </td> </tr>
这个标签生成一个HTML的“option”标签的列表。根据绑定的值,它会恰当的设置“selected”属性。
<tr> <td>Country:</td> <td> <form:select path="country"> <form:option value="-" label="--Please Select"/> <form:options items="${countryList}" itemValue="code" itemLabel="name"/> </form:select> </td> </tr>
如果这个User
生活在英国,那么“Country”这一行的HTML源代码就如下所示:
<tr> <td>Country:</td> <td> <select name="country"> <option value="-">--Please Select</option> <option value="AT">Austria</option> <option value="UK" selected="selected">United Kingdom</option> <option value="US">United States</option> </select> </td> </tr>
上面的例子展示了结合使用option
标签和options
标签产生相同的标准HTML, 也让你在JSP中显式的指定一个值只用于显示目的(在它所处的位置),例如例子中的默认字符串:“--Please Select”。
这个标签生成一个HTML的“textarea”。
<tr> <td>Notes:</td> <td><form:textarea path="notes" rows="3" cols="20" /></td> <td><form:errors path="notes" /></td> </tr>
这个标签使用绑定的值生成类型为“hidden”的HTML“input”标签。 在生成的HTML代码中,input标签的值和表单支持对象相应属性的值保持一致。 如果要提交一个未绑定的值,就只能使用类型为“hidden”的HTML input
标签了。
<form:hidden path="house" />
如果我们选择以隐藏形式提交“house”的值,HTML代码将如下所示:
<input name="house" type="hidden" value="Gryffindor"/>
这个标签通过一个HTML“span”标签展现字段的错误。它提供了访问由你的控制器或者与你的控制器关联的任何验证器产生的错误的途径。
假设我们想要在表单提交时显示所有与firstName
和lastName
字段有关的错误。 我们为User
类的实例编写了名为UserValidator
的验证器。
public class UserValidator implements Validator { public boolean supports(Class candidate) { return User.class.isAssignableFrom(candidate); } public void validate(Object obj, Errors errors) { ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", "required", "Field is required."); ValidationUtils.rejectIfEmptyOrWhitespace(errors, "lastName", "required", "Field is required."); } }
form.jsp
将如下所示:
<form:form> <table> <tr> <td>First Name:</td> <td><form:input path="firstName" /></td> <%-- Show errors for firstName field --%> <td><form:errors path="firstName" /></td> </tr> <tr> <td>Last Name:</td> <td><form:input path="lastName" /></td> <%-- Show errors for lastName field --%> <td><form:errors path="lastName" /></td> </tr> <tr> <td colspan="3"> <input type="submit" value="Save Changes" /> </td> </tr> </table> </form:form>
如果我们提交的表单的firstName
和lastName
字段均为空值, HTML页面将如下所示:
<form method="POST"> <table> <tr> <td>First Name:</td> <td><input name="firstName" type="text" value=""/></td> <%-- Associated errors to firstName field displayed --%> <td><span name="firstName.errors">Field is required.</span></td> </tr> <tr> <td>Last Name:</td> <td><input name="lastName" type="text" value=""/></td> <%-- Associated errors to lastName field displayed --%> <td><span name="lastName.errors">Field is required.</span></td> </tr> <tr> <td colspan="3"> <input type="submit" value="Save Changes" /> </td> </tr> </table> </form>
如果我们想要显示一个指定页面上的所有错误清单,又该如何呢? 下面的例子展示了errors
标签也支持一些基本的通配符功能。
path="*"
:显示所有错误
path="lastName*"
:显示所有与lastName
字段有关的错误
下面的例子将会在页面头部显示一个错误清单,后面则在每个字段旁边显示该字段特有的错误:
<form:form> <form:errors path="*" cssClass="errorBox" /> <table> <tr> <td>First Name:</td> <td><form:input path="firstName" /></td> <td><form:errors path="firstName" /></td> </tr> <tr> <td>Last Name:</td> <td><form:input path="lastName" /></td> <td><form:errors path="lastName" /></td> </tr> <tr> <td colspan="3"> <input type="submit" value="Save Changes" /> </td> </tr> </table> </form:form>
HTML代码将如下所示:
<form method="POST"> <span name="*.errors" class="errorBox">Field is required.<br/>Field is required.</span> <table> <tr> <td>First Name:</td> <td><input name="firstName" type="text" value=""/></td> <td><span name="firstName.errors">Field is required.</span></td> </tr> <tr> <td>Last Name:</td> <td><input name="lastName" type="text" value=""/></td> <td><span name="lastName.errors">Field is required.</span></td> </tr> <tr> <td colspan="3"> <input type="submit" value="Save Changes" /> </td> </tr> </form>