Déclencheurs JavaScript

En dépit de la stricte séparation, les couches de présentation et de comportement ont besoin d’instructions de la couche structurelle. Elles ont besoin de savoir où ajouter cette jolie touche de style, quand initier ces petites actions qui font la différence. Elles ont besoin de déclencheurs.

Traduction de l’article JavaScript Triggers.
Translated with the permission of A List Apart Magazine (http://alistapart.com) and the author[s].

L’affichage d’une page web consiste en 3 couches. Le fichier XHTML représentent la couche structurelle qui contient la structure, la sémantique et le contenu du site. À cette couche, il est possible de joindre une couche de mise en page (CSS) et une couche comportementale (JavaScript) afin de rendre le site plus beau et plus facile à utiliser. Ces trois couches doivent rester strictement séparées. On peut de cette façon changer toute la mise en page sans toucher ni à la structure ni aux comportements.

En dépit de la stricte séparation, les couches de présentation et de comportement ont besoin d’instructions de la couche structurelle. Elles en ont besoin pour savoir où ajouter cette jolie touche de style, quand initier cette petite action qui fait la différence. Elles ont besoin de déclencheurs

Les déclencheurs CSS sont bien connus. Les attributs class et id vous permettent de contrôler totalement la présentation de vos sites web. Bien qu’il soit possible de travailler sans ces déclencheurs, en plaçant les instructions en ligne dans l’attribut style, cette méthode de codage est à proscrire. Si vous voulez redéfinir la présentation de votre site avec cette méthode, vous allez devoir modifier la couche structurelle en même temps, il s’agit d’une entorse à la régle de séparation de la présentation et de la structure.

Déclencheurs JavaScript

La couche comportementale doit fonctionner de la même manière. Il faut séparer le comportement de la structure en vous séparant des gestionnaires d’évenements en ligne comme celui-ci : onmouseover="switchImages('fearful',6,false)". À la place, comme en CSS, nous allons utiliser des déclencheurs pour informer le script permettant la mise en oeuvre du comportement adéquate.

Le déclencheur JavaScript le plus simple est l’attribut id :

<div id="navigation">
 <ul>
  <li><a href="#">Link 1</a></li>
  <li><a href="#">Link 2</a></li>
  <li><a href="#">Link 3</a></li>
 </ul>
</div>
var x = document.getElementById('navigation');
if (!x) return;
var y = x.getElementsByTagName('a');
for (var i=0;i<y.length;i++)
 y[i].onmouseover = addBehavior;

Avec ce code, le comportement est déclenché par la présence ou l’absence de id="navigation". S’il est absent, il ne se passe rien (if (!x) return), mais s’il est présent tous les éléments liens qui en descendent auront un comportement au survol. Cette solution est simple et élégante, et elle fonctionne dans tous les navigateurs. Si l’id permet de couvrir vos besoins, vous n’avez pas besoin de lire la suite de cet article.

Déclencheurs avancés

Malheureusement, dans certaines situations il est impossible d’utiliser l’attribut id comme déclencheur:

  1. Un id doit être unique dans tout le document et vous pouvez avoir besoin d’un même comportement pour plusieurs (un groupe) éléments.
  2. Parfois un script a besoin de plus d’information que simplement “exécutez-moi ici.”

Prenons pour ces 2 problèmes l’exemple d’un script de gestion de formulaire. Il serait pratique d’ajouter un déclencheur permettant d’indiquer qu’un champ est obligatoire. Si nous avions un tel déclencheur, nous pourrions utiliser un script aussi simple que le suivant.

function validateForm()
{
 var x = document.forms[0].elements;
 for (var i=0;i<x.length;i++)
 {
  if (<strong>[ce champ est obligatoire]</strong> && !x[i].value)
    // Informer l'utilisateur de l'anomalie
 }
}

Mais comment créer un déclencheur XHTML permettant d’indiquer au script que certains champs sont obligatoires ? Utilisez un id n’est pas envisageable : car nous avons besoin d’une solution qui puisse fonctionner sur plusieurs champs. Il serait possible d’utiliser l’attribut class pour piloter ce comportement :

<input name="name" class="required" />

if (<strong>x[i].className == 'required'</strong> && !x[i].value)
  // Informer l'utilisateur de l'anomalie

Pourtant, l’attribut class est plutôt dédié à la définition de styles CSS. Mélanger des déclencheurs CSS et JavaScript n’est pas impossible mais peut mener rapidement à un amas de code confus:

<input name="name" class="largefield required" />

if (
  <strong>x[i].className.indexOf('required') != -1</strong> &&
  !x[i].value
)

À mon avis, l’attribut class ne devrait être utilisé que pour le CSS. Les classes sont les premiers déclencheurs XHTML pour la couche de présentation et les utiliser aussi pour porter une information de comportement complique les choses. Déclencher la couche de présentation et de comportement avec le même attribut class représente aussi une entorse à la stricte séparation du comportement et de la présentation , bien qu’en fin de compte c’est à vous de choisir ce qui est acceptable à ce sujet.

Déclencheurs porteurs d’information

De plus ces déclencheurs peuvent prendre plus d’importance et être plus compliqués que la simple commande “déployez ce comportement ici”. Vous aurez certainement besoin de joindre une valeur à un déclencheur. Une valeur de déclenchement rendrait la couche comportementale beaucoup plus souple, puisqu’elle pourrait répondre à différentes conditions d’exécution pour chaque élément XHTML au lieu d’exécuter bêtement le même script.

Prenez un formulaire dans lequel des zones de texte ont des tailles maximum pour paramètre. Le vieil attribut MAXLENGTH ne fonctionne pas avec les zones de texte, nous devons donc ecrire un petit script pour cela. De plus toutes les zones de textes n’ont pas la même longueur maximum, nous allons donc avoir à renseigner cette information quelque part de façon individuelle pour chaque zone de texte.

Nous voulons avoir quelquechose de la forme :

var x = document.getElementsByTagName('textarea');
for (var i=0;i<x.length;i++)
{
 if (<strong>[cette zone de texte a une longueur max]</strong>)
  x[i].onkeypress = checkLength;
}

function checkLength()
{
 var max = <strong>[Lecture de la longueur max]</strong>;
 if (this.value.length > max)
  // Informer l'utilisateur de l'anomalie
}

Ce script a besoin de 2 paramètres :

  1. Est-ce que cette zone de texte a une longueur maximum? Il s’agit ici du déclencheur qui informe le script qu’il y a un comportement à gérer.
  2. Quelle est la longueur max? C’est la valeur que le script doit vérifier pour valider la saisie utilisateur.

Et c’est typiquement dans ce cas que la solution basée sur les classes montre ses limites. C’est toujours techniquement possible, mais le code à implémenter devient trop compliqué. Prenez une zone de texte avec la classe CSS « large » qui soit aussi obligatoire et dont la longueur max. est de 300 caractères:

<textarea class="large required maxlength=300">
</textarea>

Non seulement cet exemple mélange de la présentation et 2 types de comportement, mais cela rend la longueur maximum de la zone de texte plus difficile à lire:

var max = <strong>this.className.substring(this.className.indexOf('maxlength')+10)</strong>;
if (this.value.length > max)
 // Informer l'utilisateur de l'anomalie

À noter que ce bout de code ne fonctionne que si le paramètre longueur max. est placé en fin de valeur de classe. Si on veut placer la longueur max. n’importe où dans la valeur de classe (au cas où on voudrait ajouter un autre déclencheur après celui-ci) le code devient bien plus compliqué.

Le problème

C’est bien notre problème du jour. Comment ajouter de bon déclencheurs JavaScript qui nous permettent à la fois de couvrir le cas d’un simple déclenchement d’action (“exécutez-moi ici”) et celui du passage de valeur spécifique à l’éléments ?

Techniquement, nous avons vu qu’il était possible de gérer ces cas avec l’attribut class, mais avons-nous le droit de détourner la fonction l’usage de cet attribut pour porter des informations pour lequel il n’est pas fait ? Est ce une entorse à la règle de séparation de la présentation et du comportement ? Et même si vous ne voyez pas d’obstacle à cette solution, elle reste tout de même compliquée à mettre en oeuvre et requiert un code JavaScript assez ardu.

Il est tout de fois possible d’utiliser d’ajouter des déclencheurs sur d’autres attributs comme lang ou dir, mais , encore une fois, ce serait une utilisation détournée d’attributs.

Attributs personnalisées

Je vos propose une autre solution. Reprenons l’exemple de la longueur max. de la zone de texte. Nous avons besoin de 2 informations:

  1. Est-ce que la zone de texte a une limite de taille ?
  2. Quelle est cette limite ?

La manière naturelle et sémantique d’exprimer cette information serait d’ajouter un attribut à la zone de texte:

<textarea  class="large" maxlength="300" >
</textarea>

La présence de l’attribut maxlength informe le script qu’il faut vérifier la saisie utilisateur dans cette zone de texte et il peut trouver la taille limite pour cette zone dans la valeur de l’attribut. De la même manière, pour le contrôle d’obligation de saisie, nous pouvons aussi créer un attribut personnalisé. required="true", par exemple, même si n’importe quelle valeur permet de déclencher le contrôle.

<textarea class="large" maxlength="300" required="true">
</textarea>

Techniquement ce n’est pas un problème. La méthode DOM du W3C getAttribute() permet de lire n’importe quel attribut d’une balise. Seul Opera jusqu’à sa version 7.54 ne permet pas de lire un attribut existant sur la mauvaise balise (comme src sur la balise <h2>). Heureusement les version ultérieur assure un support complet de la méthode getAttribute().

Voici donc ma solution :

function validateForm()
{
 var x = document.forms[0].elements;
 for (var i=0;i<x.length;i++)
 {
  if (<strong>x[i].getAttribute('required')</strong> && !x[i].value)
    // Informer l'utilisateur que le champ est obligatoire
 }
}

var x = document.getElementsByTagName('textarea');

for (var i=0;i<x.length;i++)
{
 if (<strong>x[i].getAttribute('maxlength')</strong>)
  x[i].onkeypress = checkLength;
}

function checkLength()
{
 var max = <strong>this.getAttribute('maxlength')</strong>;
 if (this.value.length > max)
  // Informer l'utilisateur que la longueur max est atteinte
}

À mon avis cette solution est simple à implémenter et cohérente avec la forme que les déclencheurs JavaScript peuvent prendre: une paire clé/valeur à l’endroit où la clé déclenche l’action et la valeur permet de conditionner l’action en fonction de chaque élément. Finalement, ajouter ces déclencheurs au code XHTML est très simple même pour des webmaster débutants.

DTDs personnalisées

Après l’implémentation de cette solution vous pourrez noter que la page résultante n’est pas valide. À la validation les attributs required et maxlength sont rejetés car il ne sont pas autorisées dans le document. C’est bien sur complètement vrai : le premier attribut ne fait pas partie des spécifications XHTML, tandis que le second n’est valide que sur un élément <input>.

Pour résoudre ce problème, il suffit de les rendre valides; en créant une Définition de Type de Document (DTD) personnalisée qui étend XHTML pour inclure ces attributs déclencheurs. Cette DTD personnalisée définit nos attributs spéciaux et leur place dans le document, l’outil de validation permet de confronter la structure du document à notre version personnalisée de XHTML Si la DTD déclare les attributs valides, c’est qu’ils le sont.

Si vous ne savez pas créer de DTD personnalisée, vous pouvez lire l’article de J. David Eisenberg’s intitulé Creating Custom DTDs (NDT: ou la traduction français Valider une DTD personnalisée) pour tout savoir sur le sujet.

À mon avis, utiliser des attributs personnalisés pour contrôler la couche comportementale et écrire des DTD personnalisées pour définir ces attributs correctement contribue à séparer comportement et structure et à écrire des scripts simples et efficaces. De plus une fois ces attributs déclarés et les scripts rédigés, même un webmaster débutant est capable d’ajouter facilement des comportements complexes dans les documents XHTML.

Peter-Paul Koch est développeur web indépendant à Amsterdam en Hollande. Il écrit et maintient www.quirksmode.org, un condensé d’environ 180 articles, trucs et astuces CSS et JavaScript, ainsi qu’un système de rapport de bogue et un blog

Cet article a été publié le 29/04/2005 par Seb