Validation non-obstrusive de formulaire côté client par Javascript

Catégorie
Programmation
Tags
accessibilité cours html javascript liens programmation

Il y a quelques temps de cela, je publiais sur le (défunt ?) site du dernier rang – une association de malfaiteurs officiant au fond des salles de cours durant notre formation, avec laquelle je n’ai absolument rien à voir – un article d’introduction à la validation de formulaire par Javascript. Introduction, puisque nombre de librairies font mieux pour moins d’efforts, l’objectif ici étant de comprendre les problématiques sous-jacentes aux mécanismes dynamiques du web, et d’aborder par la même occasion la question de l’accessibilité. Oui, je sais, vous avez vérifié déjà deux fois si vous étiez bien sur mon blog, je ne vous ai définitivement pas habitué à ça.

Le site du dernier rang étant dans un coma profond, voici l’article en question, au titre pompeux et à la plus faible teneur en alcool des billets de ce blog !


Sur le web, il est fréquent d’avoir recours aux formulaires, aussi bien pour mener une recherche que pour entrer et modifier des informations. Les données saisies par vos visiteurs sont dans ce cas mises en relation avec la partie applicative de votre site, l’exposant à certaines failles ou défauts de fonctionnement. Ce caractère critique implique une vérification de toutes les données saisies par vos utilisateurs, aussi bien pour éviter les maladresses que pour déjouer les tentatives d’intrusions ou d’altération du système.

La vérification des données côté serveur a principalement attrait à la sécurité de votre application1. Cependant, cette méthode n’est pas adaptée pour assister vos visiteurs dans leur utilisation de votre formulaire, du fait de la latence des allers-retours entre client et serveur, mais également par le manque de détails et la nature des informations retournées en cas d’échec. C’est là qu’intervient la validation de formulaires côté client, grâce à l’utilisation de Javascript.

Gardez à l’esprit que la validation des données côté client ne doit en aucun cas suppléer à la vérification au sein même de votre application. L’utilisateur peut désactiver le Javascript, et un visiteur malveillant peut soumettre ses données corrompues via un formulaire de sa conception.

Enfin, il est plus que probable que vous trouviez des méthodes de validation bien plus efficaces au sein de librairies Javascript telles que script.aculo.us ou jQuery. L’objectif de cet article est avant tout pédagogique.

Concevoir un bon formulaire

Notre objectif est d’enrichir et de faciliter les interactions de nos utilisateurs avec notre site, et si la validation des données côté client constitue l’objectif de cet article, il est important de partir sur de bonnes bases. Votre formulaire doit être accessible2, simple3, et idéalement agréable à l’œil.

Le processus de validation côté client passe par l’utilisation de Javascript. Cependant, tous les internautes ne disposent pas de cette technologie, et certains s’en affranchissent volontairement. Les robots (Google en tête) entrent dans cette catégorie également. La conclusion est simple : pour être accessible à tous, le fonctionnement de votre formulaire ne doit en aucun cas dépendre de la présence de Javascript.

Les différentes données à saisir n’ont pas toutes la même signification ni la même valeur, aussi bien pour l’internaute que pour vous, webmaster. Il est important pour clarifier le remplissage du formulaire de grouper et hiérarchiser les informations. Faites apparaître les champs obligatoires en premier, et mettez les en valeur.

Enfin, n’ayez pas peur d’être verbeux quant à l’utilisation des différents champs. Quelques explications permettront à l’utilisateur de cibler directement votre demande, et un formulaire bien rempli vous évitera une validation inutile.

Pour résumer, trois lignes de conduites simples à adopter dans la conception de vos formulaires :

  • développez d’abord sans Javascript,
  • groupez et mettez en valeur les champs,
  • n’ayez pas peur d’expliquer ce que vous attendez de vos utilisateurs.

J’ai choisi à titre d’exemple un cas très courant, à savoir une inscription en ligne. Celui-ci ne comporte aucun élément dynamique. Les champs ont été groupés selon trois thèmes à savoir les données utilisateur, ses données personnelles et d’autres informations cette fois dédiées au webmaster. J’utilise la balise fieldset sémantiquement adaptée.

Pour mettre en avant la mise en valeur des champs, prenons l’exemple du nom d’utilisateur et de l’extrait de code correspondant :


<p>
<label for="user" class="required" >Nom d'utilisateur&nbsp;:</label>
<input type="text" id="user" name="user" class="required alphanum" size="15" maxlength="50" />
<span class="hints">
<span class="constraint">Peut contenir lettres et chiffres.</span>
<span class="example">Ex.&nbsp;: JamesBond007</span>
</span>
</p>

Le label précise l’intitulé du champ, si vous ne devez donner qu’une indication, c’est celle-là. J’y adjoins ici la classe CSS required (d’un usage différent lorsqu’elle est apposée sur l’un des champs, comme nous le verrons plus loin), qui présente le texte associé en gras et y ajoute un astérisque4. À cela s’ajoute des indications sur les contraintes appliquées sur ce champ, aussi bien sur la vérification côté serveur que sur notre futur système. Ces indications sont groupées dans la classe hints qui contient à la fois les éléments constraints (le détail des contraintes appliquées sur le champ, comme une longueur minimale) et example (un exemple, toujours plus parlant qu’un long discours). N’hésitez pas à consulter le code source de l’exemple, aussi bien pour le HTML que pour la CSS afin d’obtenir le détail des autres champs.

Vérifier les données côté client

Passons à la validation des données côté client, qui représente le gros du sujet. Plusieurs techniques peuvent être utilisées dans ce but (voir le paragraphe sur les différentes méthodes de validation), nous exploiterons ici la technique des classes CSS avant tout pour sa simplicité de mise en œuvre.

Avant toute chose, vous pouvez tester le formulaire d’exemple, cette fois-ci avec la partie Javascript. N’hésitez pas à jouer avec les différents contrôles.

Les champs de votre formulaire doivent être marqués par une ou des classes qui représentent les différentes contraintes, comme par exemple required (le champ est obligatoire) ou alphanum (ne peut être constitué que de caractères alphanumériques). L’analyse des classes permet ainsi d’appeler les différentes fonctions de validation. Voyons plus en détail la procédure.

Au chargement de la page, on associe à chaque champ la fonction de validation elementValidation, appelée lorsque l’utilisateur quitte le champ (événement onblur). On précise de plus que lorsque le formulaire est soumis (événement onsubmit), on appelle sur chacun de ces champs la fonction de validation. Si l’une des validations échoue, la validation du formulaire renvoi false, la cible de l’attribut action (typiquement votre prise en charge du formulaire côté serveur) n’est donc pas appelée. C’est ce principe qui garantie que la validation côté serveur n’intervient que lorsqu’elle est effective côté client.

Voyons le code, premièrement la fonction appelée au chargement de la page :


window.onload = function() {
// On cible ici le seul formulaire de la page, rien n'empêche d'appliquer cette méthode sur plusieurs formulaires.
var form = document.forms['signup_form'];
// Lorsque le formulaire est soumis, on appelle la méthode formValidation sur celui-ci.
form.onsubmit = formValidation;

// Pour chacun des champs du formulaire, on appelle la fonction elementValidation quand l'utilisateur quitte le champ.
var formElements = form.elements;
for (var i = formElements.length - 1 ; i >= 0 ; --i ) {
formElements[i].onblur = elementValidation;
}
}

Puis la fonction formValidation, déclenchée par soumission du formulaire :


function formValidation() {
// this cible le formulaire. On le considère valide par défaut.
this.isValid = true;
var formElements = this.elements;
for (var i = formElements.length - 1 ; i >= 0 ; --i ) {
// Pour chaque élément du formulaire, on appelle la fonction de validation.
elementValidation.call(formElements[i]);
}
return this.isValid;
}

Et enfin l’extrait du code de la fonction elementValidation qui met à jour la variable isValid du formulaire :


// Si un champ ne passe pas la validation, il met à false la variable isValid du formulaire auquel il est associé.
this.form.isValid = false;

Quand est-il de la validation d’un champ en elle-même ? Comme évoqué précédemment, elle se base sur l’utilisation des classes CSS associées à un champ. En fonction de celles-ci, on appelle différentes fonctions de vérification des données. Le système de validation par CSS expose ici son plus gros défaut puisqu’il est difficile d’attribuer une valeur variable comme paramètre d’une fonction de validation. Pour tenter de contrer ceci, on peut convenir d’un délimiteur qui dissocie le nom de la validation d’un éventuel paramètre.

Voyez plutôt un extrait du code de la fonction elementValidation :


