KahunaCoder
KahunaCoder

Reputation: 615

I'm trying to implement VeeValidate to check if at least one checkbox is checked

I found a jsfiddle example that I forked and then edited. I don't understand what's going on or how to fix it. In my example I'm using checkboxes with values but when I click a checkbox the value is changed to true or false depending on if the checkbox is clicked.

const Checkboxes = {
	template: '#checkboxTmpl',
  data() {
  	return {
    	text: '',
    	options: [
      	{
        	name: 'Web',
        	slug: 'web'
        },
        {
        	name: 'iOS',
        	slug: 'ios'
        },
        {
        	name: 'Android',
        	slug: 'android'
        }
      ]
    };
  },
  created() {
  	this.$validator.extend('oneChecked', {
        getMessage: field => 'At least one ' + field + ' needs to be checked.',
        validate: (value, [testProp]) => {
          const options = this.options;
          // console.log('questions', value, testProp, options.some((option) => option[testProp]));
          return value || options.some((option) => option[testProp]);
        }
      });
  },
  methods: {
  	validateBeforeSubmit(e) {
      this.$validator.validateAll(); // why is oneChecked not validated here? --> manually trigger validate below
      this.options.forEach((option) => {
      	this.$validator.validate('platforms', option.slug, ['checked'])
      });
      
      console.log('validator', this.errors);
      if (!this.errors.any()) {
          alert('succesfully submitted!');
      } 
    }
  }
};

Vue.use(VeeValidate);

const app = new Vue({
	el: '#app',
  render: (h) => h(Checkboxes)
})
<script src="https://cdn.jsdelivr.net/vee-validate/2.0.0-beta.18/vee-validate.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.8/vue.js"></script>
<div id="app">

</div>

<script id="checkboxTmpl" type="text/template">
	<form @submit.prevent="validateBeforeSubmit">
  
 		<label v-for="(option, index) in options">
    	<input type="checkbox" 
      	v-model="option.slug"
        name="platform"
        v-validate.initial="option.slug"
        data-vv-rules="oneChecked:checked" 
        data-vv-as="platform"/> {{option.name}}
    </label>

    <p v-show="errors.has('platform')">{{ errors.first('platform') }}</p>
    
    <pre>{{options}}</pre>
    
    <button type="submit">Submit</button>
  </form>
</script>

I don't understand why all of the checkboxes are checked and unchecking one of them returns a validation error even though two are still checked. I like that errors are shown before the form is submitted but unchecking all and then submitting doesn't trigger the validation error.

I'm using VeeValidate because that is what the example uses but any other solution would be fine. I don't want to use jQuery in my vue.js application.

I would really like to understand what is going on.

Upvotes: 4

Views: 8577

Answers (1)

Thomas Lombart
Thomas Lombart

Reputation: 433

There was two main problems going on :

  1. Using v-model on the wrong key. In fact, each time the checkbox was checked or unchecked, it will emit an input event that will modify the original slug of the option (in your data). Instead, you need to add a checked field in your option. Then in your template add the :checked attribute and modify your v-model to be :option.checked.
  2. As the docs of VeeValidate say, you can just use the required rule to make sure a checkbox has to be checked to submit your form. Here is the link towards the docs. Therefore, you don't need your created block.

Additionally, the validateAll function returns a promise containing the result of the validation. So no need to use this.errors.any() too.

Also, I upgraded the VeeValidate library to the latest as you used a beta.

Here is the working code :

const Checkboxes = {
  template: '#checkboxTmpl',
  data() {
    return {
      text: '',
      options: [{
          name: 'Web',
          slug: 'web',
          checked: false
        },
        {
          name: 'iOS',
          slug: 'ios',
          checked: true
        },
        {
          name: 'Android',
          slug: 'android',
          checked: true
        }
      ]
    };
  },
  methods: {
    validateBeforeSubmit(e) {
      this.$validator.validateAll().then(value => {
        if (value) {
          alert('successfully submitted')
        }
      })
    }
  }
};

Vue.use(VeeValidate);

const app = new Vue({
  el: '#app',
  render: (h) => h(Checkboxes)
})
<div id="app"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.8/vue.js"></script>
<script src="https://unpkg.com/vee-validate@latest"></script>

<script id="checkboxTmpl" type="text/template">
  <form @submit.prevent="validateBeforeSubmit">

    <label v-for="(option, index) in options">
    	<input type="checkbox"
      	:checked="option.checked"
        v-model="option.checked"
        name="platform"
        v-validate="'required'"/> {{option.name}}
    </label>

    <p v-show="errors.has('platform')">{{ errors.first('platform') }}</p>

    <pre>{{options}}</pre>

    <button type="submit">Submit</button>
  </form>
</script>

Hope that helps!

Upvotes: 3

Related Questions