Gestion des Événements

Présentation

Un événement est un signal envoyé par le navigateur pour indiquer que quelque chose vient de se produire. Par exemple un clic de souris, une touche du clavier enfoncée ou la soumission d'un formulaire.

Ces événements peuvent être déclenchés par l'utilisateur (clic, déplacement du curseur, saisie au clavier, etc.) ou par le navigateur lui-même (chargement de la page, redimensionnement de la fenêtre, fin d'une animation…).

Avec JavaScript, on peut écouter ces événements et réagir en appelant une fonction. Cette fonction peut alors modifier le contenu du DOM, changer le style d'un élément, afficher un message ou encore envoyer des données à un serveur. C'est ce mécanisme qui permet de transformer une page statique en interface interactive.

Déclaration d'un gestionnaire d'événements

Un gestionnaire d'événements est une fonction JavaScript qui définit le comportement à exécuter lorsqu'un événement se produit. Ces fonctions peuvent être adaptées aux besoins de votre application. Par exemple, un gestionnaire de clic peut afficher une boîte de dialogue, modifier le contenu de la page ou envoyer des données à un serveur.

Les attributs HTML d'événements

Une première méthode consiste à utiliser les attributs HTML d'événements. Ces attributs sont ajoutés directement dans les balises HTML et indiquent la fonction à exécuter lorsqu'un événement survient. Par exemple, l'attribut onclick permet de réagir à un clic sur un élément.

Dans l'exemple suivant, lorsqu'un clic se produit sur le bouton, la fonction maFonction() est exécutée.


                    <button onclick="maFonction()">Cliquez-moi</button>
                

Cette technique est rapide à mettre en place, mais elle mélange HTML et JavaScript et rend le code plus difficile à organiser. Elle impose aussi que la fonction appelée soit accessible au niveau global. Dans un projet structuré, on préfère généralement déclarer les gestionnaires directement en JavaScript.

Les propriétés d'événements JavaScript

Chaque élément du DOM dispose de propriétés d'événements JavaScript qui permettent de définir des gestionnaires, par exemple onclick, onmouseover ou onkeydown.

Dans cet exemple, on sélectionne un bouton avec document.querySelector(), puis on affecte une fonction à la propriété onclick.


                    const bouton = document.querySelector('button');

                    bouton.onclick = function()
                    {
                        // Code à exécuter…
                    };
                

Cette approche sépare déjà mieux HTML et JavaScript. Elle a toutefois une limite importante. Pour une propriété donnée comme onclick, un seul gestionnaire peut être enregistré à la fois. Une nouvelle affectation remplace la précédente.


                    const bouton = document.querySelector('button');

                    const premiereAction = () =>
                    {
                        console.log('Première action');
                    };

                    const secondeAction = () =>
                    {
                        console.log('Seconde action');
                    };

                    bouton.onclick = premiereAction;
                    bouton.onclick = secondeAction; // Remplace la première action
                

Avec cette technique, le bouton ne déclenche que secondeAction. Impossible donc d'empiler plusieurs comportements sur le même événement avec une simple propriété. Pour cela, on utilise addEventListener().

La méthode addEventListener()

La méthode addEventListener() est la manière moderne et flexible de gérer les événements. Elle permet d'ajouter plusieurs gestionnaires sur le même élément et le même type d'événement, et de les retirer facilement.

La syntaxe de base de addEventListener() est la suivante.


                    element.addEventListener(type, gestionnaire, options);
                
  • element  l'élément du DOM sur lequel on souhaite écouter un événement.
  • type  le type d'événement à écouter, par exemple "click", "keydown", "mouseover".
  • gestionnaire  la fonction appelée lorsque l'événement se produit.
  • options (facultatif)  un objet pour affiner le comportement de l'écouteur.

Dans l'exemple suivant, un clic sur le bouton déclenche la fonction gererClickSurElement().


                    const bouton = document.querySelector('button');

                    const gererClickSurElement = () =>
                    {
                        console.log("J'affiche quelque chose dans la console…");
                    };

                    // On passe la fonction sans parenthèses pour que le navigateur l'appelle plus tard.
                    bouton.addEventListener('click', gererClickSurElement);
                

Avec addEventListener(), on peut enregistrer plusieurs fonctions sur le même événement, retirer un gestionnaire précis et utiliser des options avancées si nécessaire. La section suivante présente quelques types d'événements fréquents avec des exemples concrets.

Propagation des événements

Lorsqu'un événement se produit sur un élément du DOM, il ne s'arrête pas à cet élément. Le navigateur fait circuler l'événement à travers la structure du document. Cette circulation suit un parcours précis dans l'arbre des nœuds. On parle alors de propagation des événements. Comprendre ce parcours permet d'utiliser les options de addEventListener() de façon plus précise.

Les trois phases de propagation

Le DOM est organisé sous forme d'arborescence. Pour la plupart des événements déclenchés sur un élément (clic, toucher, survol, saisie clavier, etc.), le navigateur fait circuler l'événement dans cette arborescence en trois étapes successives. Chaque élément traversé peut réagir s'il possède un gestionnaire correspondant au type d'événement.

  1. Phase de capture L'événement part de la racine du document (document) et descend dans l'arborescence en passant par chaque parent jusqu'à atteindre l'élément ciblé.
  2. Phase de cible L'événement atteint l'élément exact sur lequel l'action s'est produite, par exemple un bouton cliqué.
  3. Phase de bouillonnement (ou bubbling) L'événement remonte l'arborescence depuis l'élément cible en repassant par ses parents, dans l'ordre inverse de la phase de capture, jusqu'à revenir à la racine.

