Validation des formulaires de données

Bien que vous deviez toujours valider les données côté serveur, la validation des données d'un formulaire au niveau de la page Web elle-même (côté client) présente plusieurs avantages. De bien des manières, les formulaires agacent les utilisateurs (article en anglais). En validant les données d'un formulaire lorsque l'utilisateur le remplit, ce dernier sait immédiatement s'il a commis des erreurs ; il n'a donc plus à attendre la réponse d'une requête HTTP, et le serveur n'a plus à traiter des données mal formatées. Cet article traite des moyens qui existent pour valider le contenu de vos formulaires.

Validation du formulaire par le navigateur

Une des fonctionnalités de HTML5 est de pouvoir valider la plupart des données des utilisateurs sans avoir recours à des scripts. On utilise pour cela des attributs de validation sur les éléments du formulaire.

Lorsqu'un élément est invalide

Lorsqu'un élément est invalide, deux choses ont lieu :

  • L'élément possède la pseudo-classe CSS :invalid ; cela vous permet d'appliquer un style spécifique sur les éléments invalides. De la même façon, les éléments valides possèdent la pseudo-classe CSS :valid.
  • Si l'utilisateur tente d'envoyer les données, le navigateur bloquera le formulaire et affichera le message d'erreur.

Contraintes de validation sur les éléments <input>

Tous les éléments <input> peuvent être validés en utilisant l'attribut pattern. Cet attribut doit recevoir comme valeur une expression rationnelle sensible à la casse. Si la valeur de l'élément n'est pas nulle et ne correspond pas à l'expression rationnelle décrite par l'attribut pattern, l'élément est alors considéré comme invalide.

Un exemple simple

<form>
  <label for="choose">Préférez-vous une banane ou une cerise ?</label>
  <input id="choose" name="j_aime" pattern="banane|cerise">
  <button>Envoyer</button>
</form>
input:invalid {
  border: 1px solid red;
}

input:valid {
  border: 1px solid green;
}

Dans cet exemple, l'élément <input> accepte une de ces trois valeurs : une chaîne vide, la chaîne "banane" ou la chaîne "cerise".

L'attribut required

Si un élément doit avoir une valeur avant que le formulaire puisse être soumis, vous pouvez marquer cet élément avec l'attribut required. Lorsque cet attribut vaut true, le champ ne doit pas être laissé vide.

<form>
  <label for="choose">Préférez-vous une banane ou une cerise ?</label>
  <input id="choose" name="j_aime" pattern="banane|cerise" required>
  <button>Envoyer</button>
</form>
input:invalid {
  border: 1px solid red;
}

input:valid {
  border: 1px solid green;
}

Remarquez la bordure du champ qui est différente de celle du premier exemple.

Note : Les éléments <input> dont l'attribut type est défini à email ou url ne nécessitent pas d'attribut pattern pour être validés. Spécifier le type email contraint à avoir une adresse email bien formatée (ou une liste d'adresses email séparées par une virgule, si le champ présente l'attribut multiple). Les champs de type url requièrent automatiquement une URL correctement formatée.

Autres contraintes de validation

Tous les éléments de formulaire qui peuvent recevoir des données saisies par l'utilisateur (<textarea>, <select>, etc.) acceptent l'attribut required, mais il faut savoir que l'élément  <textarea> n'accepte pas l'attribut pattern.

Tous les champs texte (<input> ou <textarea>) peuvent voir leur taille contrainte en utilisant l'attribut maxlength. Un champ est invalide si la longueur de sa valeur dépasse celle de son attribut maxlength. Toutefois, la plupart du temps, les navigateurs n'autorisent pas l'utilisateur à saisir des valeurs plus longues que ce qui est attendu par les champs texte.

Pour les champs numériques, les attributs min et max fournissent aussi des contraintes de validation. Si la valeur est inférieure à l'attribut min ou supérieure à l'attribut max, le champ est alors invalide.

Voici un exemple complet:

