第10章 在Seam 中的JSF 表单校验
在简单JSF中,校验被定义在视窗:
<h:form>
<h:messages/>
<div>
Country:
<h:inputText value="#{location.country}" required="true">
<my:validateCountry/>
</h:inputText>
</div>
<div>
Zip code:
<h:inputText value="#{location.zip}" required="true">
<my:validateZip/>
</h:inputText>
</div>
<h:commandButton/>
</h:form>
在实践中,这种方法通常违反DRY(Don't Repeat Yourself)原则,因为实际上大部分“校验”执行的约束是数据模型的一部分,并且所有存在的方法下放到数据库方案(schema)定义。Seam提供对使用Hibernate校验器定义的基于模型约束的支持。
让我们开始在我们的Location类上定义我们的约束:
public class Location {
private String country;
private String zip;
@NotNull
@Length(max=30)
public String getCountry() { return country; }
public void setCountry(String c) { country = c; }
@NotNull
@Length(max=6)
@Pattern("^\d*$")
public String getZip() { return zip; }
public void setZip(String z) { zip = z; }
}
好的,那是一个象样的首次收获,但是在实践中,使用自定义约束代替内建的Hibernate 校验器中的一个,可能是更优雅:
public class Location {
private String country;
private String zip;
@NotNull
@Country
public String getCountry() { return country; }
public void setCountry(String c) { country = c; }
@NotNull
@ZipCode
public String getZip() { return zip; }
public void setZip(String z) { zip = z; }
}
无论我们使用那一个方法,我不再需要指定被用在JSF页面的校验类型。作为替代,我们能使用<s:validate>,根据定义在模型对象上的约束进行校验。
<h:form>
<h:messages/>
<div>
Country:
<h:inputText value="#{location.country}" required="true">
<s:validate/>
</h:inputText>
</div>
<div>
Zip code:
<h:inputText value="#{location.zip}" required="true">
<s:validate/>
</h:inputText>
</div>
<h:commandButton/>
</h:form>
注意:在模型上指定@NotNull,并不能在显示控件上消除required="true"这个必要条件!这个由于一个JSF校验体系结构的限制。
这种方法在模型上定义约束,并且在视窗引入了约束违反——一个引人注目的更好的设计。
可是,它并不比我们开始使用的更少冗余,所以让我们试试<s:validateAll>:
<h:form>
<h:messages/>
<s:validateAll>
<div>
Country:
<h:inputText value="#{location.country}" required="true"/>
</div>
<div>
Zip code:
<h:inputText value="#{location.zip}" required="true"/>
</div>
<h:commandButton/>
</s:validateAll>
</h:form>
这个标签对表单中的每一个输入只增加了一个<s:validate>。对大型表单而言,它能节省大量键入!
现在我们需要做一些事情,当校验失败时显示反馈给用户。通常,我们显示所有消息在表单的顶端。我们真正喜欢的是带着错误的消息紧挨着字段显示(在这简单JSF中这是可能的),高光显示字段和标签(这是不可能的),以及,再加上,紧挨着字段显示一些图像(也不可能)。我们也想为每一个必需的表单字段,紧挨标签显示一个彩色小星号。
对我们表单的每一个字段,那是相当多我们需要的功能。 我们不想为在表单上的每一个字段指定高光、图像层、消息和输入字段。
所以,作为替代,我们用一个facelets模板指定公共层:
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:s="http://jboss.com/products/seam/taglib">
<div>
<s:label styleClass="#{invalid?'error':''}">
<ui:insert name="label"/>
<s:span styleClass="required" rendered="#{required}">*</s:span>
</s:label>
<span class="#{invalid?'error':''}">
<h:graphicImage value="/img/error.gif" rendered="#{invalid}"/>
<s:validateAll>
<ui:insert/>
</s:validateAll>
</span>
<s:message styleClass="error"/>
</div>
</ui:composition>
我们能使用<s:decorate>,为我们表单字段的每个包括这个模板。
<h:form>
<h:messages globalOnly="true"/>
<s:decorate template="edit.xhtml">
<ui:define name="label">Country:</ui:define>
<h:inputText value="#{location.country}" required="true"/>
</s:decorate>
<s:decorate template="edit.xhtml">
<ui:define name="label">Zip code:</ui:define>
<h:inputText value="#{location.zip}" required="true"/>
</s:decorate>
<h:commandButton/>
</h:form>
最后,当用户在表单的四处导航时,我们能使用RichFaces Ajax显示校验消息。
<h:form>
<h:messages globalOnly="true"/>
<s:decorate id="countryDecoration" template="edit.xhtml">
<ui:define name="label">Country:</ui:define>
<h:inputText value="#{location.country}" required="true">
<a:support event="onblur" reRender="countryDecoration" bypassUpdates="true"/>
</h:inputText>
</s:decorate>
<s:decorate id="zipDecoration" template="edit.xhtml">
<ui:define name="label">Zip code:</ui:define>
<h:inputText value="#{location.zip}" required="true">
<a:support event="onblur" reRender="zipDecoration" bypassUpdates="true"/>
</h:inputText>
</s:decorate>
<h:commandButton/>
</h:form>
为页面上重要的控件定义明确的id是好的风格,特别是如果你想使用一些Selenium类工具包来对UI做自动测试。如果你没有提供明确的id,JSF会产生它们,但是,如果你改变了在页面上的任何事,产生的值也会改变。
<h:form id="form">
<h:messages globalOnly="true"/>
<s:decorate id="countryDecoration" template="edit.xhtml">
<ui:define name="label">Country:</ui:define>
<h:inputText id="country" value="#{location.country}" required="true">
<a:support event="onblur" reRender="countryDecoration" bypassUpdates="true"/>
</h:inputText>
</s:decorate>
<s:decorate id="zipDecoration" template="edit.xhtml">
<ui:define name="label">Zip code:</ui:define>
<h:inputText id="zip" value="#{location.zip}" required="true">
<a:support event="onblur" reRender="zipDecoration" bypassUpdates="true"/>
</h:inputText>
</s:decorate>
<h:commandButton/>
</h:form>
如果当校验失败时,你想指定一个不同的消息显示?你能和Hibernate校验器一起使用Seam消息束(所有象在消息内的EL表达式,每视窗消息束)???:
public class Location {
private String name;
private String zip;
// Getters and setters for name
@NotNull
@Length(max=6)
@ZipCode(message="#{messages['location.zipCode.invalid']}")
public String getZip() { return zip; }
public void setZip(String z) { zip = z; }
}
location.zipCode.invalid = The zip code is not valid for #{location.name}