We use cookies to enhance your experience on the site
CodeWorlds

Event Propagation (Event Bubbling)

"Look," Ray Arnold leads you to the central alarm panel of Jurassic Park. "When a sensor detects a threat in enclosure 7-B, the signal doesn't stop there. It goes higher - to the sector system, then to the main control center, and finally to the central alarm. This is called signal propagation."

Ray shows the alarm system diagram. "In the DOM, it works exactly the same way. When you click an element, the event 'bubbles' up through the entire DOM tree - from the target element, through its parents, all the way to the document itself."

What Is Event Bubbling?

Event bubbling is a fundamental mechanism in JavaScript where events propagate from the element on which they occurred, upward through all its ancestors in the DOM tree.

Three Phases of Event Propagation

"Every event," Ray explains, "goes through three phases. It's like a security protocol - we check everything in a specific order."

1// Event phases:
2// 1. CAPTURE (capturing) - from document down to the target element
3// 2. TARGET (target) - on the target element itself
4// 3. BUBBLE (bubbling) - from the target element up to document
5
6document.addEventListener('click', function(event) {
7  // eventPhase shows the current phase:
8  // 1 = CAPTURING_PHASE
9  // 2 = AT_TARGET
10  // 3 = BUBBLING_PHASE
11  console.log('Event phase:', event.eventPhase);
12}, true); // true = listen in capture phase

Practical Propagation Example

"Let's see it in action," says Ray, opening the dinosaur enclosure structure in the system.

1// HTML structure:
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 on the button (most nested)
16emergencyBtn.addEventListener('click', function(event) {
17  console.log('1. ALARM! Emergency button pressed');
18  console.log('   Target:', event.target.id);
19  console.log('   CurrentTarget:', event.currentTarget.id);
20  console.log('   Phase:', event.eventPhase); // 2 (AT_TARGET)
21
22  activateLocalAlarm();
23});
24
25// Listener on the enclosure
26enclosure7.addEventListener('click', function(event) {
27  console.log('2. Enclosure 7 - alarm activation detected');
28  console.log('   Target:', event.target.id); // emergency-btn
29  console.log('   CurrentTarget:', event.currentTarget.id); // enclosure-7
30  console.log('   Phase:', event.eventPhase); // 3 (BUBBLING_PHASE)
31
32  closeEnclosureGates();
33});
34
35// Listener on the sector
36sectorB.addEventListener('click', function(event) {
37  console.log('3. Sector B - alarm propagated to sector');
38  console.log('   Target:', event.target.id); // emergency-btn
39  console.log('   CurrentTarget:', event.currentTarget.id); // sector-b
40  console.log('   Phase:', event.eventPhase); // 3 (BUBBLING_PHASE)
41
42  lockSectorGates();
43});
44
45// Listener on the entire park zone
46parkZone.addEventListener('click', function(event) {
47  console.log('4. Park Zone - alarm reached central control');
48  console.log('   Target:', event.target.id); // emergency-btn
49  console.log('   CurrentTarget:', event.currentTarget.id); // park-zone
50  console.log('   Phase:', event.eventPhase); // 3 (BUBBLING_PHASE)
51
52  notifyCentralControl();
53});
54
55// Listener on the entire document
56document.addEventListener('click', function(event) {
57  console.log('5. DOCUMENT - highest level of the system notified');
58  console.log('   The event reached the very top of the hierarchy');
59
60  logSystemEvent(event);
61});
62
63function activateLocalAlarm() {
64  console.log('   → Activating local alarm in enclosure 7');
65}
66
67function closeEnclosureGates() {
68  console.log('   → Closing enclosure 7 gates');
69}
70
71function lockSectorGates() {
72  console.log('   → Locking all gates in sector B');
73}
74
75function notifyCentralControl() {
76  console.log('   → Notifying central control center');
77}
78
79function logSystemEvent(event) {
80  console.log('   → Logging event in the main system');
81}
82
83// After clicking the button, we'll see all 5 messages in order!

