0
votes

I have a series of components that go several levels deep. Each component has its own piece of data that it loads over AJAX and uses to render each instance of child components. The days parent template for example:

<template>
    <accordion :one-at-atime="true" type="info">
        <panel :is-open="index === 0" type="primary" :header="'Day ' + day.day" v-for="(day, index) in days" :key="day.id">
            <br/>
            <div class="panel panel-success">
                <div class="panel-heading">
                    <h3 class="panel-title">Cycles</h3>
                </div>
                <div class="panel-body">
                        <cycles
                            :day="day"
                        >
                        </cycles>
                </div>
            </div>
        </panel>
    </accordion>
</template>

The cycles child template for example:

<template>
    <accordion :one-at-atime="true" type="info">
        <panel :is-open="index === 0" type="primary" :header="'Week ' + cycle.week + ': ' + cycle.name" v-for="(cycle, index) in cycles" :key="cycle.id">
            <form v-on:submit.prevent="update">
                ....misc input fields here...
<button type="button" class="btn btn-warning" v-if="cycle.id" v-on:click="destroy">Delete</button>
            </form>
        </panel>
    </accordion>
</template>

<script>
    export default {
        props: [
            'day'
        ],
        data() {
            return {
                cycles: []
            }
        },
        beforeMount: function () {
            var self = this;

            if (this.cycles.length === 0) {
                axios.get('/plans/days/' + this.day.id + '/cycles')
                    .then(function (response) {
                        self.cycles = response.data;
                    })
                    .catch(function (error) {
                        console.log(error);
                    });
            }
        },
        methods: {
            destroy: function (event) {
                var self = this;

                axios.delete('/plans/cycles/' + event.target.elements.id.value)
                    .then(function (response) {
                        self.cycles.filter(function (model) {
                            return model.id !== response.data.model.id;
                        });
                    })
                    .catch(function (error) {
                        console.log(error);
                    });
            }
        }
    }
</script>

Each cycles component then renders another component in a v-for loop, which renders another type of component, and so on and so forth. What is created is a tree like structure of components.

When I need to send a generic request to the server that then updates the data in the component it is called from, I don't want to have to duplicate that request method in every component. I'd rather just have one instance of the method on the root Vue instance.

For example, this would be preferred:

const app = new Vue({
    el: '#app',
    store,
    created: function () {
        this.$on('destroy', function (event, type, model, model_id, parent_id) {
            this.destroy(event, type, model, model_id, parent_id);
        })
    },
    methods: {
        destroy: function (event, type, model, model_id, parent_id) {
            var self = this;

            axios.delete('/plans/' + type + '/' + model_id)
                .then(function (response) {
                    model = model.filter(function (model) {
                        return model.id !== response.data.model.id;
                    });

                    this.$emit('modified-' + type + '-' + parent_id, model);
                })
                .catch(function (error) {
                    console.log(error);
                });
        }
    }
});

Then in the cycles.vue delete button call this on click:

<button type="button" class="btn btn-warning" v-if="cycle.id" v-on:click="$root.$emit('destroy', event, 'cycles', cycles, cycle.id, day.id)">Delete</button>

And add this to cycles.vue events:

    created: function () {
        this.$on('modified-cycles-' + this.day.id, function (cycles) {
            this.cycles = cycles;
        })
    },

However, that doesn't work because the child element never gets the emitted 'modified-' + type + '-' + parent_id event from root.

I also tried this.$children.$emit('modified-' + type + '-' + parent_id, model); but that didn't work either.

What's the Vue 2.5.16 way to do this? And is there a better design pattern than I am currently using?

1
Have you looked at Vuex? vuex.vuejs.orgTomas Buteler
I'm using Vuex in this project, but I don't know how to get it to work in this use case.eComEvo
Do you want to include this in the payload of the emit?Roy J
@RoyJ what would be the advantage of that?eComEvo
It would make the component that emitted the event available to have its data updated by the root instance.Roy J

1 Answers

1
votes

Each Vue instance (root and children) is an independent event hub.

You $emit events on an instance and you can subscribe to event notifications with $on on the same instance.

You can use this.$root.$on() and this.$root.$emit() to use the root instance as an event bus and achieve your purpose.

However, it's not very clear to me what separation of concerns you are trying to get, so I'm not ready to give better advice.