Proper websites, done properly

Part 6: Custom events

5 minute read time / 615 words

A nice little touch we can add is to trigger and listen for custom events - meaning we can pass messages between different components in our JavaScript setup.

How to create and trigger a custom event

Imagine we have more menus that are opened by triggering and toggle element. They're all on the same page, and are in close enough proximity to each other that they're probably going to get in each other's way and annoy us if we don't do something sensible with them. Try this site out on mobile, and you'll see that this method is implemented in the way that both the mobile burger menu and all the separate site settings menus work.

Open one, and any other open menu will close, whether it's of the same type using the same JavaScript class or not. This is done by triggering a custom event in one area of our code, and have some other code elsewhere listening for that custom event. The first step is to create our custom event.

Let's add a new function to our class for this purpose (imagining we're going to add site options like those implemented on Shffld: _triggerCustomEvent():

_triggerCustomEvent () {
    window.dispatchEvent(
        new CustomEvent('siteOption', {
            detail: {
                triggeredId: this.container.id,
            },
        }),
    );
}

Here, we simply tell the browser that we're dispatching an event, and that it's a custom event called 'siteOption'. The detail object is where we can pass data or messages between classes in our code - in this case we are passing it the ID of our container element.

Imagine elsewhere we have a site options class running, which does menus for font size, colour theme, etc. In that class we could dispatch a custom event for 'mainNav' when we trigger our 'open site option menu' functionality. We would in that instance pass the ID of the menu we're currently opening.

From this, we can set listeners in our code and do things based off triggers fired elsewhere.

How to listen for a custom event

Let's add an event listener now, add this new function into our MobileNav class:

_addCustomListener() {
    window.addEventListener('mainNav', e => {
        if (this.container.id === e.detail.triggeredId) {
            return;
        }

        this._close();
    });
}

Here, we're listening for anything that triggers the 'mainNav' custom event, and if it's triggered we are checking if the ID passed from it matches the ID of the MobileNav container. If it matches, we do nothing.

If it doesn't match, we trigger the close function in our MobileNav class and in doing so we're setting up the first part of our chain of events to close any open menus when another open menu function is run.

In our other menu class for site options, we can do something similar but instead we listen for the 'siteOption' custom event instead.

_addCustomListener() {
    window.addEventListener('siteOption', e => {
        if (this.container.id === e.detail.triggeredId) {
            return;
        }

        this._close();
    });
}

Now, when a menu is opened in MobileNav, we can trigger a custom event to close any open Site Option menus, and vice versa. To get that custom event firing, we only need to get our _open() function to run the _triggerCustomEvent() function:

_open () {
    this.toggle.classList.add(this.activeClass);
    this.toggle.setAttribute('aria-pressed', true);
    this.context.innerHTML = this.openContextText;
    this.container.classList.add(this.activeClass);
    this._triggerCustomEvent();
}

In our other menu classes, we just add the same functionality to open functions and we're done. Try out the menus on this site on desktop and mobile, and you'll see it in action.