function elementValidation() {

var errorMessage = "";
// On récupère les classes CSS associées à l'élément.
var classes = this.className.split(' ');
if (classes != "") {
for (var i = classes.length - 1 ; i >= 0 ; --i) {
// Pour chaque classe, on vérifie la présence d'éventuels paramètres à la fonction de validation.
// Le délimiteur arbitrairement choisi est le tiret haut.
var multipleParamClasses = classes[i].split('-');
switch (multipleParamClasses[0]) {

// La validation par longueur requière des paramètres.
case 'length':
if (this.value != '') {
switch (multipleParamClasses[1]) {
case 'min':
errorMessage += minLength(this.value, multipleParamClasses[2]);
break;
case 'max':
errorMessage += maxLength(this.value, multipleParamClasses[2]);
break;
default:
break;

On appelle ainsi en fonction des valeurs trouvées les fonctions de validation correspondantes. Dans notre exemple précédent, on trouve les fonction minLength et maxLength qui sont des validations basiques en fonction de la taille des données saisies dans le champ.

Le code de la fonction minLength :


function minLength(value, minLength) {
if (value.length < minLength) {
return "Ce champ doit comporter au minimum "+minLength+" caractères. ";
} else {
return "";
}
}

D’autres types de vérifications peuvent être mises en place, notamment au niveau du contrôle de la syntaxe des données. Ainsi, on peut vérifier que le champ ne contient que des lettres, pas d’espaces ou qu’une adresse mail est correctement formatée. On utilise pour cela des expressions régulières en fonction de notre besoin5.

Un exemple parlant est la fonction isMail :


function isMail(value) {
if (value.match(/^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$/i) == null) {
return "Cette adresse de messagerie est invalide. ";
} else {
return "";
}
}

Pour plus d’exemples, jetez un œil sur le script Javascript complet.

Informer l'utilisateur

Donner le détail des erreurs

Nous avons mis en place un système de validation automatique du formulaire, qui permet le cas échéant de refuser sa soumission au serveur si il ne répond pas à nos critères. Mais encore faut-il informer l’utilisateur de ce qui ne correspond pas dans ses données.

Les fonctions de validation retournent ou non un message d’erreur. C’est celui-ci que nous allons exploiter. Et plutôt que d’afficher une popup toujours désagréable pour l’utilisateur, nous allons modifier dynamiquement le contenu de la page pour afficher le détail de nos erreurs et mettre en valeur les champs concernés.

Pour chaque validation, nous allons donc procéder comme il suit :

  • supprimer les éventuels messages d’erreurs (précédentes validations),
  • valider le champ,
  • si le champ présente des erreurs, les afficher.

La modification dynamique du contenu d’une page passe par la manipulation du DOM6. Ainsi pour insérer un message d’erreur, on créé un noeud correspondant à un nouveau paragraphe, que l’on insère juste après le champ en question. Au niveau de l’arborescence de votre page, cela revient à ajouter un nœud enfant au nœud parent du champ.

Voyons plutôt le code de la fonction addError :


function addError(element, errorMessage) {
// Le noeud d'erreur ici un paragraphe, auquel on adjoint la classe CSS "error" pour une éventuelle mise en valeur.
var errorNode = document.createElement("p");
errorNode.className = "error";
var errorTextNode = document.createTextNode(errorMessage);
errorNode.appendChild(errorTextNode);
// On ajoute l'erreur comme dernier enfant du noeud parent au champ.
element.parentNode.appendChild(errorNode);
// On donne également au champ la classe CSS d'erreur.
element.className += " error";
}

À l’inverse, pour nettoyer la page du code superflu (l’erreur à été corrigée), on supprime le dernier nœud enfant du nœud parent du champ incriminé, si celui-ci est un message d’erreur.

Le code de removeError :


function removeError(element) {
// Si une erreur est présente, elle est le denrier noeud enfant du noeud parent du champ.
var lastNode = element.parentNode.lastChild;
// On contrôle que ce dernier noeud est un message d'erreur avant de supprimer.
if (lastNode.tagName == "P") {
if (lastNode.className == "error") {
element.parentNode.removeChild(lastNode);
}
}
// On supprime également le marquage du champ avec la classe CSS d'erreur.
element.className = element.className.replace(/(.*)error(.*)/, "$1$2");
}

Aides à la saisie

Si l’information à l’utilisateur passe avant tout par un report des erreurs à la saisie, il peut être judicieux d’améliorer son expérience avec votre site par d’autres moyens. J’ouvre ici une petite parenthèse à notre sujet pour donner un exemple basique d’aide à la saisie n’ayant pas attrait au contrôle d’erreur.

Le formulaire d’exemple contient un textfield, qui par défaut a pour valeur un texte incitant l’utilisateur à y poser ses remarques. Par défaut, ce dernier doit donc supprimer le texte d’aide avant de taper le sien, une démarche lourde pour un champ qui n’est de plus utile qu’au webmaster. Nous allons donc ajouter une fonction qui, lorsque l’utilisateur sélectionne le textfield (événement onfocus), vide le contenu si il est identique à celui par défaut.

Le code de la fonction en question est simpliste :


function blankValue() {
if (this.value == this.defaultValue) {
this.value = "";
}
}

Il n’y a pas d’attribut pour fixer la longueur maximale d’une entrée pour un textfield (comme le maxlength sur un input). Pour palier partiellement à cela, on ajoute un compteur du nombre de caractères présents dans le champ, qui s’incrémente à chaque frappe clavier (événement onkeyup).

On ajoute tout d’abord dynamiquement la base du compteur par le DOM, au chargement de la page (le nœud n’est pas présent sur la page par défaut pour ne pas être inutilement affiché si le Javascript est désactivé) :


function addCounter(element, maxValue) {
// Création du noeud compteur.
var counterNode = document.createElement("span");
counterNode.className = "counter";
var counterTextNode = document.createTextNode("/"+maxValue);
counterNode.appendChild(counterTextNode);
// Pour ajouter le noeud compteur, on l'enregistre comme dernier noeud enfant du noeud parent de l'élément à cibler.
element.parentNode.appendChild(counterNode);
charCount.call(element);
}

Enfin, le code de la fonction de comptage :


function charCount() {
var counter = 0;
if (this.value != null) {
counter = this.value.length;
}
var counterNode = this.parentNode.lastChild;
// On n'utilise pas ici le textContent (pas de prise en charge IE) ni innerHTML (pas DOM).
// Le texte en question est le premier (et unique) noeud enfant de notre noeud compteur.
var counterText = counterNode.firstChild.nodeValue;
counterText = counterText.replace(/\d*(\/\d+)/, counter+"$1");
counterNode.firstChild.nodeValue = counterText;
}

Là encore, n'hésitez pas à consulter le script Javascript complet. Vous avez testé le formulaire d’exemple ?

Voilà qui clos ce tutorial sur la validation de formulaire côté client. Le parti pris étant pédagogique, nous avons opté pour une méthode simple. Si vous souhaitez approfondir le sujet et décider quelle est la solution qui convient pour vos formulaires, le chapitre suivant dresse un bilan des techniques possibles.

Annexe : le point sur les méthodes de validation

Le point commun des méthodes de traitement de formulaire côté client est qu’elles dépendent de la présence des contraintes de validation dans le code source HTML.

Balises input hidden

Chaque champ utile du formulaire se voit associé à un champ masqué dont la valeur est la contrainte de validation. Pour valider un champ, on joue donc avec le DOM pour trouver son nœud successeur, qui nous renseigne sur les règles à appliquer.

  • Avantage : relative flexibilité.
  • Inconvénient : ajout de balisage inutile.

Classes CSS

C’est la méthode que nous avons utilisé pour notre exemple. Les règles tout comme d’éventuels paramètres sont ajoutés à la classe CSS de l’élément.

  • Avantages : pas de balisage inutile, générique.
  • Inconvénient : manque de flexibilité, notamment lors de l’utilisation d’arguments.

Attributs HTML personnalisés

Cette technique particulière consiste en l’adjonction des fonctions et arguments de validation directement dans le code de la balise. La récupération par le DOM est aisée, et les possibilités sont potentiellement importantes. Le code source HTML n’est cependant plus valide, ce qui peut poser quelques soucis lorsque l’environnement n’est pas parfaitement maîtrisé.

  • Avantages : grande souplesse et richesse d’utilisation, pas de balisage inutile.
  • Inconvénient : code invalide, nécessité d’utiliser une DTD personnalisée.

Utilisation de XmlHttpRequest

A la différence des méthodes précédentes, le processus de validation passe (dans le sens littéral du terme) par le serveur. Pour chaque champ, on appelle une fonction Javascript qui va chercher sur le serveur (AJAX, vous connaissez ?) non pas les informations de validation, mais bien la fonction entière (retournée en JSON par exemple, plus tout à fait de l’AJAX donc), telle qu’elle serait exécutée sur le serveur.

  • Avantages : la validation suit exactement les règles édictées par le serveur, facilité dans la mise à jour du code.
  • Inconvénient : la validation n’est pas uniquement un procédé côté client.
  1. La série d'articles Writing Secure PHP de Dave Child, bien que dédiée à ce langage, reste une introduction pragmatique à la sécurisation d'applications web en général. Pour plus de détails, vous pouvez consulter le site de l'Open Web Application Security Project.
  2. Le site OpenWeb donne un bon aperçu sur la conception de formulaires accessibles.
  3. Fred Cavazza donne ses lignes de conduite pour la constitution de tableaux et formulaires simples d’utilisation.
  4. Par le biais de la pseudo-classe before, qui n’est malheureusement pas prise en charge par IE6.
  5. À propos des expressions régulières, et particulièrement celle employée ici pour valider une adresse mail, les lecteurs pointus signaleront que cette expression n’est pas tout à fait exacte. La bonne syntaxe est néanmoins un peu plus volumineuse…
  6. Une bonne introduction sur le DOM est disponible sur Quirksmode de Peter-Paul Koch.

À suivre : moins de code, plus de fun.