Dans le cadre d'une stricte séparation entre le fond, la forme et les comportements dynamiques d'une page, le fond est pris en charge par le code HTML, la forme par la feuille de style CSS et les comportements dynamiques par le JavaScript.
L'utilisateur interagit avec une page par l'entremise d'événements.
La plupart des événements sont reliés à l'interface utilisateur du navigateur, tels les déplacements de souris, les actions sur des boutons ou des touches du clavier, les réponses à des formulaires... Ces événements sont en général reliés à un élément particulier dans la page. D'autres sont des événements activés en réponse à une action spécifique du navigateur (par exemple le chargement d'un document, voire d'une image seule).
Quelques objets ont des actions définies par défaut en réponse à certains événements. Par exemple, un clic sur un élément a active le lien hypertexte, et force le navigateur à charger l'URL indiquée dans l'attribut href.
En fait, tous les événements suivent le même modèle. Le DOM fournit des méthodes pour capturer les événements et lancer une action quelconque en réponse. Il définit également un objet event, dont les propriétés permettent d'accéder à des informations spécifiques à l'événement (par exemple, quelle touche du clavier a été activée...).
1. Propagation d'événements
Revenons un instant sur ce que recouvre la notion de Modèle Objet de Document. Cela signifie que tout nœud du document (à l'exception de l'élément racine bien entendu) possède une collection d'ancêtres dans l'arborescence. Par conséquent, quand par exemple on clique sur un élément, cela signifie que l'on clique aussi sur chacun des éléments qui le contiennent. Considérons par exemple le code HTML ci-dessous :
<div>
<p>Un peu de texte dans un paragraphe.</p>
<p>Un autre texte dans un deuxième paragraphe.</p>
<p>Encore du texte dans un paragraphe, avec <a href="index.html">un lien</a>.</p>
</div>
Si on clique sur le lien dans le troisième paragraphe, sera généré un événement click. Mais on aura également cliqué sur le paragraphe contenant le lien, ainsi que sur l'élément div contenant le paragraphe, aussi bien que sur l'élément body englobant le tout...
N'importe lequel des éléments de cette chaîne peut avoir un gestionnaire d'événement associé au clic de souris, quel que soit l'élément duquel l'événement est parti.
Selon les standards, il existe deux manières pour un événement de « naviguer » de haut en bas de la chaîne d'éléments : il s'agit de l'event bubbling et de l'event capture.
2. Event bubbling (remontée d'événements)
Le comportement par défaut des éléments est appelée l'event bubbling. Il recouvre ce concept de remontée des événements le long de l'arborescence du fichier HTML, tout comme une bulle monte dans un liquide. Un gestionnaire d'événement invoqué au cours de cette remontée de l'événement dans l'arborescence du DOM peut stopper cette remontée ; mais si rien n'est fait, l'événement poursuit sa propagation jusqu'à la racine. Tout élément rencontré dans l'arborescence au cours de cette phase peut avoir un gestionnaire d'événement.
Cette phase est appelée bubble phase dans le DOM. Tous les événements ne remontent pas de la sorte ; par exemple focus et blur ne remontent jamais. De même, certains événements ne peuvent être annulés à un moment quelconque de leur remontée. On peut le savoir grâce à l'objet event et à ses propriétés, ainsi que nous le verrons.
3. Event capture (capture d'événement)
a. Le standard
Le pendant de la phase de bubbling est la phase de capture. En effet, par exemple dans le cas d'un clic, le logiciel commence par détecter qu'un clic a eu lieu. Il lui faut ensuite explorer l'arbre pour identifier l'élément sur lequel l'utilisateur a cliqué. Il est possible de lancer un gestionnaire d'événement quand le navigateur inspecte l'arbre en descendant dans l'arborescence. Ainsi, la phase de capture a lieu en premier ; l'événement descend le long de l'arborescence du document depuis la racine jusqu'à l'élément, avant de remonter.
Lors de cette phase, un élément parent est affecté par un événement avant que ce dernier ne se propage jusqu'à l'élément sur lequel l'événement a effectivement eu lieu. Cela permet de gérer des priorités, et permet d'intercepter un événement pour un élément, quel que soit l'élément descendant de ce dernier sur lequel l'événement a effectivement eu lieu. Dans notre exemple précédent, le clic sur le lien aurait d'abord été géré par le gestionnaire d'événement de l'élément body, puis celui du div, puis celui du paragraphe, enfin celui du lien.
4. Exemple
L'exemple suivant montre la remontée et descente des événements dans la structure du DOM
1. Par un attribut HTML
a. Rappels
Des événements sont liés à beaucoup d'éléments HTML. On peut accéder à ces événements par un simple appel à un attribut décrivant la nature de l'événement (c'est ce que nous avons fait tout au long des cours précédents). Le nom de cet attribut se construit en préfixant par on le nom de l'événement. L'exemple suivant montre comment utiliser un attribut pour définir un événement.
<span style="background-color: yellow" onmouseover="this.style.backgroundColor='black" onmouseout="this.style.backgroundColor='yellow">
Un élément avec un gestionnaire de base.</span>
En fait, tout se passe comme si, lors de l'appel au code JavaScript dans chaque attribut, le navigateur créait dynamiquement une fonction anonyme contenant le code inscrit dans l'attribut. L'exemple suivant montre comment lancer une boîte d'alerte affichant le contenu de l'attribut onclick de l'élément sur lequel on a cliqué.
<span style="background-color:yellow;" onclick="alert(this.onclick)"> Exemple de contenu avec un événement <code>click</code>.</span>
Le code précis de la fonction dépend du navigateur utilisé, mais le principe reste le même. Cette fonction est appelée le gestionnaire d'événement.
Attention, certains événements ne sont pas applicables à n'importe quel élément. Par exemple, les événements focus et blur (qui désignent respectivement l'action de sélectionner un élément par la souris ou l'emploi de la touche Tabulation, et l'action de le désélectionner) ne sont applicables qu'aux champs de formulaire et aux liens.
b. Compatibilité inter-navigateurs
Netscape et Mozilla créent une fonction portant le nom de l'événement et utilisant un unique argument, event, qui est l'objet événement passé comme paramètre au gestionnaire d'événements.
function onclick(event){
alert(this.onclick) ;
}
Internet Explorer utilise une fonction anonymous. Il ne suit pas le standard, et donc aucun argument n'est défini (nous y reviendrons plus loin).
function anonymous(){
alert(this.onclick) ;
}
Opera utilise une fonction anonymous avec un argument event.
function anonymous(event){
alert(this.onclick) ;
}
c. Les attributs possibles
N'importe quel attribut n'est pas utilisable avec n'importe quel élément.
d. Limitations
Un inconvénient majeur de cette méthode est de mêler le code HTML avec le code Javascript. Cela complique la maintenance de l'un comme de l'autre.
2. Par l'utilisation d'une propriété d'un élément
Afin de pouvoir séparer un peu plus le traitement d'un événement du code HTML auquel il s'applique, il est possible d'accéder aux événements en ne modifiant que le code Javascript. Si un objet-élément a été précédemment identifié (par une méthode getElementById, par exemple), alors l'événement visé est accessible comme une simple propriété de l'objet élément. L'exemple suivant montre comment utiliser une propriété de l'objet-élément pour définir un événement.
<span id="exemple1">Exemple avec un événement souris.</span>
et le code JavaScript associé :
document.getElementById('exemple1').onmouseover = miseEnGras ;
document.getElementById('exemple1').onmouseout = normal ;
function miseEnGras(event){
this.style.fontWeight="bold" ;
this.style.color="red" ;
}
function normal(event){
this.style.fontWeight="normal" ;
this.style.color="" ;
}
Il est à remarquer qu'on affecte à la propriété onmouseover la valeur miseEnGras, autrement dit, le nom de la fonction sans les parenthèses. Si on avait écrit miseEnGras(), au moment de l'affectation de l'événement à l'objet-élément, la fonction aurait été évaluée. En affectant le nom de la fonction, en fait on affecte un objet de type function à l'événement, ce qui reproduit le mécanisme interne d'affectation d'un gestionnaire à un événement vu plus haut.
Exercice 1. Utilisation des propriétés des éléments
3. Par des « event listeners »
a. Principe
Utiliser une propriété pour affecter un événement à un élément n'est pas la manière la plus « orientée objet » de coder. Il est ainsi difficile pour le code JavaScript de se manipuler lui-même afin de modifier le gestionnaire attaché à un élément particulier.
Un moyen plus respectueux d'une conception orientée objet est d'ajouter des « event listeners » aux éléments. Cela offre en outre l'avantage de permettre le rattachement d'un gestionnaire à plusieurs événements (chose qui, certes, n'était pas interdite par la méthode précédente), mais aussi de facilement affecter plusieurs gestionnaires en réponse à un même événement, sur un élément donné, en fonction du contexte et/ou de ce qui s'est passé. Cela permet aussi de spécifier un comportement différent lors du « cascadage » des événements d'un élément à son parent -ou à un de ses enfants. Nous y reviendrons. Enfin, il est possible d'affecter des événements à n'importe quel nœud de l'arborescence, y compris un nœud de type texte. Pour mémoire, comme un nœud de ce type ne comporte pas d'attribut, il ne possède pas par exemple de propriété onmouseover.
La syntaxe de base est la suivante :
noeud.addEventListener(eventType, fonction, useCapture) ;
noeud.removeEventListener(eventType, fonction, useCapture) ;
où eventType est un événement prédéfini, comme mouseover ou click (il reprend le même nom que celui de l'attribut correspondant, mais sans le "on"), fonction est le nom de la fonction gérant l'événement, et useCapture un booléen qui spécifie dans quelle phase du flot d'événement la fonction sera appelée.
b. Exemple
Par exemple, pour continuer avec le même cas que précédemment, on aurait pu écrire
var el=.document. getElementById("exemple1") ;
el.addEventListener("mouseover", miseEnGras, false) ;
el.addEventListener("mouseout", normal, false) ;
Des gestionnaires supplémentaires auraient pu tout aussi bien être affectés à cet élément :
el.addEventListener("mouseover", autreGestionnaire, true) ;
el.addEventListener("mouseover", etUnTroisieme, false) ;
De même, on peut les supprimer...
el.removeEventListener("mouseover", autreGestionnaire, true) ;
el.removeEventListener("mouseout", etUnTroisieme, false) ;
Il faut spécifier le même useCapture que lors de l'ajout du gestionnaire. En effet...
el.addEventListener("mouseover", miseEnGras, true) ;
el.addEventListener("mouseover", miseEnGras, false) ;
... crée deux event listeners uniques, appelant certes la même fonction, mais actifs lors de phases différentes.
c. Le cas Internet Explorer
Internet Explorer jusqu'à la version 8 incluse, et Opera jusqu'à la version 5 incluse ne supportent pas les event listeners. Internet Explorer 9 les supporte cependant. Internet Explorer fournit les méthodes attachEvent et detachEvent qui permettent d'affecter ou de détacher plusieurs gestionnaires pour un même événement. Par exemple :
var el = .document. getElementById("sample1") ;
el.attachEvent("onmouseover", highlight) ;
el.attachEvent("onmouseover", highlight2) ;
el.attachEvent("onmouseover", highlight3) ;
el.attachEvent("onmouseout", normal) ;
...
el.detachEvent("onmouseover", highlight2) ;
el.detachEvent("onmouseover", highlight3) ;
Plusieurs remarques s'imposent :
Plusieurs développeurs ont mis au point des librairies de méthodes JavaScript, compatibles avec un maximum de navigateurs. Elles complètent des manques (par exemple elles ajoutent souvent une méthode getElementsByClassName), ou bien pallient des bogues. Elles permettent de manipuler les événements de manière totalement transparente, sans faire appel à de multiples tests pour tenir compte de plusieurs navigateurs.
Exercice 1. Utilisation simple d'un gestionnaire d'événement
1. Propriétés et méthodes générales
a. Le standard
Nous avons déjà signalé qu'à tout appel de gestionnaire d'événement, un objet de type événement est passé en argument à la fonction. Plusieurs propriétés décrivent cet objet et son état. On peut les utiliser, notamment, afin de déterminer d'où est issu l'événement, et à quelle étape précise de son « voyage » il en est (montée ou descente...). Il est également possible de l'intercepter, et de faire en sorte qu'il cesse sa propagation.
Nom de la propriété Description
bubbles Un booléen indiquant si l'événement remonte l'arborescence ou non.
cancelable Un booléen indiquant si l'événement peut être annulé.
currentTarget Le nœud auquel est affecté le gestionnaire d'événement.
eventPhase Un entier indiquant l'étape où l'événement se trouve dans le flot : il vaut soit CAPTURING_PHASE(1), soit AT_TARGET(2), soit BUBBLING_PHASE(3).
target Le nœud d'où est parti l'événement.
timeStamp L'heure à laquelle l'événement a eu lieu.
type Une chaîne de caractères donnant le type d'évenement, comme "mouseover" ou "click", etc.
Nom de la méthode Description
preventDefault() Peut être utilisé pour annuler l'événement. Cela empêche le navigateur de procéder à l'action par défaut pour l'événement, comme par exemple charger une URL quand un lien a été cliqué. Attention, l'événement va continuer sa propagation le long de l'arbre.
stopPropagation() Coupe le flot de l'événement. Cette méthode peut être utilisée lors de la phase de remontée ou de descente.
Table 1. Propriétés et méthodes de l'objet event.
b. Internet Explorer
Les versions d'Internet Explorer inférieures ou égales à 8 ne supportent pas le modèle event du W3C. Au lieu de passer un argument de type event à la fonction, ces versions recourent à un objet global, window.event, qui contient le même genre d'informations.
Malheureusement, les propriétés et méthodes de cet objet ne suivent pas les mêmes conventions de notation que celles du W3C.
Standard W3C Internet Explorer window.event Remarques
currentTarget aucun Voir ci-dessous.
eventPhase aucun Ne s'applique pas à Internet Explorer.
target srcElement Le nœud d'où est issu l'événement.
timeStamp aucun Non applicable à Internet Explorer.
type type Même fonction que dans le standard.
preventDefault() returnValue Propriété à mettre à false pour stopper tout traitement par défaut de l'événement.
stopPropagation() cancelBubble Propriété à mettre à true pour stopper la remontée de l'événement.
Table 2. Équivalents Internet Explorer ≤8 pour les propriétés et méthodes de l'objet Event.
Afin d'obtenir un équivalent de la propriété currentTarget avec Internet Explorer ≤8, il faut utiliser le mot-clef this comme argument lors de l'appel au gestionnaire :
<cite onmouseover="monGestionnaire(event, this)"> <a href="http://www.paroles.net/texte/18608/artis/1005">Mon légionnaire</a>
</cite>
function monGestionnaire(event, toto){
alert("Il sentait bon le sable chaud")
}
Cette possibilité n'existe toutefois pas lorsque l'on utilise la méthode propriétaire attachEvent pour allouer un gestionnaire d'événement.
2. Événements souris
a. Propriétés
Les événements souris sont :
Nom de la propriété Description
altKey, ctrlKey, metaKey, shiftKey Booléens. Ils valent true si la touche correspondante était enfoncée quand l'événement souris a été activé.
button Un entier indiquant quel bouton de souris était enfoncé : 0=gauche, 1=milieu, 2=droit. Une souris à un seul bouton (type Mac) ne retournera que la valeur 1 ; une souris à deux boutons retournera les valeurs 0 ou 2.
clientX, clientY Donne les coordonnées du pointeur de la souris quand l'événement a eu lieu, par rapport à la fenêtre du navigateur effectivement disponible (hors barre de défilement).
relatedTarget Sur un mouseover, cette propriété indique le nœud que vient de quitter la souris ; sur un mouseout, il s'agit du nœud sur lequel la souris vient de se placer. Cette propriété n'est pas supportée parInternet Explorer : voir ci-après.
screenX, screenY Donne les coordonnées du pointeur de la souris quand l'événement a eu lieu, par rapport à l'écran.
Table 3. Liste des propriétés de l'objet event liées à la souris.
b. Remarques
Les événements souris sont toujours attachés à l'élément de plus bas niveau dans l'arborescence. Par exemple, cela sera le lien si l'utilisateur a cliqué sur un lien inclus dans un paragraphe.
Lors du clic, trois événements ont lieu, toujours dans le même ordre : mousedown, mouseup et click. Ces événements sont toujours traités successivement, le traitement de chacun d'eux ne pouvant débuter que si celui du précédent est terminé.
c. Compatibilité inter-navigateurs
Opera considère -de manière erronée- que les propriétés clientX et et clientY sont relatives à l'origine de la page (autrement dit, la largeur des barres de défilement est incluse).
Les conventions d'Internet Explorer diffèrent encore des standards. Voici une liste des équivalences :
Exercice 1. Manipulation des gestionnaires d'événements
Exercice 2. Manipulation des gestionnaires d'événements (suite)
3. Événements clavier
a. Introduction
Le DOM niveau 2 n'inclut pas de consigne à propos des événements clavier. Cependant, la recommandation HTML permet l'utilisation de plusieurs attributs, et par conséquent des événements correspondants. Ce sont les événements keydown, keypress et keyup, indiquant respectivement qu'une touche a été enfoncée, appuyée ou relevée. Dans le cas de la frappe d'une touche clavier, ces trois événements sont produits et gérés dans cet ordre. Netscape (et par souci de rétro-compatibilité, FireFox) et Internet Explorer ont inclus des propriétés afin de permettre la gestion des événements clavier, et notamment le codage des touches du clavier éventuellement enfoncées. Heureusement, de fortes similitudes existent.
Dans FireFox et Netscape, le code ASCII de la touche enfoncée est donné par la propriété keyCode quand l'événement keydown a été activé, et par la propriété charCode pour l'événement keypress.
Internet Explorer stocke le code Unicode de la touche dans la propriété keyCode pour chacun des trois types d'événements.
Dans les deux cas, les événements keydown et keypress sont activés dès qu'une touche quelconque est enfoncée (y compris une touche de fonction, ou une touche Ctrl, Shift ou Alt). Pour saisir une combinaison de touches comme Shift-a, par exemple, il est nécessaire d'avoir recours à l'événement keypress.
Voici quelques exemples de codes renvoyés par ces propriétés pour Internet Explorer et FireFox. Il ne faut pas oublier que certaines combinaisons de touches sont interceptées par le système d'exploitation (par exemple souvent Ctrl-n pour ouvrir une nouvelle fenêtre). De plus, certaines extensions sous Firefox redéfinissent leurs propres raccourcis clavier. Selon le système et le navigateur, vous aurez accès à ces combinaisons de touches ou non.
b. Internet Explorer
Table 4. Exemples d'événements clavier pour Internet Explorer.
c. Netscape et FireFox
Table 5. Exemples d'événements clavier pour Netscape et Mozilla.
4. Événements divers
On consultera la liste détaillée des nombreux événements sur le site du Mozilla Developer Network. Voici une petite sélection d'événements particulièrement intéressants...
b. Événements liés à la manipulation de la fenêtre
resize est activé quand la fenêtre est redimensionnée, scroll quand on fait défiler le document, ou bien un élément.
c. Évenements liés au drag&drop
Ces événements sont nécessaires à l'utilisation de l'attribut draggable de HTML5. Il s'agit de drag et drop au début et à la fin de la phase de drag&drop, dragstart, dragend quand le drag&drop est terminé, dragenter qui est activé quand on entre au-dessus d'une zone où l'objet en cours de déplacement peut être déposé, dragleave quand on sort d'une zone où l'objet peut être déposé, et dragover au survol.
d. Événements liés au copier/coller
Il s'agit, très classiquement, de copy, cut et paste.
e. Événements liés aux animations CSS
animationstart et animationend sont activés récemment au début et à la fin d'une animation, tandis qu'animationiteration est activé à chaque boucle.
f. Événements liés à la connexion Internet
Ces événements sont particulièrement utiles quand on doit gérer des Progressive Web Applications : online et offline sont des événements qui sont activés quand le navigateur obtient, ou perd, une connexion au réseau.