Cours Vue.js · Module 5: Événements et formulaires
Gestion des événements - Boutons de contrôle
Dans cette leçon5
Bienvenue dans le Simulateur de Contrôle de NOVA LAB ! Jusqu'ici, les panneaux de la station se contentaient d'afficher des données : ils calculaient, filtraient et rendaient compte. Mais l'équipage doit les piloter, et un bouton qui ne réagit pas au clic n'est qu'une décoration. En JavaScript pur, chaque bouton exigerait de trouver l'élément, de brancher un écouteur avec addEventListener et de penser à le retirer quand le panneau disparaît. Vue le fait pour toi avec une seule directive.
Ce que tu vas apprendre
- la gestion des événements - comment brancher une procédure sur un bouton, une touche ou un mouvement de souris.
- les modificateurs d'événements - comment bloquer le rechargement de la page, arrêter la propagation et réagir à des touches précises.
- v-model - comment relier les champs de formulaire à des variables dans les deux sens.
- les modificateurs de v-model - comment nettoyer le signal des champs avec
.lazy,.numberet.trim. - les formulaires avancés - la sélection multiple, les options générées depuis un tableau et la validation.
- le formulaire en pratique et le projet - un enregistrement complet de module et un panneau d'enregistrement de mission.
Gestion basique des événements
La directive v-on écoute un événement du DOM et exécute du code quand il se produit. La forme complète est v-on:click, et le raccourci utilisé au quotidien est @click. La valeur peut être le nom d'une fonction, un method handler, ou un court morceau de JavaScript, un inline handler. Vue les distingue tout seul : un nom comme handleClick ou un chemin comme panel.start est une méthode, et tout le reste, par exemple count++ ou initiateSystem('OXYGEN'), est du code en ligne. D'abord, le template du panneau avec cinq variantes de bouton.
1<template>
2 <div class="control-panel" style="background: #0a0e27; color: #00ff88;">
3 <!-- Syntaxe complète -->
4 <button v-on:click="handleClick" style="background: #00b4d8; color: #0a0e27;">Activer</button>
5
6 <!-- Syntaxe raccourcie (recommandée) -->
7 <button @click="handleClick" style="background: #00b4d8; color: #0a0e27;">Activer</button>
8
9 <!-- Gestionnaire en ligne -->
10 <button @click="powerLevel++" style="background: #00b4d8; color: #0a0e27;">Puissance : {{ powerLevel }}%</button>
11
12 <!-- Avec un argument -->
13 <button @click="initiateSystem('OXYGEN')" style="background: #00b4d8; color: #0a0e27;">Démarrer O2</button>
14
15 <!-- Avec l'objet événement -->
16 <button @click="logEvent($event)" style="background: #00b4d8; color: #0a0e27;">Journaliser la commande</button>
17 </div>
18</template>Les deux premiers boutons font exactement la même chose et ne diffèrent que par l'écriture. Le troisième incrémente powerLevel directement dans le template. Le quatrième appelle une fonction avec un argument, et le cinquième lui passe la variable spéciale $event, que Vue remplit avec l'objet événement natif. Voici maintenant le script avec les fonctions que ces boutons appellent.
1<script setup>
2import { ref } from 'vue'
3
4const powerLevel = ref(0)
5
6function handleClick() {
7 console.log('Système activé !')
8}
9
10function initiateSystem(systemName) {
11 console.log(`Initialisation du système ${systemName}...`)
12}
13
14function logEvent(event) {
15 console.log('Événement :', event.type, event.target)
16}
17</script>Regarde handleClick : dans le template, nous avons donné seulement le nom, sans parenthèses, donc Vue appelle la fonction et lui passe l'objet événement comme premier argument. Nous ne l'utilisons pas ici, mais il est disponible. Ce qui n'a pas changé par rapport au JavaScript pur : ce sont toujours des fonctions ordinaires et des événements ordinaires du navigateur. Vue ajoute seulement le branchement de l'écouteur et son retrait automatique quand le composant quitte la page.
L'événement et ton propre argument
Parfois, une procédure a besoin à la fois de l'objet événement et d'une information à elle, par exemple le nom d'un système. Dans un inline handler, tu passes alors $event à côté de l'argument. Une forme équivalente est une fonction fléchée qui reçoit l'événement en paramètre.
1<template>
2 <button @click="logAction($event, 'oxygen')">Log O2</button>
3 <button @click="(event) => logAction(event, 'water')">Log H2O</button>
4</template>
5
6<script setup>
7function logAction(event, system) {
8 console.log(system, event.type)
9}
10</script>Les deux boutons appellent la même fonction et affichent le nom du système et le type d'événement, c'est-à-dire click. C'est toi qui choisis l'ordre des arguments, du moment qu'il correspond à la définition de la fonction. Retiens que dans un inline handler, seul $event désigne l'événement : un simple event ou e est une variable inconnue pour le template.
Événements courants
Après @, tu peux écrire le nom de n'importe quel événement du DOM. Le template ci-dessous est un catalogue des plus courants : événements de souris, de clavier et de formulaire, ainsi que le défilement et le chargement d'une image. Nous avons laissé de côté les fonctions de traitement, car seuls les noms d'événements comptent ici.
1<template>
2 <!-- Événements de la souris -->
3 <div @click="onClick" @dblclick="onDoubleClick">Cliquer</div>
4 <div @mouseenter="onEnter" @mouseleave="onLeave">Survol</div>
5 <div @mousedown="onDown" @mouseup="onUp">Appui</div>
6
7 <!-- Événements du clavier -->
8 <input @keydown="onKeyDown" @keyup="onKeyUp" @keypress="onKeyPress" />
9
10 <!-- Événements de formulaire -->
11 <form @submit="onSubmit">
12 <input @input="onInput" @change="onChange" @focus="onFocus" @blur="onBlur" />
13 </form>
14
15 <!-- Autres -->
16 <div @scroll="onScroll">Contenu défilable</div>
17 <img @load="onLoad" @error="onError" :src="imageUrl" />
18</template>mouseenter et mouseleave signalent l'entrée du curseur sur un élément et sa sortie, et contrairement à mouseover, ils ne se redéclenchent pas quand le curseur passe d'un élément intérieur à un autre. input arrive après chaque changement du contenu d'un champ, et change après la validation de ce changement. focus et blur signifient l'entrée dans un champ et la sortie. Attention à keypress : selon MDN, cet événement est obsolète (deprecated), donc utilise keydown dans le nouveau code. Vue n'invente pas d'événements ici : ce sont tous des événements natifs du navigateur.
Accéder à l'objet événement
Un method handler reçoit automatiquement l'objet événement comme premier paramètre. Les propriétés les plus utiles sont : target, l'élément sur lequel l'événement s'est produit, clientX et clientY avec la position du curseur dans la fenêtre, buttons avec le masque des boutons de souris enfoncés, et pour le clavier key, code et les indicateurs ctrlKey et shiftKey.
1<template>
2 <button @click="handleClick">Cliquer</button>
3 <input @keyup="handleKey" />
4</template>
5
6<script setup>
7function handleClick(event) {
8 console.log('Cible :', event.target)
9 console.log('Position :', event.clientX, event.clientY)
10 console.log('Boutons :', event.buttons)
11}
12
13function handleKey(event) {
14 console.log('Touche :', event.key)
15 console.log('Code :', event.code)
16 console.log('Ctrl :', event.ctrlKey)
17 console.log('Shift :', event.shiftKey)
18}
19</script>key donne la valeur de la touche, par exemple "Enter" ou "a", et code sa place physique sur le clavier, par exemple "KeyA", quelle que soit la disposition. Pour les raccourcis qui réagissent à un caractère, je te recommande key, car les modificateurs de touches de la prochaine leçon reposent dessus. Choisis code pour les commandes qui dépendent de la position des touches, comme WASD. Avec buttons, garde en tête que pendant un événement click le bouton est parfois déjà relâché, et que le bouton qui a déclenché l'événement est indiqué par la propriété button.
Souviens-toi : @ est le câble entre un bouton du panneau et une procédure de l'ordinateur de bord - tu lui donnes le nom d'une fonction ou une courte commande, et Vue branche et débranche le câble tout seul.
Code de cette leçon : App.vue
1<script setup>
2import { ref } from 'vue'
3
4const signalsSent = ref(0)
5const lastSignalType = ref('')
6const emergencyActive = ref(false)
7
8const sendSignal = (type) => {
9 signalsSent.value++
10 lastSignalType.value = type
11}
12
13const activateEmergency = (event) => {
14 event.preventDefault()
15 emergencyActive.value = true
16 setTimeout(() => {
17 emergencyActive.value = false
18 }, 3000)
19}
20
21const sendOnce = () => {
22 console.log('Signal unique envoyé !')
23}
24</script>
25
26<template>
27 <div class="communication-panel">
28 <div class="panel-header">
29 <h2>Panneau de Communication NOVA LAB</h2>
30 <p class="subtitle">Zone de Communication - Gestion des événements</p>
31 </div>
32
33 <div class="stats">
34 <div class="stat-item">
35 <span class="label">Signaux envoyés :</span>
36 <span class="value">{{ signalsSent }}</span>
37 </div>
38 <div class="stat-item">
39 <span class="label">Dernier type :</span>
40 <span class="value">{{ lastSignalType || 'Aucun' }}</span>
41 </div>
42 </div>
43
44 <div class="signal-buttons">
45 <button @click="sendSignal('Routinier')" class="signal-btn routine">
46 Signal de routine
47 </button>
48
49 <button @click="sendSignal('Urgent')" class="signal-btn urgent">
50 Signal urgent
51 </button>
52
53 <button @click.once="sendOnce" class="signal-btn once">
54 Unique (.once)
55 </button>
56 </div>
57
58 <div class="emergency-section">
59 <a href="#" @click.prevent="activateEmergency" class="emergency-link">
60 Activer l'alarme (.prevent)
61 </a>
62
63 <div v-if="emergencyActive" class="emergency-alert">
64 ALARME ACTIVE !
65 </div>
66 </div>
67
68 <div class="event-info">
69 <h3>Modificateurs d'événements :</h3>
70 <ul>
71 <li><code>.prevent</code> - empêche l'action par défaut</li>
72 <li><code>.stop</code> - arrête la propagation de l'événement</li>
73 <li><code>.once</code> - l'événement ne se déclenche qu'une seule fois</li>
74 </ul>
75 </div>
76 </div>
77</template>
78
79<style scoped>
80.communication-panel {
81 background: linear-gradient(135deg, #0a0e27 0%, #1a1e3f 100%);
82 color: #00ff88;
83 padding: 30px;
84 border-radius: 15px;
85 border: 2px solid #00b4d8;
86 font-family: 'Courier New', monospace;
87 max-width: 800px;
88 margin: 0 auto;
89}
90
91.panel-header {
92 text-align: center;
93 margin-bottom: 30px;
94 padding-bottom: 20px;
95 border-bottom: 2px solid #00b4d8;
96}
97
98.panel-header h2 {
99 color: #00ff88;
100 margin: 0 0 10px 0;
101 font-size: 28px;
102 text-shadow: 0 0 10px #00ff88;
103}
104
105.subtitle {
106 color: #00b4d8;
107 margin: 0;
108 font-size: 14px;
109}
110
111.stats {
112 display: grid;
113 grid-template-columns: 1fr 1fr;
114 gap: 15px;
115 margin-bottom: 30px;
116}
117
118.stat-item {
119 background: rgba(0, 180, 216, 0.1);
120 padding: 15px;
121 border-radius: 8px;
122 border: 1px solid #00b4d8;
123 display: flex;
124 justify-content: space-between;
125 align-items: center;
126}
127
128.label {
129 color: #00b4d8;
130 font-size: 14px;
131}
132
133.value {
134 color: #00ff88;
135 font-size: 20px;
136 font-weight: bold;
137}
138
139.signal-buttons {
140 display: grid;
141 gap: 15px;
142 margin-bottom: 30px;
143}
144
145.signal-btn {
146 background: linear-gradient(135deg, #00b4d8 0%, #0077b6 100%);
147 color: white;
148 border: none;
149 padding: 15px 30px;
150 border-radius: 8px;
151 font-size: 16px;
152 font-weight: bold;
153 cursor: pointer;
154 transition: all 0.3s ease;
155 font-family: 'Courier New', monospace;
156}
157
158.signal-btn:hover {
159 transform: translateY(-2px);
160 box-shadow: 0 5px 15px rgba(0, 180, 216, 0.4);
161}
162
163.signal-btn.urgent {
164 background: linear-gradient(135deg, #ff6b35 0%, #d64545 100%);
165}
166
167.signal-btn.once {
168 background: linear-gradient(135deg, #9d4edd 0%, #7209b7 100%);
169}
170
171.emergency-section {
172 text-align: center;
173 margin-bottom: 30px;
174 padding: 20px;
175 background: rgba(255, 107, 53, 0.1);
176 border-radius: 8px;
177 border: 1px solid #ff6b35;
178}
179
180.emergency-link {
181 color: #ff6b35;
182 font-size: 18px;
183 text-decoration: none;
184 font-weight: bold;
185 padding: 10px 20px;
186 display: inline-block;
187 border: 2px solid #ff6b35;
188 border-radius: 8px;
189 transition: all 0.3s ease;
190}
191
192.emergency-link:hover {
193 background: rgba(255, 107, 53, 0.2);
194 transform: scale(1.05);
195}
196
197.emergency-alert {
198 margin-top: 15px;
199 padding: 15px;
200 background: #ff6b35;
201 color: white;
202 border-radius: 8px;
203 font-weight: bold;
204 animation: pulse 1s infinite;
205}
206
207@keyframes pulse {
208 0%, 100% { opacity: 1; }
209 50% { opacity: 0.7; }
210}
211
212.event-info {
213 background: rgba(0, 255, 136, 0.05);
214 padding: 20px;
215 border-radius: 8px;
216 border: 1px solid #00ff88;
217}
218
219.event-info h3 {
220 color: #00ff88;
221 margin-top: 0;
222 font-size: 18px;
223}
224
225.event-info ul {
226 margin: 0;
227 padding-left: 20px;
228}
229
230.event-info li {
231 color: #00b4d8;
232 margin: 8px 0;
233}
234
235.event-info code {
236 background: rgba(0, 255, 136, 0.2);
237 padding: 2px 6px;
238 border-radius: 4px;
239 color: #00ff88;
240 font-weight: bold;
241}
242</style>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 ajoute-t-on un écouteur d'événement de clic dans Vue.js ?
2. Quel symbole est un raccourci pour la directive v-on ?
Voici 2 des 4 questions de cette leçon. Réponds aux autres dans le jeu.
Tâches pratiques dans le jeu
- Éditeur de code
Ajoute un gestionnaire @click qui incrémente un compteur et affiche la valeur.
- Clique dans l'ordre
Ordonne la syntaxe pour ajouter un gestionnaire de clic dans Vue :
- Éditeur de code
Ajoute les gestionnaires @mouseenter, @mouseleave et @dblclick à un élément div.
- Tri horizontal
Ordonne la syntaxe correcte pour un gestionnaire passant l'événement et un argument :