Cours JavaScript et TypeScript · Module 8: Événements et DOM

L'objet Event et ses propriétés

11 min de lecture
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 2

stopImmediatePropagation() - 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 log

Systè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énement
  • event.currentTarget - l'élément avec l'écouteur
  • event.type - le type d'événement
  • event.clientX/Y, event.pageX/Y - positions du curseur
  • event.ctrlKey, event.shiftKey, event.altKey - touches de modification
  • event.button - bouton de la souris

Méthodes clés :

  • event.preventDefault() - bloquer l'action par défaut
  • event.stopPropagation() - arrêter la propagation
  • event.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. 1. Quelle propriété de l'objet event contient l'élément qui a déclenché l'événement ?

  2. 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 :

Articles utiles