Cours JavaScript et TypeScript · Module 8: Événements et DOM
L'objet Event et ses propriétés
Dans cette leçon8
« Chaque rapport de capteur du parc », Ray Arnold ouvre la documentation système détaillée, « contient des informations précises : où, quand, quel type d'événement, avec quelle intensité. De la même façon, l'objet Event en JavaScript est un rapport complet sur ce qui s'est passé dans le navigateur. »
Ray pointe un moniteur affichant les données d'un capteur de mouvement près de l'enclos du T-Rex. « Tu vois ? Heure : 14:23:47, lieu : secteur B-7, type de mouvement : rapide, direction : nord. Chaque information est cruciale pour réagir correctement. »
Qu'est-ce que l'objet Event ?
Chaque événement en JavaScript crée automatiquement un objet Event, qui est passé à la fonction gestionnaire comme premier paramètre. Cet objet contient toutes les informations pertinentes sur l'événement - de la position du curseur aux touches enfoncées, en passant par l'élément qui a déclenché l'événement.
Anatomie de base de l'objet Event
« Commençons par les fondamentaux », dit Ray en ouvrant le panneau de diagnostic.
1const parkMap = document.getElementById('interactive-map');
2
3parkMap.addEventListener('click', function(event) {
4 // event est un objet contenant toutes les informations
5
6 console.log('=== RAPPORT D\'ÉVÉNEMENT ===');
7 console.log('Type d\'événement :', event.type); // 'click'
8 console.log('Horodatage :', event.timeStamp); // temps en ms depuis le chargement de la page
9 console.log('Phase de l\'événement :', event.eventPhase); // 1=capture, 2=cible, 3=bouillonnement
10
11 // Informations sur la position du clic
12 console.log(`Cliqué à la position : X=${event.clientX}, Y=${event.clientY}`);
13
14 // L'élément qui a été cliqué
15 console.log('Élément cliqué :', event.target);
16 console.log('Nom de balise :', event.target.tagName);
17 console.log('ID de l\'élément :', event.target.id);
18
19 // L'élément auquel l'écouteur d'événement est attaché
20 console.log('Élément avec l\'écouteur :', event.currentTarget);
21
22 // L'action par défaut peut-elle être annulée ?
23 console.log('Annulable :', event.cancelable);
24
25 // L'événement bouillonne-t-il ?
26 console.log('Bouillonne :', event.bubbles);
27});Propriétés de position - les systèmes de coordonnées
« À Jurassic Park », explique Ray, « on utilise différents systèmes de coordonnées - GPS, coordonnées locales de secteur, positions relatives aux portails. De la même façon, JavaScript propose plusieurs systèmes de coordonnées pour la position de la souris. »
Différents types de coordonnées
1const viewingPlatform = document.getElementById('platform');
2
3viewingPlatform.addEventListener('click', function(event) {
4 console.log('=== SYSTÈMES DE COORDONNÉES ===');
5
6 // 1. clientX/Y - position relative à la fenêtre du navigateur (viewport)
7 console.log('Viewport :', event.clientX, event.clientY);
8 console.log('Comme une position relative à ton moniteur de contrôle');
9
10 // 2. pageX/Y - position relative à toute la page (tient compte du défilement)
11 console.log('Page :', event.pageX, event.pageY);
12 console.log('Comme les coordonnées sur la carte complète du parc');
13
14 // 3. screenX/Y - position relative à tout l'écran
15 console.log('Écran :', event.screenX, event.screenY);
16 console.log('Comme une position GPS - localisation absolue');
17
18 // 4. offsetX/Y - position relative à l'élément cliqué
19 console.log('Offset :', event.offsetX, event.offsetY);
20 console.log('Comme une position à l\'intérieur d\'un enclos précis');
21
22 // Utilisation pratique - marquer une zone sur la carte
23 markLocationOnMap(event.pageX, event.pageY);
24 showCoordinatesInfo(event.clientX, event.clientY);
25});
26
27function markLocationOnMap(x, y) {
28 const marker = document.createElement('div');
29 marker.className = 'location-marker';
30 marker.style.left = x + 'px';
31 marker.style.top = y + 'px';
32 document.body.appendChild(marker);
33
34 console.log(`Emplacement marqué sur la carte : (${x}, ${y})`);
35}
36
37function showCoordinatesInfo(clientX, clientY) {
38 const tooltip = document.getElementById('coordinates-tooltip');
39 tooltip.textContent = `Position : X=${clientX}, Y=${clientY}`;
40 tooltip.style.left = (clientX + 10) + 'px';
41 tooltip.style.top = (clientY + 10) + 'px';
42 tooltip.style.display = 'block';
43}target vs currentTarget - la différence clé
« C'est l'une des erreurs les plus fréquentes », prévient Ray. « Confondre target et currentTarget. C'est comme confondre le dinosaure qui s'est échappé avec l'enclos d'où il s'est échappé. »
Comprendre la différence
1// HTML :
2// <div id="enclosure" class="dino-enclosure">
3// <div class="info-panel">
4// <h3>Enclos du T-Rex</h3>
5// <button class="feed-button">Nourrir</button>
6// <button class="status-button">Statut</button>
7// </div>
8// </div>
9
10const enclosure = document.getElementById('enclosure');
11
12enclosure.addEventListener('click', function(event) {
13 // target - l'élément qui a RÉELLEMENT été cliqué
14 console.log('Target (élément cliqué) :', event.target);
15 console.log('Balise de target :', event.target.tagName);
16
17 // currentTarget - l'élément auquel l'écouteur est attaché
18 console.log('CurrentTarget (élément avec l\'écouteur) :', event.currentTarget);
19 console.log('ID de CurrentTarget :', event.currentTarget.id); // toujours 'enclosure'
20
21 // Exemple pratique
22 if (event.target.classList.contains('feed-button')) {
23 console.log('Bouton de nourrissage cliqué');
24 console.log('Dans l\'enclos :', event.currentTarget.id);
25 feedDinosaur(event.currentTarget.id);
26 } else if (event.target.classList.contains('status-button')) {
27 console.log('Bouton de statut cliqué');
28 console.log('Pour l\'enclos :', event.currentTarget.id);
29 showEnclosureStatus(event.currentTarget.id);
30 } else {
31 console.log('Cliqué quelque part dans l\'enclos, mais pas sur un bouton');
32 }
33});
34
35function feedDinosaur(enclosureId) {
36 console.log(`Démarrage du nourrissage dans l'enclos : ${enclosureId}`);
37 console.log('Activation du système de sécurité...');
38 console.log('Distribution de la nourriture...');
39 console.log('Nourrissage terminé');
40}
41
42function showEnclosureStatus(enclosureId) {
43 console.log(`=== STATUT DE L'ENCLOS ${enclosureId} ===`);
44 console.log('Température : 28°C');
45 console.log('Humidité : 65%');
46 console.log('Dernier nourrissage : 12:30');
47 console.log('Activité du dinosaure : Normale');
48}Touches de modification - actions multifonctions
« Dans les systèmes critiques », explique Ray, « on a souvent besoin de combinaisons de touches pour la sécurité. Comme deux clés pour l'armurerie. L'objet Event nous indique quelles touches sont enfoncées. »
Détecter les touches de modification
1const dinosaurCard = document.querySelectorAll('.dinosaur-card');
2
3dinosaurCard.forEach(card => {
4 card.addEventListener('click', function(event) {
5 console.log('=== ANALYSE DES TOUCHES ===');
6 console.log('Ctrl enfoncé :', event.ctrlKey);
7 console.log('Shift enfoncé :', event.shiftKey);
8 console.log('Alt enfoncé :', event.altKey);
9 console.log('Meta (Cmd/Win) enfoncé :', event.metaKey);
10
11 const dinoId = this.dataset.dinoId;
12 const dinoName = this.dataset.dinoName;
13
14 // Actions différentes selon les combinaisons de touches
15 if (event.ctrlKey && event.shiftKey) {
16 // Ctrl + Shift + Clic = Options avancées
17 console.log('Ouverture des options avancées pour :', dinoName);
18 openAdvancedOptions(dinoId);
19
20 } else if (event.ctrlKey) {
21 // Ctrl + Clic = Ajouter à la sélection multiple
22 console.log('Ajout à la sélection :', dinoName);
23 addToSelection(dinoId);
24 this.classList.toggle('selected');
25
26 } else if (event.shiftKey) {
27 // Shift + Clic = Sélectionner une plage
28 console.log('Sélection de plage jusqu\'à :', dinoName);
29 selectRange(dinoId);
30
31 } else if (event.altKey) {
32 // Alt + Clic = Aperçu rapide
33 console.log('Aperçu rapide :', dinoName);
34 showQuickPreview(dinoId);
35
36 } else {
37 // Clic normal
38 console.log('Sélectionné :', dinoName);
39 clearSelection();
40 selectSingle(dinoId);
41 this.classList.add('selected');
42 }
43 });
44});
45
46// Implémentations des fonctions utilitaires
47let selectedDinosaurs = [];
48
49function addToSelection(dinoId) {
50 if (!selectedDinosaurs.includes(dinoId)) {
51 selectedDinosaurs.push(dinoId);
52 } else {
53 selectedDinosaurs = selectedDinosaurs.filter(id => id !== dinoId);
54 }
55 console.log('Dinosaures sélectionnés :', selectedDinosaurs);
56}
57
58function clearSelection() {
59 selectedDinosaurs = [];
60 document.querySelectorAll('.dinosaur-card.selected').forEach(card => {
61 card.classList.remove('selected');
62 });
63 console.log('Sélection effacée');
64}
65
66function selectSingle(dinoId) {
67 selectedDinosaurs = [dinoId];
68 console.log('Sélection simple :', dinoId);
69}
70
71function selectRange(endId) {
72 console.log('Implémentation de la sélection de plage jusqu\'à :', endId);
73 // Logique de sélection de plage entre le dernier sélectionné et l'actuel
74}Informations sur le bouton de la souris
« Différents capteurs réagissent à différents types de signaux », Ray montre un panneau à trois boutons. « De la même façon, on peut réagir différemment aux boutons gauche, droit et milieu de la souris. »
Détecter le bouton de la souris
1const interactiveMap = document.getElementById('park-map');
2
3interactiveMap.addEventListener('mousedown', function(event) {
4 console.log('=== INFORMATIONS SUR LE BOUTON DE LA SOURIS ===');
5 console.log('Bouton :', event.button);
6 console.log('0 = gauche, 1 = milieu, 2 = droit');
7 console.log('Buttons (masque binaire) :', event.buttons);
8
9 const clickX = event.clientX;
10 const clickY = event.clientY;
11
12 switch(event.button) {
13 case 0:
14 // Bouton gauche - action standard
15 console.log('Bouton gauche : sélection de l\'emplacement');
16 selectLocation(clickX, clickY);
17 break;
18
19 case 1:
20 // Bouton milieu - nouvel onglet/fenêtre
21 console.log('Bouton milieu : ouverture dans une nouvelle fenêtre');
22 event.preventDefault();
23 openInNewWindow(clickX, clickY);
24 break;
25
26 case 2:
27 // Bouton droit - menu contextuel
28 console.log('Bouton droit : menu contextuel');
29 showContextMenu(clickX, clickY);
30 break;
31
32 default:
33 console.log('Bouton inconnu :', event.button);
34 }
35});
36
37// Bloquer le menu contextuel par défaut
38interactiveMap.addEventListener('contextmenu', function(event) {
39 event.preventDefault();
40 console.log('Menu par défaut bloqué - utilisation d\'un menu personnalisé');
41});
42
43function selectLocation(x, y) {
44 console.log(`Emplacement sélectionné : (${x}, ${y})`);
45 highlightArea(x, y);
46}
47
48function showContextMenu(x, y) {
49 const menu = document.getElementById('custom-context-menu');
50 menu.style.left = x + 'px';
51 menu.style.top = y + 'px';
52 menu.style.display = 'block';
53
54 console.log('Menu contextuel affiché à la position :', x, y);
55}Les méthodes de l'objet Event
« En plus des propriétés », Ray montre l'écran suivant, « l'objet Event possède aussi des méthodes - des outils pour contrôler le comportement de l'événement. »
preventDefault() - bloquer l'action par défaut
1// Exemple 1 : validation de formulaire personnalisée
2const securityForm = document.getElementById('security-access-form');
3
4securityForm.addEventListener('submit', function(event) {
5 event.preventDefault(); // BLOQUER l'envoi par défaut du formulaire
6
7 console.log('Envoi du formulaire intercepté');
8
9 const formData = new FormData(securityForm);
10 const accessCode = formData.get('access-code');
11 const securityLevel = formData.get('security-level');
12
13 // Validation personnalisée
14 if (validateAccessCode(accessCode)) {
15 console.log('Code d\'accès correct');
16 grantAccess(securityLevel);
17 } else {
18 console.error('Code d\'accès invalide !');
19 showError('Le code d\'accès est invalide');
20 }
21});
22
23// Exemple 2 : navigation personnalisée
24const navigationLinks = document.querySelectorAll('a.custom-navigation');
25
26navigationLinks.forEach(link => {
27 link.addEventListener('click', function(event) {
28 event.preventDefault(); // BLOQUER la navigation standard
29
30 const targetUrl = this.getAttribute('href');
31 console.log('Clic sur le lien intercepté :', targetUrl);
32
33 // Logique de navigation personnalisée (ex : routage SPA)
34 navigateToPage(targetUrl);
35 });
36});
37
38function validateAccessCode(code) {
39 const validCodes = ['ALPHA-7', 'BETA-4', 'GAMMA-9'];
40 return validCodes.includes(code);
41}
42
43function navigateToPage(url) {
44 console.log('Navigation vers :', url);
45 // Implémentation du routage SPA
46 history.pushState({}, '', url);
47 loadPageContent(url);
48}stopPropagation() - arrêter la propagation
1// HTML :
2// <div class="park-section" id="section-a">
3// <div class="enclosure" id="enclosure-1">
4// <button class="action-button">Action</button>
5// </div>
6// </div>
7
8const parkSection = document.getElementById('section-a');
9const enclosure = document.getElementById('enclosure-1');
10const actionButton = document.querySelector('.action-button');
11
12// Écouteur sur la section du parc
13parkSection.addEventListener('click', function(event) {
14 console.log('3. Section du parc cliquée');
15 updateSectionStatus();
16});
17
18// Écouteur sur l'enclos
19enclosure.addEventListener('click', function(event) {
20 console.log('2. Enclos cliqué');
21 event.stopPropagation(); // STOP ! Ne pas propager plus haut
22 highlightEnclosure();
23});
24
25// Écouteur sur le bouton
26actionButton.addEventListener('click', function(event) {
27 console.log('1. Bouton d\'action cliqué');
28 performAction();
29 // L'événement se propagera vers l'enclos, mais y sera arrêté
30});
31
32// Sans stopPropagation() dans enclosure - on verrait les 3 logs
33// Avec stopPropagation() - on ne verra que 1 et 2stopImmediatePropagation() - arrêt complet
1const emergencyButton = document.getElementById('emergency');
2
3// Premier écouteur
4emergencyButton.addEventListener('click', function(event) {
5 console.log('1. Premier écouteur - activation de l\'alarme');
6 activateAlarm();
7
8 event.stopImmediatePropagation(); // TOUT ARRÊTER !
9});
10
11// Deuxième écouteur - NE S'EXÉCUTERA PAS !
12emergencyButton.addEventListener('click', function(event) {
13 console.log('2. Cet écouteur NE S\'EXÉCUTERA PAS');
14 closeGates();
15});
16
17// Troisième écouteur - NE S'EXÉCUTERA PAS !
18emergencyButton.addEventListener('click', function(event) {
19 console.log('3. Celui-ci NON PLUS ne s\'exécutera pas');
20 notifyStaff();
21});
22
23// Après le clic, on ne verra que le premier logSystème de surveillance pratique
« Combinons tout cela en un système fonctionnel », dit Ray en ouvrant le code principal.
1class DinosaurMonitoringSystem {
2 constructor() {
3 this.selectedDinosaurs = [];
4 this.monitoringActive = false;
5 this.initialize();
6 }
7
8 initialize() {
9 console.log('Initialisation du système de surveillance des dinosaures');
10 this.setupEventListeners();
11 }
12
13 setupEventListeners() {
14 const dinosaurCards = document.querySelectorAll('.dinosaur-card');
15
16 dinosaurCards.forEach(card => {
17 // Événement click avec analyse complète
18 card.addEventListener('click', (event) => {
19 this.handleDinosaurClick(event);
20 });
21
22 // Menu contextuel
23 card.addEventListener('contextmenu', (event) => {
24 event.preventDefault();
25 this.showDinosaurMenu(event);
26 });
27
28 // Effets au survol
29 card.addEventListener('mouseenter', (event) => {
30 this.showQuickInfo(event);
31 });
32
33 card.addEventListener('mouseleave', (event) => {
34 this.hideQuickInfo(event);
35 });
36 });
37 }
38
39 handleDinosaurClick(event) {
40 const card = event.currentTarget;
41 const dinoId = card.dataset.dinoId;
42 const dinoName = card.dataset.dinoName;
43
44 console.log('=== RAPPORT DE CLIC ===');
45 console.log('Dinosaure :', dinoName);
46 console.log('Position du clic :', event.clientX, event.clientY);
47 console.log('Horodatage :', event.timeStamp);
48 console.log('Ctrl :', event.ctrlKey);
49 console.log('Shift :', event.shiftKey);
50 console.log('Alt :', event.altKey);
51 console.log('Bouton de la souris :', event.button);
52
53 // Logique de sélection
54 if (event.ctrlKey) {
55 this.toggleSelection(dinoId, card);
56 } else if (event.shiftKey) {
57 this.selectRange(dinoId);
58 } else {
59 this.selectSingle(dinoId, card);
60 }
61 }
62
63 toggleSelection(dinoId, card) {
64 const index = this.selectedDinosaurs.indexOf(dinoId);
65
66 if (index === -1) {
67 this.selectedDinosaurs.push(dinoId);
68 card.classList.add('selected');
69 console.log(`${dinoId} ajouté à la sélection`);
70 } else {
71 this.selectedDinosaurs.splice(index, 1);
72 card.classList.remove('selected');
73 console.log(`${dinoId} retiré de la sélection`);
74 }
75
76 console.log('Dinosaures sélectionnés :', this.selectedDinosaurs);
77 }
78
79 selectSingle(dinoId, card) {
80 // Effacer la sélection précédente
81 document.querySelectorAll('.dinosaur-card.selected').forEach(c => {
82 c.classList.remove('selected');
83 });
84
85 this.selectedDinosaurs = [dinoId];
86 card.classList.add('selected');
87
88 console.log('Dinosaure unique sélectionné :', dinoId);
89 }
90
91 showDinosaurMenu(event) {
92 const card = event.currentTarget;
93 const dinoId = card.dataset.dinoId;
94
95 console.log('Affichage du menu contextuel pour :', dinoId);
96 console.log('Position :', event.clientX, event.clientY);
97
98 // La logique d'affichage du menu irait ici
99 }
100
101 showQuickInfo(event) {
102 const card = event.currentTarget;
103 const dinoName = card.dataset.dinoName;
104
105 console.log(`Survol de : ${dinoName}`);
106 console.log('Position de la souris :', event.clientX, event.clientY);
107 }
108
109 hideQuickInfo(event) {
110 console.log('Carte de dinosaure quittée');
111 }
112}
113
114// Initialisation du système
115const monitoringSystem = new DinosaurMonitoringSystem();Récapitulatif
« L'objet Event », résume Ray, « est ta fenêtre sur le monde de l'interaction utilisateur. Chaque propriété, chaque méthode a son sens et son usage. Tout comme chaque relevé de capteur à Jurassic Park nous aide à maintenir la sécurité, les propriétés de Event t'aident à créer des applications précises et réactives. »
Propriétés clés :
event.target- l'élément qui a déclenché l'événementevent.currentTarget- l'élément avec l'écouteurevent.type- le type d'événementevent.clientX/Y,event.pageX/Y- positions du curseurevent.ctrlKey,event.shiftKey,event.altKey- touches de modificationevent.button- bouton de la souris
Méthodes clés :
event.preventDefault()- bloquer l'action par défautevent.stopPropagation()- arrêter la propagationevent.stopImmediatePropagation()- arrêter tous les écouteurs
Code de cette leçon : index.html
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 <title>Enregistrement des visiteurs - Parc Jurassique</title>
7 <style>
8 body {
9 font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
10 background: linear-gradient(135deg, #2c3e50 0%, #34495e 100%);
11 margin: 0;
12 padding: 20px;
13 color: white;
14 }
15
16 .registration-container {
17 max-width: 500px;
18 margin: 0 auto;
19 background: rgba(0, 0, 0, 0.8);
20 border-radius: 15px;
21 padding: 30px;
22 box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
23 }
24
25 h1 {
26 text-align: center;
27 margin-bottom: 30px;
28 color: #ecf0f1;
29 }
30
31 .form-group {
32 margin-bottom: 20px;
33 }
34
35 label {
36 display: block;
37 margin-bottom: 5px;
38 color: #bdc3c7;
39 font-weight: 500;
40 }
41
42 input[type="text"],
43 input[type="email"],
44 input[type="number"],
45 input[type="date"],
46 select {
47 width: 100%;
48 padding: 12px;
49 border: 2px solid #34495e;
50 border-radius: 8px;
51 background: rgba(255, 255, 255, 0.1);
52 color: white;
53 font-size: 16px;
54 transition: all 0.3s ease;
55 box-sizing: border-box;
56 }
57
58 input[type="text"]:focus,
59 input[type="email"]:focus,
60 input[type="number"]:focus,
61 input[type="date"]:focus,
62 select:focus {
63 outline: none;
64 border-color: #3498db;
65 background: rgba(255, 255, 255, 0.15);
66 }
67
68 .input-valid {
69 border-color: #27ae60 !important;
70 background: rgba(39, 174, 96, 0.1) !important;
71 }
72
73 .input-invalid {
74 border-color: #e74c3c !important;
75 background: rgba(231, 76, 60, 0.1) !important;
76 }
77
78 .error-message {
79 color: #e74c3c;
80 font-size: 14px;
81 margin-top: 5px;
82 display: none;
83 }
84
85 .submit-btn {
86 width: 100%;
87 padding: 15px;
88 background: linear-gradient(45deg, #27ae60, #2ecc71);
89 color: white;
90 border: none;
91 border-radius: 8px;
92 font-size: 18px;
93 font-weight: bold;
94 cursor: pointer;
95 transition: all 0.3s ease;
96 margin-top: 20px;
97 }
98
99 .submit-btn:hover {
100 transform: translateY(-2px);
101 box-shadow: 0 10px 25px rgba(39, 174, 96, 0.3);
102 }
103
104 .submit-btn:disabled {
105 background: #95a5a6;
106 cursor: not-allowed;
107 transform: none;
108 box-shadow: none;
109 }
110
111 .success-message {
112 background: #27ae60;
113 color: white;
114 padding: 15px;
115 border-radius: 8px;
116 margin-top: 20px;
117 text-align: center;
118 display: none;
119 }
120
121 .form-info {
122 background: rgba(52, 152, 219, 0.2);
123 border: 1px solid #3498db;
124 border-radius: 8px;
125 padding: 15px;
126 margin-bottom: 20px;
127 }
128
129 .required {
130 color: #e74c3c;
131 }
132 </style>
133</head>
134<body>
135 <div class="registration-container">
136 <h1>Enregistrement des visiteurs</h1>
137
138 <div class="form-info">
139 Fill out the form to register for a Jurassic Park tour.
140 Fields marked with <span class="required">*</span> are required.
141 </div>
142
143 <form id="visitor-form">
144 <div class="form-group">
145 <label for="name">Full name <span class="required">*</span></label>
146 <input type="text" id="name" name="name" required>
147 <div class="error-message" id="name-error">Full name must have at least 2 characters</div>
148 </div>
149
150 <div class="form-group">
151 <label for="email">Adresse email <span class="required">*</span></label>
152 <input type="email" id="email" name="email" required>
153 <div class="error-message" id="email-error">Enter a valid email address</div>
154 </div>
155
156 <div class="form-group">
157 <label for="age">Âge <span class="required">*</span></label>
158 <input type="number" id="age" name="age" min="5" max="120" required>
159 <div class="error-message" id="age-error">Age must be between 5 and 120 years</div>
160 </div>
161
162 <div class="form-group">
163 <label for="phone">Numéro de téléphone</label>
164 <input type="text" id="phone" name="phone" placeholder="+48 123 456 789">
165 <div class="error-message" id="phone-error">Enter a valid phone number</div>
166 </div>
167
168 <div class="form-group">
169 <label for="visit-date">Date de visite <span class="required">*</span></label>
170 <input type="date" id="visit-date" name="visit-date" required>
171 <div class="error-message" id="date-error">Visit date must be in the future</div>
172 </div>
173
174 <div class="form-group">
175 <label for="tour-type">Type de visite <span class="required">*</span></label>
176 <select id="tour-type" name="tour-type" required>
177 <option value="">Choisis un type de visite</option>
178 <option value="standard">Standard (2h)</option>
179 <option value="vip">VIP avec guide (4h)</option>
180 <option value="night">Visite de nuit (3h)</option>
181 <option value="research">Visite scientifique (6h)</option>
182 </select>
183 <div class="error-message" id="tour-error">Choisis un type de visite</div>
184 </div>
185
186 <button type="submit" id="submit-btn" class="submit-btn" disabled>
187 Register for the tour
188 </button>
189 </form>
190
191 <div id="success-message" class="success-message">
192 Registration completed successfully! Confirmation has been sent to the provided email address.
193 </div>
194 </div>
195
196 <script>
197 // Get references to elements du formulaire
198 const form = document.getElementById('visitor-form');
199 const submitBtn = document.getElementById('submit-btn');
200 const successMessage = document.getElementById('success-message');
201
202 // Functions de validation
203 function validateName(name) {
204 return name.length >= 2;
205 }
206
207 function validateEmail(email) {
208 const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
209 return emailRegex.test(email);
210 }
211
212 function validateAge(age) {
213 const ageNum = parseInt(age);
214 return ageNum >= 5 && ageNum <= 120;
215 }
216
217 function validatePhone(phone) {
218 if (phone === '') return true; // Champ optionnel
219 const phoneRegex = /^(\+\d{1,3}\s?)?\d{3}\s?\d{3}\s?\d{3}$/;
220 return phoneRegex.test(phone.replace(/\s/g, ''));
221 }
222
223 function validateDate(date) {
224 const selectedDate = new Date(date);
225 const today = new Date();
226 today.setHours(0, 0, 0, 0);
227 return selectedDate > today;
228 }
229
230 function validateTourType(tourType) {
231 return tourType !== '';
232 }
233
234 // Function to show/hide errors
235 function showError(fieldId, show) {
236 const errorElement = document.getElementById(fieldId + '-error');
237 const inputElement = document.getElementById({ date: 'visit-date', tour: 'tour-type' }[fieldId] || fieldId);
238
239 if (show) {
240 errorElement.style.display = 'block';
241 inputElement.classList.add('input-invalid');
242 inputElement.classList.remove('input-valid');
243 } else {
244 errorElement.style.display = 'none';
245 inputElement.classList.remove('input-invalid');
246 inputElement.classList.add('input-valid');
247 }
248 }
249
250 // Function checking if form is valid
251 function checkFormValidity() {
252 const name = document.getElementById('name').value;
253 const email = document.getElementById('email').value;
254 const age = document.getElementById('age').value;
255 const phone = document.getElementById('phone').value;
256 const visitDate = document.getElementById('visit-date').value;
257 const tourType = document.getElementById('tour-type').value;
258
259 const isValid = validateName(name) &&
260 validateEmail(email) &&
261 validateAge(age) &&
262 validatePhone(phone) &&
263 validateDate(visitDate) &&
264 validateTourType(tourType);
265
266 submitBtn.disabled = !isValid;
267 return isValid;
268 }
269
270 // Event listeners pour la validation en temps réel
271 document.getElementById('name').addEventListener('input', function(e) {
272 const isValid = validateName(e.target.value);
273 showError('name', !isValid);
274 checkFormValidity();
275 });
276
277 document.getElementById('email').addEventListener('input', function(e) {
278 const isValid = validateEmail(e.target.value);
279 showError('email', !isValid);
280 checkFormValidity();
281 });
282
283 document.getElementById('age').addEventListener('input', function(e) {
284 const isValid = validateAge(e.target.value);
285 showError('age', !isValid);
286 checkFormValidity();
287 });
288
289 document.getElementById('phone').addEventListener('input', function(e) {
290 const isValid = validatePhone(e.target.value);
291 showError('phone', !isValid);
292 checkFormValidity();
293 });
294
295 document.getElementById('visit-date').addEventListener('input', function(e) {
296 const isValid = validateDate(e.target.value);
297 showError('date', !isValid);
298 checkFormValidity();
299 });
300
301 document.getElementById('tour-type').addEventListener('change', function(e) {
302 const isValid = validateTourType(e.target.value);
303 showError('tour', !isValid);
304 checkFormValidity();
305 });
306
307 // Handle form submission
308 form.addEventListener('submit', function(e) {
309 e.preventDefault(); // Prevent default form submission
310
311 // Check validity once more
312 if (!checkFormValidity()) {
313 alert('Form contains errors. Fix them before submitting.');
314 return;
315 }
316
317 // Collecte des données du formulaire
318 const formData = new FormData(form);
319 const visitorData = {
320 name: formData.get('name'),
321 email: formData.get('email'),
322 age: parseInt(formData.get('age')),
323 phone: formData.get('phone') || 'Non fourni',
324 visitDate: formData.get('visit-date'),
325 tourType: formData.get('tour-type')
326 };
327
328 console.log('Visitor data:', visitorData);
329
330 // Simulate sending data
331 submitBtn.textContent = 'Registering...';
332 submitBtn.disabled = true;
333
334 setTimeout(() => {
335 // Masquage du formulaire et affichage du message de succès
336 form.style.display = 'none';
337 successMessage.style.display = 'block';
338
339 console.log('Registration completed successfully!');
340
341 // Après 3 secondes, on réaffiche le formulaire
342 setTimeout(() => {
343 form.style.display = 'block';
344 successMessage.style.display = 'none';
345 form.reset();
346 submitBtn.textContent = 'Register for the tour';
347 checkFormValidity();
348
349 // Reset field styles
350 document.querySelectorAll('input, select').forEach(input => {
351 input.classList.remove('input-valid', 'input-invalid');
352 });
353
354 document.querySelectorAll('.error-message').forEach(error => {
355 error.style.display = 'none';
356 });
357 }, 3000);
358 }, 2000);
359 });
360
361 // Set minimum date to tomorrow
362 const tomorrow = new Date();
363 tomorrow.setDate(tomorrow.getDate() + 1);
364 document.getElementById('visit-date').min = tomorrow.toISOString().split('T')[0];
365 </script>
366</body>
367</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. Quelle propriété de l'objet event contient l'élément qui a déclenché l'événement ?
2. À quoi sert la méthode event.preventDefault() ?
Tâches pratiques dans le jeu
- Éditeur de code
Le fichier index.html contient un formulaire d'inscription des visiteurs (champs name et email) et un script dans une balise <script>. Complète les blancs : ___BLANK1___ est l'événement d'envoi du formulaire, ___BLANK2___ est la méthode de l'objet event qui empêche l'envoi par défaut et le rechargement de la page, et ___BLANK3___ est une condition vraie quand l'e-mail ne contient pas le signe @. Des données correctes affichent un message de bienvenue avec le prénom, des données incorrectes un message d'erreur.
- Tri horizontal
Place les éléments permettant d'empêcher l'action par défaut dans un gestionnaire d'événement :