Kurs JavaScript i TypeScript · Moduł 8: Zdarzenia i DOM
Event Listeners - nasłuchiwanie na wydarzenia
W tej lekcji7
Ray Arnold prowadzi Cię głębiej do centrum kontrolnego. "To tutaj wszystko się zaczyna," wskazuje na ogromny panel z tysiącami przycisków i wskaźników. "Każdy element tego systemu jest event listenerem - nasłuchiwaczem oczekującym na sygnał."
"W Parku Jurajskim," kontynuuje, "mamy sensory ruchu, czujniki temperatury, kamery, mikrofony. Każdy z nich ciągle słucha, czeka na coś nietypowego. Gdy to wykryje, natychmiast reaguje. W JavaScript mamy addEventListener() - metodę która działa dokładnie tak samo."
addEventListener() - podstawowa metoda
Metoda addEventListener() to fundament obsługi wydarzeń w nowoczesnym JavaScript. Pozwala przypisać funkcję obsługującą do konkretnego typu wydarzenia na wybranym elemencie.
Składnia
1element.addEventListener(eventType, handlerFunction, options);- eventType - typ wydarzenia (string): 'click', 'keydown', 'submit' itd.
- handlerFunction - funkcja wywoływana gdy wydarzenie wystąpi
- options - opcjonalne parametry (o nich później)
Podstawowy przykład
"Zacznijmy od czegoś prostego," mówi Ray. "Przycisk karmienia Velociraptorów."
1// Pobierz element przycisku
2const feedButton = document.getElementById('velociraptor-feed');
3
4// Zarejestruj listener
5feedButton.addEventListener('click', function(event) {
6 console.log('Rozpoczynam procedurę karmienia Velociraptorów');
7 console.log('Kliknięto w:', event.target);
8
9 // Logika karmienia
10 feedVelociraptors();
11});
12
13function feedVelociraptors() {
14 console.log('Klatka bezpieczeństwa: ZAMKNIĘTA');
15 console.log('Podajnik pokarmu: AKTYWNY');
16 console.log('Monitoring: WŁĄCZONY');
17 console.log('Procedura zakończona pomyślnie');
18}Wiele listenerów na tym samym elemencie
"Co jeśli potrzebujemy wielu reakcji na to samo wydarzenie?" pyta Ray. "W systemach parku to normalne - jedno wydarzenie może uruchamiać wiele protokołów."
1const emergencyButton = document.getElementById('emergency');
2
3// Pierwszy listener - aktywacja alarmu
4emergencyButton.addEventListener('click', function() {
5 console.log('ALARM: Aktywuję syreny alarmowe');
6 activateSirens();
7});
8
9// Drugi listener - zamykanie bram
10emergencyButton.addEventListener('click', function() {
11 console.log('BEZPIECZEŃSTWO: Zamykam wszystkie bramy');
12 lockAllGates();
13});
14
15// Trzeci listener - powiadamianie personelu
16emergencyButton.addEventListener('click', function() {
17 console.log('KOMUNIKACJA: Wysyłam powiadomienia do zespołu');
18 notifySecurityTeam();
19});
20
21// Wszystkie trzy funkcje wykonają się po kliknięciuRóżne typy funkcji obsługujących
Ray pokazuje Ci różne sposoby definiowania handlerów:
1const enclosureGate = document.getElementById('gate-5');
2
3// 1. Funkcja anonimowa
4enclosureGate.addEventListener('click', function(event) {
5 console.log('Kliknięto bramę', event.target.id);
6});
7
8// 2. Arrow function (nowoczesny JavaScript)
9enclosureGate.addEventListener('click', (event) => {
10 console.log('Kliknięto bramę', event.target.id);
11});
12
13// 3. Named function (najlepsza praktyka)
14function handleGateClick(event) {
15 console.log('Kliknięto bramę', event.target.id);
16
17 if (event.target.dataset.status === 'open') {
18 closeGate(event.target.id);
19 } else {
20 openGate(event.target.id);
21 }
22}
23
24enclosureGate.addEventListener('click', handleGateClick);"Nazwane funkcje są najlepsze," tłumaczy Ray, "bo możesz je później usunąć i testować osobno, a kod jest bardziej czytelny."
Obiekt Event - pełen raport z wydarzenia
"Gdy sensor wykryje ruch," Ray pokazuje raport na ekranie, "dostajemy pełny pakiet danych: gdzie, kiedy, jaki typ ruchu, jak szybki. Obiekt Event w JavaScript działa tak samo."
Podstawowe właściwości
1document.addEventListener('click', function(event) {
2 // Podstawowe informacje o wydarzeniu
3 console.log('Typ wydarzenia:', event.type); // 'click'
4 console.log('Timestamp:', event.timeStamp); // czas w ms od załadowania strony
5
6 // Element, który był celem wydarzenia
7 console.log('Element docelowy:', event.target);
8 console.log('Bieżący element:', event.currentTarget);
9
10 // Informacje o pozycji (dla wydarzeń myszy)
11 console.log('Pozycja na ekranie - X:', event.screenX, 'Y:', event.screenY);
12 console.log('Pozycja w oknie - X:', event.clientX, 'Y:', event.clientY);
13 console.log('Pozycja względem strony - X:', event.pageX, 'Y:', event.pageY);
14
15 // Klawisze modyfikujące
16 console.log('Shift:', event.shiftKey);
17 console.log('Ctrl:', event.ctrlKey);
18 console.log('Alt:', event.altKey);
19 console.log('Meta (Cmd/Win):', event.metaKey);
20
21 // Przycisk myszy
22 console.log('Przycisk:', event.button); // 0=lewy, 1=środek, 2=prawy
23});Praktyczne wykorzystanie właściwości
"Zobaczmy to w realnym systemie," mówi Ray, otwierając kod interaktywnej mapy parku.
1const parkMap = document.getElementById('interactive-park-map');
2
3parkMap.addEventListener('click', function(event) {
4 // Pobierz współrzędne kliknięcia
5 const x = event.clientX;
6 const y = event.clientY;
7
8 // Sprawdź czy kliknięto z Ctrl (wielokrotny wybór)
9 if (event.ctrlKey) {
10 console.log('Wybór wielokrotny - dodaję do selekcji');
11 addToSelection(event.target);
12 } else {
13 console.log('Wybór pojedynczy - czyszczę poprzednią selekcję');
14 clearSelection();
15 selectSingle(event.target);
16 }
17
18 // click przychodzi tylko dla lewego (głównego) przycisku myszy,
19 // prawy przycisk obsługuje niżej wydarzenie contextmenu
20 showEnclosureDetails(event.target);
21
22 // Zapisz pozycję dla późniejszego użycia
23 lastClickPosition = { x, y };
24});
25
26// Blokowanie menu kontekstowego (prawy przycisk)
27parkMap.addEventListener('contextmenu', function(event) {
28 event.preventDefault();
29 console.log('Własne menu kontekstowe zamiast domyślnego');
30 showContextMenu(event.clientX, event.clientY);
31});Różne typy wydarzeń
Ray pokazuje kolejne ekrany z różnymi systemami. "Każdy typ sensora wykrywa co innego. Podobnie mamy różne typy wydarzeń."
Wydarzenia myszy
1const dinoCard = document.getElementById('trex-card');
2
3// Kliknięcia
4dinoCard.addEventListener('click', (e) => {
5 console.log('Pojedyncze kliknięcie');
6 showBasicInfo();
7});
8
9dinoCard.addEventListener('dblclick', (e) => {
10 console.log('Podwójne kliknięcie');
11 showDetailedView();
12});
13
14// Najechanie i opuszczenie
15dinoCard.addEventListener('mouseenter', (e) => {
16 console.log('Mysz weszła na kartę');
17 e.target.classList.add('highlighted');
18 showQuickPreview();
19});
20
21dinoCard.addEventListener('mouseleave', (e) => {
22 console.log('Mysz opuściła kartę');
23 e.target.classList.remove('highlighted');
24 hideQuickPreview();
25});
26
27// Ruch myszy (używaj ostrożnie - może generować setki wydarzeń)
28dinoCard.addEventListener('mousemove', (e) => {
29 // Aktualizuj pozycję tooltipa
30 updateTooltipPosition(e.clientX, e.clientY);
31});
32
33// Naciśnięcie i puszczenie przycisku
34dinoCard.addEventListener('mousedown', (e) => {
35 console.log('Przycisk myszy wciśnięty');
36 startDrag(e);
37});
38
39dinoCard.addEventListener('mouseup', (e) => {
40 console.log('Przycisk myszy puszczony');
41 endDrag(e);
42});Wydarzenia klawiatury
"Wiele systemów sterujemy z klawiatury," wyjaśnia Ray. "Kody dostępu, skróty klawiszowe, nawigacja."
1// Nasłuchuj na całym dokumencie
2document.addEventListener('keydown', function(event) {
3 console.log('Wciśnięto klawisz:', event.key);
4 console.log('Kod klawisza:', event.code);
5 console.log('Czy to powtórzenie:', event.repeat);
6
7 // Skróty klawiszowe
8 if (event.ctrlKey && event.key === 's') {
9 event.preventDefault(); // Zablokuj domyślny zapis przeglądarki
10 saveParkConfiguration();
11 console.log('Konfiguracja zapisana');
12 }
13
14 if (event.key === 'Escape') {
15 closeAllModals();
16 console.log('Zamknięto wszystkie okna');
17 }
18
19 // Nawigacja strzałkami
20 switch(event.key) {
21 case 'ArrowUp':
22 moveCamera('north');
23 break;
24 case 'ArrowDown':
25 moveCamera('south');
26 break;
27 case 'ArrowLeft':
28 moveCamera('west');
29 break;
30 case 'ArrowRight':
31 moveCamera('east');
32 break;
33 }
34});
35
36// Nasłuchuj na konkretnym polu
37const securityCodeInput = document.getElementById('security-code');
38
39securityCodeInput.addEventListener('keydown', function(event) {
40 // Tylko cyfry; klawisze sterujące (Backspace, strzałki, Tab) mają dłuższe nazwy i przechodzą
41 if (event.key.length === 1 && !event.ctrlKey && !event.metaKey && (event.key < '0' || event.key > '9')) {
42 event.preventDefault();
43 console.log('Tylko cyfry są dozwolone');
44 }
45});Wydarzenia formularzy
"Formularze to krytyczna część systemów parku," mówi Ray. "Raportowanie incydentów, rejestracja gości, monitoring."
1const incidentForm = document.getElementById('incident-report');
2const severitySelect = document.getElementById('severity');
3const descriptionArea = document.getElementById('description');
4
5// Submit - wysłanie formularza
6incidentForm.addEventListener('submit', function(event) {
7 event.preventDefault(); // WAŻNE: zablokuj domyślne wysłanie
8
9 console.log('Formularz wysłany');
10
11 // Zbierz dane
12 const formData = new FormData(incidentForm);
13 const data = {
14 severity: formData.get('severity'),
15 location: formData.get('location'),
16 description: formData.get('description'),
17 reporter: formData.get('reporter'),
18 timestamp: new Date().toISOString()
19 };
20
21 // Walidacja
22 if (validateIncidentReport(data)) {
23 submitIncidentReport(data);
24 incidentForm.reset();
25 } else {
26 console.error('Nieprawidłowe dane formularza');
27 }
28});
29
30// Change - zmiana wartości (po opuszczeniu pola)
31severitySelect.addEventListener('change', function(event) {
32 const severity = event.target.value;
33 console.log('Zmieniono poziom zagrożenia na:', severity);
34
35 // Dostosuj priorytet i kolor
36 if (severity === 'critical') {
37 incidentForm.classList.add('critical');
38 setPriority('HIGH');
39 } else {
40 incidentForm.classList.remove('critical');
41 setPriority('NORMAL');
42 }
43});
44
45// Input - zmiana wartości (na bieżąco)
46descriptionArea.addEventListener('input', function(event) {
47 const length = event.target.value.length;
48 const maxLength = 500;
49
50 console.log(`Znaków: ${length}/${maxLength}`);
51 updateCharacterCounter(length, maxLength);
52
53 if (length > maxLength) {
54 event.target.classList.add('error');
55 } else {
56 event.target.classList.remove('error');
57 }
58});
59
60// Focus i Blur - aktywacja i dezaktywacja pola
61descriptionArea.addEventListener('focus', function(event) {
62 console.log('Pole opisu aktywne - pokazuję pomocnicze wskazówki');
63 showHelpTips();
64});
65
66descriptionArea.addEventListener('blur', function(event) {
67 console.log('Pole opisu nieaktywne - ukrywam wskazówki');
68 hideHelpTips();
69});removeEventListener() - usuwanie listenerów
"Systemy które już nie są potrzebne," Ray pokazuje wyłączony panel, "należy wyłączyć. Oszczędza energię i zasoby."
Podstawowe usuwanie
1// Do usunięcia potrzebna jest ta sama funkcja: nazwana albo zapisana w zmiennej
2function handleClick(event) {
3 console.log('Kliknięto');
4}
5
6const button = document.getElementById('temp-button');
7
8// Dodaj listener
9button.addEventListener('click', handleClick);
10
11// Później usuń (musi być ta sama funkcja)
12button.removeEventListener('click', handleClick);
13
14// To NIE ZADZIAŁA - to dwie różne funkcje
15button.addEventListener('click', () => console.log('Test'));
16button.removeEventListener('click', () => console.log('Test'));Praktyczny przykład - czasowy monitoring
1function setupMaintenanceMode() {
2 console.log('Włączanie trybu konserwacji na 5 minut');
3
4 const maintenanceAlert = function(event) {
5 event.preventDefault();
6 alert('Park jest w trybie konserwacji. Spróbuj za chwilę.');
7 };
8
9 // Zablokuj wszystkie akcje
10 document.addEventListener('click', maintenanceAlert, true); // true = capture
11
12 // Po 5 minutach wyłącz tryb konserwacji
13 setTimeout(() => {
14 document.removeEventListener('click', maintenanceAlert, true);
15 console.log('Tryb konserwacji wyłączony');
16 }, 5 * 60 * 1000);
17}Opcje addEventListener()
Ray pokazuje zaawansowany panel konfiguracji. "Czasami potrzebujemy specjalnych ustawień dla sensorów."
1element.addEventListener(eventType, handler, {
2 capture: false, // Użyj fazy capture zamiast bubbling
3 once: false, // Usuń listener po pierwszym wywołaniu
4 passive: false // Nie wywołuj preventDefault()
5});Przykład: once - jednorazowy listener
1const welcomeScreen = document.getElementById('welcome');
2
3// Pokaż ekran powitalny tylko raz
4welcomeScreen.addEventListener('click', function() {
5 console.log('Witamy w Parku Jurajskim!');
6 showTour();
7}, { once: true });
8
9// Po pierwszym kliknięciu listener jest automatycznie usuwanyPrzykład: passive - płynne przewijanie przy dotyku
Listener, który tylko śledzi ruch palca i nie blokuje przewijania, może to obiecać przeglądarce opcją passive.
1// passive: true to obietnica, że listener nie wywoła preventDefault(),
2// więc przeglądarka przewija stronę od razu, bez czekania na listener
3window.addEventListener('touchmove', function(event) {
4 trackFinger(event.touches[0]);
5 // Nie możemy użyć event.preventDefault() z passive: true
6}, { passive: true });Opcja ma znaczenie przy touchstart, touchmove i wheel, bo te wydarzenia mogą zatrzymać przewijanie. Wydarzenia scroll nie da się anulować, więc tam passive niczego nie przyspiesza.
System monitoringu w praktyce
"Połączmy wszystko w jeden system," mówi Ray, otwierając główny kod monitoringu.
1class ParkMonitoringSystem {
2 constructor() {
3 this.alerts = [];
4 this.isActive = false;
5 this.initialize();
6 }
7
8 initialize() {
9 console.log('Inicjalizacja systemu monitoringu...');
10
11 // Wydarzenia dokumentu
12 document.addEventListener('DOMContentLoaded', () => {
13 this.onSystemReady();
14 });
15
16 // Wydarzenia okna
17 window.addEventListener('beforeunload', (e) => {
18 return this.onBeforeExit(e);
19 });
20
21 // Wydarzenia błędów
22 window.addEventListener('error', (e) => {
23 this.logError(e);
24 });
25
26 this.isActive = true;
27 }
28
29 onSystemReady() {
30 console.log('System monitoringu gotowy');
31 this.setupEnclosureMonitors();
32 this.setupSecuritySystem();
33 this.startPeriodicChecks();
34 }
35
36 setupEnclosureMonitors() {
37 const enclosures = document.querySelectorAll('.enclosure');
38
39 enclosures.forEach(enclosure => {
40 // Status zagrody
41 enclosure.addEventListener('click', (e) => {
42 this.showEnclosureStatus(enclosure.id);
43 });
44
45 // Szczegóły po podwójnym kliknięciu
46 enclosure.addEventListener('dblclick', (e) => {
47 this.showDetailedView(enclosure.id);
48 });
49
50 // Highlight przy najechaniu
51 enclosure.addEventListener('mouseenter', (e) => {
52 e.target.classList.add('active');
53 this.showQuickInfo(enclosure.id);
54 });
55
56 enclosure.addEventListener('mouseleave', (e) => {
57 e.target.classList.remove('active');
58 this.hideQuickInfo();
59 });
60 });
61 }
62
63 setupSecuritySystem() {
64 // Skróty klawiszowe
65 document.addEventListener('keydown', (e) => {
66 if (e.ctrlKey && e.shiftKey && e.key === 'E') {
67 this.triggerEmergency();
68 }
69
70 if (e.key === 'F1') {
71 e.preventDefault();
72 this.showHelp();
73 }
74 });
75
76 // Przycisk awaryjny
77 const emergencyBtn = document.getElementById('emergency');
78 if (emergencyBtn) {
79 emergencyBtn.addEventListener('click', () => {
80 this.triggerEmergency();
81 });
82 }
83 }
84
85 startPeriodicChecks() {
86 // Co 30 sekund sprawdź status wszystkich systemów
87 this.checkInterval = setInterval(() => {
88 if (this.isActive) {
89 this.performSystemCheck();
90 }
91 }, 30000);
92 }
93
94 performSystemCheck() {
95 console.log(`[${new Date().toLocaleTimeString()}] Sprawdzanie systemów...`);
96 // Logika sprawdzania
97 }
98
99 triggerEmergency() {
100 console.error('ALARM AWARYJNY!');
101 this.addAlert({
102 type: 'emergency',
103 message: 'Alarm awaryjny aktywowany',
104 timestamp: new Date()
105 });
106 }
107
108 addAlert(alert) {
109 this.alerts.push(alert);
110 const event = new CustomEvent('parkAlert', { detail: alert });
111 document.dispatchEvent(event);
112 }
113
114 onBeforeExit(event) {
115 if (this.alerts.some(a => a.type === 'emergency')) {
116 event.returnValue = 'Są aktywne alerty! Czy na pewno chcesz wyjść?';
117 return event.returnValue;
118 }
119 }
120
121 logError(event) {
122 console.error('Błąd systemu:', event.error);
123 this.addAlert({
124 type: 'error',
125 message: event.error.message,
126 timestamp: new Date()
127 });
128 }
129
130 destroy() {
131 // Wyłącz monitoring
132 this.isActive = false;
133 clearInterval(this.checkInterval);
134 console.log('System monitoringu wyłączony');
135 }
136}
137
138// Inicjalizacja
139const parkMonitoring = new ParkMonitoringSystem();Podsumowanie
"Event listenery to rdzeń każdego interaktywnego systemu," podsumowuje Ray. "Podobnie jak sensory w Parku Jurajskim czuwają nad bezpieczeństwem, tak event listenery czuwają nad funkcjonalnością Twojej aplikacji."
Kluczowe zasady:
- Używaj
addEventListener()dla wszystkich listenerów - Zawsze sprawdzaj czy element istnieje przed dodaniem listenera
- Listenery, które trzeba będzie usunąć, zapisuj w nazwanej funkcji albo w zmiennej
- Pamiętaj o
removeEventListener()gdy listener nie jest już potrzebny - Używaj opcji
{ once: true }dla jednorazowych operacji - Używaj
{ passive: true }przytouchstart,touchmoveiwheel, gdy listener nie wywołujepreventDefault()
Kod do tej lekcji: index.html
1<!DOCTYPE html>
2<html lang="pl">
3<head>
4 <meta charset="UTF-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 <title>Galeria Dinozaurów - Event Delegation</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 .gallery-container {
17 max-width: 800px;
18 margin: 0 auto;
19 }
20
21 h1 {
22 text-align: center;
23 margin-bottom: 30px;
24 }
25
26 .dinosaur-gallery {
27 display: grid;
28 grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
29 gap: 20px;
30 }
31
32 .dino-card {
33 background: rgba(255, 255, 255, 0.1);
34 border-radius: 10px;
35 padding: 20px;
36 text-align: center;
37 cursor: pointer;
38 transition: all 0.3s ease;
39 border: 2px solid transparent;
40 }
41
42 .dino-card:hover {
43 transform: translateY(-5px);
44 box-shadow: 0 10px 25px rgba(0, 0, 0, 0.3);
45 border-color: #3498db;
46 background: rgba(255, 255, 255, 0.15);
47 }
48
49 .dino-card h3 {
50 margin: 10px 0;
51 color: #ecf0f1;
52 }
53
54 .dino-emoji {
55 font-size: 48px;
56 margin-bottom: 10px;
57 display: block;
58 }
59
60 .dino-details {
61 font-size: 14px;
62 color: #bdc3c7;
63 margin: 10px 0;
64 }
65
66 .action-buttons {
67 margin-top: 15px;
68 }
69
70 .details-btn, .favorite-btn {
71 background: #3498db;
72 color: white;
73 border: none;
74 padding: 8px 15px;
75 border-radius: 5px;
76 cursor: pointer;
77 margin: 0 5px;
78 transition: all 0.3s ease;
79 }
80
81 .details-btn:hover {
82 background: #2980b9;
83 }
84
85 .favorite-btn {
86 background: #e74c3c;
87 }
88
89 .favorite-btn:hover {
90 background: #c0392b;
91 }
92
93 .favorite-btn.active {
94 background: #f39c12;
95 }
96
97 .info-panel {
98 background: rgba(0, 0, 0, 0.8);
99 border-radius: 10px;
100 padding: 20px;
101 margin-top: 30px;
102 border: 1px solid #34495e;
103 }
104
105 .clicked-info {
106 color: #3498db;
107 font-weight: bold;
108 }
109 </style>
110</head>
111<body>
112 <div class="gallery-container">
113 <h1>Galeria Dinozaurów Parku Jurajskiego</h1>
114
115 <!-- Używamy jednego event listenera na kontenerze -->
116 <div id="dino-gallery" class="dinosaur-gallery">
117 <div class="dino-card" data-dino-id="trex" data-dino-name="Tyrannosaurus Rex">
118 <span class="dino-emoji">◆</span>
119 <h3>Tyrannosaurus Rex</h3>
120 <div class="dino-details">
121 Era: Kreda<br>
122 Dieta: Mięsożerca<br>
123 Długość: 12-13m
124 </div>
125 <div class="action-buttons">
126 <button class="details-btn">Szczegóły</button>
127 <button class="favorite-btn">Ulubione</button>
128 </div>
129 </div>
130
131 <div class="dino-card" data-dino-id="velociraptor" data-dino-name="Velociraptor">
132 <span class="dino-emoji">◆</span>
133 <h3>Velociraptor</h3>
134 <div class="dino-details">
135 Era: Kreda<br>
136 Dieta: Mięsożerca<br>
137 Długość: 2m
138 </div>
139 <div class="action-buttons">
140 <button class="details-btn">Szczegóły</button>
141 <button class="favorite-btn">Ulubione</button>
142 </div>
143 </div>
144
145 <div class="dino-card" data-dino-id="triceratops" data-dino-name="Triceratops">
146 <span class="dino-emoji">◆</span>
147 <h3>Triceratops</h3>
148 <div class="dino-details">
149 Era: Kreda<br>
150 Dieta: Roślinożerca<br>
151 Długość: 9m
152 </div>
153 <div class="action-buttons">
154 <button class="details-btn">Szczegóły</button>
155 <button class="favorite-btn">Ulubione</button>
156 </div>
157 </div>
158
159 <div class="dino-card" data-dino-id="brachiosaurus" data-dino-name="Brachiosaurus">
160 <span class="dino-emoji">◆</span>
161 <h3>Brachiosaurus</h3>
162 <div class="dino-details">
163 Era: Jura<br>
164 Dieta: Roślinożerca<br>
165 Długość: 23m
166 </div>
167 <div class="action-buttons">
168 <button class="details-btn">Szczegóły</button>
169 <button class="favorite-btn">Ulubione</button>
170 </div>
171 </div>
172 </div>
173
174 <div class="info-panel">
175 <h3>Panel Informacyjny</h3>
176 <div id="info-display">Kliknij na kartę dinozaura, aby zobaczyć szczegóły...</div>
177 </div>
178 </div>
179
180 <script>
181 // Pobieramy referencje do elementów
182 const dinoGallery = document.getElementById('dino-gallery');
183 const infoDisplay = document.getElementById('info-display');
184
185 // JEDEN event listener na kontenerze - Event Delegation!
186 dinoGallery.addEventListener('click', function(event) {
187 // Sprawdzamy, co zostało kliknięte
188
189 // Obsługa kliknięcia na kartę dinozaura
190 if (event.target.closest('.dino-card')) {
191 const card = event.target.closest('.dino-card');
192 const dinoId = card.dataset.dinoId;
193 const dinoName = card.dataset.dinoName;
194
195 console.log('Kliknięto kartę dinozaura:', dinoName, '(ID:', dinoId + ')');
196
197 // Aktualizujemy panel informacyjny
198 infoDisplay.innerHTML = `
199 <span class="clicked-info">Wybrano: ${dinoName}</span><br>
200 ID: ${dinoId}<br>
201 Czas kliknięcia: ${new Date().toLocaleTimeString('pl-PL')}
202 `;
203 }
204
205 // Obsługa przycisku szczegółów
206 if (event.target.classList.contains('details-btn')) {
207 event.stopPropagation(); // Zatrzymujemy propagację
208
209 const card = event.target.closest('.dino-card');
210 const dinoName = card.dataset.dinoName;
211
212 alert(`Szczegółowe informacje o ${dinoName}\n\nOkaz znajduje się w zagrodzie nr ${Math.floor(Math.random() * 10) + 1}\nStan zdrowia: Doskonały\nOstatnie karmienie: ${new Date().toLocaleTimeString('pl-PL')}`);
213
214 infoDisplay.innerHTML = `
215 <span class="clicked-info">Wyświetlono szczegóły: ${dinoName}</span><br>
216 Akcja: Przycisk szczegółów<br>
217 Czas: ${new Date().toLocaleTimeString('pl-PL')}
218 `;
219 }
220
221 // Obsługa przycisku ulubionych
222 if (event.target.classList.contains('favorite-btn')) {
223 event.stopPropagation(); // Zatrzymujemy propagację
224
225 const card = event.target.closest('.dino-card');
226 const dinoName = card.dataset.dinoName;
227 const favoriteBtn = event.target;
228
229 // Toggle ulubione
230 favoriteBtn.classList.toggle('active');
231
232 if (favoriteBtn.classList.contains('active')) {
233 favoriteBtn.textContent = '★ Ulubione';
234 infoDisplay.innerHTML = `
235 <span class="clicked-info">Dodano do ulubionych: ${dinoName}</span><br>
236 Akcja: Dodanie do ulubionych<br>
237 Czas: ${new Date().toLocaleTimeString('pl-PL')}
238 `;
239 } else {
240 favoriteBtn.textContent = 'Ulubione';
241 infoDisplay.innerHTML = `
242 <span class="clicked-info">Usunięto z ulubionych: ${dinoName}</span><br>
243 Akcja: Usunięcie z ulubionych<br>
244 Czas: ${new Date().toLocaleTimeString('pl-PL')}
245 `;
246 }
247 }
248 });
249
250 // Demonstracja zalet event delegation
251 function addNewDinosaur() {
252 const newDino = document.createElement('div');
253 newDino.className = 'dino-card';
254 newDino.setAttribute('data-dino-id', 'stegosaurus');
255 newDino.setAttribute('data-dino-name', 'Stegosaurus');
256 newDino.innerHTML = `
257 <span class="dino-emoji">◆</span>
258 <h3>Stegosaurus</h3>
259 <div class="dino-details">
260 Era: Jura<br>
261 Dieta: Roślinożerca<br>
262 Długość: 9m
263 </div>
264 <div class="action-buttons">
265 <button class="details-btn">Szczegóły</button>
266 <button class="favorite-btn">Ulubione</button>
267 </div>
268 `;
269
270 dinoGallery.appendChild(newDino);
271
272 infoDisplay.innerHTML = `
273 <span class="clicked-info">Dodano nowego dinozaura: Stegosaurus</span><br>
274 Event delegation automatycznie obsługuje nowe elementy!<br>
275 Czas: ${new Date().toLocaleTimeString('pl-PL')}
276 `;
277 }
278
279 // Dodajemy nowego dinozaura po 3 sekundach
280 setTimeout(addNewDinosaur, 3000);
281 </script>
282</body>
283</html>Widzisz błąd w tej lekcji?
Sprawdź się
Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.
1. Co jest wymagane do prawidłowego usunięcia event listenera?
Zadania praktyczne w grze
- Edytor kodu
W pliku index.html jest przycisk alarmu #alarm-btn, akapit #alarm-status i skrypt w znaczniku <script>. Uzupełnij luki: ___BLANK1___ to metoda przycisku, która rejestruje listener zdarzenia, a ___BLANK2___ to nazwa zdarzenia kliknięcia. Po kliknięciu przycisku listener ma wpisać komunikat o alarmie do akapitu #alarm-status. Kliknij przycisk w podglądzie, żeby sprawdzić, czy komunikat się pojawia.
- Klikanie w kolejności
Ułóż elementy w prawidłowej kolejności, aby stworzyć event listener.
- Klikanie w kolejności
Ułóż elementy dodawania event listenera z opcją once: