Validation des contraintes

La création de formulaires web a toujours été une tâche complexe. Bien que le balisage du formulaire en lui-même soit plutôt simple, c'est la vérification de la validité et de la cohérence des valeurs de chaque champ qui s'avère difficile. Informer l'utilisatrice ou l'utilisateur à propos de la validité (ou de l'invalidité) des champs est parfois un casse-tête. HTML5 introduit de nouveaux mécanismes pour les formulaires : de nouveaux types sémantiques pour <input> et la validation des contraintes pour simplifier la vérification du contenu d'un formulaire côté client. Les contraintes de base usuelles peuvent être vérifiées sans recourir à JavaScript à l'aide de nouveaux attributs. Des contraintes plus complexes peuvent être testées à l'aide de l'API Constraint Validation.

Pour une introduction à ces concepts avec des exemples, voir le tutoriel sur la validation des formulaires.

Note : La validation des contraintes HTML ne signifie pas qu'il n'est plus nécessaire de vérifier côté serveur. Même si cela réduit les risques d'envoi de formulaires invalides, des acteurs malveillants pourraient passer outre ces vérifications côté client. Aussi, assurez-vous de toujours valider les contraintes de saisie côté serveur, en étant cohérent avec ce qui est fait côté client.

Contraintes intrinsèques et contraintes de base

En HTML, les contraintes de base peuvent être déclarées de deux façons :

  • En choisissant la valeur sémantique la plus appropriée pour l'attribut type de l'élément <input>. Ainsi, choisir le type email créera automatiquement une contrainte vérifiant que la valeur est une adresse électronique valide.
  • En définissant des valeurs pour les attributs relatifs à la validation qui permettent de décrire des contraintes simplement, sans avoir besoin de JavaScript.

Types de champs

Les contraintes intrinsèques portées par l'attribut type sont :

Type de champ Description de la contrainte Violation correspondante
<input type="URL"> La valeur doit être une URL absolue, telle que définie dans le standard évolutif URL. TypeMismatch (en-US)
<input type="email"> La valeur doit être une adresse électronique avec une syntaxe valide (généralement au format nom@domaine.tld ou nom@domaine). TypeMismatch (en-US)

Pour ces deux types de champ, si l'attribut multiple est utilisé, plusieurs valeurs peuvent être passées dans le champ sous la forme d'une liste séparée par des virgules. Si au moins une des valeurs ne respecte pas les conditions décrites ici, la violation de contrainte TypeMismatch est déclenchée.

On notera que la plupart des types de champ n'ont pas de contraintes intrinsèques : soit il n'y a pas de contrainte particulière, soit le navigateur applique un algorithme de transformation pour que les valeurs incorrectes utilisent une valeur par défaut correcte.

Attributs relatifs à la validation

En complément de l'attribut type mentionné ci-avant, les attributs suivants permettent de décrire des contraintes basiques :

Attribut Types de champ prenant en charge cet attribut Valeurs possibles Description de la contrainte Violation correspondante
pattern text, search, url, tel, email, password Une expression rationnelle JavaScript (compilée avec les marqueurs global, ignoreCase, et multiline désactivés). La valeur doit correspondre au motif décrit par l'expression. patternMismatch (en-US)
min range, number Un nombre valide La valeur du champ doit être supérieure ou égale à la valeur de l'attribut. rangeUnderflow (en-US)
date, month, week Une date valide
datetime-local, time Un horodatage valide
max range, number Un nombre valide La valeur du champ doit être inférieure ou égale à la valeur de l'attribut. rangeOverflow (en-US)
date, month, week Une date valide
datetime-local, time Un horodatage valide
required text, search, url, tel, email, password, date, datetime-local, month, week, time, number, checkbox, radio, file. Également utilisable sur les éléments <select> et <textarea>. Aucune valeur, il s'agit d'un attribut booléen : sa présence indique que la valeur est requise et son absence indique que la valeur est facultative. Si l'attribut est présent, il doit y avoir une valeur. valueMissing (en-US)
step date Un nombre entier (qui exprime des jours) À moins que l'attribut vaille any, la valeur devra être min + un multiple entier de l'incrément. stepMismatch (en-US)
month Un nombre entier de mois
week Un nombre entier de semaines
datetime-local, time Un nombre entier de secondes
range, number Un entier
minlength text, search, url, tel, email, password. Également disponible sur l'élément <textarea>. Une longueur entière Le nombre de caractères, exprimé en points de code, ne doit pas être inférieur à la valeur de l'attribut si ce dernier n'est pas vide. Pour l'élément <textarea>, les passages à la ligne sont normalisés en un seul caractère (contrairement aux paires CRLF). tooShort (en-US)
maxlength text, search, url, tel, email, password. Également disponible sur l'élément <textarea>. Une longueur entière Le nombre de caractères, exprimé en points de code, ne doit pas dépasser la valeur de l'attribut. tooLong (en-US)

Processus de validation des contraintes

En complément de la validation native effectuée par le navigateur, on peut manipuler la validation des contraintes en JavaScript à l'aide de l'API Constraint Validation, sur un élément du formulaire ou sur le formulaire (<form>). La validation des contraintes a lieu quand :

  • On appelle la méthode checkValidity() ou reportValidity() depuis une instance d'une interface du DOM correspondant à un élément de formulaire, (HTMLInputElement, HTMLSelectElement, HTMLButtonElement, HTMLOutputElement (en-US) ou HTMLTextAreaElement (en-US)). Dans ce cas, seules les contraintes de l'élément correspondant sont évaluées et permettent au script d'obtenir l'état de validité. La méthode checkValidity() renvoie un booléen qui indique si la valeur de l'élément respecte les contraintes (c'est généralement ce qui est fait par l'agent utilisateur pour déterminer quelle pseudo-classe CSS s'applique entre :valid et :invalid). La méthode reportValidity() renvoie quant à elle le détail des contraintes qui ne sont pas respectées.
  • On appelle la méthode checkValidity() ou reportValidity() de l'objet HTMLFormElement correspondant au formulaire.
  • On envoie le formulaire.