stopPropagation() - Stopping Propagation

"Sometimes," Ray presses a red button, "we want to stop propagation. Like a safety valve that isolates the problem locally."

Controlled Stopping

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. Button clicked');
7  performLocalAction();
8
9  // STOP propagation - the event won't go higher
10  event.stopPropagation();
11  console.log('   Propagation stopped - the event won\'t reach parents');
12});
13
14container.addEventListener('click', function(event) {
15  console.log('2. This listener WON\'T EXECUTE (due to stopPropagation)');
16  performContainerAction();
17});
18
19section.addEventListener('click', function(event) {
20  console.log('3. This listener also WON\'T EXECUTE');
21  performSectionAction();
22});
23
24// After clicking the button, we'll only see message "1."

Practical Use Case - Modal Window

"The most common case," Ray explains, "is closing modal windows."

1// Modal that closes when clicking the background (overlay)
2const modal = document.getElementById('dino-info-modal');
3const modalContent = document.getElementById('modal-content');
4const closeButton = document.getElementById('modal-close');
5
6// Clicking the background (overlay) closes the modal
7modal.addEventListener('click', function(event) {
8  console.log('Background clicked - closing modal');
9  closeModal();
10});
11
12// Clicking the modal content does NOT close it
13modalContent.addEventListener('click', function(event) {
14  event.stopPropagation(); // IMPORTANT! Stop propagation to overlay
15  console.log('Content clicked - modal stays open');
16});
17
18// Close button
19closeButton.addEventListener('click', function(event) {
20  event.stopPropagation(); // Optional, but safe
21  console.log('Close button - closing modal');
22  closeModal();
23});
24
25function closeModal() {
26  modal.style.display = 'none';
27  console.log('Modal closed');
28}
29
30function openModal(dinosaurId) {
31  modal.style.display = 'block';
32  loadDinosaurData(dinosaurId);
33  console.log('Modal opened for dinosaur:', dinosaurId);
34}

Capture Phase - The Capturing Phase

"Most developers," says Ray, "only use the bubble phase. But sometimes we need the capture phase - checking everything BEFORE it reaches the target."

Listening in the Capture Phase

1const outer = document.getElementById('outer');
2const middle = document.getElementById('middle');
3const inner = document.getElementById('inner');
4
5// Bubble phase (default, 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 enables 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// After clicking inner, we'll see:
32// CAPTURE: Outer (going down)
33// CAPTURE: Middle (going down)
34// CAPTURE: Inner (target) (target)
35// BUBBLE: Inner (target) (target)
36// BUBBLE: Middle (going up)
37// BUBBLE: Outer (going up)

Practical Use of Capture - Global Interception

