Używamy cookies, żeby zwiększyć Twoje doświadczenia na stronie
CodeWorlds

Propagacja wydarzeń (Event Bubbling)

"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."

Czym jest event bubbling?

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.

Trzy fazy propagacji wydarzenia

"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

Praktyczny przykład propagacji

"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!

stopPropagation() - zatrzymywanie propagacji

"Czasami," Ray przyciska czerwony przycisk, "chcemy zatrzymać propagację. Jak zawór bezpieczeństwa, który izoluje problem lokalnie."

Kontrolowane zatrzymywanie

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."

Praktyczny przypadek użycia - modalne okno

"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}

Capture phase - faza przechwytywania

"Większość developerów," mówi Ray, "używa tylko fazy bubble. Ale czasami potrzebujemy fazy capture - sprawdzenia wszystkiego PRZED dotarciem do celu."

Nasłuchiwanie w fazie capture

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ę)

Praktyczne zastosowanie capture - globalne przechwytywanie

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}

stopImmediatePropagation() - natychmiastowe zatrzymanie

"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}

Praktyczny system powiadomień

"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);

Podsumowanie

"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:

  • Wydarzenia "bąbelkują" od elementu docelowego w górę do
    document
  • Trzy fazy: Capture → Target → Bubble
  • event.stopPropagation()
    zatrzymuje propagację do rodziców
  • event.stopImmediatePropagation()
    zatrzymuje wszystkie pozostałe listenery
  • Capture phase (trzeci parametr
    true
    ) wykonuje się przed bubble
  • event.target
    vs
    event.currentTarget
    - kluczowa różnica podczas propagacji
Przejdź do CodeWorlds