"Zobacz," Ray Arnold prowadzi Cię do centralnego panelu alarmowego Parku Jurajskiego. "Gdy sensor wykryje zagrożenie w zagrodzie 7-B, sygnał nie zatrzymuje się tylko tam. Przechodzi wyżej - do systemu sektorowego, następnie do głównego centrum kontrolnego, aż w końcu do centralnego alarmu. To się nazywa propagacja sygnału."
Ray pokazuje schemat systemu alarmowego. "W DOM działa dokładnie tak samo. Gdy klikniesz element, wydarzenie 'bąbelkuje' w górę przez całe drzewo DOM - od elementu docelowego, przez jego rodziców, aż do samego dokumentu."
Event bubbling (bąbelkowanie wydarzeń) to fundamentalny mechanizm w JavaScript, gdzie wydarzenia rozprzestrzeniają się od elementu, na którym wystąpiły, w górę przez wszystkich jego przodków w drzewie DOM.
"Każde wydarzenie," wyjaśnia Ray, "przechodzi przez trzy fazy. To jak protokół bezpieczeństwa - sprawdzamy wszystko w określonej kolejności."
1// Fazy wydarzenia:
2// 1. CAPTURE (przechwytywanie) - od document w dół do elementu docelowego
3// 2. TARGET (cel) - na samym elemencie docelowym
4// 3. BUBBLE (bąbelkowanie) - od elementu docelowego w górę do document
5
6document.addEventListener('click', function(event) {
7 // eventPhase pokazuje aktualną fazę:
8 // 1 = CAPTURING_PHASE
9 // 2 = AT_TARGET
10 // 3 = BUBBLING_PHASE
11 console.log('Faza wydarzenia:', event.eventPhase);
12}, true); // true = nasłuchuj w fazie capture"Zobaczmy to w akcji," mówi Ray, otwierając strukturę zagrody dinozaurów w systemie.
1// HTML struktura:
2// <div id="park-zone" class="zone">
3// <div id="sector-b" class="sector">
4// <div id="enclosure-7" class="enclosure">
5// <button id="emergency-btn" class="button">ALARM</button>
6// </div>
7// </div>
8// </div>
9
10const parkZone = document.getElementById('park-zone');
11const sectorB = document.getElementById('sector-b');
12const enclosure7 = document.getElementById('enclosure-7');
13const emergencyBtn = document.getElementById('emergency-btn');
14
15// Listener na przycisku (najbardziej zagnieżdżony)
16emergencyBtn.addEventListener('click', function(event) {
17 console.log('1. ALARM! Przycisk awaryjny wciśnięty');
18 console.log(' Target:', event.target.id);
19 console.log(' CurrentTarget:', event.currentTarget.id);
20 console.log(' Faza:', event.eventPhase); // 2 (AT_TARGET)
21
22 activateLocalAlarm();
23});
24
25// Listener na zagrodzie
26enclosure7.addEventListener('click', function(event) {
27 console.log('2. Zagroda 7 - wykryto aktywację alarmu');
28 console.log(' Target:', event.target.id); // emergency-btn
29 console.log(' CurrentTarget:', event.currentTarget.id); // enclosure-7
30 console.log(' Faza:', event.eventPhase); // 3 (BUBBLING_PHASE)
31
32 closeEnclosureGates();
33});
34
35// Listener na sektorze
36sectorB.addEventListener('click', function(event) {
37 console.log('3. Sektor B - propagacja alarmu do sektora');
38 console.log(' Target:', event.target.id); // emergency-btn
39 console.log(' CurrentTarget:', event.currentTarget.id); // sector-b
40 console.log(' Faza:', event.eventPhase); // 3 (BUBBLING_PHASE)
41
42 lockSectorGates();
43});
44
45// Listener na całej strefie parku
46parkZone.addEventListener('click', function(event) {
47 console.log('4. Strefa Parku - alarm dotarł do centrum kontrolnego');
48 console.log(' Target:', event.target.id); // emergency-btn
49 console.log(' CurrentTarget:', event.currentTarget.id); // park-zone
50 console.log(' Faza:', event.eventPhase); // 3 (BUBBLING_PHASE)
51
52 notifyCentralControl();
53});
54
55// Listener na całym dokumencie
56document.addEventListener('click', function(event) {
57 console.log('5. DOKUMENT - najwyższy poziom systemu powiadomiony');
58 console.log(' Wydarzenie dotarło na sam szczyt hierarchii');
59
60 logSystemEvent(event);
61});
62
63function activateLocalAlarm() {
64 console.log(' → Aktywacja lokalnego alarmu w zagrodzie 7');
65}
66
67function closeEnclosureGates() {
68 console.log(' → Zamykanie bram zagrody 7');
69}
70
71function lockSectorGates() {
72 console.log(' → Blokada wszystkich bram w sektorze B');
73}
74
75function notifyCentralControl() {
76 console.log(' → Powiadamianie centralnego centrum kontrolnego');
77}
78
79function logSystemEvent(event) {
80 console.log(' → Logowanie wydarzenia w systemie głównym');
81}
82
83// Po kliknięciu przycisku zobaczymy wszystkie 5 komunikatów w kolejności!"Czasami," Ray przyciska czerwony przycisk, "chcemy zatrzymać propagację. Jak zawór bezpieczeństwa, który izoluje problem lokalnie."
1const button = document.getElementById('local-action-btn');
2const container = document.getElementById('action-container');
3const section = document.getElementById('main-section');
4
5button.addEventListener('click', function(event) {
6 console.log('1. Przycisk kliknięty');
7 performLocalAction();
8
9 // ZATRZYMAJ propagację - wydarzenie nie pójdzie wyżej
10 event.stopPropagation();
11 console.log(' Propagacja zatrzymana - wydarzenie nie dotrze do rodziców');
12});
13
14container.addEventListener('click', function(event) {
15 console.log('2. Ten listener SIĘ NIE WYKONA (z powodu stopPropagation)');
16 performContainerAction();
17});
18
19section.addEventListener('click', function(event) {
20 console.log('3. Ten listener też SIĘ NIE WYKONA');
21 performSectionAction();
22});
23
24// Po kliknięciu przycisku zobaczymy tylko komunikat "1.""Najczęstszy przypadek," wyjaśnia Ray, "to zamykanie okien modalnych."
1// Modal zamykający się po kliknięciu w tło (overlay)
2const modal = document.getElementById('dino-info-modal');
3const modalContent = document.getElementById('modal-content');
4const closeButton = document.getElementById('modal-close');
5
6// Kliknięcie w tło (overlay) zamyka modal
7modal.addEventListener('click', function(event) {
8 console.log('Kliknięto w tło - zamykanie modala');
9 closeModal();
10});
11
12// Kliknięcie w zawartość modala NIE zamyka go
13modalContent.addEventListener('click', function(event) {
14 event.stopPropagation(); // WAŻNE! Zatrzymaj propagację do overlay
15 console.log('Kliknięto w zawartość - modal pozostaje otwarty');
16});
17
18// Przycisk zamykania
19closeButton.addEventListener('click', function(event) {
20 event.stopPropagation(); // Opcjonalne, ale bezpieczne
21 console.log('Przycisk zamknięcia - zamykanie modala');
22 closeModal();
23});
24
25function closeModal() {
26 modal.style.display = 'none';
27 console.log('Modal zamknięty');
28}
29
30function openModal(dinosaurId) {
31 modal.style.display = 'block';
32 loadDinosaurData(dinosaurId);
33 console.log('Modal otwarty dla dinozaura:', dinosaurId);
34}"Większość developerów," mówi Ray, "używa tylko fazy bubble. Ale czasami potrzebujemy fazy capture - sprawdzenia wszystkiego PRZED dotarciem do celu."
1const outer = document.getElementById('outer');
2const middle = document.getElementById('middle');
3const inner = document.getElementById('inner');
4
5// Bubble phase (domyślnie, capture: false)
6outer.addEventListener('click', function(event) {
7 console.log('BUBBLE: Outer');
8});
9
10middle.addEventListener('click', function(event) {
11 console.log('BUBBLE: Middle');
12});
13
14inner.addEventListener('click', function(event) {
15 console.log('BUBBLE: Inner (target)');
16});
17
18// Capture phase (capture: true)
19outer.addEventListener('click', function(event) {
20 console.log('CAPTURE: Outer');
21}, true); // true włącza capture phase
22
23middle.addEventListener('click', function(event) {
24 console.log('CAPTURE: Middle');
25}, true);
26
27inner.addEventListener('click', function(event) {
28 console.log('CAPTURE: Inner (target)');
29}, true);
30
31// Po kliknięciu inner zobaczymy:
32// CAPTURE: Outer (w dół)
33// CAPTURE: Middle (w dół)
34// CAPTURE: Inner (target) (cel)
35// BUBBLE: Inner (target) (cel)
36// BUBBLE: Middle (w górę)
37// BUBBLE: Outer (w górę)1// Globalny handler dla wszystkich kliknięć (np. do analytics)
2document.addEventListener('click', function(event) {
3 const targetElement = event.target;
4
5 console.log('=== GLOBALNY TRACKER KLIKNIĘĆ ===');
6 console.log('Element:', targetElement.tagName);
7 console.log('ID:', targetElement.id);
8 console.log('Klasy:', targetElement.className);
9 console.log('Pozycja:', event.clientX, event.clientY);
10 console.log('Czas:', new Date().toISOString());
11
12 // Wyślij do systemu analytics
13 trackClickEvent({
14 element: targetElement.tagName,
15 elementId: targetElement.id,
16 elementClass: targetElement.className,
17 timestamp: Date.now(),
18 position: { x: event.clientX, y: event.clientY }
19 });
20}, true); // Capture - wykona się PRZED wszystkimi innymi handlerami
21
22function trackClickEvent(data) {
23 console.log('Wysyłanie danych analytics:', data);
24 // Wyślij do serwera analytics
25}"To bardziej drastyczny środek," ostrzega Ray. "Zatrzymuje NIE TYLKO propagację do rodziców, ale też wszystkie pozostałe listenery na tym samym elemencie."
1const criticalButton = document.getElementById('critical-system-btn');
2
3// Pierwszy listener
4criticalButton.addEventListener('click', function(event) {
5 console.log('1. Pierwszy listener - wykonuje się');
6
7 const systemStatus = checkSystemStatus();
8
9 if (systemStatus === 'CRITICAL') {
10 console.log(' SYSTEM KRYTYCZNY - zatrzymuję wszystkie pozostałe akcje!');
11 event.stopImmediatePropagation(); // STOP WSZYSTKO!
12 handleCriticalSituation();
13 return;
14 }
15});
16
17// Drugi listener - NIE wykona się jeśli stopImmediatePropagation został wywołany
18criticalButton.addEventListener('click', function(event) {
19 console.log('2. Drugi listener - normalny flow');
20 performNormalAction();
21});
22
23// Trzeci listener - NIE wykona się jeśli stopImmediatePropagation został wywołany
24criticalButton.addEventListener('click', function(event) {
25 console.log('3. Trzeci listener - logging');
26 logAction();
27});
28
29function checkSystemStatus() {
30 // Symulacja sprawdzania stanu systemu
31 const random = Math.random();
32 return random > 0.7 ? 'CRITICAL' : 'NORMAL';
33}
34
35function handleCriticalSituation() {
36 console.log(' → Aktywacja protokołów awaryjnych');
37 console.log(' → Blokada wszystkich systemów');
38 console.log(' → Powiadamianie administracji');
39}"Zobaczmy jak wykorzystać propagację w realnym systemie," mówi Ray, otwierając kod systemu powiadomień parku.
1class ParkNotificationSystem {
2 constructor() {
3 this.notifications = [];
4 this.setupGlobalListeners();
5 }
6
7 setupGlobalListeners() {
8 // Globalny listener w fazie capture
9 document.addEventListener('click', (e) => {
10 this.trackInteraction(e);
11 }, true);
12
13 // Listener dla wszystkich powiadomień
14 const notificationContainer = document.getElementById('notifications');
15
16 if (notificationContainer) {
17 notificationContainer.addEventListener('click', (e) => {
18 this.handleNotificationClick(e);
19 });
20 }
21 }
22
23 trackInteraction(event) {
24 const target = event.target;
25
26 // Śledź tylko kluczowe interakcje
27 if (target.matches('.trackable')) {
28 console.log('Interakcja z elementem trackable:', target.id);
29
30 this.logInteraction({
31 element: target.id,
32 type: event.type,
33 timestamp: Date.now()
34 });
35 }
36 }
37
38 handleNotificationClick(event) {
39 const notification = event.target.closest('.notification');
40
41 if (!notification) return;
42
43 // Zatrzymaj propagację - nie chcemy żeby kliknięcie w powiadomienie
44 // uruchamiało inne handlery
45 event.stopPropagation();
46
47 const notificationId = notification.dataset.id;
48 const action = event.target.dataset.action;
49
50 console.log(`Kliknięto powiadomienie: ${notificationId}`);
51 console.log(`Akcja: ${action}`);
52
53 switch(action) {
54 case 'dismiss':
55 this.dismissNotification(notificationId);
56 break;
57
58 case 'view':
59 this.viewNotification(notificationId);
60 break;
61
62 case 'action':
63 this.performNotificationAction(notificationId);
64 break;
65
66 default:
67 console.log('Kliknięto gdzieś w powiadomienie - pokazuję szczegóły');
68 this.showNotificationDetails(notificationId);
69 }
70 }
71
72 dismissNotification(id) {
73 console.log(`Zamykanie powiadomienia: ${id}`);
74 const notification = document.querySelector(`[data-id="${id}"]`);
75
76 if (notification) {
77 notification.classList.add('dismissing');
78
79 setTimeout(() => {
80 notification.remove();
81 this.notifications = this.notifications.filter(n => n.id !== id);
82 console.log(`Powiadomienie ${id} usunięte`);
83 }, 300);
84 }
85 }
86
87 viewNotification(id) {
88 console.log(`Wyświetlanie szczegółów powiadomienia: ${id}`);
89 // Logika wyświetlania szczegółów
90 }
91
92 performNotificationAction(id) {
93 const notification = this.notifications.find(n => n.id === id);
94
95 if (notification && notification.actionCallback) {
96 console.log(`Wykonywanie akcji dla powiadomienia: ${id}`);
97 notification.actionCallback();
98 }
99 }
100
101 showNotificationDetails(id) {
102 console.log(`Pokazywanie pełnych szczegółów powiadomienia: ${id}`);
103 // Logika wyświetlania pełnych szczegółów
104 }
105
106 logInteraction(data) {
107 console.log('Logowanie interakcji:', data);
108 // Zapis do systemu analytics
109 }
110
111 createNotification(message, type = 'info', actionCallback = null) {
112 const id = 'notif-' + Date.now();
113
114 const notification = {
115 id,
116 message,
117 type,
118 actionCallback,
119 timestamp: new Date()
120 };
121
122 this.notifications.push(notification);
123 this.renderNotification(notification);
124
125 console.log('Utworzono nowe powiadomienie:', id);
126 return id;
127 }
128
129 renderNotification(notification) {
130 const container = document.getElementById('notifications');
131
132 const notifElement = document.createElement('div');
133 notifElement.className = `notification notification-${notification.type}`;
134 notifElement.dataset.id = notification.id;
135
136 notifElement.innerHTML = `
137 <div class="notification-content">
138 <p>${notification.message}</p>
139 <small>${notification.timestamp.toLocaleTimeString()}</small>
140 </div>
141 <div class="notification-actions">
142 ${notification.actionCallback ? '<button data-action="action">Akcja</button>' : ''}
143 <button data-action="dismiss">Zamknij</button>
144 </div>
145 `;
146
147 container.appendChild(notifElement);
148 }
149}
150
151// Inicjalizacja systemu
152const parkNotifications = new ParkNotificationSystem();
153
154// Przykładowe użycie
155parkNotifications.createNotification(
156 'Wykryto nietypową aktywność w zagrodzie T-Rexa',
157 'warning',
158 () => {
159 console.log('Akcja: Otwieranie panelu monitoringu T-Rexa');
160 openTrexMonitoring();
161 }
162);"Propagacja wydarzeń," podsumowuje Ray, "to potężny mechanizm. Podobnie jak w systemach alarmowych parku, gdzie sygnał rozprzestrzenia się na odpowiednie poziomy, tak w JavaScript wydarzenia przepływają przez drzewo DOM. Zrozumienie tego mechanizmu jest kluczowe dla efektywnego event handlingu."
Kluczowe koncepcje:
documentevent.stopPropagation() zatrzymuje propagację do rodzicówevent.stopImmediatePropagation() zatrzymuje wszystkie pozostałe listenerytrue) wykonuje się przed bubbleevent.target vs event.currentTarget - kluczowa różnica podczas propagacji