1// Global handler for all clicks (e.g., for analytics)
2document.addEventListener('click', function(event) {
3  const targetElement = event.target;
4
5  console.log('=== GLOBAL CLICK TRACKER ===');
6  console.log('Element:', targetElement.tagName);
7  console.log('ID:', targetElement.id);
8  console.log('Classes:', targetElement.className);
9  console.log('Position:', event.clientX, event.clientY);
10  console.log('Time:', new Date().toISOString());
11
12  // Send to analytics system
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 - executes BEFORE all other handlers
21
22function trackClickEvent(data) {
23  console.log('Sending analytics data:', data);
24  // Send to analytics server
25}

stopImmediatePropagation() - Immediate Stop

"This is a more drastic measure," Ray warns. "It stops NOT ONLY propagation to parents, but also all remaining listeners on the same element."

1const criticalButton = document.getElementById('critical-system-btn');
2
3// First listener
4criticalButton.addEventListener('click', function(event) {
5  console.log('1. First listener - executes');
6
7  const systemStatus = checkSystemStatus();
8
9  if (systemStatus === 'CRITICAL') {
10    console.log('   SYSTEM CRITICAL - stopping all remaining actions!');
11    event.stopImmediatePropagation(); // STOP EVERYTHING!
12    handleCriticalSituation();
13    return;
14  }
15});
16
17// Second listener - WON'T execute if stopImmediatePropagation was called
18criticalButton.addEventListener('click', function(event) {
19  console.log('2. Second listener - normal flow');
20  performNormalAction();
21});
22
23// Third listener - WON'T execute if stopImmediatePropagation was called
24criticalButton.addEventListener('click', function(event) {
25  console.log('3. Third listener - logging');
26  logAction();
27});
28
29function checkSystemStatus() {
30  // Simulation of system status check
31  const random = Math.random();
32  return random > 0.7 ? 'CRITICAL' : 'NORMAL';
33}
34
35function handleCriticalSituation() {
36  console.log('   → Activating emergency protocols');
37  console.log('   → Locking all systems');
38  console.log('   → Notifying administration');
39}

Practical Notification System

"Let's see how to use propagation in a real system," says Ray, opening the park notification system code.

1class ParkNotificationSystem {
2  constructor() {
3    this.notifications = [];
4    this.setupGlobalListeners();
5  }
6
7  setupGlobalListeners() {
8    // Global listener in capture phase
9    document.addEventListener('click', (e) => {
10      this.trackInteraction(e);
11    }, true);
12
13    // Listener for all notifications
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    // Track only key interactions
27    if (target.matches('.trackable')) {
28      console.log('Interaction with trackable element:', 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    // Stop propagation - we don't want the notification click
44    // to trigger other handlers
45    event.stopPropagation();
46
47    const notificationId = notification.dataset.id;
48    const action = event.target.dataset.action;
49
50    console.log(`Notification clicked: ${notificationId}`);
51    console.log(`Action: ${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('Clicked somewhere in the notification - showing details');
68        this.showNotificationDetails(notificationId);
69    }
70  }
71
72  dismissNotification(id) {
73    console.log(`Closing notification: ${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(`Notification ${id} removed`);
83      }, 300);
84    }
85  }
86
87  viewNotification(id) {
88    console.log(`Displaying notification details: ${id}`);
89    // Details display logic
90  }
91
92  performNotificationAction(id) {
93    const notification = this.notifications.find(n => n.id === id);
94
95    if (notification && notification.actionCallback) {
96      console.log(`Executing action for notification: ${id}`);
97      notification.actionCallback();
98    }
99  }
100
101  showNotificationDetails(id) {
102    console.log(`Showing full notification details: ${id}`);
103    // Full details display logic
104  }
105
106  logInteraction(data) {
107    console.log('Logging interaction:', data);
108    // Save to analytics system
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('New notification created:', 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">Action</button>' : ''}
143        <button data-action="dismiss">Close</button>
144      </div>
145    `;
146
147    container.appendChild(notifElement);
148  }
149}
150
151// System initialization
152const parkNotifications = new ParkNotificationSystem();
153
154// Example usage
155parkNotifications.createNotification(
156  'Unusual activity detected in the T-Rex enclosure',
157  'warning',
158  () => {
159    console.log('Action: Opening T-Rex monitoring panel');
160    openTrexMonitoring();
161  }
162);

Summary

"Event propagation," Ray summarizes, "is a powerful mechanism. Just as in the park's alarm systems, where a signal propagates to the appropriate levels, events in JavaScript flow through the DOM tree. Understanding this mechanism is crucial for effective event handling."

Key concepts:

  • Events "bubble" from the target element up to
    document
  • Three phases: Capture → Target → Bubble
  • event.stopPropagation()
    stops propagation to parents
  • event.stopImmediatePropagation()
    stops all remaining listeners
  • Capture phase (third parameter
    true
    ) executes before bubble
  • event.target
    vs
    event.currentTarget
    - crucial difference during propagation
Go to CodeWorlds