4

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.

KahunaCoder
  • 615
  • 7
  • 14

1 Answers1

3

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!

Thomas Lombart
  • 423
  • 2
  • 11
  • Thanks! This simplified version works but in my actual app I'm passing an object in props to the component to populate the form in edit mode. If I remove the v-model attribute the validation works but the checkboxes aren't populated. I need value and v-model for the fields to populate. – KahunaCoder Sep 11 '18 at 17:13
  • Ok. I modified the original post. Is it what you expect ? – Thomas Lombart Sep 11 '18 at 17:40
  • Brilliant! Thanks, I was able to get it to work using a v-if v-else where checked is either present or not depending on the object passed into props. – KahunaCoder Sep 11 '18 at 18:01