Évènements Javascript et fonctions callback

Transcription

Évènements Javascript et fonctions callback
Évènements, fonctions callback, closure
Programmation événementielle
Javascript est un langage de programmation évènementielle. Au lieu d’exécuter des
instructions de façon séquentielle, les diverses fonctions sont exécutées selon les
évènements qui seront captés, comme par exemple un clic de souris, l’appui sur une
touche donnée, etc.
Capteurs d’évènements et fonctions callback
Version HTML
Historiquement, les éléments HTML peuvent capter un évènement en leur ajoutant un
attribut comme onclick par exemple :
<a href="somewhere.html" onclick="alert('On a cliqué !’)">
Cette solution permet de facilement ajouter des interactions dans une page web, mais
possède un inconvénient majeur : la définition des évènements à capter est mélangée au
code HTML et ne permet donc pas la séparation du contenu et de la logique des
interactions.
Capteur d’évènement en Javascript
Pour permettre de séparer les interactions et le code HTML, on peut ajouter un capteur
d’évènement sur un élément de façon programmatique. Deux possibilités pour faire cela :
• monElement.onclick = faireAction; // attention sans parenthèses !!
• monElement.addEventListener( ‘click’, faireAction, false); // version DOM Events
La référence à l’action à exécuter (ici faireAction) est ce qu’on appelle un callback : il s’agit
juste de définir le nom de l’action à exécuter, ce qui est différent d’un appel à une fonction
(qui serait alors fait avec des parenthèses : faireAction(); )
Le DOM permet aussi d’enlever un capteur d’évènement :
monElement.removeEventListener( ‘click’, faireAction, false);
Fonctions callback
Une fonction callback est une fonction passée en paramètre. C’est typiquement ce qui se
passe avec l’instruction monElement.addEventListener( ‘click’, faireAction, false). La
fonction faireAction sera exécutée lorsque l’évènement “click” est capté.
La difficulté des fonctions callback par rapport à la programmation classique est que l’on ne
peut pas choisir les arguments de la fonction.
En Javascript, toute fonction callback exécutée suite à la captation d’un évènement reçoit
en argument l’objet Javascript de type Event.
L’objet Event en Javascript
L’objet Event de Javascript contient des informations sur l’évènement et des méthodes
pour agir (par exemple pour l’arrêter).
Propriétés :
• target : c’est l’élément sur lequel se trouve la souris lorsque l’évènement est capté
• currentTarget : c’est l’élément qui a capté l’événement (peut être différent de target)
• type : le type de l’élément (click, focus, mouseover, etc.)
Méthodes :
• stopPropagation : stoppe la propagation de l’évènement dans l’arbre DOM (cf. plus
bas)
• preventDefault : empêche l’élément qui a capté l’évènement de déclencher l’action
implicite. Par exemple si un élément <a> capte un clic, alors le lien est activé, sauf si
on l’y empêche avec la méthode preventDefault().
Exemple :
HTML
<a id=“test” href=“http://google.com”>bla bla </div>
Javascript
document.getElementById(‘test’).addEventListener(‘click’, stoppeLien);
function stoppeLien(ev) {
console.log(ev.target);
console.log(ev.type);
ev.preventDefault(); // empêche le lien d’être activé lors du clic
}
Propagation des évènements
Après la capture d’un évènement, celui-ci “remonte” à la surface de l’arbre DOM (phase de
bubbling). Si plusieurs éléments dans la branche du DOM doivent capter le même type
d’évènement, alors l’élément le plus “profond” dans l’arbre capte l’élément en premier puis
l’évènement est alors capté par ses ancêtres jusqu’à la racine du document.
Exemple :
HTML
<div id=“elt1”>
<p id=“elt2”>un texte <strong id=“elt3”>en gras</strong></p>
</div>
Javascript
document.getElementById('elt1').addEventListener('click', affiche1);
document.getElementById('elt2').addEventListener('click', affiche2);
document.getElementById('elt3').addEventListener('click', affiche3);
function affiche1(ev) {
console.log("Élément elt1 a capté le clic");
affiche(ev);
}
function affiche2(ev) {
console.log("Élément elt2 a capté le clic");
affiche(ev);
}
function affiche3(ev) {
console.log("Élément elt3 a capté le clic");
affiche(ev);
}
function affiche(ev) {
console.log(ev.target);
console.log(ev.currentTarget);
}
Exécution lorsque l’on clique sur le texte en gras, la console affiche alors :
Élément elt3 a capté le clic
<strong id="elt3">
<strong id="elt3">
Élément elt2 a capté le clic
<strong id="elt3">
<p id="elt2">
Élément elt1 a capté le clic
<strong id="elt3">
<div id="elt1">
Fonctions anonymes
Javascript permet l’utilisation de fonction anonymes, qui permettent parfois d’écrire un code
plus concis.
Exemple
monElt.onclick = function(ev) {
console.log(“evenement capté”);
}
Fonction anonyme et évènement load
Toute page web possède l’évènement load sur l’objet window qui est déclenché lorsque la
page est complètement chargée, c’est à dire que le navigateur a reçu tout le code HTML de
la page. On utilise cet évènement pour initialiser les interactions de la page web :
window.onload = function() {
// mettre les capteurs d’évènements
document.getElementById(‘test’).addEventListener(‘click’, faireAction, false);
// …
}
Objet this
En programmation orientée objet, le mot-clé this désigne en général l’objet dans lequel le
code se trouve. C’est par exemple le cas avec les langages Java ou PHP entre autres.
Dans la cadre de son utilisation en Javascript, this peut lui aussi désigner l’objet dans le
quel on travaille, mais lorsque Javascript est utilisé en programmation évènementielle,
l’objet this est alors
l’objet qui a capté l’évènement déclencheur de l’action en cours.
Exemple
HTML
<div id=“elt”>une div qui capte en clic</div>
Javascript
document.getElementById('elt').addEventListener('click', affiche);
function affiche(ev) {
console.log("Élément elt a capté le clic");
console.log(ev.target);
console.log(ev.currentTarget);
// affiche <div id=“elt”> en console car c’est l’élément qui a capté le clic
console.log(this);
}
Notion de closure
En Javascript, une fonction peut être contenue dans une fonction parente, par exemple:
var parent = function(jour, mois)
{
var afficher = function()
{
var ladate = new Date(annee, mois, jour);
console.log('The date entered is ' + ladate.toDateString());
}
var annee = 2015;
afficher();
}
parent(12, 2); // affiche : The date entered is Thu Mar 12 2015
afficher(); // ReferenceError: afficher is not defined
La fonction afficher() est seulement définie à l’intérieur de la fonction parent(). Mais la
notion de closure est le fait que la “sous-fonction” a accès aux variables de la fonction
englobante. Elle a accès aux variables locales dans sa portée, puis aux variables de la
fonction englobante (et ses paramètres), et enfin aux variables globales.
Les closures sont beaucoup utilisées en Javascript et il est important de connaitre leurs
principes de base.
Attention cependant, la closure stocke une référence vers les variables de la fonction
englobante, et non leur valeur. Cela pose alors parfois des problèmes si la valeur d’une
variable change avant l’exécution de la closure (ou pendant son exécution). L’exemple
typique est avec l’utilisation de boucles. Prenons l’exemple fourni pat SitePoint (voir lien
plus bas) :
<html lang="en">
<head>
<title>Closures</title>
<meta charset="UTF-8" />
<script>
window.addEventListener("load", function() {
for (var i = 1; i < 4; i++) {
var button = document.getElementById("button" + i);
button.addEventListener("click", function() {
console.log(“Clicked button " + i);
});
}
});
</script>
</head>
<body>
<input type="button" id="button1" value="One" />
<input type="button" id="button2" value="Two" />
<input type="button" id="button3" value="Three" />
</body>
</html>
Quel que soit le bouton cliqué, cela affichera Clicked button 4 puisque la boucle est
forcément finie avant qu’un bouton ne soit cliqué, et donc i vaut 4. La solution est alors de
découpler la closure de la boucle. La solution est expliquée dans le tutoriel de SitePoint :
http://www.sitepoint.com/javascript-closures-demystified/
Autre tutoriel sur les closures :
http://javascriptissexy.com/understand-javascript-closures-with-ease/
Conclusion
La gestion des évènements avec Javascript nécessite de l’entrainement pour s’extraire de
la logique de la programmation séquentielle. La séparation du code HTML et de la logique
des interactions permet de créer des bibliothèques de fonctionnalités en Javascript qui
seront réutilisées dans diverses pages web.
Les closures sont difficiles à maitriser lorsque l’on débute en Javascript. Nous serons
amenés à les utiliser lors de divers exercices manipulant l’arbre DOM et utilisant la gestion
d’évènements.