On qualifie parfois un appel à checkValidity() de validation statique des contraintes, en opposition à reportValidity() ou à l'envoi du formulaire qui constituent une validation interactive.

Note :

  • Si l'attribut novalidate est placé sur l'élément <form>, la validation interactive des contraintes n'a pas lieu.
  • Appeler la méthode submit() d'un objet HTMLFormElement ne déclenchera pas de validation des contraintes. Autrement dit, cette méthode envoie les données du formulaire au serveur, même si elles ne respectent pas les contraintes. Pour passer par la validation, on pourra appeler la méthode click() du bouton d'envoi.

Implémenter des contraintes complexes à l'aide de l'API

Grâce à JavaScript et à l'API Constraint Validation, on peut implémenter des contraintes plus complexes, qui portent par exemple sur plusieurs champs à la fois ou qui impliquent des calculs avancés.

Le principe consiste à déclencher une fonction JavaScript lorsqu'un évènement d'un champ de formulaire a lieu (par exemple change) et de calculer à ce moment si la contrainte est respectée ou non puis d'utiliser field.setCustomValidity() pour fournir le résultat de la validation : une chaîne vide indiquera que la contrainte est respectée et n'importe quelle autre chaîne indiquera une erreur et c'est alors cette chaîne de caractères qui sera affichée à l'utilisatrice ou à l'utilisateur.

Exemple de contrainte portant sur plusieurs champs : validation du code postal

Le format utilisé pour les codes postaux varie d'un pays à l'autre. Certains pays autorisent un préfixe avec le code du pays (comme D- en Allemagne, F- en France, etc.), d'autres ont des codes postaux avec un nombre précis de chiffres et d'autres encore, comme au Royaume-Uni, ont des structures plus complexes, où on peut avoir des lettres à certaines positions.

Note : Ce qui suit ne constitue pas une bibliothèque exhaustive de validation des codes postaux, il ne s'agit que d'un exemple.

Pour cet exemple, nous allons ajouter un script de vérification pour ce formulaire :

html
<form>
  <label for="ZIP">Code postal : </label>
  <input type="text" id="ZIP" />
  <label for="Country">Pays : </label>
  <select id="Country">
    <option value="ch">Suisse</option>
    <option value="fr">France</option>
    <option value="de">Allemagne</option>
    <option value="nl">Pays-Bas</option>
  </select>
  <input type="submit" value="Valider" />
</form>

Ce fragment HTML affiche le formulaire suivant :

Pour commencer, on écrit une fonction qui vérifie la contrainte :

