We use cookies to enhance your experience on the site
CodeWorlds

Custom Events and dispatchEvent in JavaScript

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

What Are Custom Events?

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.

The CustomEvent Constructor

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.

CustomEvent Options

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 pass
  • bubbles
    - 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()

Dispatching Events - dispatchEvent

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.

Listening for Custom Events

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

Practical Example: Park Alarm System

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

Bubbling in Custom Events

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

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

Use Cases for Custom Events

Custom Events work great in many scenarios:

  1. Communication between components - when two independent modules need to exchange information
  2. Notification system - a central hub where different modules emit events
  3. Code decoupling - the emitting module doesn't need to know who's listening
  4. Plugin system - external modules can react to application events
  5. User action tracking - emitting analytics events
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});

Summary

Custom Events are a powerful tool for building loosely coupled systems in JavaScript:

  • new CustomEvent(name, options)
    - creates a non-standard event
  • element.dispatchEvent(event)
    - dispatches the event on an element
  • detail
    - object with data passed in the event
  • bubbles: true
    - enables event propagation up the DOM
  • cancelable: true
    - allows canceling the event via
    preventDefault()
  • Custom Events are listened to the same way as regular DOM events - via
    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."

Go to CodeWorlds