DEV Community

Cover image for Creating and Consuming Custom Events
Aubrey Barnes
Aubrey Barnes

Posted on

Creating and Consuming Custom Events

This week, I've learned something new about the DOM API, and that is how to create and consume custom events. Imagine, instead of listening for a click event, you create your own event to listen for! Sounds cool, right? Allow me to show you more about this neat feature that I didn't even know about, as long as I've been using event listeners.
I've built a project to illustrate one possible use of using custom events. For instance, I want to listen for an order in a kitchen system so the cooks know what's coming up next, then I want to print that order to the screen. I've simulated this in a project on github.

// fetch data from api using netlify functions
const fetchData = async () => {
    //get the data from my super secret netlify function
    const res = await fetch(`/.netlify/functions/token-hider`)
    //if something goes wrong, throw an error
    if (!res.ok) {
        throw new Error(`http response: ${res.status}`)
    }
    // if something goes right, save the json response
    const getMeals = await res.json();
    // pass the fetched data to a new function to process
    processOrder(getMeals)

}
Enter fullscreen mode Exit fullscreen mode

We begin our journey with a standard data fetch, I'm using netlify cli to hide my API key, which I bought for $2 a month, but for those of you who do not care to purchase one, the free api can be found here. The major work behind this program is done after passing the fetched data into a function for processing the data into our own free-range, organic, ethically sourced events.

// This function is called to get a random selection of meals from the array passed in from fetch
const processOrder = (getMeals) => {
    //save an array of randomly selected meals
    const mealsData = getMeals.meals
    // select a random number of meals starting from the beginning of the mealsData array
    // at least one, up to the length of the array given.
    const customerOrder = mealsData.slice(0, Math.floor(Math.random() * mealsData.length) + 1)
// map an array of objects
    const orderDetails = customerOrder.map(item => (
        {
            mealName: item.strMeal,
            mealImg: item.strMealThumb
        }
    ))

// create and initialize event. 

    var orderEvent = new CustomEvent(
        'order',
        { detail: orderDetails }
    );
    // tell the browser api to fire the event
    window.dispatchEvent(orderEvent)
}

const stopMe = setInterval(fetchData, 2000);
Enter fullscreen mode Exit fullscreen mode

First, take a random number of entries from the data array, and map an array of objects, saving the meal name and image. For our next trick we create and initialize a custom event, using the syntax

var orderEvent = new CustomEvent('whatever name you want to call the event', { detail: data-you-want-to-pass }
Enter fullscreen mode Exit fullscreen mode

Afterwards, we fire the event using window.dispatchEvent(variable-you-saved-custom-event-to).

My initial thought for this project was to have a random interval of time to fire the event, but I have not yet figured out how to insert a random time interval into the setInterval function, so for now I set it to two seconds. Every two seconds, I'm calling the fetchData function, which calls the processOrder function, which fires the order event. You consume the order event like any other built in event from the browsers' API. The order event was dispatched to the window interface, so we add an event listener to the window interface, and then a callback function to process the data using event.detail (remember setting what we get from detail when initializing the custom event).

window.addEventListener('order', (evt) => {
    let root = document.getElementById('root');
    const section = document.createElement('section');
    let orderNumber = document.createElement('p')

    section.className = 'order-container'
    orderNumber.className = 'order-number'
    orderNumber.innerText = `Order ${Math.floor(Math.random() * 1000)}`
    section.append(orderNumber)

    const displayOrder = evt.detail.map(item => {
        const p = document.createElement('p')
        const img = document.createElement('img')
        const div = document.createElement('div')
        div.className = 'item-in-order'
        img.src = `${item.mealImg}/preview`
        img.alt = item.mealName
        p.innerText = item.mealName
        div.append(p)
        div.append(img)

        return div

    })

    displayOrder.forEach(item => {
        section.append(item)
    })
    root.append(section)
})
Enter fullscreen mode Exit fullscreen mode

Slap that data into the DOM and you get something like this

Image description

That's all there is to it! I wanted to share this knowledge since I did not know this during an online assessment with essentially a dream company, and wanted to fill in that gap. The best way to learn something is to explain it to someone else, and I hope my explanation was simple and easy to follow, if not, please let me know how I can improve!

Top comments (1)

Collapse
 
parenttobias profile image
Toby Parent

Recently, there was a question in the Odin Project's Discord group about handling event listeners - in particular, "can we bind a key to a given element or action?" Key-binding is a useful utility, and can be done with vanilla js,but it could be handy to bind directly to a keystroke, rather than to all keyup events.

To see what I mean, we could do things like this:

document.addEventListener("ArrowLeft.up", (e)=>{
  if(e.detail.modifers.shift){
    counter -= 10;
  } else {
    counter -= 1;
  }
});
document.addEventListener("ArrowRight.up", (e)=>{
  if(e.detail.modifiers.shift){
    counter += 10;
  } else{
    counter += 1;
  }
});
Enter fullscreen mode Exit fullscreen mode

With that, for example, we could do a simple js game loop. We can listen for arrow keys or specific action keys, and check for modifiers on them, and respond appropriately.

And we're not having to duplicate the if(e.key==='ArrowLeft'') or if(e.key==='ArrowUp') checks each time. We bind to a custom event indicating which key is pressed: in effect, key binding.

Here's the code I put together in playing with this one.

const init = ()=>{
  document.addEventListener("keydown", (e)=>{
    document.dispatchEvent(new CustomEvent(`${e.key}.down`, {
      detail: {
        modifiers: {
          alt: e.altKey,
          shift: e.shiftKey,
          meta: e.metaKey,
          ctrl: e.ctrlKey
        }
      }
    }))
  });
  document.addEventListener("keyup", (e)=>{
    document.dispatchEvent(new CustomEvent(`${e.key}.up`, {
      detail: {
        modifiers: {
          alt: e.altKey,
          shift: e.shiftKey,
          meta: e.metaKey,
          ctrl: e.ctrlKey
        }
      }      
    }))
  })
}
Enter fullscreen mode Exit fullscreen mode

And to tinker with it: replit.com/@TobiasParent/CustomKey...