純粋な 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>
<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>
Copy to Clipboard
Copied!
Toggle word wrap
Toggle overflow
実際にはこの方法は通常 DRY に違反しています。データモデルの一部であり、データベーススキーマの定義全体にわたって存在する制約をほとんどの「検証」が実際は強制実行するためです。Seam は Hibernate Validator を使って定義されるモデルベースの制約に対するサポートを提供しています。
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; }
}
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; }
}
Copy to Clipboard
Copied!
Toggle word wrap
Toggle overflow
実際には 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; }
}
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; }
}
Copy to Clipboard
Copied!
Toggle word wrap
Toggle overflow
いずれの方法を取るにしても、 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>
<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>
Copy to Clipboard
Copied!
Toggle word wrap
Toggle overflow
このモデルで @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>
<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>
Copy to Clipboard
Copied!
Toggle word wrap
Toggle overflow
このタグは <s:validate> をフォーム内のすべての入力に追加します。 フォームが大きくなる場合は、 入力の手間をかなり省くことができます。
次に、 検証が失敗した場合にユーザーにフィードバックを表示させる必要があります。 現在すべてのメッセージはフォームの冒頭に表示されます。 メッセージと入力を関連付けられるようにするためには、 入力コンポーネントで標準の label 属性を使いラベルを定義する必要があります。
<h:inputText value="#{location.zip}" required="true" label="Zip:">
<s:validate/>
</h:inputText>
<h:inputText value="#{location.zip}" required="true" label="Zip:">
<s:validate/>
</h:inputText>
Copy to Clipboard
Copied!
Toggle word wrap
Toggle overflow
プレースホルダーの {0} (Hiberate Validator の制約用に JSF メッセージに渡される最初で唯一のパラメータ) を使ってこの値をメッセージ文字列にインジェクトします。これらのメッセージを定義する場所の詳細は「国際化」の項をご覧ください。
validator.length={0} length must be between {min} and {max}
エラーがあるフィールドの隣にメッセージを表示させ、 フィールドとラベルをハイライトさせて、フィールドの隣にイメージを表示させたいとします。純粋な 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>
<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>
Copy to Clipboard
Copied!
Toggle word wrap
Toggle overflow
<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>
<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>
Copy to Clipboard
Copied!
Toggle word wrap
Toggle overflow
最後に、ユーザーがフォーム内を移動しながら 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>
<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>
Copy to Clipboard
Copied!
Toggle word wrap
Toggle overflow
重要なページのコントロールには明示的な ID を定義すると便利なスタイルになります。 特に 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>
<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>
Copy to Clipboard
Copied!
Toggle word wrap
Toggle overflow
検証が失敗したときに表示させるメッセージを変えたい場合、 Seam メッセージバンドルを Hibernate Validator で使用することができます。
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; }
}
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; }
}
Copy to Clipboard
Copied!
Toggle word wrap
Toggle overflow
location.zipCode.invalid = The zip code is not valid for #{location.name}
location.zipCode.invalid = The zip code is not valid for #{location.name}
Copy to Clipboard
Copied!
Toggle word wrap
Toggle overflow