ofey
ofey

Reputation: 3327

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>

Upvotes: 1

Views: 664

Answers (2)

tony19
tony19

Reputation: 138226

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/[email protected]/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>

Upvotes: 1

ittus
ittus

Reputation: 22393

You can use @change event handler:

<input type="radio" name="grp" id="grp" value="true" v-model="form.selected" @change="onChange($event, index)">

then handle in method:

methods: {
  onChange (event, index) {
    this.graphicState[index].selected = event.target.value
    this.graphicState = JSON.parse(JSON.stringify(this.graphicState))
  }
}

Upvotes: 2

Related Questions