É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.