[Vuejs]-Component with multiple checkboxes and ONE v-model

9👍

Take a look what v-model on custom components does

<mycomponent v-model="data" />

is same as

<mycomponent 
  v-bind:value="data"
  v-on:input="data= $event" />

So if you do $emit("input", $event.target.value) inside of your component, whatever $event.target.value is, it replaces your model. But you need an "input" event so the v-model on your component works. value emitted just has to be in the same format as a model you are receiving.

On top of that whats the point of having a model and at the same time support entries.active ? It’s clear duplicity. Passed model should be only thing that decides whether checkbox is checked or not….

Do it like this:

Vue.component("checkbox-group", {
  props: {
    name: String,
    alignment: String,
    block: Boolean,
    entries: Array,
    errors: Array,
    value: Array
  },
  computed: {
    model: {
      get() { return this.value },
      set(newValue) { this.$emit('input', newValue)}
    }
  },
  template: `
  <div class="field">
    <p class="help is-danger" v-for="message in errors" :key="message">
      {message}
    </p>
    <div class="control" :class="'has-text-' + alignment">
      <template v-for="entry in entries">
        <label class="checkbox" :key="entry.value">
          <input
            type="checkbox"
            :name="name"
            :value="entry.value"
            v-model="model"
          />
          {{ entry.label }}
        </label>
        <br v-if="block" :key="'br_'+entry.value"/>
      </template>
    </div>
  </div>
`
});

Changes:

  1. added value prop – its set by Vue to a value inside v-model attribute of your component
  2. instead of binding :checked and @change on each input, we use v-model instead as it handles all the array slicing/pushing (model is an array)
  3. But because our model (value prop) is from prop, we cannot us it directly in v-model (child cannot update prop from parent). In this kind of situations the computed with setter are very useful.
    1. when the computed is read, we return value of value prop
    2. when the computed is set (by v-model used on input), we just $emit the value to the parent
    3. Parent will update its property used in v-model and our child component receives the new value via a value prop

Working example

Update

As this my old answer still receives upvotes I have decided to update it. Previous version was working but had one little quirk – component was not reactive to potential value prop changes from the parent . This new version is simply better – works in all cases and is less code…

Bonus – Vue 3 version

Vue.component("checkbox-group", {
  props: {
    name: String,
    alignment: String,
    block: Boolean,
    entries: Array,
    errors: Array,
    modelValue: Array
  },
  emits: ['update:modelValue'],
  computed: {
    model: {
      get() { return this.modelValue },
      set(newValue) { this.$emit('update:modelValue', newValue)}
    }
  },
  template: `
  <div class="field">
    <p class="help is-danger" v-for="message in errors" :key="message">
      {message}
    </p>
    <div class="control" :class="'has-text-' + alignment">
      <template v-for="entry in entries" :key="entry.value">
        <label class="checkbox">
          <input
            type="checkbox"
            :name="name"
            :value="entry.value"
            v-model="model"
          />
          {{ entry.label }}
        </label>
        <br v-if="block" />
      </template>
    </div>
  </div>
`
});

0👍

Repeat the checkboxes as necessary, using v-model with the same name, different values.

Such as this HTML:

<form>
  <input type="checkbox" id="support_1" value="1" v-model="support">
  <label for="support_1">Restart the app</label>

  <input type="checkbox" id="support_2" value="2" v-model="support">
  <label for="support_2">Log off and on</label>
</form>
<p>
{{ support }}
</p>

And the code:

data() {
  return {
    support: [],
  }
},

Leave a comment