<form>
  <p>
    <fieldset>
      <legend>titre<abbr title="Ce champ est obligatoire">*</abbr></legend>
      <input type="radio" required name="titre" id="r1" value="M" ><label for="r1">M</label>
      <input type="radio" required name="titre" id="r2" value="Mme"><label for="r2">Mme</label>
    </fieldset>
  </p>
  <p>
    <label for="n1">Quel âge avez-vous ?</label>
    <!-- L'attribut "pattern" n'est pas requis pour un champ numérique mais il peut servir de recours pour les navigateurs qui n'ont pas implémenté le champ "number" mais acceptent l'attribut "pattern", comme Firefox -->
    <input type="number" min="12" max="120" step="1" id="n1" name="age"
           pattern="\d+">
  </p>
  <p>
    <label for="t1">Quel est votre fruit préféré?<abbr title="Ce champ est obligatoire">*</abbr></label>
    <input type="text" id="t1" name="fruit" list="l1" required
           pattern="[Bb]anane|[Cc]erise|[Pp]omme|[Ff]raise|[Cc]itron|[Oo]range">
    <datalist id="l1">
        <option>Banane</option>
        <option>Cerise</option>
        <option>Pomme</option>
        <option>Fraise</option>
        <option>Citron</option>
        <option>Orange</option>
    </datalist>
  </p>
  <p>
    <label for="t2">Quelle est votre adresse e-mail?</label>
    <input type="email" id="t2" name="email">
  </p>
  <p>
    <label for="t3">Laissez un (court) message</label>
    <textarea id="t3" name="msg" maxlength="140" rows="5"></textarea>
  </p>
  <p>
    <button>Envoyer</button>
  </p>
</form>
body {
  font: 1em sans-serif;
  padding: 0;
  margin : 0;
}

form {
  max-width: 200px;
  margin: 0;
  padding: 0 5px;
}

p > label {
  display: block;
}

input[type=text],
input[type=email],
input[type=number],
textarea,
fieldset {
/* nécessaire, sur les navigateurs basés sur Webkit, pour appliquer correctement le style des éléments de formulaire */
  -webkit-appearance: none;
  
  width : 100%;
  border: 1px solid #333;
  margin: 0;
  
  font-family: inherit;
  font-size: 90%;
  
  -moz-box-sizing: border-box;
  box-sizing: border-box;
}

input:invalid {
  box-shadow: 0 0 5px 1px red;
}

input:focus:invalid {
  outline: none;
}

Messages d'erreur personnalisés

Comme nous avons vu dans les exemples précédents, à chaque fois qu'un utilisateur tente d'envoyer un formulaire invalide, le navigateur affiche un message d'erreur. La manière dont le message est affiché dépend du navigateur.

Ces messages automatiques présentent deux inconvénients:

  • Il n'y a pas de façon standard de changer leur apparence avec CSS.
  • Ils dépendent des paramètres régionaux du navigateur, ce qui signifie que vous pouvez avoir une page dans une langue mais les messages d'erreurs affichés dans une autre.
Versions françaises des navigateurs sur une page en anglais
Navigateur Affichage
Firefox 17 (Windows 7) Example of an error message with Firefox in French on an English page
Chrome 22 (Windows 7) Example of an error message with Chrome in French on an English page
Opera 12.10 (Mac OSX) Example of an error message with Opera in French on an English page

Pour personnaliser l'apparence et le texte de ces messages, vous devez utiliser JavaScript ; il n'est pas possible de l'implémenter en utilisant uniquement HTML et CSS.

HMTL5 fournit une API de contraintes de validation pour vérifier et personnaliser l'état des élément d'un formulaire. Il est possible, entre autres, de changer le texte des messages d'erreur. Voici un court exemple :

<form>
  <label for="mail">Pourriez-vous nous fournir une adresse mail ?</label>
  <input type="email" id="mail" name="mail">
  <button>Envoyer</button>
</form>

En JavaScript, il faut appeler la méthode setCustomValidity():

var email = document.getElementById("mail");

email.addEventListener("keyup", function (event) {
  if(email.validity.typeMismatch) {
    email.setCustomValidity("Nous voudrions une adresse e-mail.");
  } else {
    email.setCustomValidity("");
  }
});

Validation de formulaires avec JavaScript

