Home > Mobile >  Vue 2: global event listener receives only once
Vue 2: global event listener receives only once

Time:10-05

To sumarize what I'm trying to achieve, I'm creating a calendar, where each day is a component instance, and inside each day there can be zero to multiple instances of a smaller component, named Task, kind of like this.

<Root>
  <CalendarDay>
    <Task></Task>
    <Task></Task>
    ...
  </CalendarDay>
  <CalendarDay></CalendarDay>
  ...
</Root>

The number of smaller components to draw is dependent of a data array placed on the root component, which is expected to change multiple times via API calls.

The flow I had in mind is, first draw the CalendarDay components, then make an API call to obtain the Tasks that must be drawn on all days drawn, and finally fire a global event so that each CalendarDay retrieves the Tasks assigned to it. To achieve it, I added an eventBus to Vue following the steps of an answer of another question here, which I can't find, but basically is setting an extra property on the Vue prototype.

Vue.prototype.$eventHub = new Vue(); // Global event bus

Everything is set up, and on the first execution the API retrieves the Tasks, a global event is fired, and all CalendarDay components retrieve and draw the Tasks; but when a second global event is fired, the listeners either don't receive anything or they are not responding.

Here are the root and components

// ROOT Component
var app = new Vue({
    el: '#appVue',
    data: function() {
        return {
          ...
          tasks: [],
          calendar_days: []   // Each element in this array is an object with 2 properties: numday and weekday
          ...
        }
    },
    methods: {
        db_get_tasks_month: function(){
            REMOTE_WS.get(`<API route>`)
                .then(response => {
                    this.tasks= response.data.tasks;
                    
                    this.$eventHub.$emit('sort-tasks-in-days');
                })
                .catch(err => {
                    console.error('err');
                });
        },
        ...
     }
})

// CalendarDay Component
Vue.component('calendar-day', {
    props: {'day': Object}, //{numday, weekday}
    data: function(){
        return {
            tasks: []
        };
    },
    methods: {
        sortTasksInDays: function(){
            this.tasks= this.$root.$data.tasks.filter(t => {
                return t.hasOwnProperty("data_ts") && new Date(t.data_ts).getDate() === this.day.numday;
            });
        }
    },
    created: function(){ this.$eventHub.$on('sort-tasks-in-days', this.sortTasksInDays) },
    beforeDestroy: function(){ this.$eventHub.$off('sort-tasks-in-days'); }
});

Could someone point out what am I doing wrong?

Thanks,

EDIT: To add a bit more info, I checked if the global event is even firing, and using the Vue plugin on Chrome I can confirm that it is indeed firing, so what I'm missing must be on the listeners. EDIT 2: Adding the property in ROOT where the calendar days are stored to display on a v-for

SOLVED: as Niels pointed out, passing the Tasks array as a prop was a solution. For future reference, the changes made to make it work are as follows:

// ROOT Component: The calendar_days data in root is this
data: function() {
        return {
          ...
          tasks: [],
          calendar_days: []   // Each element in this array is an object with 3 properties: numday, weekday and tasks
          ...
        }
    },
methods: {
    // The method db_get_tasks_month no longer does the global event emit
    // The tasks are passed as a prop to each component

}

// CalendarDay Component
Vue.component('calendar-day', {
    props: {
        numday: { type: Number, default: 0},
        weekday: { type: Number, default: 0},
        tasks: { type: Array, default: []}
    }
});

CodePudding user response:

First of all, I was wondering why you are using a global event bus for this. This issue seems rather trivial and can be solved using component properties. This would create a more readable structure.

As it appears, every calendar day needs an array of Tasks. This can be defined in the 'props'-property of your CalendarDay. Your CalendarDay holds too much inforamtion now. It is using root-information whch is later on filtered out. There's plenty of ways to get the data in the format you need but I think mapping whenever you fetch the data is the easiest. You map the response.data to the format:

days: {
  '1': [],
  '2': ['task A', 'Task B'],
  ...
}

Then it is a matter of looping over this object in your root-template and passing the tasks along as props (these are reactive).

<calendar-day v-for="(tasks, day) in days" :key="day" :tasks="tasks" :day-number="day"></calendar-day>
  • Related