6

I'm trying to build a custom checkbox component with options that are generated with a v-for loop from an array with options and values. How can I bind the v-model correctly to the checkbox component so that it's correctly updated?

The problem now is that the model only updates to the latest checkbox that is checked and does not give an array with all checked options.

Vue.component('ui-checkbox', {
	 props: {   
    label: {
      type: String,
      required: true,
    },
    index: {
      type: Number
    },
    inputValue: {
      type: String
    }
  },
  methods: {
    onChange(e) {
      this.$emit('input', e.target.value);
    },
  },
	template: `<div>
    <input 
      :id="index"
      type="checkbox"
      :value="inputValue"
      @change="onChange" />
    <label :for="index">
      {{ label }}
    </label>
  </div>`,
})

new Vue({
  el: "#app",
  data: {
    checkOptions: [
      {
        label: 'Option 1',
        value: 'value of option 1',
      },
      {
        label: 'Option 2',
        value: 'value of option 2',
      },
      {
        label: 'Option 3',
        value: 'value of option 3',
      },
      {
        label: 'Option 4',
        value: 'value of option 4',
      },
    ],
    myCheckBoxModel: []
  },
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
   <span>checked Checkboxes: {{ myCheckBoxModel }} </span>
   <ui-checkbox
     v-for="(option, index) in checkOptions"
     v-model="myCheckBoxModel"
     :key="index"
     :index="index"
     :input-value="option.value"
     :label="option.label" />    
</div>

0

2 Answers 2

8

When you do

this.$emit('input', e.target.value);

it works like

myCheckBoxModel = e.target.value

So it just assigns the value of the last checkbox you clicked to myCheckBoxModel. If you want to keep all checked items in myCheckBoxModel, you need to do the following:

  1. add value property to ui-checkbox component to have access to the current value of myCheckBoxModel. Value is default property name for this goal (see vue guide).
  2. in your onChange method copy the current value to the variable, because it's not good to mutate value property directly
  3. if your checkbox is checked, push the correspondent value to the array. If the checkbox is not checked, delete to correspondent value from the array
  4. emit input event with the resulting array as value

Vue.component('ui-checkbox', {
         props: {   
        label: {
          type: String,
          required: true,
        },
        index: {
          type: Number
        },
        inputValue: {
          type: String
        },
        value: {
          type: Array
        }
      },
      methods: {
        onChange(e) {
          let currentValue = [...this.value]
          if (e.target.checked) {
            currentValue.push(e.target.value) 
          } else {
            currentValue = currentValue.filter(item => item !== e.target.value)
          }
          this.$emit('input', currentValue);
        },
      },
        template: `<div>
        <input 
          :id="index"
          type="checkbox"
          :value="inputValue"
          @change="onChange" />
        <label :for="index">
          {{ label }}
        </label>
      </div>`,
    })

    new Vue({
      el: "#app",
      data: {
        checkOptions: [
          {
            label: 'Option 1',
            value: 'value of option 1',
          },
          {
            label: 'Option 2',
            value: 'value of option 2',
          },
          {
            label: 'Option 3',
            value: 'value of option 3',
          },
          {
            label: 'Option 4',
            value: 'value of option 4',
          },
        ],
       myCheckBoxModel: []
      }
    })
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
  <div id="app">
  checked Checkboxes:
       <span v-for="item in myCheckBoxModel"> {{ item }}; </span>
       <ui-checkbox
         v-for="(option, index) in checkOptions"
         v-model="myCheckBoxModel"
         :key="index"
         :index="index"
         :input-value="option.value"
         :label="option.label" />    
  </div>

I don't know if you need to set checkbox state programmatically, i.e. when you change myCheckBoxModel the state of checkboxes changes correspondently. If you do, you need to watch value property in your ui-checkbox component: set the state of the check box in dependance of if its value is in value array. Do the same also in created hook if you want to initialize the state of checkboxes by myChexkboxModel

Sign up to request clarification or add additional context in comments.

Comments

8

The solution presented by @Lana is just too complicated. The onChange method is not needed at all - what you want is to use build-in power of v-model. See below...

Vue.component('ui-checkbox', {
  props: {
    label: {
      type: String,
      required: true,
    },
    index: {
      type: Number
    },
    inputValue: {
      type: String
    },
    value: {
      type: Array
    }
  },
  computed: {
    model: {
      get() {
        return this.value
      },
      set(value) {
        this.$emit('input', value)
      }
    },
  },
  template: `<div>
        <input 
          :id="index"
          type="checkbox"
          :value="inputValue"
          v-model="model" />
        <label :for="index">
          {{ label }}
        </label>
      </div>`,
})

new Vue({
  el: "#app",
  data: {
    checkOptions: [{
        label: 'Option 1',
        value: 'value of option 1',
      },
      {
        label: 'Option 2',
        value: 'value of option 2',
      },
      {
        label: 'Option 3',
        value: 'value of option 3',
      },
      {
        label: 'Option 4',
        value: 'value of option 4',
      },
    ],
    myCheckBoxModel: []
  }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
  model: {{ myCheckBoxModel }}
  <ui-checkbox v-for="(option, index) in checkOptions" v-model="myCheckBoxModel" :key="index" :index="index" :input-value="option.value" :label="option.label" />
</div>

NOTE that using v-for index as a key works in this case BUT is not recommended in general, especially if set of checkboxes is dynamic and can change over the lifetime of the component that is rendering them.

3 Comments

hey @michal. I can't seem to get this to work with multiple checkboxes. It just seems to replace the selectedCheckbox array with whatever the last checkbox input value was even if its been unchecked. Do you know what I might be doing wrong?
@ToddPadwick Without the code it is impossible to tell what's wrong. Anyway my example works with multiple checkboxes just fine...
@ToddPadwick Sounds like it would be an issue with your array transformation(s).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.