Cours JavaScript et TypeScript · Module 8: Événements et DOM
Custom Events et dispatchEvent en JavaScript
Dans cette leçon9
« Écoute attentivement », dit le Dr Alan Grant, penché sur la console du système de surveillance. « Les dinosaures communiquent entre eux à l'aide de sons - des grondements graves aux rugissements terrifiants. Dans notre parc, on a besoin d'un système de communication similaire entre les modules. Et c'est là qu'interviennent les Custom Events ! »
Que sont les Custom Events ?
Jusqu'ici, on a découvert les événements DOM intégrés - click, keydown, submit et scroll. Mais JavaScript nous permet de créer nos propres événements non standard. Grâce à eux, on peut construire des systèmes de communication entre différentes parties d'une application, exactement comme le système d'alarme de Jurassic Park envoie des notifications à différentes zones.
Les Custom Events sont des événements définis par le programmeur qui peuvent transporter n'importe quelles données et être écoutés exactement comme les événements DOM ordinaires.
Le constructeur CustomEvent
Pour créer un événement personnalisé, on utilise le constructeur CustomEvent :
1// Création de base d'un Custom Event
2const alertEvent = new CustomEvent('dinoAlert');
3
4// Custom Event avec des données supplémentaires (detail)
5const escapeEvent = new CustomEvent('dinoEscape', {
6 detail: {
7 dinosaur: 'Velociraptor',
8 zone: 'B',
9 severity: 'critical'
10 }
11});
12
13// Accéder aux données de l'événement
14console.log(escapeEvent.detail.dinosaur); // "Velociraptor"
15console.log(escapeEvent.detail.severity); // "critical"
16console.log(escapeEvent.type); // "dinoEscape"L'objet detail est un champ spécial des Custom Events où l'on peut transmettre n'importe quelles données. C'est l'emplacement standard des données d'un CustomEvent. On peut aussi écrire sa propre classe qui hérite de Event avec des champs en plus, mais detail suffit en général.
Les options de CustomEvent
Le constructeur CustomEvent accepte un deuxième argument - un objet d'options :
1const event = new CustomEvent('systemUpdate', {
2 detail: { module: 'security', status: 'active' }, // Données de l'événement
3 bubbles: true, // Si l'événement se propage vers le haut du DOM
4 cancelable: true, // Si preventDefault() peut être appelé
5 composed: false // S'il franchit les frontières du Shadow DOM
6});detail- un objet avec les données qu'on veut transmettrebubbles- vautfalsepar défaut ; metstruesi l'événement doit se propager vers le haut de l'arbre du DOM (bouillonnement)cancelable- vautfalsepar défaut ; metstruesi tu veux pouvoir annuler l'événement viapreventDefault()
Émettre des événements - dispatchEvent
L'objet événement en lui-même ne fait rien - on doit l'émettre (dispatch) sur un élément du DOM précis :
1// Créer un élément qui émettra des événements
2const controlPanel = document.getElementById('controlPanel');
3
4// Créer l'événement
5const fenceAlert = new CustomEvent('fenceBreak', {
6 detail: {
7 zone: 'A',
8 voltage: 0,
9 dinosaur: 'T-Rex'
10 },
11 bubbles: true
12});
13
14// Émettre l'événement sur l'élément
15controlPanel.dispatchEvent(fenceAlert);La méthode dispatchEvent() est disponible sur chaque élément du DOM. Après son appel, l'événement est immédiatement émis et tous les gestionnaires à l'écoute sont exécutés de manière synchrone.
Écouter les Custom Events
On écoute les Custom Events exactement de la même façon que les événements ordinaires - via addEventListener :
1const controlPanel = document.getElementById('controlPanel');
2
3// Enregistrer un écouteur pour notre événement
4controlPanel.addEventListener('fenceBreak', function(event) {
5 const { zone, voltage, dinosaur } = event.detail;
6 console.log('ALARME ! Clôture de la zone ' + zone + ' endommagée !');
7 console.log('Dinosaure : ' + dinosaur);
8 console.log('Tension : ' + voltage + 'V');
9});
10
11// Un autre module peut aussi écouter le même événement
12controlPanel.addEventListener('fenceBreak', function(event) {
13 if (event.detail.dinosaur === 'T-Rex') {
14 console.log('ÉVACUEZ IMMÉDIATEMENT !');
15 }
16});
17
18// Émettre l'événement - les deux écouteurs seront appelés
19controlPanel.dispatchEvent(new CustomEvent('fenceBreak', {
20 detail: { zone: 'A', voltage: 0, dinosaur: 'T-Rex' }
21}));Exemple pratique : système d'alarme du parc
Voyons un exemple complet de communication inter-modules :
1// Système de surveillance de Jurassic Park
2const parkSystem = document.getElementById('parkSystem');
3
4// Le module de sécurité écoute les alertes
5parkSystem.addEventListener('securityAlert', function(event) {
6 const { type, zone, message } = event.detail;
7 const alertDiv = document.createElement('div');
8 alertDiv.className = 'alert alert-' + type;
9 alertDiv.textContent = '[' + zone + '] ' + message;
10 document.getElementById('alerts').appendChild(alertDiv);
11});
12
13// Le module de surveillance des dinosaures écoute les statuts
14parkSystem.addEventListener('dinoStatus', function(event) {
15 const { name, heartRate, location } = event.detail;
16 console.log(name + ' : FC=' + heartRate + ', emplacement=' + location);
17});
18
19// Fonction qui émet une alerte de sécurité
20function emitSecurityAlert(type, zone, message) {
21 parkSystem.dispatchEvent(new CustomEvent('securityAlert', {
22 detail: { type, zone, message },
23 bubbles: true
24 }));
25}
26
27// Fonction qui émet un statut de dinosaure
28function emitDinoStatus(name, heartRate, location) {
29 parkSystem.dispatchEvent(new CustomEvent('dinoStatus', {
30 detail: { name, heartRate, location },
31 bubbles: true
32 }));
33}
34
35// Utilisation
36emitSecurityAlert('warning', 'Zone B', 'Activité des raptors élevée');
37emitSecurityAlert('critical', 'Zone A', 'Clôture endommagée !');
38emitDinoStatus('T-Rex', 85, 'Enclos principal');Le bouillonnement dans les Custom Events
Par défaut, les Custom Events ne se propagent pas vers le haut de l'arbre du DOM. Pour activer le bouillonnement, il faut définir bubbles: true :
1const child = document.getElementById('zoneA');
2const parent = document.getElementById('parkSystem');
3
4// On enregistre l'écouteur AVANT l'envoi, car dispatchEvent() est synchrone
5parent.addEventListener('alert', (e) => {
6 console.log('Le parent a capté :', e.detail.msg);
7});
8
9// SANS bubbles (par défaut) - l'écouteur sur le parent NE captera PAS l'événement
10child.dispatchEvent(new CustomEvent('alert', {
11 detail: { msg: 'sans bubbles' }
12 // bubbles vaut false par défaut
13}));
14// (rien ne s'affiche dans la console)
15
16// AVEC bubbles: true - l'événement se propage vers le parent
17child.dispatchEvent(new CustomEvent('alert', {
18 detail: { msg: 'avec bubbles' },
19 bubbles: true // Maintenant l'événement se propagera !
20}));
21// Le parent a capté : avec bubblesC'est particulièrement utile quand tu veux écouter, sur un seul parent, les événements de nombreux enfants (la délégation d'événements pour les Custom Events).
Annuler les Custom Events
Si tu définis cancelable: true, tu peux annuler l'événement :
1const event = new CustomEvent('transferDino', {
2 detail: { dinosaur: 'T-Rex', from: 'A', to: 'B' },
3 cancelable: true,
4 bubbles: true
5});
6
7// Un écouteur peut annuler l'événement
8parkSystem.addEventListener('transferDino', function(e) {
9 if (e.detail.dinosaur === 'T-Rex') {
10 e.preventDefault(); // Annuler le transfert du T-Rex !
11 console.log('Transfert du T-Rex bloqué pour des raisons de sécurité !');
12 }
13});
14
15// Vérifier si l'événement a été annulé
16const wasNotCancelled = parkSystem.dispatchEvent(event);
17console.log('Transfert autorisé :', wasNotCancelled); // false (à cause de preventDefault)La méthode dispatchEvent() retourne false si l'événement a été annulé par preventDefault(). Cela permet à l'émetteur de vérifier si l'action doit se poursuivre.
Cas d'usage des Custom Events
Les Custom Events sont parfaits dans de nombreux scénarios :
- Communication entre composants - quand deux modules indépendants doivent échanger des informations
- Système de notification - un hub central où différents modules émettent des événements
- Découplage du code - le module émetteur n'a pas besoin de savoir qui écoute
- Système de plugins - des modules externes peuvent réagir aux événements de l'application
- Suivi des actions utilisateur - émettre des événements d'analytics
1// Exemple : découplage avec les Custom Events
2// Le module panier ne sait rien du module de notification
3const cart = [];
4
5function addToCart(product) {
6 cart.push(product);
7
8 // Émet un événement - quiconque le souhaite peut écouter
9 document.dispatchEvent(new CustomEvent('cartUpdated', {
10 detail: { product, totalItems: cart.length },
11 bubbles: true
12 }));
13}
14
15// Le module de notification écoute indépendamment
16document.addEventListener('cartUpdated', (e) => {
17 console.log('Ajouté : ' + e.detail.product.name);
18});
19
20// Le module du badge du panier écoute aussi indépendamment
21document.addEventListener('cartUpdated', (e) => {
22 console.log('Panier : ' + e.detail.totalItems + ' articles');
23});Récapitulatif
Les Custom Events sont un outil puissant pour construire des systèmes faiblement couplés en JavaScript :
new CustomEvent(name, options)- crée un événement non standardelement.dispatchEvent(event)- émet l'événement sur un élémentdetail- objet avec les données transmises dans l'événementbubbles: true- active la propagation de l'événement vers le haut du DOMcancelable: true- permet d'annuler l'événement viapreventDefault()- Les Custom Events s'écoutent de la même façon que les événements DOM ordinaires - via
addEventListener
« À Jurassic Park », résume le Dr Grant, « chaque système doit communiquer avec les autres. Les Custom Events sont notre système radio interne - chaque module peut diffuser et chaque module peut recevoir. Sans cela, tout le parc sombrerait dans le chaos. »
Code de cette leçon : index.html
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <title>Custom Events - Parc Jurassique</title>
6 <style>
7 body { font-family: monospace; background: #1a1a2e; color: #e0e0e0; padding: 20px; }
8 h1 { color: #e94560; text-align: center; }
9 h2 { color: #4ecdc4; margin-top: 25px; }
10 .section { background: rgba(255,255,255,0.05); padding: 20px; border-radius: 10px; margin: 15px 0; border-left: 3px solid #e94560; }
11 .output { background: #000; padding: 12px; border-radius: 6px; margin: 10px 0; white-space: pre-wrap; font-size: 13px; color: #0f0; max-height: 200px; overflow-y: auto; }
12 button { padding: 10px 20px; border: none; border-radius: 6px; cursor: pointer; margin: 5px; font-size: 14px; color: white; }
13 .btn-alert { background: #e94560; }
14 .btn-status { background: #0f3460; }
15 .btn-transfer { background: #533483; }
16 .btn-clear { background: #555; }
17 button:hover { opacity: 0.85; }
18 .zone { display: inline-block; padding: 5px 12px; margin: 3px; border-radius: 4px; background: rgba(255,255,255,0.1); }
19 .zone.active { background: rgba(233,69,96,0.3); border: 1px solid #e94560; }
20 </style>
21</head>
22<body>
23 <h1>Custom Events - Système de surveillance du parc</h1>
24
25 <div class="section">
26 <h2>1. Émettre des Custom Events</h2>
27 <p>Clique sur le bouton pour émettre un événement personnalisé avec des données :</p>
28 <button class="btn-alert" onclick="emitAlert('critical', 'A', 'Clôture endommagée !')">Alerte zone A</button>
29 <button class="btn-alert" onclick="emitAlert('warning', 'B', 'Activité des raptors élevée')">Alerte zone B</button>
30 <button class="btn-status" onclick="emitStatus('T-Rex', 85, 'Enclos principal')">Statut T-Rex</button>
31 <button class="btn-status" onclick="emitStatus('Raptor', 120, 'Zone B')">Statut Raptor</button>
32 <div class="output" id="event-log">En attente d'événements...</div>
33 <button class="btn-clear" onclick="document.getElementById('event-log').textContent = ''">Effacer le journal</button>
34 </div>
35
36 <div class="section">
37 <h2>2. Bouillonnement - propagation des événements</h2>
38 <p>Un événement avec bubbles:true se propage vers le parent :</p>
39 <div id="parkSystem" style="padding: 10px; border: 1px dashed #4ecdc4; border-radius: 8px;">
40 <span style="color: #4ecdc4;">parkSystem (parent)</span>
41 <div style="display: flex; gap: 10px; margin-top: 10px;">
42 <div class="zone" id="zoneA" onclick="emitZoneEvent('A')">Zone A (clique)</div>
43 <div class="zone" id="zoneB" onclick="emitZoneEvent('B')">Zone B (clique)</div>
44 <div class="zone" id="zoneC" onclick="emitZoneEvent('C')">Zone C (clique)</div>
45 </div>
46 </div>
47 <div class="output" id="bubble-log">Clique sur une zone...</div>
48 </div>
49
50 <div class="section">
51 <h2>3. Cancelable - annuler les événements</h2>
52 <p>Le transfert du T-Rex est bloqué par preventDefault() :</p>
53 <button class="btn-transfer" onclick="tryTransfer('T-Rex', 'A', 'B')">Transférer T-Rex (A->B)</button>
54 <button class="btn-transfer" onclick="tryTransfer('Triceratops', 'B', 'C')">Transférer Triceratops (B->C)</button>
55 <div class="output" id="transfer-log">Essaie un transfert...</div>
56 </div>
57
58 <script>
59 const eventLog = document.getElementById('event-log');
60 const bubbleLog = document.getElementById('bubble-log');
61 const transferLog = document.getElementById('transfer-log');
62 const parkSystemEl = document.getElementById('parkSystem');
63
64 function log(target, msg) {
65 const time = new Date().toLocaleTimeString();
66 target.textContent += '\n[' + time + '] ' + msg;
67 target.scrollTop = target.scrollHeight;
68 }
69
70 // === 1. Custom Events avec detail ===
71 document.addEventListener('securityAlert', function(e) {
72 const { type, zone, message } = e.detail;
73 log(eventLog, '[' + type.toUpperCase() + '] Zone ' + zone + ': ' + message);
74 });
75
76 document.addEventListener('dinoStatus', function(e) {
77 const { name, heartRate, location } = e.detail;
78 log(eventLog, '[STATUS] ' + name + ' - HR: ' + heartRate + ', loc: ' + location);
79 });
80
81 function emitAlert(type, zone, message) {
82 document.dispatchEvent(new CustomEvent('securityAlert', {
83 detail: { type, zone, message },
84 bubbles: true
85 }));
86 }
87
88 function emitStatus(name, heartRate, location) {
89 document.dispatchEvent(new CustomEvent('dinoStatus', {
90 detail: { name, heartRate, location },
91 bubbles: true
92 }));
93 }
94
95 // === 2. Bubbling demo ===
96 parkSystemEl.addEventListener('zoneAlert', function(e) {
97 log(bubbleLog, 'parkSystem A INTERCEPTÉ : Zone ' + e.detail.zone + ' (bubbling!)');
98 document.querySelectorAll('.zone').forEach(z => z.classList.remove('active'));
99 document.getElementById('zone' + e.detail.zone).classList.add('active');
100 });
101
102 function emitZoneEvent(zone) {
103 const el = document.getElementById('zone' + zone);
104 log(bubbleLog, 'Émission depuis zone' + zone + '...');
105 el.dispatchEvent(new CustomEvent('zoneAlert', {
106 detail: { zone: zone },
107 bubbles: true
108 }));
109 }
110
111 // === 3. Cancelable demo ===
112 parkSystemEl.addEventListener('transferDino', function(e) {
113 if (e.detail.dinosaur === 'T-Rex') {
114 e.preventDefault();
115 log(transferLog, 'TRANSFERT BLOQUÉ : ' + e.detail.dinosaur + ' !');
116 }
117 });
118
119 function tryTransfer(dino, from, to) {
120 const event = new CustomEvent('transferDino', {
121 detail: { dinosaur: dino, from: from, to: to },
122 cancelable: true,
123 bubbles: true
124 });
125 const allowed = parkSystemEl.dispatchEvent(event);
126 if (allowed) {
127 log(transferLog, 'OK : ' + dino + ' transféré de ' + from + ' vers ' + to);
128 } else {
129 log(transferLog, 'Transfer ' + dino + ' cancelled (preventDefault)');
130 }
131 }
132
133 console.log('Custom Events demo prêt !');
134 </script>
135</body>
136</html>Tu as repéré une erreur dans cette leçon ?
Teste tes connaissances
Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.
1. Comment crée-t-on un événement personnalisé avec des données supplémentaires en JavaScript ?
2. Quelle méthode permet d'émettre (dispatch) un Custom Event sur un élément du DOM ?
Voici 2 des 3 questions de cette leçon. Réponds aux autres dans le jeu.
Tâches pratiques dans le jeu
- Éditeur de code
Dans le fichier index.js, la fonction createMonitoring() construit un centre de contrôle avec un enclos à l'intérieur (l'enclos est un enfant du centre) et renvoie sendAlert(zone, level) et reportStatus(dino, status). Complète les blancs de sendAlert : ___BLANK1___ est le constructeur d'un événement qui transporte des données dans detail, ___BLANK2___ est l'option qui permet à l'événement securityAlert de remonter de l'enclos jusqu'au centre de contrôle, et ___BLANK3___ est la méthode qui envoie l'événement depuis l'enclos. Chaque alerte doit arriver dans le journal du centre sous la forme « zone: niveau », par exemple « A: élevé ».
- Clique dans l'ordre
Place les fragments de code pour créer un Custom Event « dinoAlert » avec un objet detail :