:tocdepth: 2 ============================== Programmation événementielle ============================== .. include:: common.rst.inc .. ifslides:: .. include:: credits.rst.inc Fonction anonyme ================ Objets de premier niveau ------------------------ * En Javascript, les fonctions sont des objets de *premier niveau*, ce qui signifie qu'ils peuvent être manipulés au même titre que, par exemple, les entiers, les tableaux ou les objets. * On peut par exemple - affecter une fonction à une variable ; - la passer en paramètre d'une autre fonction (par exemple ``addEventListener``) ; - l'affecter comme attribut à un objet (ce qui est une manière de doter un objet de méthodes, comme en Java). .. index:: fonction anonyme Fonctions anonymes ------------------ * Javascript autorise la création de fonctions *anonymes* :: function (param1, param2) { /* instructions here */ } * Contrairement à une *déclaration* de fonction, une fonction anonyme peut apparaître *partout* où une fonction est acceptée. * C'est utile dans les cas où cette fonction n'a pas vocation à être réutilisée ailleurs, en particulier pour les abonnements. Exemple d'utilisation des fonctions anonymes ```````````````````````````````````````````` .. code:: window.addEventListener("load", function() { document.getElementsByTagName('span')[0] .addEventListener("mouseover", function() { console.log("haha, tickles"); } ); document.getElementById('b1') .addEventListener("click", function() { console.log("click"); } ); }); `Voir et modifier cet exemple.`__ __ http://jsbin.com/quzuku/17/edit?html,js,console,output .. _exemple_setTimeout: Autre exemple d'utilisation de fonction anonyme ``````````````````````````````````````````````` .. code:: window.addEventListener("load", function() { const b1 = document.getElementById('b1'); b1.addEventListener("click", function() { b1.textContent = "(en attente)"; b1.disabled = true; setTimeout(function() { alert("message"); b1.disabled = false; b1.textContent = "Afficher un message dans 2s"; }, 2000); }); }); `Voir et modifier cet exemple.`__ __ https://jsbin.com/kedotah/2/edit?html,js,output .. index:: asynchronisme Exécution asynchrone ==================== Motivation ---------- Dans l'`exemple précédent`__, pourquoi utiliser la fonction ``setTimeout`` et non une fonction qui bloquerait l'exécution pendant 2s, comme dans l'exemple ci-dessous ? __ exemple_setTimeout_ .. code:: // ⚠ MAUVAIS EXEMPLE ⚠ b1.textContent = "(en attente)"; b1.disabled = true; sleep(2000); // fonction imaginaire (n'existe pas en JS) alert("message"); b1.disabled = false; b1.textContent = "Afficher un message dans 2s"; .. warning:: La fonction ``sleep`` n'existe pas réellement en Javascript, et pour cause puisque ce n'est pas la bonne manière de faire. .. nextslide:: * Réponse : parce que le navigateur n'utilise qu'*un seul* thread par page, dédié à la fois à l'affichage du HTML et à l'exécution du code Javascript. * Pendant qu'une fonction Javascript s'exécute, la page est donc totalement "gelée" (saisie, défilement...). * Si la fonction garde la main pendant une durée trop longue, ce phénomène sera perceptible, et dégradera l'expérience utilisateur. Portée et fermeture =================== .. index:: portée Portée (:en:`scope`) -------------------- * La **portée** d'une variable est la partie du code sur laquelle cette variable est définie. * Pour les variables globales, c'est l'ensemble des scripts exécutés par la page (y compris les scripts écrits par d'autres). * Pour une variable locale, avec les mots-clefs ``let`` ou ``const``, c'est le bloc dans lequel elle est déclarée. * Pour une variable locale avec le mot-clef ``var``, c'est la fonction dans laquelle elle est déclarée. * Une variable locale d'un bloc est donc accessible par les fonctions (anonymes ou non) définies *à l'intérieur* de ce bloc. .. index:: fermeture Fermeture (:en:`closure`) ------------------------- * Une fonction porte avec elle le *contexte* dans lequel elle a été créée, c'est-à-dire l'état des variables locales qui lui sont accessibles. * Une telle fonction assortie d'un contexte est appelée une **fermeture**. * Dans l'`exemple précédent`__, la variable ``b1`` est définie au chargement de la page, mais « survit » à cette fonction, puisqu'elle est réutilisée au clic sur ce bouton, et encore deux secondes plus tard, dans le :en:`callback` de la fonction ``setTimeout``. * Visualisez l'exécution pas à pas d'un autre exemple de fermeture sur pythontutor__. .. admonition:: Vocabulaire Une fonction de :en:`callback` est utilisée en paramètre d’une méthode ou d’une fonction qui l'exécute le moment voulu. __ exemple_setTimeout_ __ http://pythontutor.com/visualize.html#code=//%20patron%20de%20Crockford%0A%0Afunction%20make_point%28%29%20%7B%0A%20%20var%20x%20%3D%200%3B%0A%20%20var%20y%20%3D%200%3B%0A%20%20return%20%7B%0A%20%20%20%20get_x%3A%20function%28%29%20%7B%0A%20%20%20%20%20%20return%20x%3B%0A%20%20%20%20%7D,%0A%20%20%20%20get_y%3A%20function%28%29%20%7B%0A%20%20%20%20%20%20return%20y%3B%0A%20%20%20%20%7D,%0A%20%20%20%20translate%3A%20function%28dx,%20dy%29%20%7B%0A%20%20%20%20%20%20x%20%2B%3D%20dx%3B%0A%20%20%20%20%20%20y%20%2B%3D%20dy%3B%0A%20%20%20%20%7D,%0A%20%20%7D%0A%7D%0A%0Avar%20p%20%3D%20make_point%28%29%3B%0Ap.translate%281,2%29%3B%0Aconsole.log%28p.get_x%28%29,%20p.get_y%28%29%29%3B&cumulative=false&curInstr=16&heapPrimitives=false&mode=display&origin=opt-frontend.js&py=js&rawInputLstJSON=%5B%5D&textReferences=false .. ES6 version: http://pythontutor.com/visualize.html#code=//%20patron%20de%20Crockford%0A%0Afunction%20make_point%28%29%20%7B%0A%20%20let%20x%20%3D%200%3B%0A%20%20let%20y%20%3D%200%3B%0A%20%20return%20%7B%0A%20%20%20%20get_x%3A%20%28%29%20%3D%3E%20%7B%0A%20%20%20%20%20%20return%20x%3B%0A%20%20%20%20%7D,%0A%20%20%20%20get_y%3A%20%28%29%20%3D%3E%20%7B%0A%20%20%20%20%20%20return%20y%3B%0A%20%20%20%20%7D,%0A%20%20%20%20translate%3A%20%28dx,%20dy%29%20%3D%3E%20%7B%0A%20%20%20%20%20%20x%20%2B%3D%20dx%3B%0A%20%20%20%20%20%20y%20%2B%3D%20dy%3B%0A%20%20%20%20%7D,%0A%20%20%7D%0A%7D%0A%0Alet%20p%20%3D%20make_point%28%29%3B%0Ap.translate%281,2%29%3B%0Aconsole.log%28p.get_x%28%29,%20p.get_y%28%29%29%3B&cumulative=false&curInstr=16&heapPrimitives=false&mode=display&origin=opt-frontend.js&py=js&rawInputLstJSON=%5B%5D&textReferences=false .. index:: arbre DOM .. _dom: L'arbre DOM =========== Rappel ------ Les balises HTML décrivent une `structure d'arbre`__. __ http://champin.net/enseignement/intro-web/html.html#structure-en-arbre .. graphviz:: graph { node [ shape=box, style=rounded] html -- head head -- title -- title_txt html -- body body -- h1 -- h1_txt body -- p p -- p_txt p -- a -- a_txt body -- img a [ label="a\nhref='./link'" ] img [ label="img\nsrc='./pic'" ] title_txt [ shape=box, style=filled, label="Le document" ] h1_txt [ shape=box, style=filled, label="Message important" ] p_txt [ shape=box, style=filled, label="Bonjour le " ] a_txt [ shape=box, style=filled, label="monde" ] } .. index:: élément Terminologie ------------ * Cet arbre s'appelle l'arbre DOM (pour :en:`Document Object Model`). * Les nœuds correspondant aux balises sont appelés des **éléments**. * Les nœuds contenant le contenu textuel sont simplement appelés des « nœuds texte ». .. admonition:: Remarque Il existe d'autres types de nœuds (par exemple les nœuds commentaire), mais ils sont plus rarement utiles. .. _manipulation_du_dom: Consultation du DOM ------------------- Récupérer des éléments depuis l'objet ``document``\  : `getElementById `_, `getElementsByTagName `_, `getElementsByClassName `_, `querySelector `_, `querySelectorAll `_ Récupérer des éléments depuis un autre élément ``e``\  : `e.getElementsByTagName `_, `e.getElementsByClassName `_, `e.querySelector `_, `e.querySelectorAll `_, `e.childNodes `_, `e.children `_, `e.parentNode `_, Propriétés d'un nœud : `nodeName `_, `nodeType `_, `nodeValue `_, `textContent `_ .. index:: createElement, createTextNode, cloneNode, insertBefore, replaceChild, removeChild, appendChild Modification du DOM ------------------- Création d'un nœud : `document.createElement `_, `document.createTextNode `_, `elt.cloneNode `_ Une fois créé, le nœud est encore *hors* de l'arborescence du document (et donc, non affiché). Il est nécessaire de le rattacher à un nœud parent par l'une des méthodes suivantes : `insertBefore `_, `replaceChild `_, `removeChild `_, `appendChild `_, Cheminement d'un événement -------------------------- .. figure:: _static/event-flow.* :width: 60% Source : http://www.w3.org/TR/DOM-Level-3-Events/#event-flow .. index:: listener Paramètre d'un :en:`listener` ----------------------------- * Par défaut, les :en:`listeners` dont déclenchés à la remontée de l'événement. * Il est donc possible de s'abonner à tous les événements se produisant *à l'intérieur* d'un élément (et pas uniquement *sur* cet élément). * Le :en:`listener` reçoit en paramètre un `événement `_, dont l'attribut ``target`` contient l'élément cible. .. hint:: On peut également forcer un listener à se déclencher à la descente (:en:`capture` d'un événement) plutôt qu'à sa remontée (:en:`bubbling`). Pour cela on passera ``true`` en troisième paramètre de `addEventListener `_. .. nextslide:: .. code:: document.body .addEventListener("click", function(evt) { const msg = document.getElementById("msg"); if (evt.target.tagName === "BUTTON") { msg.textContent = "Vous avez cliqué sur " + evt.target.textContent; } else { console.log(evt.target); msg.textContent = "Vous avez raté les boutons..."; } }); `Voir et modifier cet exemple`__ __ https://jsbin.com/xidedom/2/edit?html,js,output .. index: preventDefault .. hint:: L'objet événement passé en paramètre aux :en:`listeners` a également une méthode ``preventDefault`` qui permet d'inhiber le comportement par défaut de l'événement. Note sur les arguments des fonctions et Javascript `````````````````````````````````````````````````` Comme on vient de le voir pour les :en:`listeners`, Javascript est très tolérant avec le nombre de paramètres des fonctions. * Si on appelle une fonction avec trop de paramètres, les paramètres supplémentaires sont disponibles dans une variable spéciale nommée ``arguments`` (comparable à un tableau). * Si on appelle une fonction avec pas assez de paramètres, les paramètres oubliés reçoivent comme valeur ``undefined``. TP: Horloge avec alarme ======================= .. rst-class:: exercice Sujet ----- * Créez une horloge digitale qui rafraîchit son affichage automatiquement. Vous aurez besoin pour cela de la classe `Date `_ et de la fonction `setTimeout `_. * Vous ajouterez ensuite une fonctionnalité permettant d'ajouter, de modifier et de supprimer des alarmes. Pour faire sonner votre alarme, vous pourrez notamment créer un élément `