Autrement dit, un clic sur un bouton situé dans un conteneur déclenche aussi l'événement dans ce conteneur, puis potentiellement dans ses propres parents. La propagation explique donc pourquoi un événement peut atteindre plusieurs éléments, même s'il a été déclenché sur un seul.

Par défaut, les gestionnaires ajoutés avec addEventListener() s'exécutent pendant la phase de bouillonnement.


                        <div id="container">
                            <button id="mon-bouton">Cliquez-moi</button>
                        </div>
                    

                        const container = document.getElementById('container');
                        const bouton = document.getElementById('mon-bouton');

                        // Gestionnaire exécuté en phase de bouillonnement (par défaut).
                        container.addEventListener('click', () =>
                        {
                            console.log('Container (bouillonnement)');
                        });

                        // Gestionnaire exécuté en phase de capture.
                        container.addEventListener('click', () =>
                        {
                            console.log('Container (capture)');
                        }, { capture: true });

                        bouton.addEventListener('click', () =>
                        {
                            console.log('Bouton');
                        });
                    

Si l'utilisateur clique sur le bouton, l'ordre d'exécution est en général le suivant. Le conteneur en capture, puis le bouton, puis le conteneur en bouillonnement. Cet ordre montre clairement la descente de l'événement jusqu'à la cible, puis sa remontée parmi les mêmes éléments.

Ce parcours en trois étapes n'est pas seulement un fonctionnement interne du navigateur. Il apporte un véritable contrôle au développeur. Les parents d'un élément peuvent réagir avant que l'action n'atteigne la cible (capture) ou après que la cible ait traité l'événement (bouillonnement). Ce mécanisme permet aussi de gérer les événements de manière centralisée sur un conteneur plutôt que sur chaque élément interne, ou encore d'intercepter une interaction pour la modifier ou l'arrêter. La propagation offre donc plusieurs moments possibles pour intervenir sur un même événement.

Options de addEventListener()

Jusqu'ici, nous avons utilisé addEventListener() dans sa forme la plus simple. Cette forme suffit pour une grande partie des cas courants, mais le troisième paramètre de la méthode permet d'aller plus loin et d'adapter le comportement d'un écouteur à des besoins plus précis.

Ce paramètre peut modifier le moment où le gestionnaire se déclenche dans la propagation, limiter sa durée de vie, améliorer la fluidité de certaines interactions ou encore faciliter la suppression de plusieurs écouteurs d'un coup.

Voici le rappel de la signature complète de la méthode.


                        element.addEventListener(type, gestionnaire, options);
                    

Le troisième argument peut être un simple booléen ou un objet d'options. Le booléen correspond à l'ancienne forme historique. Il sert uniquement à choisir la phase d'écoute (true pour la capture, false ou omission pour le bouillonnement).


                        element.addEventListener('click', gestionnaire, true);  // capture
                        element.addEventListener('click', gestionnaire, false); // bouillonnement
                    

Aujourd'hui, on privilégie un objet d'options. Cette forme rend le code plus explicite et permet d'activer plusieurs réglages en même temps :

  • capture : indique la phase d'écoute de l'événement.
  • once : exécute le gestionnaire une seule fois puis le retire automatiquement.
  • passive : informe le navigateur que le gestionnaire ne bloquera pas le défilement.
  • signal : permet de retirer plusieurs gestionnaires d'un coup avec un AbortController.

Option capture

L'option capture précise à quelle phase de la propagation le gestionnaire doit se déclencher. Elle vaut false par défaut, ce qui correspond à la phase de bouillonnement.


                        // Écouteur en bouillonnement (par défaut).
                        element.addEventListener('click', gestionnaire);

                        // Écouteur en capture, équivalent au troisième paramètre "true".
                        element.addEventListener('click', gestionnaire, { capture: true });
                    

On utilise la capture principalement lorsque l'on veut intercepter un événement très tôt, avant que les enfants n'aient l'occasion de le traiter, ou pour mettre en place des comportements globaux très contrôlés.

Option once

L'option once demande au navigateur d'exécuter le gestionnaire une seule fois. Dès que la fonction a été appelée, l'écouteur est automatiquement supprimé. Cela revient au même résultat qu'un removeEventListener() exécuté juste après la première activation, mais sans intervention manuelle.


                        const bouton = document.querySelector('button');

                        const gererClickUneSeuleFois = () =>
                        {
                            console.log('Ce message apparaît une seule fois.');
                        };

                        bouton.addEventListener('click', gererClickUneSeuleFois, { once: true });
                    

Ce fonctionnement est adapté aux actions ponctuelles comme un message d'accueil, la première ouverture d'un panneau ou l'initialisation d'un élément interactif.

Option passive

L'option passive indique au navigateur que le gestionnaire n'utilisera pas event.preventDefault(). Cette information lui permet d'optimiser la gestion de certains événements, en particulier ceux liés au défilement et aux interactions tactiles.

