Search code examples
vue.jsvuejs2radio-buttonv-for

Setting a value to true for v-for generated radio buttons


The rows of a table are generated using a v-for loop over an array of objects in graphicState. I am trying to create a column of radio buttons. When a radio button is checked, this should set graphicState[index].selected to true.

This post is interesting, but how can I use it set graphicState[index].selected to true?

<form>
  <div class="row">
    <div class="col-md-12 " align="center">
      <table class="table-striped" v-on:mouseleave="mouseleave()">
        <thead>
          <tr>
            <th></th>
            <th>Show</th>
            <th>Select</th>
            <th>Shape</th>
          </tr>
        </thead>
        <tbody>
          <tr v-for="(form, index) in graphicState" :key="index">
            <td @click="removeDate(index)"><i class="far fa-trash-alt"></i></td>
            <td>
              <input type="checkbox" v-model="form.show">
            </td>
            <td>
              <input type="radio" name="grp" id="grp" value="true" v-model="form.selected">
            </td>
            <td v-on:click="toggleShape(index)">
              {{ form.shape }}
            </td>
          </tr>
        </tbody>
      </table>
      <div v-for="(form, index) in graphicState " :key="index">
      </div>
    </div>
  </div>
</form>

Solution

  • The code you have already should set the graphicState[index].selected to true for the radio inputs, but the input values (and thus graphicState[index].selected through v-model) are never set to false, which is a problem if the user is allowed to change their mind to select a different input. This occurs because the radio input's change-event is only fired when the its checked property is set to a truthy value (upon selection).

    One solution is to add a change-event handler that clears the .selected value for the non-selected inputs.

    // template
    <tr v-for="(form, index) in graphicState">
      <input @change="onRadioChange(index)" ...>
    </tr>
    
    // script
    onRadioChange(selectedIndex) {
      this.graphicState
        .filter((x,i) => i !== selectedIndex) // get non-selected inputs
        .forEach(x => x.selected = false)
    }
    

    But there's still another problem if you're using HTMLFormElement's native submit. In the following template, when the v-model value is true, Vue sets the radio input's checked property to true, which tells the HTMLFormElement to use this particular input's value as the group value...

    <input type="radio"
           name="grp"
           v-model="form.selected"
           value="true">
    

    All the radio inputs have the same value, so the receiver of the form data won't be able to tell which input is selected. To address this, assign a unique value to each input based on the iterator item. For example, you could use ID:

    <input type="radio"
           name="grp"
           v-model="form.selected"
           value="form.id">
    

    new Vue({
      el: '#app',
      data() {
        return {
          graphicState: [
            {id: 'A', selected: false, show: false},
            {id: 'B', selected: false, show: false},
            {id: 'C', selected: false, show: false},
          ]
        }
      },
      methods: {
        mouseleave() {},
        removeDate() {},
        toggleShape() {},
        onRadioChange(selectedIndex) {
          this.graphicState
            .filter((x,i) => i !== selectedIndex)
            .forEach(x => x.selected = false)
        },
      }
    })
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">
    <script src="https://unpkg.com/vue@2.6.8/dist/vue.min.js"></script>
    
    <div id="app">
      <form method="post" action="//httpbin.org/post">
        <div class="row">
          <div class="col-md-12" align="center">
            <table class="table-striped" v-on:mouseleave="mouseleave()">
              <thead>
                <tr>
                  <th></th>
                  <th>Show</th>
                  <th>Select</th>
                  <th>Shape</th>
                </tr>
              </thead>
              <tbody>
                <tr v-for="(form, index) in graphicState" :key="form.id">
                  <td @click="removeDate(index)"><i class="far fa-trash-alt"></i></td>
                  <td>
                    <input type="checkbox" v-model="form.show">
                  </td>
                  <td>
                    <input type="radio" name="grp" :value="form.id" v-model="form.selected" @change="onRadioChange(index)">
                  </td>
                  <td v-on:click="toggleShape(index)">
                    {{ form.shape }}
                  </td>
                </tr>
              </tbody>
            </table>
            
            <pre>{{graphicState}}</pre>
          </div>
        </div>
        <button>Submit</button>
      </form>
    </div>