js
function checkZIP() {
  // Pour chaque pays, on définit le motif que doit suivre le code
  const constraints = {
    ch: [
      "^(CH-)?\\d{4}$",
      "Les codes postaux suisses ont 4 chiffres : par exemple CH-1950 ou 1950",
    ],
    fr: [
      "^(F-)?\\d{5}$",
      "Les codes postaux français ont 5 chiffres : par exemple F-75012 ou 75012",
    ],
    de: [
      "^(D-)?\\d{5}$",
      "Les codes postaux allemands ont 5 chiffres : par exemple D-12345 ou 12345",
    ],
    nl: [
      "^(NL-)?\\d{4}\\s*([A-RT-Z][A-Z]|S[BCE-RT-Z])$",
      "Les codes postaux néerlandais ont 4 chiffres, suivi par deux lettres sauf SA, SD et SS",
    ],
  };

  // On récupère l'identifiant du pays
  const country = document.getElementById("Country").value;

  // On récupère le champ du code postal
  const ZIPField = document.getElementById("ZIP");

  // On construit le validateur pour la contrainte
  const constraint = new RegExp(constraints[country][0], "");
  console.log(constraint);

  // On vérifie la valeur par rapport à la contrainte !
  if (constraint.test(ZIPField.value)) {
    // Le code postal respecte la contrainte, on communique ce résultat via l'API
    ZIPField.setCustomValidity("");
  } else {
    // Le code postal ne respecte pas la contrainte, on envoie un message
    // via l'API pour fournir des informations sur le format attendu
    ZIPField.setCustomValidity(constraints[country][1]);
  }
}

Ensuite, on ajoute des gestionnaires d'évènements pour l'évènement change du champ <select> et pour l'évènement input de l'élément <input> :

js
window.onload = () => {
  const countrySelect = document.getElementById("Country");
  const zipInput = document.getElementById("ZIP");
  countrySelect.addEventListener("change", checkZIP);
  zipInput.addEventListener("input", checkZIP);
};

Limiter la taille d'un fichier avant son envoi

Une autre contrainte fréquemment rencontrée consiste à appliquer une limite sur la taille d'un fichier à téléverser. Pour vérifier cela côté client avant d'envoyer le fichier, nous allons combiner l'API Constraint Validation (notamment la méthode field.setCustomValidity()), avec une autre API, l'API File.

Voici le fragment HTML utilisé pour l'exemple :

html
<label for="FS">Veuillez choisir un fichier qui ne dépasse pas 75ko : </label>
<input type="file" id="FS" />

Cela donnera le formulaire suivant :

Pour le code JavaScript, on lit le fichier sélectionné avec la méthode File.size() pour obtenir sa taille et on compare cette valeur avec la limite (ici codée en dur), puis on appelle l'API de validation pour indiquer au navigateur si la contrainte est respectée :

js
function checkFileSize() {
  const FS = document.getElementById("FS");
  const files = FS.files;

  // S'il y a (au moins) un fichier sélectionné
  if (files.length > 0) {
    if (files[0].size > 75 * 1024) {
      // La contrainte n'est pas respectée
      FS.setCustomValidity("Le fichier sélectionné ne doit pas dépasser 75ko.");
      return;
    }
  }
  // La contrainte spécifique est bien respectée
  FS.setCustomValidity("");
}

Pour finir, on attache cette méthode au gestionnaire d'évènement correspondant :

js
window.onload = () => {
  const fsInput = document.getElementById("FS");
  fsInput.addEventListener("change", checkFileSize);
};

Mise en forme visuelle pour la validation des contraintes

En plus de définir des contraintes, lors du développement, on voudra contrôler la façon dont les contraintes sont communiquées aux utilisatrices et utilisateurs : quels messages sont utilisés et quelle mise en forme est appliquée pour les champs valides/invalides.

Contrôler l'aspect des éléments

L'aspect des éléments peut être personnalisé grâce aux pseudo-classes CSS suivantes.

:required et :optional

Les pseudo-classes :required et :optional permettent d'écrire des sélecteurs pour cibler les éléments qui ont ou non l'attribut required.

:placeholder-shown

Voir :placeholder-shown.

:valid et :invalid

Les pseudo-classes :valid et :invalid sont utilisées pour représenter des éléments <input> dont le contenu est valide (respectivement invalide) par rapport au type de champ. Ces classes permettent de mettre en forme les éléments de formulaire valides ou invalides afin d'en faciliter l'identification.

Contrôler le texte utilisé pour la validation des contraintes

Plusieurs outils peuvent vous aider à contrôler le texte utilisé pour indiquer une erreur de validation :

  • La méthode setCustomValidity(message) pour les éléments suivants :
    • <fieldset>. Note : fournir un message d'invalidité personnalisé pour les éléments <fieldset> n'empêchera pas l'envoi du formulaire dans la plupart des navigateurs.
    • <input>
    • <output>
    • <select>
    • Les boutons d'envoi (créés avec un élément <button> de type submit ou avec un élément <input> de type submit. Les autres types de bouton ne contribuent pas à la validation des contraintes.)
    • <textarea>
  • L'interface ValidityState (en-US) décrit l'objet renvoyé par la propriété validity des types d'éléments listés ci-avant. Elle représente différentes façons selon lesquelles une valeur saisie peut être invalide. Avec la méthode précédente, elle permet d'expliquer la raison pour laquelle la valeur d'un champ est invalide.