Pour des événements comme scroll ou touchmove, le navigateur doit savoir s'il peut laisser l'interface défiler immédiatement ou s'il doit attendre la fin du gestionnaire au cas où celui-ci souhaiterait annuler l'action. Cette attente correspond simplement au temps nécessaire pour que la fonction de rappel s'exécute complètement, afin que le navigateur puisse vérifier l'éventuelle présence d'un preventDefault().

Cette pause répétée peut créer des ralentissements sur mobile. Avec passive: true, le navigateur sait d'avance qu'aucune annulation ne sera tentée. Il peut donc laisser le défilement ou le geste tactile se produire immédiatement, sans attendre la fin du gestionnaire.


                        const gererScroll = (event) =>
                        {
                            // Analyse du scroll sans bloquer l'interaction.
                            console.log(window.scrollY);
                        };

                        window.addEventListener('scroll', gererScroll, { passive: true });
                    

L'option passive convient bien lorsque l'on travaille avec des événements déclenchés très souvent comme scroll, wheel ou touchmove. Elle est également adaptée aux interactions tactiles où la fluidité du geste est importante, ainsi qu'aux écouteurs qui se contentent d'observer un comportement sans chercher à le bloquer.

En revanche, il ne faut pas utiliser passive: true si le gestionnaire doit réellement empêcher le comportement par défaut. Par exemple pour bloquer le défilement dans une zone précise, interdire un geste tactile ou annuler l'action d'un élément. Dans ces cas, on garde l'écouteur non passif afin de pouvoir utiliser preventDefault().

Option signal et AbortController

L'option signal permet d'associer un écouteur à un AbortController. Lorsque abort() est appelé sur ce contrôleur, tous les écouteurs liés à son signal sont automatiquement supprimés. Cela évite de devoir appeler removeEventListener() pour chaque gestionnaire.


                        const controller = new AbortController();

                        const gererClick = () =>
                        {
                            console.log('Clic détecté');
                        };

                        document.addEventListener('click', gererClick, {
                            signal: controller.signal
                        });

                        // Plus tard, lorsqu'on n'a plus besoin de cet écouteur.
                        controller.abort(); // Supprime automatiquement les écouteurs associés.
                    

Cette approche facilite la gestion d'écouteurs temporaires. Par exemple lorsqu'une modale, un menu ou une zone interactive doit écouter des événements pendant son affichage puis cesser d'écouter dès qu'elle disparaît.

Quelques événements courants

Les événements couvrent de nombreux cas. Pour commencer, quelques types reviennent très souvent en développement web. Les exemples ci-dessous illustrent leur usage avec addEventListener().

Clic de souris ("click")

L'événement "click" se déclenche lorsqu'un élément reçoit un clic de souris. Il est très utilisé pour les boutons, les liens ou les éléments interactifs en général.


                    const bouton = document.querySelector('button');

                    const gererClickSurElement = () =>
                    {
                        console.log('Bouton cliqué');
                    };

                    bouton.addEventListener('click', gererClickSurElement);
                

Pression de touche ("keydown")

L'événement "keydown" se déclenche dès qu'une touche du clavier est enfoncée. Il permet par exemple de réagir aux raccourcis clavier ou de contrôler un jeu.


                    const gererKeydownSurDocument = (event) =>
                    {
                        console.log('Touche pressée : ' + event.key);
                    };

                    document.addEventListener('keydown', gererKeydownSurDocument);
                

Survol de souris ("mouseover")

L'événement "mouseover" se déclenche lorsque le pointeur de la souris entre sur un élément. Il est utile pour mettre un élément en évidence au survol.


                    const zone = document.querySelector('.zone');

                    const gererMouseoverSurZone = () =>
                    {
                        console.log('La souris survole la zone');
                    };

                    zone.addEventListener('mouseover', gererMouseoverSurZone);
                

Redimensionnement de la fenêtre ("resize")

L'événement "resize" se déclenche lorsqu'on modifie la taille de la fenêtre du navigateur. On l'utilise par exemple pour adapter une mise en page ou recalculer des positions.


                    const gererResizeSurWindow = () =>
                    {
                        console.log('Nouvelle largeur : ' + window.innerWidth);
                    };

                    window.addEventListener('resize', gererResizeSurWindow);
                

Supprimer un gestionnaire d'événements

L'un des avantages importants de addEventListener() est la possibilité de supprimer un gestionnaire d'événements spécifique quand on n'en a plus besoin. Pour cela, il faut garder une référence vers la même fonction que celle utilisée lors de l'ajout de l'écouteur.


                    const bouton = document.querySelector('button');

                    const gererClickSurBouton = (event) =>
                    {
                        console.log("Le bouton a été cliqué!");
                    };

                    // Ajouter le gestionnaire sur l'élément "bouton" pour l'événement "click".
                    bouton.addEventListener('click', gererClickSurBouton);

                    // Supprimer le même gestionnaire.
                    bouton.removeEventListener('click', gererClickSurBouton);
                

Pour que la suppression fonctionne, le type d'événement, l'élément cible et la fonction de rappel doivent être identiques à ceux utilisés lors de l'appel à addEventListener(). Si des options sont utilisées, elles doivent également correspondre.

Objet Event

L'objet Event est une sorte de boîte à outils qui contient des informations sur ce qui vient de se produire sur la page. À chaque fois qu'un événement se déclenche, comme un clic de souris ou une pression sur une touche du clavier, le navigateur crée un objet Event et l'associe à cet événement.

