站内搜索: 请输入搜索关键词
当前页面: 在线文档首页 > JBoss Seam 1.1.6 正式版英文参考手册

Chapter 9. JSF form validation in Seam - JBoss Seam 1.1.6 正式版英文参考手册

Chapter 9. JSF form validation in Seam

In plain JSF, validation is defined in the view:

<h:form>
    <div>
        <h:messages/>
    </div>
    <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>
    <div>
        <h:commandButton/>
    </div>
</h:form>

In practice, this approach usually violates DRY, since most "validation" actually enforces constraints that are part of the data model, and exist all the way down to the database schema definition. Seam provides support for model-based constraints defined using Hibernate Validator.

Let's start by defining our constraints, on our Location class:

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; }
}

Well, that's a decent first cut, but in practice it might be more elegant to use custom constraints instead of the ones built into Hibernate Validator:

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; }
}

Whichever route we take, we no longer need to specify the type of validation to be used in the JSF page. Instead, we can use <s:validate> to validate against the constraint defined on the model object.

<h:form>
    <div>
        <h:messages/>
    </div>
    <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>
    <div>
        <h:commandButton/>
    </div>
</h:form>

Note: specifying @NotNull on the model does not eliminate the requirement for required="true" to appear on the control! This is due to a limitation of the JSF validation architecture.

This version is not much less verbose than what we started with, so let's try <s:validateAll>:

<h:form>
    <div>
        <h:messages/>
    </div>
    <s:validateAll>
        <div>
            Country:
            <h:inputText value="#{location.country}" required="true"/>
        </div>
        <div>
            Zip code:
            <h:inputText value="#{location.zip}" required="true"/>
        </div>
        <div>
            <h:commandButton/>
        </div>
    </s:validateAll>
</h:form>

This tag simply adds an <s:validate> to every input in the form. For a large form, it can save a lot of typing!

Now we need to do something about displaying feedback to the user when validation fails. Currently we are displaying all messages at the top of the form. What we would really like to do is display the message next to the field with the error (this is possible in plain JSF), highlight the field (this is not possible) and, for good measure, display some image next the the field (also not possible).

Let's try out <s:decorate>:

<h:form>
    <div>
        <h:messages globalOnly="true"/>
    </div>
    <s:validateAll>
        <div>
            Country:
            <s:decorate>
                <f:facet name="beforeInvalidField"><h:graphicImage src="img/error.gif"/></f:facet>
                <f:facet name="afterInvalidField"><s:message/></f:facet>
                <f:facet name="aroundInvalidField"><s:span styleClass="error"/></f:facet>
                <h:inputText value="#{location.country}" required="true"/>
            </s:decorate>
        </div>
        <div>
            Zip code:
            <s:decorate>
                <f:facet name="beforeInvalidField"><h:graphicImage src="img/error.gif"/></f:facet>
                <f:facet name="afterInvalidField"><s:message/></f:facet>
                <f:facet name="aroundInvalidField"><s:span styleClass="error"/></f:facet>
                <h:inputText value="#{location.zip}" required="true"/>
            </s:decorate>
        </div>
        <div>
            <h:commandButton/>
        </div>
    </s:validateAll>
</h:form>

Well, that looks much better to the user, but it is extremely verbose. Fortunately, the facets of <s:decorate> may be defined on any parent element:

<h:form>
    <f:facet name="beforeInvalidField">
        <h:graphicImage src="img/error.gif"/>
    </f:facet>
    <f:facet name="afterInvalidField">
        <s:message/>
    </f:facet>
    <f:facet name="aroundInvalidField">
        <s:span styleClass="error"/>
    </f:facet>
    <div>
        <h:messages globalOnly="true"/>
    </div>
    <s:validateAll>
        <div>
            Country:
            <s:decorate>
                <h:inputText value="#{location.country}" required="true"/>
            </s:decorate>
        </div>
        <div>
            Zip code:
            <s:decorate>
                <h:inputText value="#{location.zip}" required="true"/>
            </s:decorate>
        </div>
        <div>
            <h:commandButton/>
        </div>
    </s:validateAll>
</h:form>

This approach defines constraints on the model, and presents constraint violations in the view—a significantly better design.

Finally, we can use Ajax4JSF to display validation messages as the user is typing:

<h:form>
    <f:facet name="beforeInvalidField">
        <h:graphicImage src="img/error.gif"/>
    </f:facet>
    <f:facet name="aroundInvalidField">
        <s:span styleClass="error"/>
    </f:facet>
    <div>
        <h:messages globalOnly="true"/>
    </div>
    <s:validateAll>
        <div>
            Country:
            <s:decorate>
                <h:inputText value="#{location.country}" required="true">
                    <a:support event="onblur" reRender="countryError"/>
                </h:inputText>
                <a:outputPanel id="countryError><s:message/></a:outputPanel>
            </s:decorate>
        </div>
        <div>
            Zip code:
            <s:decorate>
                <h:inputText value="#{location.zip}" required="true">
                    <a:support event="onblur" reRender="zipError"/>
                </h:inputText>
                <a:outputPanel id="zipError><s:message/></a:outputPanel>
            </s:decorate>
        </div>
        <div>
            <h:commandButton/>
        </div>
    </s:validateAll>
</h:form>