mika-el
4/7/2018 - 12:35 PM

Validation

h:message

  • h:message est un composant JSF pour générer un message associé à un champ de saisie en particulier.
  • h:message accepte deux attributs :
    • for: pour cibler l'id du champ concerné.
    • xxxClass: pour permettre de donner une classe CSS particulière lors de l'affichage du message généré et selon la criticité (errorClass, fatalClass, infoClass, warnClass).
    • xxxStyle: pour permettre de donner un style CSS particulier lors de l'affichage du message généré généré et selon la criticité (styleClass, styleClass, styleClass, styleClass).
  • La criticité sera attribué par example dans un bean ou une entity avec new FacesMessage( FacesMessage.SEVERITY_ERROR, EMAIL_EXISTE_DEJA, null ) );
  • Ce composant va afficher, lors du rendu de la réponse, l'éventuel message associé au champ ciblé par l'attribut for.
  • S'il s'agit d'un message d'erreur, que JSF sait différencier des messages d'information qui peuvent éventuellement être placés dans le FacesContext grâce au niveau de criticité associé à un message, alors le style erreur défini dans notre feuille CSS sera appliqué.

h:messages

  • Par défaut, celui-ci provoque l'affichage de tous les messages disponibles dans la vue, y compris ceux qui sont déjà affichés via un <h:message> ailleurs dans la page.
  • Toutefois, il est possible de n'afficher que les messages qui ne sont attachés à aucun composant défini, c'est-à-dire les messages dont l'id est null, en utilisant l'attribut optionnel globalOnly="true" (ex: <h:messages globalOnly="true" />)

Validation depuis la vue avec message d'erreur en dur sur la vue : <h:inputText id="email" value="#{inscrireBean.utilisateur.email}" requiredMessage="Veuillez saisir une adresse email" /> <h:inputSecret id="motdepasse" value="#{inscrireBean.utilisateur.motDePasse}" requiredMessage="Veuillez saisir un mot de passe" /> <h:inputSecret id="confirmation" value="#{inscrireBean.utilisateur.motDePasse}" requiredMessage="Veuillez saisir la confirmation du mot de passe" /> <h:inputText id="nom" value="#{inscrireBean.utilisateur.nom}" requiredMessage="Veuillez saisir un nom d'utilisateur" /> ## Validation depuis la vue avec message d'erreur dans un blundle : ### Le blundle : inscription.email = Veuillez saisir une adresse email inscription.motdepasse = Veuillez saisir un mot de passe inscription.confirmation = Veuillez saisir la confirmation du mot de passe inscription.nom = Veuillez saisir un nom d'utilisateur ### La vue : <!-- On charge le bundle à la vue est on l'assigne à la variable msg --> <h:head> ... <f:loadBundle basename="com.sdzee.bundle.messages" var="msg"/> </h:head> <!-- On utilise la variable de vue msg dans l'attribut requiredMessage --> <h:inputText id="email" value="#{inscrireBean.utilisateur.email}" ... requiredMessage="#{msg['inscription.email']}" /> <h:inputSecret id="motdepasse" value="#{inscrireBean.utilisateur.motDePasse}" ... requiredMessage="#{msg['inscription.motdepasse']}" /> <h:inputSecret id="confirmation" value="#{inscrireBean.utilisateur.motDePasse}" ... requiredMessage="#{msg['inscription.confirmation']}" /> <h:inputText id="nom" value="#{inscrireBean.utilisateur.nom}" ... requiredMessage="#{msg['inscription.nom']}" />

  • event: qui nous permet de définir l'action déclenchant l'envoi de la requête AJAX au serveur.
  • render: qui nous permet de définir le ou les composants dont le rendu doit être effectué, une fois la requête traitée par le serveur.
  • execute: permet de définir la portée de l'action à effectuer.

Verification d'un Input en ajax :

  • L'event que nous utilisons pour considérer que l'utilisateur a terminé la saisie du contenu d'un champ s'intitule blur. Cette propriété JavaScript va permettre de déclencher une requête dès que le champ courant perd le focus.
  • Pour désigner quel composant actualiser, nous devons simplement préciser son id dans l'attribut render. Ici le message d'erreur 'emailMessage'.
<h:inputText id="email" value="#{inscrireBean.utilisateur.email}" required="true" size="20" maxlength="60" requiredMessage="#{msg['inscription.email']}">
    <f:ajax event="blur" render="emailMessage" />
