Home > Blockchain >  Nested v-for Loop in Vue Selecting Wrong Checkbox
Nested v-for Loop in Vue Selecting Wrong Checkbox

Time:07-19

I have a nested loop in a vue component. The first loop is to create four dropdown boxes, the second loop is to create three options within each dropdown box. I have attached the image to illustrate. They work correctly in that if I select the checkbox for any of the options within any dropdown box then the correct outcome occurs. The problem is that if I click the word next to the checkbox then always the corresponding option in the first dropdown box is selected, which is not acceptable. So, if I click the checkbox next to 3b then everything is fine; if I click the 'b' next to the checkbox (which is likely for a user to do), then 1b will be selected.

How can I make clicking the 'b' in this case select 3b instead of 1b?

template code

<b-list-group v-for="number in  numbers" v-bind="number">
    <b-button v-b-toggle=number >{{ number }} </b-button>
    <b-collapse :id="number">
        <b-card>
            <b-list-group>
                <b-list-group-item v-for="letter in letters" v-bind="letter" >
                    <label :for=letter>
                        <input :id="letter" type="checkbox" @change="toggleLayerVisibility({
                            'floodType': ft,
                            'risk': $event.target.id,
                            'display': $event.target.checked
                        })">
                        {{ letter }}
                    </label>
                </b-list-group-item>
            </b-list-group>
        </b-card>
    </b-collapse>
</b-list-group>

component data

 data () {
        return {
            letters: ["a", "b", "c"],
            numbers: ["1", "2", "3", "4"]
        }
}

screenshot

enter image description here

CodePudding user response:

It happens because the input id was the same for all inputs with the same letter, so 1b and 3b have the same id. Try to modify the id attribute, e.g. add a number to differentiate ids.

new Vue({
  el: '#app',
  data() {
    return {
      letters: ["a", "b", "c"],
      numbers: ["1", "2", "3", "4"]
    }
  },
  methods: {
    toggleLayerVisibility() {},
  }
});
<link type="text/css" rel="stylesheet" href="https://unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
<link type="text/css" rel="stylesheet" href="https://unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.14/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-vue/2.21.2/bootstrap-vue.min.js"></script>
<div id="app">
  <b-list-group v-for="number in  numbers" v-bind="number">
    <b-button v-b-toggle=number >{{ number }} </b-button>
    <b-collapse :id=number>
      <b-card>
        <b-list-group>
          <b-list-group-item v-for="letter in letters" v-bind="letter" >
            <label :for="letter   number">
                        <input :id="letter   number" type="checkbox" @change="toggleLayerVisibility({
                            'floodType': ft,
                            'risk': $event.target.id,
                            'display': $event.target.checked
                        })">
                        {{ letter }}
                    </label>
          </b-list-group-item>
        </b-list-group>
      </b-card>
    </b-collapse>
  </b-list-group>

</div>

CodePudding user response:

The problem, as pointed out by @TymoteuszLao, is using the same id for each checkbox.

One way to fix it is to give each checkbox a unique id, combining the parent's key with the checkbox's key.

Another way to fix it is to remove ids altogether because, by default, a <label> element controls its first <input> child:

const initialData = () => {
  const letters = ["a", "b", "c"];
  const numbers = ["1", "2", "3", "4"];
  const selection = Object.assign({}, 
    ...numbers.map(n => ({
      [n]: Object.assign({}, 
        ...letters.map(l => ({
          [l]: false 
        }))
      )
    }))
  )
  return { letters, numbers, selection }
}

new Vue({
  el: '#app',
  data: () => initialData()
})
.list-group-item label {
  display: block;
}
<link type="text/css" rel="stylesheet" href="https://unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
<link type="text/css" rel="stylesheet" href="https://unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.css" />
<script src="https://v2.vuejs.org/js/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-vue/2.21.2/bootstrap-vue.min.js"></script>
<div id="app">
  <div>
    <b-list-group v-for="number in  numbers" :key="number">
      <b-button v-b-toggle=number >{{ number }} </b-button>
      <b-collapse :id=number>
        <b-card>
          <b-list-group>
            <b-list-group-item v-for="letter in letters" :key="letter" >
              <label>
                <input type="checkbox" v-model="selection[number][letter]">
                  {{ letter }}
              </label>
            </b-list-group-item>
          </b-list-group>
        </b-card>
      </b-collapse>
    </b-list-group>
    <pre v-text="{ selection }" />
  </div>
</div>

Note: v-model (or the selected) are not necessary. I added them to give visibility over the value, to show it works.

Note: a separate problem with your code is using v-bind instead of :key. v-bind should be used with objects, to map the key-value pairs of the object onto the current element.
To use v-bind correctly, you could use:

   <b-list-group v-for="number in numbers" v-bind="{ key: number }">

It's the exact equivalent of

   <b-list-group v-for="number in numbers" :key="number">

Same goes for <b-list-group-item>.

Tip: console is your friend. Keep it open and pay attention to what Vue is telling you. When you don't make sense out of a warning, search for its text and, if you don't find anything relevant, ask another question here.

  • Related