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 typeemail
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 |
<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 |
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
|
min
|
range , number |
Un nombre valide | La valeur du champ doit être supérieure ou égale à la valeur de l'attribut. | rangeUnderflow |
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 |
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
|
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
|
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
|
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
|
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()
oureportValidity()
depuis une instance d'une interface du DOM correspondant à un élément de formulaire, (HTMLInputElement
,HTMLSelectElement
,HTMLButtonElement
,HTMLOutputElement
ouHTMLTextAreaElement
). 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éthodecheckValidity()
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éthodereportValidity()
renvoie quant à elle le détail des contraintes qui ne sont pas respectées. - On appelle la méthode
checkValidity()
oureportValidity()
de l'objetHTMLFormElement
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 objetHTMLFormElement
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éthodeclick()
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 :
<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 :
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>
:
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 :
<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 :
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 :
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 typesubmit
ou avec un élément<input>
de typesubmit
. Les autres types de bouton ne contribuent pas à la validation des contraintes.) <textarea>
-
L'interface
ValidityState
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.