Home > Software design >  Unique content in each component in v-for
Unique content in each component in v-for

Time:11-09

Program description: I'm making a simple timetable program with Vue.js. The page has a "calendar" (a table of 7 columns, where each column correspond to a specific date) and a button on the bottom of each calendar column. On clicking a button a modal should pop up with a specific date in its body-content. The date in the body-content of the modal should be the same as in date in the column title, where the button is placed. Dates can also be changed on click of a forward or backward button. When clicking forward all dates shift one day forward. In case of backward all dates shift one day backward.

Approach to the program: I have a parent component App.vue and a child component for the modal modal.vue. In the parent component as one of the attributes I have array dateArr which contains 7 different dates in the following format: ['9/11/21', '9/12/21', ..., '15/11/21']. On App.vue mount getDatesArray method is being called which fills dateArr attribute with dates. On clicking backward and forward currentOffset is being changed and getDatesArray method is being triggered. The first row of the table consists of dates, therefore I create it with v-for and iterate over each date in dateArr. The body of the table consists of 3 rows in each column. The last row in each column contains a unique button and a modal. In the last row I want to bind each component to a specific date, so I yet again iterate over each date in dateArr. The modal component has a slot in it, where I put unique date from dateArr. On click of the button the visibility of the modal changes with change method.

Problem: Everything works well, however, whatever button I click, the content (date) of the modal component stays the same: 15/11/21, which is the last element of the array. The purpose of the program is to have unique content (date) inside each modal component with the core attribute (dateArr) being dynamic. I've tried implementing different solutions to this problem, but none were of use. I will really appreciate any help!

App.vue:

<template>
  <table class = 'dataFrame'>
    <thead>
      <tr>
        <th v-for="item in dayArr" :key="item.id">
          {{item}}
        </th>
      </tr>
    </thead>
    <tbody>
      <tr v-for="index in [1,2,3]" :key="index">
        <td v-for="item_index in dayArr.length" :key="item_index">
          Dummy content
        </td>
      </tr>
      <tr>
        <td v-for="date in dayArr" :key="date.id">
          <button @click="change"> </button>
          <modal-component @changevisibility="change" v-if="modalToggled">
            <p>{{ date }}</p>
          </modal-component>
        </td>
      </tr>
    </tbody>
  </table>
  <div class="buttonSection">
      <button @click="currentOffset --" class="back" v-show="currentOffset >= -7">forward</button>
      <button @click="currentOffset   " class="forward" v-show="currentOffset <= 7">backward</button>
  </div>
</template>

<script>
import basecard from './components/card.vue'
import navbarComponent from './components/menu.vue'
import modalComponent from './components/modal.vue'

export default {
  data() {
            return {
                currentOffset: 0,
                modalToggled : false,
                dayArr: []
            }
        },
  methods: {
      change() {
        this.modalToggled = !this.modalToggled
      },
      getDatesArray() {
          let date = new Date();
          date.setDate(date.getDate()   this.currentOffset);
          let dayArr = [];
          for (let i = 0; i < 7; i  ) {      
            let customDateArray = new Intl.DateTimeFormat('en-GB', { day: 'numeric', month: 'short', year: 'numeric', hour: 'numeric', minute: 'numeric' }).formatToParts(date)   
            let dateParts = {}
            customDateArray.map(({type, value}) => {
              dateParts[type] = value
            })
            dayArr.push(`${dateParts.day}/${date.getMonth() 1}/${dateParts.year.slice(-2)}`)
            date.setDate(date.getDate()   1);
          }
          this.dayArr = dayArr
      }
  },
  mounted () {
    this.getDatesArray()
  },
  watch: {
    currentOffset () {
      this.getDatesArray()
    }
  },
  name: 'App',
  components: {
    modalComponent
  }
}
</script>

<style>
  #app {
    display: flexbox;
    font-family: Avenir, Helvetica, Arial, sans-serif;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    color: #2c3e50
  }

  .dataFrame {
    border: 2px solid;
    width: 100%;
    border-color: #2c3e50;
  }

  .buttonSection {
    margin-top: 1rem;
    width: 100%;
    max-height: 100%;
  }

  .back {
    float: left;
  }

  .forward {
    float: right;
  }
</style>

modal.vue:

<template>
    <div class="modal">
        <div class="modal-content">    
            <slot></slot>      
            <button @click="changeVisibilityFromComponent">
                    <span class="close">Close &times;</span>
            </button>
        </div>
    </div>
</template>

<script>
export default {
    name: 'modalComponent',
    methods: {
        changeVisibilityFromComponent () {
            this.$emit('changevisibility')
        },
    }
}
</script>

<style scoped>
.modal {
  position: fixed; 
  z-index: 1; 
  left: 0;
  top: 0;
  width: 100%; 
  height: 100%; 
  overflow: auto; 
  background-color: rgb(0,0,0);
  background-color: rgba(0,0,0,0.4); 
}

.modal-content {
  background-color: #fefefe;
  margin: 15% auto; 
  padding: 20px;
  border: 1px solid #888;
  width: 80%; 
}

.close {
  color: #aaa;
  float: right;
  font-size: 28px;
  font-weight: bold;
}

.close:hover,
.close:focus {
  color: black;
  text-decoration: none;
  cursor: pointer;
}
</style>

CodePudding user response:

The first thing I thought about is to pass an index to change function, thus calculating some value independent of the v-for loop:

<td v-for="(date, index) in dayArr" :key="date.id">
  <!-- pass index to change method -->
  <button @click="change(index)" :id="index"> </button>
  <modal-component @changevisibility="change(index)" v-if="modalToggled">
    <!-- to slot pass the new variable -->
    <!-- remember to add it to data() with some init value like null -->
    <p>{{ currentDate }}</p>
  </modal-component>
</td>

...

methods: {
    change(index) {
      // set currentDate value each time change method is being run
      this.currentDate = this.dayArr[index];
      this.modalToggled = !this.modalToggled
    },
    ...
}

The reason for the issue you’ve experienced, is that v-for renders a list of items and sets the variables in a same way the code below does:

let x = 0;
for (i = 0; i < 10; i  ) {
    x = i;
}
console.log(x); // this will always console out x = 9

In contrary, event handling (like @click) makes a directive that listens to DOM events and runs a method each time the event is triggered. That’s at least how I understand it…

  • Related