Uncheck radio button

2019-06-24 06:32发布

I'm trying to set up a simple Vue instance with a collection of radio buttons. The goal is that, if the user clicks on a radio button that is already checked, it will uncheck the respective radio button. But I couldn't do this using Vue yet. Here's my code so far:

HTML:

<div id="app">
    <div v-for="(val, key) in list">
        <input type="radio" name="radio" :value="val" v-model="selected" :id="val">
        <label :for="val" @click="uncheck( val )">{{ val }}</label>
    </div>
    <button @click="uncheckAll">Uncheck all</button>
</div>

JS:

var app = new Vue({
        el: '#app',
        data : {
            list: [ 'one', 'two', 'three' ],
            selected: 'two',
        },
        methods : {
            uncheck: function( val ){
                console.log( val, this.selected );
                if ( val == this.selected ){
                    this.selected = false;
                }
            },
            uncheckAll: function(){
                this.selected = false;
            }
        }
})

Seems like the uncheck method is called, but then the radio button triggers a change event and then updates the value of selected again. uncheckAll method works as expected, probably because it's not tied to the data using v-model.

Any tips or suggestions to make this work? Here's a pen I created for this example: https://codepen.io/diegoliv/pen/JrPBbG

3条回答
手持菜刀,她持情操
2楼-- · 2019-06-24 07:25

You can use the prevent modifier to prevent the default behavior of clicking the label:

<label :for="val" @click.prevent="uncheck( val )">{{ val }}</label>

Here's a working Codepen.

查看更多
Explosion°爆炸
3楼-- · 2019-06-24 07:30

The difficulty is that v-model is changing the value of selected before you can examine it to see what its previous value is. You need another variable.

The idea is: when you click an item, it checks whether it matches previouslySelected (rather than selected). If it matches, unselect. Then set previouslySelected to the value of selected.

The click handler should be on the input, not on the label; it is the function of the label to forward clicks to the input, anyway.

var app = new Vue({
  el: '#app',
  data: {
    list: ['one', 'two', 'three'],
    selected: 'two',
    previouslySelected: 'two'
  },
  methods: {
    uncheck: function(val) {
      if (val === this.previouslySelected) {
        this.selected = false;
      }
      this.previouslySelected = this.selected;
    },
    uncheckAll: function() {
      this.selected = false;
    }
  }
})
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="app">
  <div v-for="(val, key) in list">
    <input type="radio" name="radio" :value="val" v-model="selected" :id="val" @click="uncheck(val)">
    <label :for="val">{{ val }}</label>
  </div>
  <button @click="uncheckAll">Uncheck all</button>
</div>
Note: the below method does not work with Vue 2.5 or later because clicking an already-selected radio does not cause a set to happen. (thanks to Vlad T. in the comments)

A more in-the-data way of approaching it would be to have a computed based on your selected, and have its setter do the check for re-selecting. No click handler. Everything is handled in the normal course of setting the value.

var app = new Vue({
  el: '#app',
  data: {
    list: ['one', 'two', 'three'],
    d_selected: 'two'
  },
  computed: {
    selected: {
      get() {
        return this.d_selected;
      },
      set(v) {
        if (v === this.d_selected) {
          this.d_selected = false;
        } else {
          this.d_selected = v;
        }
      }
    }
  },
  methods: {
    uncheckAll: function() {
      this.d_selected = false;
    }
  }
})
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="app">
  <div v-for="(val, key) in list">
    <input type="radio" name="radio" :value="val" v-model="selected" :id="val">
    <label :for="val">{{ val }}</label>
  </div>
  <button @click="uncheckAll">Uncheck all</button>
</div>

查看更多
走好不送
4楼-- · 2019-06-24 07:30

Here goes a simple one without using v-model and just only takes one variable for storing the current selected value.

var app = new Vue({
  el: '#app',
  data: {
    list: ['one', 'two', 'three'],
    selected: 'two'
  },
  methods: {
    uncheck: function (val) {
      if (val === this.selected) {
        this.selected = false
      } else {
        this.selected = val
      }
    }
  }
})
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="app">
  <div v-for="(val, key) in list">
    <input type="radio" :checked="selected===val" :id="val" @click="uncheck(val)" :key="'input' + key">
    <label :for="val" :key="'label' + key">{{ val }}</label>
  </div>
</div>

查看更多
登录 后发表回答