Search code examples
javascriptarraysvuejs2v-for

v-for inside v-for displaying data in onclick event


hello everyone i have array of objects, and inside each object i have also array of objects.. i did the v-for inside the v-for to display data at first i wanted for each row to show the data of the first element of the each child of the parent array and onClick event, i wanted to change the data only in the specifique row.

infos: [{
    name: 'name1',
    infosName: [{
        place: 'place.1.1',
        surface: '100'
      },
      {
        place: 'place.1.2',
        surface: '200'
      }
    ]
  },
  {
    name: 'name2',
    infosName: [{
        place: 'place.2.1',
        surface: '300'
      },
      {
        place: 'place.2.2',
        surface: '400'
      }
    ]
  }
]

i created a method to display the data and got on parametres Two indexs this a jsfiddle to understand more the problem

Thank you

https://jsfiddle.net/f0ehwacm/2


Solution

  • There are several issues needing fixing, but you are on the right lines.

    Most importantly, you need to store not just one "myIndex" but a separate "myIndex" for each row

    That is the root cause of your problem.

    Let me rephrase your question?

    I believe you are hoping for four buttons. The top two buttons choose between two options.

    Completely separately, the bottom two buttons choose between two options.

    It would be easier for readers to understand your intention if you called the two top buttons "Question 1, Option A", and "Question 1, Option B". And then the bottom two "Question 2...". Then they would understand why when you click on one of the buttons, you want to affect the output of the table for that row only.

    enter image description here

    Avoid using generic terms like "index" and "i"

    These make it unnecessarily difficult for people to understand what you intend. Better to use a concrete noun, in this case "question" or "answer", and prefix it with "i" when you mean the index, such as "iQuestion" for the index of the question and "question" for the question itself.

    You seem to have a single function "getInfos" which does BOTH getting and setting of information

    This is a major problem. You should separate the two functions.

    When you click, you want to run a "set" function, that updates your index.

    When you are simply displaying, you can access a "get" function, which does not change anything.

    You need to store an index for each row

    In my terminology, you need to store the index of your answer to each question.

    So instead of this.myIndex starting at 0, you have it starting at [0,0]. Each of the two values can be updated separately, allowing the program to update the answer to one row (i.e. one question), while leaving the other row unchanged.

    I have renamed this variable to this.myAnswer to make it easier to understand.

    this.$set when writing to an array that you want Vue to react to

    I initially wrote the "setAnswer" function as follows:

    this.myAnswer[iQuestion]=iAnswer  
    

    However, I found that the on-screen display was not updating. This is a common problem in Vue, when you update not the main property listed in data(), but an array element of that property.

    This is because Vue is not tracking the updates of the array elements, only the array itself. So if you were to reassign the entire array, Vue would notice.

    The workaround is to tell Vue explicitly that you are updating something that needs to be reactive. Vue will then update it on screen.

    To do this, change your assignment from this format:

    this.array[index] = value
    

    To this

    this.$set(this.array, index, value)
    

    Vue provides this function this.$set, which executes your normal this.array[index] = value and tells Vue to do the screen update.

    How to cope with missing "infosName"

    In response to your question in the comments. You have a convenient place to solve this: your getAnswer() function.

    Change from this:

    getAnswer(iQuestion,iAnswer){
        return {
        'name' : this.infos[iQuestion].infosName[iAnswer].place,
        'surface' : this.infos[iQuestion].infosName[iAnswer].surface
      }
    

    to this:

    getAnswer(iQuestion,iAnswer){
        if (this.infos.length>iQuestion &&
            this.infos[iQuestion].infosName &&
            this.infos[iQuestion].infosName.length>iAnswer 
        ){
            return {
                'name' : this.infos[iQuestion].infosName[iAnswer].place,
                'surface' : this.infos[iQuestion].infosName[iAnswer].surface
            }
        else return {
        name : "",
        surface: ""
    

    } }

    Solution

    html:

    <div id="app">
        <div v-for="(question,iQuestion) in infos">
            <div class="row d-flex">
    <span style="margin-right:10px" v-for="(answer,iAnswer) in question.infosName" class="badge badge-primary" @click="setAnswer(iQuestion,iAnswer)"><i class="fa fa-eye" style="margin-right:10px;cursor: pointer"></i>{{ answer.place }}</span>        </div>
            <div class="row">
                 <p>Name : {{ getAnswer(iQuestion,myAnswer[iQuestion]).name }} </p>
                <p>Surface : {{ getAnswer(iQuestion,myAnswer[iQuestion]).surface }}</p>
            </div>
        </div>
    </div>
    

    JS:

    new Vue({
        el :'#app',
      data : function(){
        return {
            myAnswer : [0,0],
        infos : [
                {
                name : 'name1',
              infosName : [
                    {
                    place : 'Question 1, Option A',
                    surface : '100'
                  },
                  {
                    place : 'Question 2, Option B',
                    surface : '200'
                  }
              ]
            },
            {
                name : 'name2',
              infosName : [
                    {
                    place : 'Question 2, Option A',
                    surface : '300'
                  },
                  {
                    place : 'Question 2, Option B',
                    surface : '400'
                  }
              ]
            }
        ]
        }
      },
      methods:{
      setAnswer(iQuestion,iAnswer){
            this.$set(this.myAnswer,iQuestion,iAnswer)  
      },
      
        getAnswer(iQuestion,iAnswer){
            return {
            'name' : this.infos[iQuestion].infosName[iAnswer].place,
            'surface' : this.infos[iQuestion].infosName[iAnswer].surface
          }
        }
      }
    })