"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."
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.
"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"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!"Sometimes," Ray presses a red button, "we want to stop propagation. Like a safety valve that isolates the problem locally."
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.""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}"Most developers," says Ray, "only use the bubble phase. But sometimes we need the capture phase - checking everything BEFORE it reaches the target."
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)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}"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}"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);"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:
documentevent.stopPropagation() stops propagation to parentsevent.stopImmediatePropagation() stops all remaining listenerstrue) executes before bubbleevent.target vs event.currentTarget - crucial difference during propagation