Introduction to Vue Custom Events

Parthiv Mohan

We know that Vue can listen to events on your elements and trigger specific functions to run when such events occur. Vue.js also allows you to listen for custom events, an ability which has its most important use case in allowing child components to fire off events that parent components can listen for.

We created a simple photo gallery component in the Vue Template Syntax article. You could click any of the photos in a row of thumbnails and the photo you clicked would be displayed in a large size below. Now, what if we wanted the background of the entire page to be set to the average color of the photo being displayed? We could call this something like theater mode.

The power of custom events is that we can fairly easily do that. The parent component of the photo gallery, App.vue, simply needs to receive the average RGB value of the photo from its child component, PhotoGallery.vue, when the photo is clicked.

Let’s get started. This tutorial picks up where the Template Syntax tutorial left off.

App Setup

Let’s use the same setup from this previous post.

Let’s Write Some Custom Event Code

We’re going to use an npm library called fast-average-color to get the average color value for a particular photo. Import it at the top of the <script> section of PhotoGallery.vue.

import from 'fast-average-color';

Our PhotoGallery.vue component, currently, has a method, highlight(), which is triggered when you click on one of the photos.

highlight() {
  event.target.id = "theater";
  this.theatrical = event.target.src;
  let eventIterator = event.target.parentNode;
  while (eventIterator.previousElementSibling != null) {
    eventIterator.previousElementSibling.getElementsByTagName('img')[0].id = "";
    eventIterator = eventIterator.previousElementSibling;
  }
  eventIterator = event.target.parentNode;
  while (eventIterator.nextElementSibling != null) {
    eventIterator.nextElementSibling.getElementsByTagName('img')[0].id = "";
    eventIterator = eventIterator.nextElementSibling;
  }
}

This method displays the clicked photo in a larger size below the thumbnails. Notice that we used event.target all over the method. event.target is the element that was clicked on. This is the image that we want to get the average color of.

By perusing the fast-average-color docs, we can find the getColorAsync function, which returns a color object where color.rgba is the average RGB value. Let’s go ahead and make that function call.

const fac = new FastAverageColor();

fac.getColorAsync(event.target)
  .then(function(color) {
  })
  .catch(function(e) {
    console.log(e);
  });

We’re not yet doing anything with color. Ultimately, we need to set the background color to color.rgba in App.vue. App.vue is going to have a method that takes color.rgba as an argument. Why don’t we just go ahead and write that method?

methods: {
  setBackground(rgba) {
    document.querySelector('body').style.backgroundColor = rgba;
  }
}

That should look good to you!

Take a look at the template section of App.vue. Here’s how it looks right now:

<template>
  <div id="app">
    <PhotoGallery />
  </div>
</template>

The App component is going to have to pick up an event from the PhotoGallery component, a custom event. Say the event was called theater-mode; when we want to listen for such an event in our component, the syntax is just like for regular events. That is, it would be: v-on:theater-mode. When theater-mode occurs we’ll call our setBackground method.

Now we need to send the value color.rgba to App.vue somehow. Go back to PhotoGallery.vue.


Every Vue component has a method $emit. This method allows you to trigger an event, in our case one called theater-mode. We’re going to call this.$emit inside the then function from the async call we made to the color library. Let’s jog your memory.

highlight() {
  event.target.id = "theater";
  this.theatrical = event.target.src;
  let eventIterator = event.target.parentNode;
  while (eventIterator.previousElementSibling != null) {
    eventIterator.previousElementSibling.getElementsByTagName('img')[0].id = "";
    eventIterator = eventIterator.previousElementSibling;
  }
  eventIterator = event.target.parentNode;
  while (eventIterator.nextElementSibling != null) {
    eventIterator.nextElementSibling.getElementsByTagName('img')[0].id = "";
    eventIterator = eventIterator.nextElementSibling;
  }
  const fac = new FastAverageColor();
  fac.getColorAsync(event.target)
    .then(function(color) {
    })
    .catch(function(e) {
      console.log(e);
    });
}

this.$emit takes the event name as its first argument and has optional further arguments in which you can pass data. We’ll pass color.rgba. So our function call is going to look like this.$emit('theater-mode', color.rgba). Here’s our new function:

highlight() {
  event.target.id = "theater";
  this.theatrical = event.target.src;
  let eventIterator = event.target.parentNode;
  while (eventIterator.previousElementSibling != null) {
    eventIterator.previousElementSibling.getElementsByTagName('img')[0].id = "";
    eventIterator = eventIterator.previousElementSibling;
  }
  eventIterator = event.target.parentNode;
  while (eventIterator.nextElementSibling != null) {
    eventIterator.nextElementSibling.getElementsByTagName('img')[0].id = "";
    eventIterator = eventIterator.nextElementSibling;
  }
  const fac = new FastAverageColor();
  fac.getColorAsync(event.target)
    .then((color) => this.$emit('theater-mode', color.rgba))
    .catch(function(e) {
      console.log(e);
    });
}

That should look good to you! Let’s look back at App.vue.

App.vue

<template>
  <div id="app">
    <PhotoGallery/>
  </div>
</template>

<script>
import PhotoGallery from './components/PhotoGallery.vue'

export default {
  name: 'App',
  components: {
    PhotoGallery
  },
  methods: {
    setBackground(rgba) {
      document.querySelector('body').style.backgroundColor = rgba;
    }
  }
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

We already discussed that listening to the theater-mode event looks like v-on:theater-mode. When we listen for a custom event, we can access any data that is passed with it via $event.

So, we write the following:

<template>
  <div id="app">
    <PhotoGallery v-on:theater-mode="setBackground($event)"/>
  </div>
</template>

Congratulations!

You just successfully emitted a custom event, listened to it from a parent component and accessed the value emitted from the event. Check your browser. Your app should be working just as we intended it to. Good job and bon voyage! 🚢

If you’re interested in furthering your knowledge around component communication in Vue, I recommend you give this article a read.

  Tweet It

🕵 Search Results

🔎 Searching...