"Listen carefully," says Dr. Alan Grant, leaning over the monitoring system console. "Dinosaurs communicate with each other using sounds - from low rumbles to terrifying roars. In our park, we need a similar communication system between modules. And that's where Custom Events come in!"
So far, we've learned about built-in DOM events -
click, keydown, submit, and scroll. But JavaScript allows us to create our own, non-standard events. Thanks to them, we can build communication systems between different parts of an application, just like the alarm system in Jurassic Park sends notifications to different zones.Custom Events are events defined by the programmer that can carry any data and be listened to just like regular DOM events.
To create a custom event, we use the
CustomEvent constructor:1// Basic Custom Event creation
2const alertEvent = new CustomEvent('dinoAlert');
3
4// Custom Event with additional data (detail)
5const escapeEvent = new CustomEvent('dinoEscape', {
6 detail: {
7 dinosaur: 'Velociraptor',
8 zone: 'B',
9 severity: 'critical'
10 }
11});
12
13// Accessing event data
14console.log(escapeEvent.detail.dinosaur); // "Velociraptor"
15console.log(escapeEvent.detail.severity); // "critical"
16console.log(escapeEvent.type); // "dinoEscape"The
detail object is a special field of Custom Events where we can pass any data. It is the only way to attach information to a non-standard event.The
CustomEvent constructor accepts a second argument - an options object:1const event = new CustomEvent('systemUpdate', {
2 detail: { module: 'security', status: 'active' }, // Event data
3 bubbles: true, // Whether the event propagates up the DOM
4 cancelable: true, // Whether preventDefault() can be called
5 composed: false // Whether it crosses Shadow DOM boundaries
6});detail - an object with data we want to passbubbles - defaults to false; set to true if the event should propagate up the DOM tree (bubbling)cancelable - defaults to false; set to true if you want to be able to cancel the event via preventDefault()The event object itself does nothing - we must dispatch it on a specific DOM element:
1// Create an element that will emit events
2const controlPanel = document.getElementById('controlPanel');
3
4// Create the event
5const fenceAlert = new CustomEvent('fenceBreak', {
6 detail: {
7 zone: 'A',
8 voltage: 0,
9 dinosaur: 'T-Rex'
10 },
11 bubbles: true
12});
13
14// Dispatch the event on the element
15controlPanel.dispatchEvent(fenceAlert);The
dispatchEvent() method is available on every DOM element. After calling it, the event is immediately dispatched and all listening handlers are executed synchronously.We listen for Custom Events exactly the same way as regular events - via
addEventListener:1const controlPanel = document.getElementById('controlPanel');
2
3// Register a listener for our event
4controlPanel.addEventListener('fenceBreak', function(event) {
5 const { zone, voltage, dinosaur } = event.detail;
6 console.log('ALARM! Zone ' + zone + ' fence damaged!');
7 console.log('Dinosaur: ' + dinosaur);
8 console.log('Voltage: ' + voltage + 'V');
9});
10
11// Another module can also listen to the same event
12controlPanel.addEventListener('fenceBreak', function(event) {
13 if (event.detail.dinosaur === 'T-Rex') {
14 console.log('EVACUATE IMMEDIATELY!');
15 }
16});
17
18// Dispatch the event - both listeners will be called
19controlPanel.dispatchEvent(new CustomEvent('fenceBreak', {
20 detail: { zone: 'A', voltage: 0, dinosaur: 'T-Rex' }
21}));Let's see a complete example of inter-module communication:
1// Jurassic Park monitoring system
2const parkSystem = document.getElementById('parkSystem');
3
4// Security module listens for alerts
5parkSystem.addEventListener('securityAlert', function(event) {
6 const { type, zone, message } = event.detail;
7 const alertDiv = document.createElement('div');
8 alertDiv.className = 'alert alert-' + type;
9 alertDiv.textContent = '[' + zone + '] ' + message;
10 document.getElementById('alerts').appendChild(alertDiv);
11});
12
13// Dinosaur monitoring module listens for statuses
14parkSystem.addEventListener('dinoStatus', function(event) {
15 const { name, heartRate, location } = event.detail;
16 console.log(name + ': HR=' + heartRate + ', location=' + location);
17});
18
19// Function that emits a security alert
20function emitSecurityAlert(type, zone, message) {
21 parkSystem.dispatchEvent(new CustomEvent('securityAlert', {
22 detail: { type, zone, message },
23 bubbles: true
24 }));
25}
26
27// Function that emits a dinosaur status
28function emitDinoStatus(name, heartRate, location) {
29 parkSystem.dispatchEvent(new CustomEvent('dinoStatus', {
30 detail: { name, heartRate, location },
31 bubbles: true
32 }));
33}
34
35// Usage
36emitSecurityAlert('warning', 'Zone B', 'Raptor activity elevated');
37emitSecurityAlert('critical', 'Zone A', 'Fence damaged!');
38emitDinoStatus('T-Rex', 85, 'Main enclosure');By default, Custom Events do not propagate up the DOM tree. To enable bubbling, you must set
bubbles: true:1// WITHOUT bubbles (default) - listener on parent WON'T catch the event
2const child = document.getElementById('zoneA');
3const parent = document.getElementById('parkSystem');
4
5child.dispatchEvent(new CustomEvent('alert', {
6 detail: { msg: 'test' }
7 // bubbles defaults to false
8}));
9
10parent.addEventListener('alert', (e) => {
11 // This handler WON'T be called!
12 console.log('Parent caught:', e.detail);
13});
14
15// WITH bubbles: true - event propagates to parent
16child.dispatchEvent(new CustomEvent('alert', {
17 detail: { msg: 'test' },
18 bubbles: true // Now the event will propagate!
19}));
20
21parent.addEventListener('alert', (e) => {
22 // This handler WILL be called!
23 console.log('Parent caught:', e.detail.msg); // "test"
24});This is especially useful when you want to listen for events from many children on a single parent (event delegation for Custom Events).
If you set
cancelable: true, you can cancel the event:1const event = new CustomEvent('transferDino', {
2 detail: { dinosaur: 'T-Rex', from: 'A', to: 'B' },
3 cancelable: true,
4 bubbles: true
5});
6
7// A listener can cancel the event
8parkSystem.addEventListener('transferDino', function(e) {
9 if (e.detail.dinosaur === 'T-Rex') {
10 e.preventDefault(); // Cancel T-Rex transfer!
11 console.log('T-Rex transfer blocked for safety reasons!');
12 }
13});
14
15// Check if the event was canceled
16const wasNotCancelled = parkSystem.dispatchEvent(event);
17console.log('Transfer allowed:', wasNotCancelled); // false (because preventDefault)The
dispatchEvent() method returns false if the event was canceled by preventDefault(). This allows the emitter to check whether the action should continue.Custom Events work great in many scenarios:
1// Example: decoupling with Custom Events
2// The cart module knows nothing about the notification module
3const cart = [];
4
5function addToCart(product) {
6 cart.push(product);
7
8 // Emits an event - whoever wants to can listen
9 document.dispatchEvent(new CustomEvent('cartUpdated', {
10 detail: { product, totalItems: cart.length },
11 bubbles: true
12 }));
13}
14
15// Notification module listens independently
16document.addEventListener('cartUpdated', (e) => {
17 console.log('Added: ' + e.detail.product.name);
18});
19
20// Cart badge module also listens independently
21document.addEventListener('cartUpdated', (e) => {
22 console.log('Cart: ' + e.detail.totalItems + ' items');
23});Custom Events are a powerful tool for building loosely coupled systems in JavaScript:
new CustomEvent(name, options) - creates a non-standard eventelement.dispatchEvent(event) - dispatches the event on an elementdetail - object with data passed in the eventbubbles: true - enables event propagation up the DOMcancelable: true - allows canceling the event via preventDefault()addEventListener"In Jurassic Park," Dr. Grant summarizes, "every system must communicate with others. Custom Events are our internal radio system - every module can broadcast and every module can receive. Without it, the entire park would descend into chaos."