Cet objet est une mine d'informations. Selon le type d'événement, il peut fournir des détails comme la touche du clavier enfoncée, les coordonnées exactes du curseur ou encore l'élément sur lequel l'événement s'est produit.

Lorsqu'un événement se produit, le navigateur appelle la fonction de rappel associée et lui transmet l'objet Event en argument. En pratique, on déclare généralement un paramètre nommé event (ou e) dans la fonction. À l'intérieur de celle-ci, on peut alors accéder à toutes les informations utiles sur l'événement.

Voici quelques exemples de propriétés utiles de l'objet Event.

Accéder à l'élément courant avec currentTarget

Dans l'exemple suivant, on sélectionne un bouton dans le DOM. Lorsque l'utilisateur clique sur ce bouton, l'événement est déclenché et la fonction de rappel utilise event.currentTarget pour accéder à l'élément sur lequel l'écouteur d'événement est attaché.


                    const bouton = document.querySelector('button');

                    const gererClickSurBouton = (event) =>
                    {
                        // Récupère la valeur de l'attribut "id" de l'élément courant.
                        console.log(`Élément courant de l'événement : ${event.currentTarget.id}`);
                    };

                    bouton.addEventListener('click', gererClickSurBouton);
                

Récupérer la position de la souris avec clientX et clientY

Dans l'exemple suivant, on attache un écouteur d'événement à l'objet window pour l'événement "mousemove". L'événement se déclenche à chaque mouvement de la souris. L'objet Event permet de récupérer la position en x et en y du curseur de la souris à l'aide des propriétés event.clientX et event.clientY.


                    const gererMousemoveSurWindow = (event) =>
                    {
                        // Coordonnée X du curseur de la souris par rapport à la fenêtre du navigateur.
                        const x = event.clientX;

                        // Coordonnée Y du curseur de la souris par rapport à la fenêtre du navigateur.
                        const y = event.clientY;

                        console.log(`X : ${x}px, Y : ${y}px`);
                    };

                    window.addEventListener('mousemove', gererMousemoveSurWindow);
                

Empêcher le comportement par défaut d'un lien avec preventDefault

Dans l'exemple ci-dessous, on attache un gestionnaire d'événement à un lien hypertexte (élément "a") pour l'événement "click". La méthode event.preventDefault() empêche le comportement par défaut du lien, c'est-à-dire le chargement de la nouvelle page, afin de gérer l'action autrement avec JavaScript.


                    const monLien = document.querySelector('a');

                    const gererClickSurMonLien = (event) =>
                    {
                        // Empêche le comportement par défaut du lien (chargement d'une autre page).
                        event.preventDefault();

                        // Code à exécuter… par exemple le chargement dynamique de contenu.
                    };

                    monLien.addEventListener('click', gererClickSurMonLien);
                

Passer des arguments à la fonction de rappel (callback)

Lorsque l'on utilise une fonction de rappel, il arrive que l'on ait besoin de lui transmettre des arguments supplémentaires en plus de l'objet événement. Pour y parvenir, une première approche consiste à utiliser une fonction anonyme intermédiaire, chargée d'appeler la fonction cible avec les arguments souhaités.


                    const bouton = document.querySelector('button');

                    // Fonction appelée lors du déclenchement de l'événement "click" sur le bouton.
                    const gererClickSurBouton = (arg1, arg2, event) =>
                    {
                        // Code à exécuter…
                        console.log(arg1, arg2, event.type);
                    };

                    // Gestionnaire d'événement qui fournit les arguments à la fonction cible.
                    bouton.addEventListener('click', (event) =>
                    {
                        gererClickSurBouton('valeur1', 'valeur2', event);
                    });
                

Cette écriture fonctionne, mais elle présente une limite importante. La fonction anonyme passée directement à addEventListener() n'est liée à aucune variable. Cela empêche de la retirer plus tard avec removeEventListener(), car il faudrait pouvoir fournir exactement la même fonction. En pratique, cela rend le gestionnaire difficile à désactiver ou à remplacer.

Pour une approche plus flexible, il est recommandé de nommer la fonction qui sert de gestionnaire. On prépare une fonction principale qui contient la logique métier, puis une fonction intermédiaire chargée de lui fournir les arguments requis tout en recevant l'objet événement. Cette fonction intermédiaire est appelée un wrapper. Elle encapsule l'appel réel, prépare les paramètres nécessaires, transmet l'événement à la fonction principale et reste stockée dans une constante, ce qui permet de retirer proprement le gestionnaire avec removeEventListener().


                    const bouton = document.querySelector('button');

                    // Fonction principale qui réalise le travail lorsque le bouton est cliqué.
                    const faireQqChose = (arg1, arg2, event) =>
                    {
                        // Code à exécuter…
                        console.log('Action :', arg1, arg2, 'événement :', event.type);
                    };

                    // Fonction intermédiaire utilisée comme gestionnaire d'événement.
                    // Elle reçoit l'objet "event" et appelle la fonction principale
                    // en lui fournissant les arguments nécessaires.
                    const gererClickSurBouton = (event) =>
                    {
                        faireQqChose('valeur1', 'valeur2', event);
                    };

                    // Déclarer le gestionnaire d'événement pour le clic sur le bouton.
                    bouton.addEventListener('click', gererClickSurBouton);

                    // Plus tard, si nécessaire, il devient possible de retirer ce gestionnaire.
                    bouton.removeEventListener('click', gererClickSurBouton);
                

