16

I do have three components.

Vue.js dev view

I don't have any influence on what the Datatable component does cause I have it from npm.

Now I want to send an event from EditButton to my Zonelist.

Zonelist component:

<template>
<datatable :columns="table_columns" :data="table_rows" filterable paginate v-on:remove="removeItem"></datatable>
</template>
<script>
import datatable from 'vuejs-datatable';
import moment from 'moment';
export default {
    data() {
        return {
            table_columns: [
                {label: "Zone", component: 'ZoneLink'},
                {label: "Last updated", callback (row) {
                    let locale = $('html').closest('[lang]').attr('lang') || 'en';
                    moment.locale(locale);
                    return moment(row.last_updated).format('D. MMM YYYY');
                }},
                {label: '', component: 'EditButton'}
            ],
            table_rows: [
                {
                    "name": "xyz.de",
                    "last_updated": "2017-10-21 17:29:50"
                }
            ],
            form: {
                name: '',
                errors: []
            }
        };
    },
    components: {
        datatable
    },
    methods: {
            removeItem (item) {
                this.table_rows.forEach((value, index, array) => {
                    if (value.name === item) {
                        Vue.delete(array, index);
                    }
                });
            }
      }
}
</script>

Now my EditButton component $emit()'s the remove event with a parameter.

But nothing happens. So I think vue is not able to locate the listener.

(I'm using method shorthands from ES6 here)

How could I do this properly without mutating Zonelist's state via this.$parent.$parent from the EditButton?

Bert
  • 80,741
  • 17
  • 199
  • 164
jankal
  • 1,090
  • 1
  • 11
  • 28

3 Answers3

17

Non parent-child communication in Vue is typically handled via either an event bus, or a state management system.

In this case, unless your application is more complex, the event bus is probably all you need. Since you are using single file components, you may need to declare the bus on the window, probably in your main script.

window.bus = new Vue()

Then in your EditButton, you can emit the event

bus.$emit('some-event', someData)

And in your ZoneList you can listen for it.

bus.$on('some-event', someData => this.doSomething(someData))
tony19
  • 125,647
  • 18
  • 229
  • 307
Bert
  • 80,741
  • 17
  • 199
  • 164
  • 2
    This doesn't answer the question of how to propagate an event through a component structure. – code4jhon Dec 26 '19 at 14:56
  • 1
    @Ryuk There's the question in the title, and what the OP actually wants, which is how to communicate with two non directly related components. So I suppose if you only read the title and wanted specifically a means to catch and re-emit all the way up the parent chain, you would be correct; this answer doesn't show how to do that. – Bert Dec 26 '19 at 15:37
  • 1
    Yeah, imo the title should be updated. You do answer the specifics of the OP. – code4jhon Dec 27 '19 at 18:02
10

Another option is to ask DataTable to pass any and all events up by adding a v:on="$listeners" attribute to it.

See https://stackoverflow.com/a/61329264/578318 for a more detailed explanation.

Edit:

A much safer option would be to simply listen to the event in the parent class and pass it on...

    <ancestor @message="console.log($event)"> <!-- cute trick see * -->
    ...
    <parent @message="$emit('message', $event)"> <!-- passes it on -->
    ...
    <child @click="$emit('Hello World')"> <!-- creates the event -->

* computed: {'console' : () => console}

sparkyspider
  • 13,195
  • 10
  • 89
  • 133
2

By default, if a Vue component only has one node in it (i.e. no child nodes), then all events that you attach to it from the parent will fire just like if it were a regular HTML element. But what if you have a child node that has children? In that case, you want to use v-on="$listeners"

Example:

<template>
  <div v-on="$listeners">
    <button>Hello world</button>
  </div>
</template>

Docs: https://v2.vuejs.org/v2/guide/components-custom-events.html#Binding-Native-Events-to-Components

tony19
  • 125,647
  • 18
  • 229
  • 307
hputzek
  • 21
  • 2