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 ×</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…