Cette organisation rend le code plus clair et plus facile à maintenir. La fonction principale se concentre sur le traitement métier, tandis que la fonction intermédiaire s'occupe d'adapter les arguments pour l'appel. Le fait de stocker le gestionnaire dans une constante permet en plus de retirer l'écouteur d'événement quand il n'est plus utile.

Exercices

Le travail se fait dans app.js. Pour chaque exercice, le résultat attendu doit être vérifié dans la console du navigateur.

Exo-gestion-evenements-serie-01

Structure

Créer un dossier nommé Exo-gestion-evenements-serie-01, puis y organiser les fichiers et dossiers en respectant l'arborescence suivante :


                    Exo-gestion-evenements-serie-01/
                    |-- index.html
                    |-- css/
                    |   └── style.css
                    └── js/
                        |-- app.js
                        └── modules/
                            |-- exo01.js
                            |-- exo02.js
                            |-- exo03.js
                            |-- exo04.js
                            |-- exo05.js
                            └── exo06.js
                

Copier le gabarit HTML suivant dans index.html. Ce gabarit contient tous les éléments nécessaires pour l'ensemble des exercices :


                    <!doctype html>
                    <html lang="fr">
                    <head>
                        <meta charset="utf-8">
                        <meta name="viewport" content="width=device-width, initial-scale=1">
                        <link rel="stylesheet" href="./css/style.css">
                        <title>JS: Exo Gestionnaire d'Événements Série 01</title>
                    </head>
                    <body>
                        <h1>JS: Exo Gestionnaire d'Événements Série 01</h1>

                        <!-- Exo 01 -->
                        <section>
                            <h2>01. Le Clic Simple</h2>
                            <button id="btn-clic">Cliquez-moi</button>
                        </section>

                        <!-- Exo 02 -->
                        <section>
                            <h2>02. La Souris et l'Objet Event</h2>
                            <div id="zone-souris" class="zone-survol">
                                Survolez-moi avec la souris !
                            </div>
                        </section>

                        <!-- Exo 03 -->
                        <section>
                            <h2>03. Le Clavier</h2>
                            <label for="champ-clavier">Saisissez du texte : </label>
                            <input type="text" id="champ-clavier" placeholder="Tapez ici...">
                        </section>

                        <!-- Exo 04 -->
                        <section>
                            <h2>04. Comportement par défaut</h2>
                            <a href="https://www.google.com" id="lien-interdit">Ne pas aller sur Google</a>
                        </section>

                        
                        <!-- Exo 05 -->
                        <section>
                            <h2>05. Arguments</h2>
                            <button class="js-btn-param">Utilisateur A</button>
                            <button class="js-btn-param">Utilisateur B</button>
                        </section>

                        <!-- Exo 06 -->
                        <section>
                            <h2>06. Ajouter/Supprimer un écouteur</h2>
                            <p>Utilisez les boutons de contrôle pour modifier le comportement du bouton de test ci-dessous.</p>
                            
                            <!-- Bouton Cible (C'est lui qui va recevoir l'événement) -->
                            <button id="btn-test">Bouton Test (Inactif au départ)</button>
                            
                            <hr>
                            
                            <!-- Boutons de contrôle -->
                            <button id="btn-activer">Activer l'écouteur</button>
                            <button id="btn-desactiver">Supprimer l'écouteur</button>
                        </section>

                        <script src="./js/app.js" type="module"></script>
                    </body>
                    </html>
                

Copier la feuille de style suivante dans style.css :


                    body { 
                        font-family: sans-serif; 
                        padding: 20px; 
                        line-height: 1.5; 
                    }
                    section { 
                        border: 1px solid #ccc; 
                        padding: 20px; 
                        margin-bottom: 20px; 
                        border-radius: 8px; 
                    }
                    h2 { 
                        margin-top: 0; 
                    }
                    .zone-survol 
                    {
                        background-color: #f0f0f0; 
                        padding: 20px; 
                        text-align: center; 
                        cursor: crosshair;
                        border: 2px dashed #999; 
                        margin-top: 10px;
                    }
                    button { 
                        padding: 10px 15px; 
                        cursor: pointer; 
                    }
                

