Kurs Vue.js · Moduł 5: Zdarzenia i formularze
Obsługa Zdarzeń - Przyciski Kontrolne
W tej lekcji5
Witaj w Symulatorze Sterowania NOVA LAB! Do tej pory panele stacji tylko wyświetlały dane: liczyły, filtrowały i raportowały. Załoga musi jednak nimi sterować, a przycisk, który nie reaguje na kliknięcie, jest tylko dekoracją. W czystym JavaScripcie każdy przycisk wymagałby odszukania elementu, podpięcia nasłuchu przez addEventListener i pamiętania o jego usunięciu, gdy panel znika. Vue robi to za Ciebie jedną dyrektywą.
Czego się nauczysz
- obsługi zdarzeń - jak podpiąć procedurę pod przycisk, klawisz albo ruch myszy.
- modyfikatorów zdarzeń - jak zablokować przeładowanie strony, zatrzymać propagację i reagować na konkretne klawisze.
- v-model - jak związać pola formularza ze zmiennymi w obie strony.
- modyfikatorów v-model - jak oczyścić sygnał z pól dzięki
.lazy,.numberi.trim. - zaawansowanych formularzy - wyboru wielu opcji, opcji generowanych z tablicy i walidacji.
- formularza w praktyce i projektu - pełnej rejestracji modułu i panelu rejestracji misji.
Podstawowa Obsługa Zdarzeń
Dyrektywa v-on nasłuchuje zdarzenia DOM i uruchamia kod, gdy ono nastąpi. Pełny zapis to v-on:click, a skrót używany na co dzień to @click. Wartością może być nazwa funkcji, czyli method handler, albo krótki kod JavaScript, czyli inline handler. Vue rozróżnia je samo: nazwa w rodzaju handleClick albo ścieżka panel.start to metoda, a wszystko inne, na przykład count++ czy initiateSystem('OXYGEN'), to kod inline. Najpierw szablon panelu z pięcioma wariantami przycisku.
1<template>
2 <div class="control-panel" style="background: #0a0e27; color: #00ff88;">
3 <!-- Pełna składnia -->
4 <button v-on:click="handleClick" style="background: #00b4d8; color: #0a0e27;">Aktywuj</button>
5
6 <!-- Skrócona składnia (zalecana) -->
7 <button @click="handleClick" style="background: #00b4d8; color: #0a0e27;">Aktywuj</button>
8
9 <!-- Inline handler -->
10 <button @click="powerLevel++" style="background: #00b4d8; color: #0a0e27;">Moc: {{ powerLevel }}%</button>
11
12 <!-- Z argumentem -->
13 <button @click="initiateSystem('OXYGEN')" style="background: #00b4d8; color: #0a0e27;">Start O2</button>
14
15 <!-- Z event object -->
16 <button @click="logEvent($event)" style="background: #00b4d8; color: #0a0e27;">Log Command</button>
17 </div>
18</template>Dwa pierwsze przyciski robią dokładnie to samo i różnią się tylko zapisem. Trzeci zwiększa powerLevel bezpośrednio w szablonie. Czwarty wywołuje funkcję z argumentem, a piąty przekazuje jej specjalną zmienną $event, pod którą Vue podstawia natywny obiekt zdarzenia. Teraz skrypt z funkcjami, które te przyciski wywołują.
1<script setup>
2import { ref } from 'vue'
3
4const powerLevel = ref(0)
5
6function handleClick() {
7 console.log('System activated!')
8}
9
10function initiateSystem(systemName) {
11 console.log(`Initializing ${systemName} system...`)
12}
13
14function logEvent(event) {
15 console.log('Event:', event.type, event.target)
16}
17</script>Zwróć uwagę na handleClick: w szablonie podaliśmy samą nazwę, bez nawiasów, więc Vue wywoła funkcję i przekaże jej obiekt zdarzenia jako pierwszy argument. Tu go nie używamy, ale jest dostępny. Co się nie zmieniło w porównaniu z czystym JavaScriptem: to nadal zwykłe funkcje i zwykłe zdarzenia przeglądarki. Vue dokłada tylko podpięcie nasłuchu i jego automatyczne odpięcie, gdy komponent znika ze strony.
Zdarzenie i własny argument
Czasem procedura potrzebuje zarówno obiektu zdarzenia, jak i własnej informacji, na przykład nazwy systemu. W inline handlerze przekazujesz wtedy $event obok argumentu. Równoważną formą jest funkcja strzałkowa, która dostaje zdarzenie jako parametr.
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>Oba przyciski wywołują tę samą funkcję i wypisują nazwę systemu oraz typ zdarzenia, czyli click. Kolejność argumentów ustalasz sam, byle zgadzała się z definicją funkcji. Pamiętaj, że w inline handlerze zdarzenie oznacza tylko $event - zwykłe event czy e są dla szablonu nieznanymi zmiennymi.
Typowe Zdarzenia
Po @ możesz wpisać nazwę dowolnego zdarzenia DOM. Poniższy szablon to katalog najczęstszych: zdarzenia myszy, klawiatury i formularzy, a także przewijania i ładowania obrazka. Funkcje obsługi pominęliśmy, bo liczą się tu same nazwy zdarzeń.
1<template>
2 <!-- Mouse events -->
3 <div @click="onClick" @dblclick="onDoubleClick">Klik</div>
4 <div @mouseenter="onEnter" @mouseleave="onLeave">Hover</div>
5 <div @mousedown="onDown" @mouseup="onUp">Press</div>
6
7 <!-- Keyboard events -->
8 <input @keydown="onKeyDown" @keyup="onKeyUp" @keypress="onKeyPress" />
9
10 <!-- Form events -->
11 <form @submit="onSubmit">
12 <input @input="onInput" @change="onChange" @focus="onFocus" @blur="onBlur" />
13 </form>
14
15 <!-- Other -->
16 <div @scroll="onScroll">Scrollable content</div>
17 <img @load="onLoad" @error="onError" :src="imageUrl" />
18</template>mouseenter i mouseleave zgłaszają wejście kursora na element i wyjście z niego, a w przeciwieństwie do mouseover nie odpalają się ponownie, gdy kursor przechodzi między elementami w środku. input przychodzi po każdej zmianie treści pola, a change po jej zatwierdzeniu. focus i blur oznaczają wejście do pola i wyjście z niego. Uwaga na keypress: według MDN to zdarzenie jest przestarzałe (deprecated), więc w nowym kodzie używaj keydown. Vue nie wymyśla tu własnych zdarzeń - wszystkie są natywnymi zdarzeniami przeglądarki.
Dostęp do Event Object
Method handler dostaje obiekt zdarzenia automatycznie jako pierwszy parametr. Najczęściej przydają się: target, czyli element, na którym zdarzenie wystąpiło, clientX i clientY z położeniem kursora w oknie, buttons z maską wciśniętych przycisków myszy, a przy klawiaturze key, code oraz flagi ctrlKey i shiftKey.
1<template>
2 <button @click="handleClick">Kliknij</button>
3 <input @keyup="handleKey" />
4</template>
5
6<script setup>
7function handleClick(event) {
8 console.log('Target:', event.target)
9 console.log('Position:', event.clientX, event.clientY)
10 console.log('Buttons:', event.buttons)
11}
12
13function handleKey(event) {
14 console.log('Key:', event.key)
15 console.log('Code:', event.code)
16 console.log('Ctrl:', event.ctrlKey)
17 console.log('Shift:', event.shiftKey)
18}
19</script>key podaje wartość klawisza, na przykład "Enter" albo "a", a code jego fizyczne miejsce na klawiaturze, na przykład "KeyA", niezależnie od układu. Do skrótów reagujących na znak polecam key, bo na nim opierają się modyfikatory klawiszy z następnej lekcji. code wybieraj przy sterowaniu zależnym od położenia klawiszy, takim jak WASD. Przy buttons pamiętaj, że w zdarzeniu click przycisk bywa już puszczony, a to, który przycisk wywołał zdarzenie, podaje pole button.
Pamiętaj: @ to przewód między przyciskiem na panelu a procedurą w komputerze pokładowym - podajesz nazwę funkcji albo krótką komendę, a Vue sam podłącza i odłącza kabel.
Kod do tej lekcji: 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('Jednorazowy sygnał wysłany!')
23}
24</script>
25
26<template>
27 <div class="communication-panel">
28 <div class="panel-header">
29 <h2>Panel Komunikacyjny NOVA LAB</h2>
30 <p class="subtitle">Strefa Komunikacji - Obsługa Zdarzeń</p>
31 </div>
32
33 <div class="stats">
34 <div class="stat-item">
35 <span class="label">Wysłane sygnały:</span>
36 <span class="value">{{ signalsSent }}</span>
37 </div>
38 <div class="stat-item">
39 <span class="label">Ostatni typ:</span>
40 <span class="value">{{ lastSignalType || 'Brak' }}</span>
41 </div>
42 </div>
43
44 <div class="signal-buttons">
45 <button @click="sendSignal('Rutynowy')" class="signal-btn routine">
46 Sygnał Rutynowy
47 </button>
48
49 <button @click="sendSignal('Pilny')" class="signal-btn urgent">
50 Sygnał Pilny
51 </button>
52
53 <button @click.once="sendOnce" class="signal-btn once">
54 Jednorazowy (.once)
55 </button>
56 </div>
57
58 <div class="emergency-section">
59 <a href="#" @click.prevent="activateEmergency" class="emergency-link">
60 Aktywuj Alarm (.prevent)
61 </a>
62
63 <div v-if="emergencyActive" class="emergency-alert">
64 ALARM AKTYWNY!
65 </div>
66 </div>
67
68 <div class="event-info">
69 <h3>Modyfikatory zdarzeń:</h3>
70 <ul>
71 <li><code>.prevent</code> - zapobiega domyślnej akcji</li>
72 <li><code>.stop</code> - zatrzymuje propagację wydarzenia</li>
73 <li><code>.once</code> - zdarzenie wykonuje się tylko raz</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>Widzisz błąd w tej lekcji?
Sprawdź się
Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.
1. Jak dodać event listener dla kliknięcia w Vue.js?
2. Jaki symbol jest skrótem dla dyrektywy v-on?
To 2 z 4 pytań do tej lekcji. Pozostałe rozwiążesz w grze.
Zadania praktyczne w grze
- Edytor kodu
Dodaj @click handler zwiększający licznik i wyświetl wartość.
- Klikanie w kolejności
Ułóż składnię dodania click handlera w Vue:
- Edytor kodu
Dodaj obsługę @mouseenter, @mouseleave i @dblclick na elemencie div.
- Układanie w poziomie
Ułóż poprawną składnię handlera przekazującego event i argument: