Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Vuejs - bubbling custom events

Tags:

Is there a way to allow events to bubble up when using a component within a component?

My application is a dynamic menu. The dynamic menu is a component (dyn-menu) and it uses a local component (menu-item) for each of the <li> elements. Each <menu-item> has a click handler associated with it that emits a custom event (with an ID for menu item in the full implementation). But the application doesn't see the events issued by <menu-item> because they are not bubbled up.

Is there a way to allow the <menu-item> component, which is local to the <dyn-menu> component, emit the event and still allow vapp to see and handle the event?

I'm pretty new to Vuejs so I might be missing something obvious. And it's possible that I'm trying to solve this by using two components and that's not the best way to handle it. Is there is a better way to approach it?

Here's a jsfiddle. You have to remove the @dyn-menu-item-click='itemClick' line in the <dyn-menu> template to illustrate that the event doesn't bubble up if the component doesn't handle the event. If that line is removed then <dyn-menu> doesn't handle the event but vapp never sees the event either.

like image 380
bmacnaughton Avatar asked Feb 02 '17 03:02

bmacnaughton


2 Answers

There are 4 options I know of

  1. Re-emit events like you did
  2. Use this.$parent (repetitively) on the child component to access the desired parent and emit the event. (see "Implement your own bubbling event plugin" below)
  3. Use an event bus that is provided by the parent and injected in the children.
  4. Use a Vuex store and push events to an event queue in the child component. Somewhere else in the app, watch that reactive event queue for new elements or just bind it to something.

Implement your own bubbling event plugin

It's very simple. The plugin adds a new $bubble method that emits events that bubble to their parents. I considered publishing a plugin that does this, but it's so simple that the overhead is not worth it.

// Add this as a Vue plugin  Vue.use((Vue) => {    Vue.prototype.$bubble = function $bubble(eventName, ...args) {      // Emit the event on all parent components      let component = this;      do {        component.$emit(eventName, ...args);        component = component.$parent;      } while (component);    };  });    // Some nested components as an example    // note usage of "$bubble" instead of "$emit"  Vue.component('component-c', {    template: `      <button type="button" @click="$bubble('my-event', 'payload')">        Emit bubbling event      </button>`,  });    Vue.component('component-b', {    template: `<component-c @my-event="onMyEvent" />`,        methods: {      onMyEvent(...args) {        console.log('component-b listener: ', ...args);      },    },  });    Vue.component('component-a', {    template: `<component-b @my-event="onMyEvent" />`,        methods: {      onMyEvent(...args) {        console.log('component-a listener: ', ...args);      },    },  });    var vapp = new Vue({    el: '#app',      methods: {      onMyEvent(...args) {        console.log('root listener: ', ...args);      },    },  });
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>    <div id="app">    <component-a @my-event="onMyEvent" />  </div>

Event bus

The event bus looks like this:

Vue.component('dyn-menu', {   components: {     'menu-item': {       template: '<li @click="itemClick">{{item.text}}</li>',       props: ['item'],       inject: ['eventBus'], // <-- Inject in the child       methods: {         itemClick() {           // Emit the event on the event bus           this.eventBus.$emit('dyn-menu-item-click', ['menu-item dyn-menu-item-click']);         }       }     }   },    // ... });  var vapp = new Vue({   el: '#app',   data: {     // ...     eventBus: new Vue(),   },   provide() {     return {       // The parent component provides the event bus to its children       eventBus: this.eventBus,     };   },    created() {     // Listen to events on the event bus     this.eventBus.$on('dyn-menu-item-click', this.menuClick);   },   methods: {     menuClick(message) {}   } }) 

Working example: https://jsfiddle.net/7vwfx52b/

There are plenty of event bus plugins listed here: https://github.com/vuejs/awesome-vue#custom-events

like image 59
bernie Avatar answered Oct 12 '22 23:10

bernie


As of Vue 2.4, components can access their parent's listeners through the $listeners property. You can set a component to pass through its parent's listeners to particular children by adding an attribute v-on="$listeners" to the tags for those child elements. See the docs at https://vuejs.org/v2/api/#vm-listeners.

You can also forward specific events with an attribute like: @dyn-menu-item-click=$listeners['dyn-menu-item-click'].

It's still not true bubbling, but a less verbose way to re-emit events.

like image 38
Patrick Stephansen Avatar answered Oct 12 '22 22:10

Patrick Stephansen