Ouvrir la page via le navigateur (via un serveur local, car les modules l'exigent), puis ouvrir la console.

Exo 01

Objectif : Clic simple - Détecter un clic sur un bouton pour déclencher une fonction et afficher un message en console.

  • Se rendre dans le fichier js/modules/exo01.js.
  • Créer une fonction exportée (export nommé) appelée execExo01.
  • Dans app.js, importer execExo01 depuis ./modules/exo01.js, puis appeler execExo01().
  • À l'intérieur de la fonction execExo01 :
    1. Dans la fonction, afficher ----- Exo 01 ----- dans la console.
    2. Sélectionner le bouton ayant l'id btn-clic et le stocker dans une constante nommée boutonClicElem.
    3. Ajouter un écouteur d'événement sur ce bouton pour l'événement "click".
    4. Utiliser une fonction fléchée anonyme directement dans le gestionnaire.
      Cette fonction doit simplement afficher le message "Clic détecté !" dans la console.
  • Tester en cliquant sur le bouton "Cliquez-moi" dans la page et vérifier l'affichage dans la console.

Exo 02

Objectif : Objet Event - Suivre le mouvement de la souris et récupérer ses coordonnées (X, Y) via l'objet event.

  • Se rendre dans le fichier js/modules/exo02.js.
  • Créer et exporter une fonction fléchée nommée execExo02.
  • Dans app.js, importer execExo02 depuis ./modules/exo02.js, puis appeler execExo02().
  • À l'intérieur de la fonction execExo02 :
    1. Dans la fonction, afficher ----- Exo 02 ----- dans la console.
    2. Sélectionner la div ayant l'id zone-souris et la stocker dans une constante nommée zoneSourisElem.
    3. Ajouter un écouteur pour l'événement "mousemove" (mouvement de souris).
    4. Déclarer la fonction de rappel en acceptant le paramètre event.
    5. Dans cette fonction, récupérer les coordonnées de la souris via les propriétés event.clientX et event.clientY.
    6. Afficher dans la console une phrase du type : "Position X: [valeur], Position Y: [valeur]".
  • Tester en survolant la zone grisée dans la page et observer le défilement des coordonnées dans la console.

Exo 03

Objectif : Clavier - Écouter la frappe au clavier (keydown) pour identifier et afficher la touche pressée par l'utilisateur.

  • Se rendre dans le fichier js/modules/exo03.js.
  • Créer et exporter une fonction fléchée nommée execExo03.
  • Dans app.js, importer execExo03 depuis ./modules/exo03.js, puis appeler execExo03().
  • À l'intérieur de la fonction execExo03 :
    1. Dans la fonction, afficher ----- Exo 03 ----- dans la console.
    2. Sélectionner le champ de saisie ayant l'id champ-clavier et le stocker dans une constante nommée champClavierElem.
    3. Ajouter un écouteur pour l'événement "keydown" (touche enfoncée).
    4. Dans la fonction de rappel, utiliser l'objet event pour récupérer le nom de la touche pressée (propriété key).
    5. Afficher dans la console : "Touche pressée : [touche]".
  • Cliquer dans le champ de texte et taper quelques lettres pour tester.

Exo 04

Objectif : PreventDefault - Intercepter le clic sur un lien et bloquer son comportement par défaut.

  • Se rendre dans le fichier js/modules/exo04.js.
  • Créer et exporter une fonction fléchée nommée execExo04.
  • Dans app.js, importer execExo04 depuis ./modules/exo04.js, puis appeler execExo04().
  • À l'intérieur de la fonction execExo04 :
    1. Dans la fonction, afficher ----- Exo 04 ----- dans la console.
    2. Sélectionner le lien ayant l'id lien-interdit et le stocker dans une constante nommée lienInterditElem.
    3. Ajouter un écouteur pour l'événement "click".
    4. Dans la fonction de rappel :
      • Appeler la méthode preventDefault() sur l'objet event pour empêcher le navigateur de suivre le lien.
      • Afficher le message "Navigation bloquée par JavaScript !" dans la console.
  • Cliquer sur le lien et vérifier que la page ne change pas, et que le message apparaît bien.

Exo 05

Objectif : Passage d'arguments - Gérer une liste de boutons et utiliser une fonction fléchée pour passer des paramètres personnalisés au gestionnaire d'événement.

  • Se rendre dans le fichier js/modules/exo05.js.
  • Créer et exporter une fonction fléchée nommée execExo05.
  • Dans app.js, importer execExo05 depuis ./modules/exo05.js, puis appeler execExo05().
  • À l'intérieur de la fonction execExo05 :
    1. Dans la fonction, afficher ----- Exo 05 ----- dans la console.
    2. Sélectionner tous les boutons ayant la classe js-btn-param avec querySelectorAll. Stocker la collection dans boutonsParamElements.
    3. Créer une fonction nommée saluerUtilisateur(nom) qui reçoit un paramètre et affiche "Bonjour [nom] !" dans la console.
    4. Parcourir la collection boutonsParamElements avec une boucle for...of.
    5. Pour chaque bouton, ajouter un écouteur "click".
    6. Utiliser une fonction fléchée anonyme (wrapper) qui appellera saluerUtilisateur() en lui passant le texte du bouton (récupérable via bouton.innerText ou en dur pour tester).
  • Vérifier que cliquer sur "Utilisateur A" affiche "Bonjour Utilisateur A !".

Exo 06

Objectif : Suppression d'écouteur - Activer et désactiver dynamiquement l'interactivité d'un bouton en ajoutant ou supprimant son écouteur.

  • Se rendre dans le fichier js/modules/exo06.js.
  • Créer et exporter une fonction fléchée nommée execExo06.
  • Dans app.js, importer execExo06 depuis ./modules/exo06.js, puis appeler execExo06().
  • À l'intérieur de la fonction execExo06 :
    1. Dans la fonction, afficher ----- Exo 06 ----- dans la console.
    2. Sélectionner les trois boutons de cette section :
      • #btn-test (stocker dans boutonTestElem)
      • #btn-activer (stocker dans boutonActiverElem)
      • #btn-desactiver (stocker dans boutonDesactiverElem)
    3. Créer une variable nommée ecouteurBoutonTestEstActif et l'initialiser à false.
      Cette variable servira de mémoire pour savoir si l'écouteur est déjà posé sur le bouton ou non.
    4. Créer une fonction fléchée nommée gestionClicBoutonTest et la stocker dans une constante.
      Important : Contrairement à l'étape 01, nous devons ici stocker la fonction dans une variable pour pouvoir y faire référence plus tard lors de la suppression.
      Cette fonction doit afficher : "Le bouton Test est actif !".
    5. Logique du bouton "Activer" :
      Ajouter un écouteur "click" sur boutonActiverElem.
      Dans la fonction de rappel :
      • Vérifier l'état : si ecouteurBoutonTestEstActif est déjà true, afficher un message indiquant que l'écouteur est déjà actif.
      • Sinon (si l'écouteur est inactif) :
        • Ajouter l'écouteur d'événement sur boutonTestElem en utilisant la fonction nommée gestionClicBoutonTest.
        • Mettre à jour la variable d'état ecouteurBoutonTestEstActif à true.
        • Afficher "Écouteur ACTIVÉ.".
    6. Logique du bouton "Désactiver" :
      Ajouter un écouteur "click" sur boutonDesactiverElem.
      Dans la fonction de rappel :
      • Vérifier l'état : si ecouteurBoutonTestEstActif est déjà false, afficher un message indiquant que l'écouteur est déjà inactif.
      • Sinon (si l'écouteur est actif) :
        • Supprimer l'écouteur d'événement sur boutonTestElem en utilisant la méthode removeEventListener et en lui passant la référence gestionClicBoutonTest.
        • Mettre à jour la variable d'état ecouteurBoutonTestEstActif à false.
        • Afficher "Écouteur SUPPRIMÉ.".
  • Scénario de test :
    1. Cliquer sur le "Bouton Test" : rien ne doit se passer (il est inactif au départ).
    2. Cliquer sur "Activer l'écouteur".
    3. Cliquer sur le "Bouton Test" : le message doit s'afficher.
    4. Cliquer plusieurs fois de suite sur "Activer" : vérifier dans la console que le message "Déjà actif" apparaît pour éviter d'empiler les écouteurs.
    5. Cliquer sur "Supprimer l'écouteur".
    6. Cliquer à nouveau sur le "Bouton Test" : plus rien ne doit se passer.

Exo-gestion-evenements-serie-02

Objectif : Explorer les fonctionnalités avancées : l'option once, la propagation (bubbling/capture) et le nettoyage avec AbortController.

Structure

Créer un dossier nommé Exo-gestion-evenements-serie-02, puis y organiser les fichiers :


                        Exo-gestion-evenements-serie-02/
                        |-- index.html
                        |-- css/
                        |   └── style.css
                        └── js/
                            |-- app.js
                            └── modules/
                                |-- exo07.js
                                |-- exo08.js
                                |-- exo09.js
                                └── exo10.js
                    

Copier le code suivant dans index.html :


                        <!doctype html>
                        <html lang="fr">
                        <head>
                            <meta charset="utf-8">
                            <meta name="viewport" content="width=device-width, initial-scale=1">
                            <link rel="stylesheet" href="./css/style.css">
                            <title>JS: Exo Gestionnaire d'Événements Série 02</title>
                        </head>
                        <body>
                            <h1>JS: Exo Gestionnaire d'Événements Série 02</h1>

                            <!-- Étape 07 -->
                            <section>
                                <h2>07. Options et Arguments</h2>
                                <button id="btn-unique">Clic unique (once)</button>
                            </section>

                            <!-- Étape 08 & 09 -->
                            <section>
                                <h2>08 & 09. Propagation (Bubbling vs Capture)</h2>
                                <div id="zone-parent" class="zone-parent">
                                    DIV PARENT
                                    <br><br>
                                    <button id="bouton-enfant" class="btn-enfant">BOUTON ENFANT</button>
                                </div>
                            </section>

                            <!-- Étape 10 -->
                            <section>
                                <h2>10. Nettoyage avec AbortController</h2>
                                <p>Le bouton "Test" ci-dessous est contrôlé par un signal.</p>

                                <!-- Bouton Cible -->
                                <button id="btn-test-signal">Bouton Test (Signal)</button>

                                <hr>

                                <!-- Boutons de contrôle -->
                                <button id="btn-activer-signal">Activer via Signal</button>
                                <button id="btn-abort-signal">Tout couper (Abort)</button>
                            </section>

                            <script src="./js/app.js" type="module"></script>
                        </body>
                        </html>
                    

Copier la feuille de style suivante dans style.css :


                        body { 
                            font-family: sans-serif; 
                            padding: 20px; 
                            line-height: 1.5; 
                        }
                        section { 
                            border: 1px solid #ccc; 
                            padding: 20px; 
                            margin-bottom: 20px; 
                            border-radius: 8px; 
                        }
                        h2 { 
                            margin-top: 0; 
                        }
                        button { 
                            padding: 8px 12px; 
                            cursor: pointer; 
                            margin-right: 5px; 
                        }
                        .zone-parent { 
                            padding: 40px; 
                            background-color: #e0e0e0;
                            border: 2px solid #999; 
                            text-align: center; 
                        }
                        .btn-enfant { 
                            padding: 15px 25px; 
                            background-color: #fff; 
                            border: 2px solid #333; 
                            font-weight: bold; 
                        }
                    

Ouvrir la page dans le navigateur (serveur local requis) et la console.

Exo 07

Objectif : Option Once - Créer un écouteur d'événement à usage unique qui s'autodétruit après la première exécution.

  • Se rendre dans le fichier js/modules/exo07.js.
  • Créer et exporter une fonction fléchée nommée execExo07.
  • Dans app.js, importer execExo07 depuis ./modules/exo07.js, puis appeler execExo07().
  • À l'intérieur de la fonction execExo07 :
    1. Dans la fonction, afficher ----- Exo 07 ----- dans la console.
    2. Sélectionner le bouton #btn-unique et le stocker dans boutonUniqueElem.
    3. Lui ajouter un écouteur "click" qui affiche "Ce message ne s'affiche qu'une seule fois !".
    4. Utiliser le troisième argument de addEventListener pour passer l'objet d'options { once: true }.
  • Cliquer plusieurs fois sur le bouton. Le message ne doit apparaître qu'au premier clic.

Exo 08

Objectif : Propagation (Bouillonnement) - Observer le mécanisme par défaut de propagation (Bubbling) où l'événement remonte de l'élément enfant vers son parent.

  • Se rendre dans le fichier js/modules/exo08.js.
  • Créer et exporter une fonction fléchée nommée execExo08.
  • Dans app.js, importer execExo08 depuis ./modules/exo08.js, puis appeler execExo08().
  • À l'intérieur de la fonction execExo08 :
    1. Dans la fonction, afficher ----- Exo 08 ----- dans la console.
    2. Sélectionner la div #zone-parent et le bouton #bouton-enfant. Stocker les références dans zoneParentElem et boutonEnfantElem.
    3. Ajouter un écouteur "click" sur le bouton enfant qui affiche "1. Enfant cliqué".
    4. Ajouter un écouteur "click" sur la zone parent qui affiche "2. Parent cliqué (Bouillonnement)".
  • Cliquer sur le bouton blanc (l'enfant). Vérifier l'ordre : l'événement remonte de l'enfant vers le parent.

Exo 09

Objectif : Propagation (Capture) - Intercepter l'événement lors de sa phase de descente vers la cible (avant le bouillonnement) en utilisant l'option.

  • Se rendre dans le fichier js/modules/exo09.js.
  • Créer et exporter une fonction fléchée nommée execExo09.
  • Dans app.js, importer execExo09 depuis ./modules/exo09.js, puis appeler execExo09().
  • À l'intérieur de la fonction execExo09 :
    1. Afficher la chaîne "----- Exo 09 -----" dans la console.
    2. Récupérer à nouveau la référence zoneParentElem (nécessaire car nous sommes dans un module isolé).
    3. Ajouter un nouvel écouteur sur le parent qui affiche "0. Parent cliqué (CAPTURE)".
    4. Activer la phase de capture en passant l'option { capture: true } en troisième argument.
  • Cliquer à nouveau sur le bouton enfant. L'ordre doit être :
    1. Parent (Capture) -> L'événement descend.
    2. Enfant -> L'événement atteint la cible.
    3. Parent (Bouillonnement) -> L'événement remonte.
  • Exo 10

    Objectif : AbortController - Utiliser l'API AbortController et son signal pour supprimer un écouteur d'événement à la demande, sans avoir besoin de référencer la fonction de rappel.

    • Se rendre dans le fichier js/modules/exo10.js.
    • Créer et exporter une fonction fléchée nommée execExo10.
    • Dans app.js, importer execExo10 depuis ./modules/exo10.js, puis appeler execExo10().
    • À l'intérieur de la fonction execExo10 :
      1. Afficher la chaîne "----- Étape 10 -----" dans la console.
      2. Sélectionner les trois boutons :
        • #btn-test-signal (stocker dans boutonTestSignalElem)
        • #btn-activer-signal (stocker dans boutonActiverSignalElem)
        • #btn-abort-signal (stocker dans boutonAbortSignalElem)
      3. Déclarer une variable controleur (avec let) et l'initialiser à null.
      4. Logique du bouton "Activer" :
        Dans l'écouteur du bouton d'activation :
        • Vérifier si controleur n'est pas null. Si c'est le cas, afficher "Déjà actif".
        • Sinon :
          • Créer une nouvelle instance : controleur = new AbortController();.
          • Ajouter l'écouteur "click" sur le bouton test qui affiche "Bouton signal cliqué !".
          • Important : Passer l'option { signal: controleur.signal } en 3ème argument.
          • Afficher "Signal créé et écouteur actif.".
      5. Logique du bouton "Abort" :
        Dans l'écouteur du bouton d'arrêt :
        • Vérifier si controleur est null. Si oui, afficher "Déjà inactif".
        • Sinon :
          • Exécuter controleur.abort() pour tuer les écouteurs.
          • Remettre la variable controleur à null pour réinitialiser l'état.
          • Afficher "Signal coupé : écouteurs morts !".
    • Test :
      1. Cliquer sur "Activer" -> ça marche.
      2. Cliquer encore sur "Activer" -> Message "Déjà actif".
      3. Cliquer sur "Bouton Test (Signal)" -> Le message s'affiche.
      4. Cliquer sur "Couper" -> Le signal meurt.
      5. Cliquer sur "Bouton Test (Signal)" -> Plus rien ne se passe.
      6. Cliquer sur "Activer" -> On peut recommencer le cycle.