Si vous souhaitez avoir le contrôle de l'apparence des messages d'erreur, ou si vous voulez gérer le comportement des navigaeurs n'ayant pas implémenté de validation de formulaire HTML5, vous n'avez pas d'autre choix que d'utiliser JavaScript.

API de contraintes de validation HTML5

De plus en plus de navigateurs supportent l'API de contraintes de validation, et celle-ci tend à être fiable. Cette API consiste en un ensemble de méthodes et de propriétés disponibles pour chaque élément de formulaire.

Propriétés de l'API de contraintes de validation

Propriété Description
validationMessage Un message (dans la langue locale) qui décrit les contraintes de validation que le contrôle ne satisfait pas (s'il y en a), ou une chaîne vide si le contrôle n'est pas soumis à validation (willValidate est alors à false), ou alors la valeur de l'élément satisfait ses contraintes.
validity Un objet ValidityState qui décrit l'état de validité de l'élément.
validity.customError Retourne true si l'élément à une erreur personnalisée, false dans le cas contraire.
validity.patternMismatch Retourne true si la valeur de l'élément ne correspond pas au motif fourni, false dans le cas contraire.

Si cela retourne true, l'élément possèdera la pseudo-classe CSS :invalid.
validity.rangeOverflow Retourne true si la valeur de l'élément est plus grande que le maximum défini, false dans le cas contraire.

Si cela retourne true, l'élément possèdera les pseudo-classes CSS :invalid et :out-of-range.
validity.rangeUnderflow Retourne true si la valeur de l'élément est plus petite que le minimum défini, false dans le cas contraire.

Si cela retourne true, l'élément possèdera les pseudo-classes CSS :invalid et :out-of-range.
validity.stepMismatch Retourne true si la valeur de l'élément ne respecte pas les règles définies par l'attribut step,false dans le cas contraire.

Si cela retourne true, l'élément possèdera les pseudo-classes CSS :invalid et :out-of-range.
validity.tooLong Retourne true si la valeur de l'élément est plus longue que la longueur maximum définie, false dans le cas contraire.

Si cela retourne true, l'élément possèdera les pseudo-classes CSS :invalid et :out-of-range.
validity.typeMismatch Retourne true si la syntaxe de la valeur de l'élément n'est pas correcte ; false dans le cas contraire.

Si cela retourne true, l'élément possèdera la pseudo-classe CSS :invalid.
validity.valid Retourne true si la valeur de l'élément n'a pas de problème de validation, false dans le cas contraire.

Si cela retourne true, l'élément possèdera la pseudo-classe CSS :valid ou la pseudo-classe CSS :invalid dans le cas contraire.
validity.valueMissing Retourne true si l'élément n'a pas de valeur mais est un champ requis, false dans le cas contraire.

Si cela retourne true, l'élément possèdera la pseudo-classe CSS :invalid.
willValidate Retourne true si l'élément est validé lorsque le formulaire est soumis, false dans le cas contraire.

Méthodes de l'API de contraintes de validation

Méthode Description
checkValidity() Retourne true si la valeur de l'élément n'a pas de problème de validation, false dans le cas contraire. Si l'élément est invalide, cette méthode déclenche aussi un événement invalid sur cet élément.
setCustomValidity(message) Ajoute un message d'erreur personnalisé à l'élément; Si vous définissez un message d'erreur personnalisé, l'élément est considéré comme invalide, et le message spécifié est affiché. Cela vous permet d'utiliser du code JavaScript pour établir une erreur de validation autre que celles offertes par l'API standard des contraintes de validation. Le message est affiché à l'utilisateur lorsque le problème est rapporté.

Si l'argument est une chaîne de caractères vide, l'erreur personnalisée est considérée comme effacée.

Pour les anciens navigateurs, il existe une prothèse d'émulation (polyfill), comme H5F, pour compenser le manque de support de cette API. Comme vous utilisez déjà JavaScript, l'utilisation d'un polyfill n'est pas une charge supplémentaire pour la conception ou l'implémentation de votre site ou application Web.

Exemple d'utilisation de l'API de contraintes de validation

Voyons comment utiliser l'API pour créer des messages d'erreur personnalisés. Tout d'abord, le HTML :

<form novalidate>
  <p>
    <label for="mail">
      <span>Veuillez saisir une adresse e-mail :</span>
      <input type="email" id="mail" name="mail">
      <span class="error" aria-live="polite"></span>
    </label>
  <p>
  <button>Envoyer</button>
</form>

Ce formulaire simple utilise l'attribut novalidate pour désactiver la validation automatique par le navigateur ; cela permet donc à notre script d'avoir le contrôle sur la validation. Toutefois, cela ne désactive le support de l'API des contraintes de validation ni l'application des pseudo-classes CSS  :valid, :invalid, :in-range and :out-of-range. Cela signifie que, même si le navigateur ne vérifie pas automitiquement la validité du formulaire avant l'envoi des données, vous pouvez toujours effectuer cette validation et définir l'apparence du formulaire par vous-même.

L'attribut aria-live garantit que nos messages d'erreur personnalisés seront affichés à tout le monde, y compris les personnes utilisant des technologies d'assistance comme des lecteurs d'écran.

CSS

Ce CSS définit le style de notre formulaire et des messages d'erreur pour les rendre plus attrayants.

/* Voici quelques règles pour améliorer la mise en forme */
body {
  font: 1em sans-serif;
  padding: 0;
  margin : 0;
}

form {
  max-width: 200px;
}

p * {
  display: block;
}

input[type=email]{
  -webkit-appearance: none;

  width: 100%;
  border: 1px solid #333;
  margin: 0;

  font-family: inherit;
  font-size: 90%;

  -moz-box-sizing: border-box;
  box-sizing: border-box;
}

/* Voici les règles à appliquer aux champs invalides */
input:invalid{
  border-color: #900;
  background-color: #FDD;
}

input:focus:invalid {
  outline: none;
}

/* Voici la mise en forme pour les erreurs */
.error {
  width  : 100%;
  padding: 0;
 
  font-size: 80%;
  color: white;
  background-color: #900;
  border-radius: 0 0 5px 5px;
 
  -moz-box-sizing: border-box;
  box-sizing: border-box;
}

.error.active {
  padding: 0.3em;
}
JavaScript

Le code JavaScript suivant gère la validation personnalisée des erreurs.

// Il y a plusieurs façon de sélectionner un nœud DOM, ici on récupère
// le formulaire et le champ d'e-mail ainsi que l'élément span
// dans lequel on placera le message d'erreur

var form  = document.getElementsByTagName('form')[0];
var email = document.getElementById('mail');
var error = document.querySelector('.error');

email.addEventListener("keyup", function (event) {
  // Chaque fois que l'utilisateur saisit quelque chose
  // on vérifie la validité du champ e-mail.
  if (email.validity.valid) {
    // S'il y a un message d'erreur affiché et que le champ
    // est valide, on retire l'erreur
    error.innerHTML = ""; // On réinitialise le contenu
    error.className = "error"; // On réinitialise l'état visuel du message
  }
}, false);
form.addEventListener("submit", function (event) {
  // Chaque fois que l'utilisateur tente d'envoyer les données
  // on vérifie que le champ email est valide.
  if (!email.validity.valid) {
    
    // S'il est invalide, on affiche un message d'erreur
    error.innerHTML = "Veuillez saisisr une adresse e-mail correcte SVP.";
    error.className = "error active";
    // Et on empêche l'envoi des données du formulaire
    event.preventDefault();
  }
}, false);

Voici le résultat:

L'API des contraintes de validation fournit un outil puissant pour gérer la validation des formulaires, en vous laissant un contrôle sur l'interface utilisateur bien supérieur à ce que vous auriez pu avoir avec uniquement HTML et CSS. 

Valider des formulaires sans API intégrée

Il arrive parfois, comme c'est le cas avec des navigateurs anciens ou de widgets personnalisés, de ne pas pouvoir (ou vouloir) utiliser l'API des contraintes de validation. Dans ce cas, vous pourrez toujours utiliser JavaScript pour valider votre formulaire. Valider un formulaire est plus une question d'interface utilisateur que de réelle validation des données.

Pour valider un formulaire, vous devez vous poser un certain nombre de questions:

Quel type de validation devrais-je réaliser?
Vous devez déterminer comment valider vos données: opération sur des chaînes de caractères, conversion de type, expressions rationnelles, etc. C'est comme vous voulez. Mais retenez simplement que les données de formulaire sont toujours du texte et sont toujours fournies à vos scripts sous forme de chaînes de caractères.
Que devrais-je faire si le formulaire n'est pas valide?
C'est clairement une affaire d'interface utilisateur. Vous devez décider comment le formulaire doit se comporter : enverra-t-il quand même les données ? Devriez-vous mettre en évidence les champs qui sont en erreur ? Devriez-vous afficher des messages d'erreur ?
Comment puis-je aider l'utilisateur à corriger ses données invalides?
Pour limiter la frustration de l'utilisateur, il est très important de fournir autant d'informations que nécessaires pour le guider dans la correction de sa saisie. Vous devriez afficher des suggestions en amont pour que l'utilisateur sache ce qui est attendu, ainsi que des messages d'erreur clairs. Si vous souhaitez vous plonger dans les exigences d'interface utilsateur pour la validation de formulaires, voici quelques articles (en anglais) utiles que vous devriez lire :

Exemple qui n'utilise pas l'API de contraintes de validation

Afin d'illustrer le propos, réécrivons le précédent exemple afin qu'il fonctionne avec d'anciens navigateurs:

<form>
  <p>
    <label for="mail">
        <span>Veuillez saisir une adresse e-mail :</span>
        <input type="text" class="mail" id="mail" name="mail">
        <span class="error" aria-live="polite"></span>
    </label>
  <p>
  <!-- Certains navigateurs historiques ont besoin de l'attribut
       `type` avec la valeur `submit` sur l'élément `button` -->
  <button type="submit">Envoyer</button>
</form>

Comme vous pouvez voir, le HTML est quasiment identique; nous avons juste enlevé les parties HTML5. Notez que comme ARIA est une spécification indépendante qui n'est pas spécifiquement liée à HTML5, nous l'avons laissée ici.

CSS

De même, nous n'avons pas eu à changer radicalement le CSS; nous avons simplement transformé la pseudo-classe :invalid en une vraie classe et évité d'utiliser le sélecteur d'attribut, qui ne marche pas avec Internet Explorer 6.

/* On améliore l'aspect de l'exemple avec ces quelques règles */
body {
  font: 1em sans-serif;
  padding: 0;
  margin : 0;
}

form {
  max-width: 200px;
}

p * {
  display: block;
}

input.mail {
  -webkit-appearance: none;

  width: 100%;
  border: 1px solid #333;
  margin: 0;

  font-family: inherit;
  font-size: 90%;

  -moz-box-sizing: border-box;
  box-sizing: border-box;
}

/* Voici les règles de mise en forme pour les champs invalides */
input.invalid{
  border-color: #900;
  background-color: #FDD;
}

input:focus.invalid {
  outline: none;
}

/* Voici les règles utilisées pour les messages d'erreur */
.error {
  width  : 100%;
  padding: 0;
 
  font-size: 80%;
  color: white;
  background-color: #900;
  border-radius: 0 0 5px 5px;
 
  -moz-box-sizing: border-box;
  box-sizing: border-box;
}

.error.active {
  padding: 0.3em;
}
JavaScript

Les changements les plus importants sont dans le code JavaScript, qui nécessite bien plus que de simples retouches.

// Il existe moins de méthode pour sélectionner un nœud DOM
// avec les navigateurs historiques
var form  = document.getElementsByTagName('form')[0];
var email = document.getElementById('mail');

// Ce qui suit est une bidouille pour atteindre le prochain nœud Element dans le DOM
// Attention à cette méthode, elle peut permettre de construire une boucle
// infinie. Pour les navigateurs plus récents, on utilisera element.nextElementSibling
var error = email;
while ((error = error.nextSibling).nodeType != 1);

// Pour respecter la spécification HTML5
var emailRegExp = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/;

// De nombreux navigateurs historiques ne supportent pas la méthode
// addEventListener. Voici une méthode simple (il en existe d'autres)
// pour mitiger ce problème.
function addEvent(element, event, callback) {
  var previousEventCallBack = element["on"+event];
  element["on"+event] = function (e) {
    var output = callback(e);
    
    // Une fonction de rappel (callback) qui renvoie `false`
    // pour arrêter la chaîne des callback
    // et interrompre l'exécution du callback d'événement.
    if (output === false) return false;

    if (typeof previousEventCallBack === 'function') {
      output = previousEventCallBack(e);
      if(output === false) return false;
    }
  }
};

// On peut désormais reconstruire notre contrainte de validation
// Étant donné qu'on n'utilise pas la pseudo-classe CSS, il faut
// explicitement gérer la classe valid/invalid du champ e-mail
addEvent(window, "load", function () {
  // Ici, on teste si le champ est vide (rappel : le champ n'est pas obligatoire)
  // S'il ne l'est pas, on vérifie que son contenu est une adresse e-mail valide.
  var test = email.value.length === 0 || emailRegExp.test(email.value);
 
  email.className = test ? "valid" : "invalid";
});

// Ici, on définit ce qui se passe lorsque l'utilisateur
// saisit quelque chose dans le champ
addEvent(email, "keyup", function () {
  var test = email.value.length === 0 || emailRegExp.test(email.value);
  if (test) {
    email.className = "valid";
    error.innerHTML = "";
    error.className = "error";
  } else {
    email.className = "invalid";
  }
});

// Ici, on définit ce qui se passe lorsque l'utilisateur
// tente d'envoyer les données du formulaire
addEvent(form, "submit", function () {
  var test = email.value.length === 0 || emailRegExp.test(email.value);
 
  if (!test) {
    email.className = "invalid";
    error.innerHTML = "Merci d'écrire une adresse e-mail valide.";
    error.className = "error active";

    // Certains navigateurs historiques ne supportent pas 
    // la méthode event.reventDefault()
    return false;
  } else {
    email.className = "valid";
    error.innerHTML = "";
    error.className = "error";
  }
});

Voici le résultat:

Comme vous avez pu le voir, il n'est pas si difficile de créer par soi-même un système de validation. La difficulté consiste à rendre le tout assez générique pour l'utiliser à la fois sur toutes les plateformes et pour chaque formulaire que vous pourriez créer. Il existe de nombreuses bibliothèques permettant ce genre de validation de formulaire ; n'hésitez pas à les utiliser. En voici quelques exemples :

Validation à distance

Il peut être utile, dans certains cas, d'effectuer une validation à distance. Ce genre de validation est nécessaire lorsque les données saisies par l'utilisateur sont liées à des données supplémentaires stockées sur le serveur hébergeant votre application. Prenons par exemple les formulaires d'inscription, pour lesquels on vous demande un nom d'utilisateur. Pour éviter toute duplication d'un nom d'utilisateur, il est plus judicieux d'effectuer une requête AJAX pour vérifier la disponibilté du nom d'utilisateur que de demander à envoyer les données saisies et de renvoyer le formulaire avec une erreur.

Pour réaliser une telle validation, plusieurs précautions doivent être prises :

  • Il est nécessaire d'exposer une API et des données ; assurez-vous que ces données ne soient pas critiques.
  • Un décalage (lag) du réseau nécessite une validtion asynchrone. L'interface utilisateur doit être conçue de façon à pas être bloquée si cette validation n'est pas réalisée correctement.

Conclusion

La validation d'un formulaire ne requiert pas de code JavaScript complexe, mais il est nécessaire de penser tout particulièrement à l'utilisateur. Rappelez-vous de toujours aider l'utilisateur à corriger les données qu'il saisit. Pour ce faire, assurez-vous de toujours :

  • Afficher des messages d'erreur explicites.
  • Être permissif quant au format des données à envoyer.
  • Indiquer exactement où se situe l'erreur (tout particulièrement pour les formulaires plutôt longs).

Étiquettes et contributeurs liés au document

 Contributeurs à cette page : SphinxKnight, HereComesJuju
 Dernière mise à jour par : SphinxKnight,