</h:inputText>
<h:message id="emailMessage" for="email" errorClass="erreur" />

Envoie du formulaire avec ajax :

En précisant @form dans l'attribut render, nous nous assurons ainsi que tout le formulaire va être actualisé lors d'un clic sur le bouton d'envoi.

<h:form id="formulaireUser">
  <h:commandButton value="Inscription" action="#{inscrireBean.inscrire}" styleClass="sansLabel">
      <f:ajax execute="@form" render="@form" />
  </h:commandButton>
</h:form>

Example de verfication de confirmation de mot de passe :

Le validateur sera le même que le Validateur de comparaison de plusieurs champs dans Validation métier au dessus.

<h:outputLabel for="motdepasse">Mot de passe <span class="requis">*</span></h:outputLabel>
<h:inputSecret id="motdepasse" value="#{inscrireBean.utilisateur.motDePasse}" binding="#{composantMotDePasse}" size="20" maxlength="20">
    <f:ajax event="blur" execute="motdepasse confirmation" render="motDePasseMessage confirmationMessage" />
</h:inputSecret>
<h:message id="motDePasseMessage" for="motdepasse" errorClass="erreur" />
<br />

<h:outputLabel for="confirmation">Confirmation du mot de passe <span class="requis">*</span></h:outputLabel>
<h:inputSecret id="confirmation" value="#{inscrireBean.utilisateur.motDePasse}" size="20" maxlength="20">
    <f:ajax event="blur" execute="motdepasse confirmation" render="motDePasseMessage confirmationMessage" />
    <f:attribute name="composantMotDePasse" value="#{composantMotDePasse}" />
    <f:validator validatorId="confirmationMotDePasseValidator" />
</h:inputSecret>
<h:message id="confirmationMessage" for="confirmation" errorClass="erreur" />
<br />
  • Le framework fournit une interface nommée javax.faces.validator.Validator, qui permet de créer une classe contenant une méthode de validation, qui pourra alors être liée à un composant très simplement depuis la vue, via un attribut placé dans une balise.
  • La seule méthode qu'il est nécessaire de surcharger est la méthode validate(), c'est elle qui va contenir le code métier chargé d'effectuer le contrôle de l'existence de l'adresse dans la base.
  • Il faut déclarer le validator auprès de JSF afin qu'il le rende accessible à notre vue avec l'annotation @FacesValidator qui permet de déclarer auprès du framework un objet comme étant un Validator, et permet ainsi de rendre cet objet accessible depuis une balise dans nos Facelets.
  • Il n'est pas possible d'injecter un EJB avec l'annotation @EJB dans un Validator JSF annoté avec @FacesValidator. Pour pouvoir injecter EJB il faudra remplacer @FacesValidator par les annotations @ManagedBean et @RequestScoped sur le validator puis remplacer sur la vue <f:validator validatorId="#{validatorName}" /> par <f:validator binding="#{validatorName}" />
  • Les validation sur les bean sont faite après celles des Validator metiers.

Classe de base d'un Validator :

package com.jsf.validators;

@FacesValidator(value = "validatorName")
public class ValidatorName implements Validator {
    @Override
    public void validate(FacesContext context, UIComponent component, Object value ) throws ValidatorException {
      ...
    }
}

Validator avec un EJB :

@ManagedBean
@RequestScoped
public class ExistenceEmailValidator implements Validator {
    private static final String EMAIL_EXISTE_DEJA = "Cette adresse email est déjà utilisée";

    @EJB
    private UtilisateurDaoutilisateurDao;

    @Override
    public void validate( FacesContext context, UIComponent component, Object value ) throws ValidatorException {
        /* Récupération de la valeur à traiter depuis le paramètre value */
        String email = (String) value;
        try {
            if ( utilisateurDao.trouver( email ) != null ) {
                /*
                 * Si une adresse est retournée, alors on envoie une exception
                 * propre à JSF, qu'on initialise avec un FacesMessage de
                 * gravité "Erreur" et contenant le message d'explication. Le
                 * framework va alors gérer lui-même cette exception et s'en
                 * servir pour afficher le message d'erreur à l'utilisateur.
                 */
                throw new ValidatorException(
                        new FacesMessage( FacesMessage.SEVERITY_ERROR, EMAIL_EXISTE_DEJA, null ) );
            }
        } catch ( DAOException e ) {
            /*
             * En cas d'erreur imprévue émanant de la BDD, on prépare un message
             * d'erreur contenant l'exception retournée, pour l'afficher à
             * l'utilisateur ensuite.
             */
            FacesMessage message = new FacesMessage( FacesMessage.SEVERITY_ERROR, e.getMessage(), null );
            FacesContext facesContext = FacesContext.getCurrentInstance();
            facesContext.addMessage( component.getClientId( facesContext ), message );
        }
    }
}

Validateur de comparaison de plusieurs champs :

Le formulaire :

...
<h:outputLabel for="motdepasse">Mot de passe <span class="requis">*</span></h:outputLabel>
<h:inputSecret id="motdepasse" value="#{inscrireBean.utilisateur.motDePasse}" binding="#{composantMotDePasse}" size="20" maxlength="20" />
<h:message id="motDePasseMessage" for="motdepasse" errorClass="erreur" />
<br />
<h:outputLabel for="confirmation">Confirmation du mot de passe <span class="requis">*</span></h:outputLabel>
<h:inputSecret id="confirmation" value="#{inscrireBean.utilisateur.motDePasse}" size="20" maxlength="20">
<h:message id="confirmationMessage" for="confirmation" errorClass="erreur" />
<br />
...

Le validateur :

@FacesValidator( value = "confirmationMotDePasseValidator" )
public class ConfirmationMotDePasseValidator implements Validator {
    private static final String CHAMP_MOT_DE_PASSE       = "composantMotDePasse";
    private static final String MOTS_DE_PASSE_DIFFERENTS = "Le mot de passe et la confirmation doivent être identiques.";

    @Override
    public void validate( FacesContext context, UIComponent component, Object value ) throws ValidatorException {
        // Récupération de l'attribut mot de passe parmi la liste des attributs du composant confirmation
        UIInput composantMotDePasse = (UIInput) component.getAttributes().get( CHAMP_MOT_DE_PASSE );
        // Récupération de la valeur du champ, c'est-à-dire le mot de passe saisi
        String motDePasse = (String) composantMotDePasse.getValue();
        // Récupération de la valeur du champ confirmation
        String confirmation = (String) value;
        // Envoi d'une exception contenant une erreur de validation JSF initialisée avec le message destiné à l'utilisateur, si les mots de passe sont différents
        if ( confirmation != null && !confirmation.equals( motDePasse ) ) {
            throw new ValidatorException(new FacesMessage(FacesMessage.SEVERITY_ERROR, MOTS_DE_PASSE_DIFFERENTS, null ));
        }
    }
}
  • Le serveur GlassFish fournit par défaut un moyen de validation, identifié sous le nom de JSR 303.
  • Le serveur JBoss fournit par défaut un moyen de validation, Hibernate Validator, identifié sous le nom de JSR 380.
  • Les validation ce font au moyen d'annotation directement sur les propriétées des entities.
  • Il est possibles d'afficher un message à la vue avec l'attibut message des validateurs. Ils seront affichés automatiquement sur la vue (si required sur la vue n'est pas présent).

Example d'Entity :

@Entity
public class Utilisateur {

    @Id
    @GeneratedValue( strategy = GenerationType.IDENTITY )
    private Long      id;

    @NotEmpty( message = "Veuillez saisir une adresse email" )
    @Pattern( regexp = "([^.@]+)(\\.[^.@]+)*@([^.@]+\\.)+([^.@]+)", message = "Merci de saisir une adresse mail valide" )
    private String    email;

    @Column( name = "mot_de_passe" )
    @NotNull( message = "Veuillez saisir un mot de passe" )
    @Size( min = 3, message = "Le mot de passe doit contenir au moins 3 caractères" )
    private String    motDePasse;

    @NotNull( message = "Veuillez saisir un nom d'utilisateur" )
    @Size( min = 3, message = "Le nom d'utilisateur doit contenir au moins 3 caractères" )
    private String    nom;

    @Column( name = "date_inscription" )
    private Timestamp dateInscription;
  ...
}

Pour que @NotNull fonctionne avec GlassFish sur avec les valeur vide, dans le fichier web.xml :

<context-param>
    <param-name>javax.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL</param-name>
    <param-value>true</param-value>
</context-param>