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.
There are 4 options I know of
this.$parent
(repetitively) on the child component to access the desired parent and emit the event. (see "Implement your own bubbling event plugin" below) provide
d by the parent and inject
ed in the children.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>
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
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://v2.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.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.