Kurs JavaScript i TypeScript · Moduł 8: Zdarzenia i DOM

Event Listeners - nasłuchiwanie na wydarzenia

9 min czytania
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ęciu

Róż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 usuwany

Przykł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 } przy touchstart, touchmove i wheel, gdy listener nie wywołuje preventDefault()
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. 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:

Przydatne artykuły