SeamFramework.orgCommunity Documentation
Nel puro JSF la validazione è definita nella vista:
<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
>
In pratica quest'approccio di norma viola il principio DRY (Don't Repeat Yourself = Non ripeterti), poiché la maggior parte della "validazione" forza i vincoli che sono parte del modello di dati, e che esistono lungo tutta la definizione dello schema di database. Seam fornisce supporto ai vincoli del modello definiti, utilizzando Hibernate Validator.
Si cominci col definire i vincoli sulla classe 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; }
}
Bene, questa è una prima buona riduzione, ma in pratica è possibile essere più eleganti utilizzando dei vincoli personalizzati invece di quelli interni a 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; }
}
Qualsiasi strada si prenda non occorre più specificare il tipo di validazione da usare nelle pagine JSF. Invece è possibile usare <s:validate>
per validare il vincolo definito nell'oggetto modello.
<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
>
Nota: specificare @NotNull
nel modello non elimina la necessità di required="true"
per farlo apparire nel controllo! Questo è dovuto ad una limitazione nell'architettura di validazione in JSF.
Quest'approccio definisce i vincoli sul modello e presenta le violazioni al vincolo nella vista — di gran lunga un miglior design.
Comunque non è molto meno corto di quanto lo era all'inizio, quindi proviamo <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
>
Questo tag semplicemente aggiunge un <s:validate>
ad ogni input nella form. Per form grandi questo fa risparmiare molto!
Adesso occorre fare qualcosa per mostrare all'utente un messaggio quando la validazione fallisce. Ora vengono mostrati tutti i messaggi in cima alla form. Per consentire all'utente di associare il messaggio al singolo input, occorre definire un'etichetta e usare l'attributo standard label
sul componente d'input.
<h:inputText value="#{location.zip}" required="true" label="Zip:">
<s:validate/>
</h:inputText
>
Si può iniettare questo valore nella stringa del messaggio usando il placeholder {0} (il primo ed unico parametro passato al messaggio JSF a causa di una restrizione in Hibernate Validator). Vedere la sezione Internazionalizzazione per ulteriori informazioni sulla definizione dei messaggi.
validator.length={0} la lunghezza deve essere tra {min} e {max}
Ciò che si vuole fare, tuttavia, è mostrare il messaggio vicino al campo con l'errore (questo è possibile nel semplice JSF), ed evidenziare il campo e l'etichetta (questo non è possibile) ed, eventualmente, mostrare un'immagine vicino al campo (anche questo non è possibile). Si vuole anche mostrare un piccolo asterisco colorato vicino all'etichetta per ciascun campo richiesto. Utilizzando quest'approccio non è più necessario identificare l'etichetta.
Si ha quindi bisogno di parecchia funzionalità per ogni singolo campo della form. Ma non si vuole essere costretti a specificare per ogni campo della form come evidenziare, come disporre l'immagine, quale messaggio scrivere e quale è il campo d'input da associare.
<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
>
Si può includere questo template per ciascun campo della form utilizzando <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
>
Infine è possibile utilizzare RichFaces Ajax per mostrare i messaggi di validazione mentre l'utente naviga nella 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
>
E' meglio definire esplicitamente gli id per i controlli importanti all'interno di una pagina, specialmente in caso di un test automatico della UI, utilizzando dei toolkit quale Selenium. Se non vengono forniti gli id in modo esplicito, JSF li genererà, ma i valori generati cambieranno se si cambia qualcosa nella pagina.
<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
>
E se si vuole specificare un messaggio diverso quando la validazione fallisce? Si può utilizzare il message bundle di Seam con Hibernate Validator (e tutte le altre finezze come le espressioni EL dentro il messaggio ed i message bundle per ogni singola vista).
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 = Codice ZIP non valido per #{location.name}