Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Vue.js v-for increment some variable in looped component

I'm trying to display a list of items on an event's agenda.

The event has a start_date end each item on the agenda has a duration in minutes, for example:

event:{
  start_date: '2017-03-01 14:00:00',
  agendas:[
      {id:1,duration:10},
      {id:2,duration:15},
      {id:3,duration:25},
      {id:4,duration:10}
  ]
}

Now, in my event component, I load agendas with a v-for:

<agenda v-for="(agenda,index) in event.agendas" 
        :key="agenda.id"
        :index="index" 
        :agenda="agenda">

In agenda component, I want to increment the time at which each item starts:

<div class="agenda">
  //adding minutes to start_date with momentJs library
  {{ moment(event.start_date).add(agenda.duration,'m') }} //this should increment, not add to the fixed event start date
</div>

Currently it only adds to the fixed event start_date... I would like to show the times 14:00 for event 1, 14:10 for event 2, 14:25 for event 3 and 14:50 for event 4.

How can I increment the value in a v-for directive in Vue.js 2.0?

like image 652
BassMHL Avatar asked Mar 02 '17 18:03

BassMHL


2 Answers

It looks like you already got an answer that works for you but I'll post this here in case anyone is looking for an alternate solution. The accepted answer might be good for an initial render of the agendas but will start to break if the agendas array is mutated or anything causes the list to re-render because the start time calculation is based on a stored value that gets incremented every iteration.

The code below adds a computed property (based on event.agendas) that returns a new array of of agenda objects, each with an added start property.

Vue.component('agenda', {
  template: '<div>Agenda {{ agenda.id }} — {{ agenda.duration }} min — {{ agenda.start }}</div>',
  props: {
    agenda: Object
  }
});

new Vue({
  el: "#root",
  data: {...}, 
  computed: {
    agendas_with_start() {
      const result = [];

      let start = moment(this.event.start_date);
      for(let agenda of this.event.agendas) {
        result.push({
          id: agenda.id,
          duration: agenda.duration,
          start: start.format('HH:mm')
        });
        start = start.add(agenda.duration, 'm');
      }
      return result;
    }
  }
});

Then, in the template, the agendas_with_start computed property is used in the v-for:

<div id="root">
  <h3>Event Start: {{ event.start_date }}</h3>
  <agenda v-for="agenda in agendas_with_start" 
          :key="agenda.id"
          :agenda="agenda"></agenda>
</div>

Here's a working codepen. The benefit of this approach is that if the underlying agendas array is mutated or re-ordered or the event start time changes or anything causes Vue to re-render the DOM, this computed property will be re-evaluated and the start times will be re-calculated correctly.

like image 50
Peter Avatar answered Sep 30 '22 04:09

Peter


Vue.js will actually let you bind prop values to methods on the parent's scope, so the easiest way will be to do something like this:

<agenda class="agenda" v-for="(agenda,index) in event.agendas"
    :key="agenda.id"
    :index="index"
    :agenda="agenda"
    :start-time="get_start_time(agenda.duration)">
</agenda>

And then the get_start_time() is a method within the parent's scope:

new Vue({
  el: '#app',
  data: {
    time_of_day: '',
    event:{
      // ...
    },
  },

  methods: {
    get_start_time(duration) {
      if (this.next_event_start === '') {
        this.next_event_start = moment(this.event.start_date);
      } 
      this.event_start = this.next_event_start.format('h:mm a');
      this.next_event_start.add(duration, 'minutes');
      return this.event_start;
    },
  },
});

I've made a quick CodePen as a basic example to show it in action, but you'll need to update the actual code to compensate for multiple days.

like image 25
K3TH3R Avatar answered Sep 30 '22 04:09

K3TH3R