Reputation: 1594
I'm building "base" components for my application, mostly for form management. So far I've made a custom input, textarea, select, and radio. However, all those components only have "one" value at a time.
For radios and checkboxes, my component builds multiple radios/checkboxes at once, all attached to the same v-model. Howevever, my checkbox component is not working as intended. Instead of storing "all the checked checkboxes" in an array in the v-model, it only registers the last one clicked :(.
How can I override the "change" event so that it updates the attached model accordingly, by adding/removing the element from the array?
Here's my code for the checkbox component:
/**
* @description Component used to display a group of checkboxes
* @author Jordan Kowal
* @date 2019-11-30
* @param name String, name attribute
* @param alignment String, defines item alignment and should be "centered", "left", or "right"
* @param block Boolean, whether items are displayed as "block" or "inline-block"
* @param entries Array, list of objects with the following keys: label, value, active
* @param errors Array, list of error messages
*/
Vue.component("checkbox-group", {
props: {
name: String,
alignment: String,
block: Boolean,
entries: Array,
errors: Array,
},
template:
"<div class='field'>\
<p class='help is-danger' v-for='message in errors'>\
@{message}\
</p>\
<div class='control' :class='\"has-text-\" + alignment'>\
<template v-for='entry in entries'>\
<label class='checkbox'>\
<input\
type='checkbox'\
:name='name'\
:checked='entry.active'\
:value='entry.value'\
@change='$emit(\"input\", $event.target.value)'\
/>\
@{entry.label}\
</label>\
<br v-if='block'>\
</template>\
</div>\
</div>",
});
Exemple of use:
<checkbox-group
name="id"
alignment="centered"
:block="true"
v-model="languageForm.fields.languages"
:entries="languageForm.entries.languages"
:errors="languageForm.errors.languages"
></checkbox-group>
Solution provided by Michal Levý is working perfectly
Upvotes: 0
Views: 7354
Reputation: 754
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: [],
}
},
Upvotes: 0
Reputation: 37763
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:
value
prop - its set by Vue to a value inside v-model
attribute of your component:checked
and @change
on each input
, we use v-model
instead as it handles all the array slicing/pushing (model is an array)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.
value
propv-model
used on input
), we just $emit
the value to the parentv-model
and our child component receives the new value via a value
propAs 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...
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>
`
});
Upvotes: 9