I'm creating a Checkbox component in Vue 3 that is using v-model
to create a binding to an an array for a checkbox group. This group binding is an optional feature and in most cases the value of the checkbox will not populate an array and either be null
or some string. I think I'm getting closer to a solution, but it seems rather complex especially considering that you can bind 3 inputs pretty simply like so: https://medium.com/dataseries/vue-3-v-model-c896566aed64
Here is my implementation in App.vue
<template>
<checkbox modelValue="apple" v-model:group="group" />
<checkbox modelValue="orange" v-model:group="group" />
<checkbox modelValue="grape" v-model:group="group" />
<p>{{ group }}</p>
</template>
<script setup>
import { ref } from 'vue';
import Checkbox from './components/Checkbox.vue'
const group = ref(["apple"]); //"Apple" should start out checked, but it currently doesn't.
</script>
And here is Checkbox.vue
<template>
<input
type="checkbox"
:value="modelValue"
@change="updateValue($event)"
/>
<label for="checkbox">{{ modelValue }}</label>
</template>
<script setup>
import { defineEmits, ref, onMounted } from 'vue';
const checkbox = ref(null);
const props = defineProps({
modelValue: String,
group: Array
})
const emit = defineEmits(['update:modelValue'])
onMounted(()=>{
//Some code to determine initial state depending on what props.group looks like?
})
function updateValue(e) {
if (e.target.checked){
props.group.push(e.target.value);
}
if(!e.target.checked){
const toRemove = props.group.indexOf(e.target.value);
props.group.splice(toRemove, 1);
}
emit('update:modelValue', props.group)
}
defineExpose({ checkbox})
</script>
Currently, the array populates and depopulates based one what gets checked and unchecked, but "apple", which is initially set up to be checked, currently isn't. It bothers me that the implementation with inputs can be so simple, but I'm finding myself writing a whole bunch of code to keep the two-way binding between the checkbox group and the group
array. Before I go down the rabbit hole of writing a whole bunch of code in the onMounted
hook, I have to wonder if there is a simpler way of doing all of this? Even the code I've written for updateValue()
seems to be a bit too much.
CodePudding user response:
Not to say that the implementation pr path is wrong, but my inclination would be to use v-model
and watch
instead of event listeners. I believe this makes it simpler to handle handle things like duplicate values.
App.vue
<template>
<checkbox value="apple" v-model="group" />
<checkbox value="orange" v-model="group" />
<checkbox value="grape" v-model="group" />
<hr/><!-- duplicates -->
<checkbox value="apple" v-model="group" />
<checkbox value="grape" v-model="group" />
<p>{{ group }}</p>
</template>
<script setup>
import { ref } from 'vue';
import Checkbox from './Checkbox.vue'
const group = ref(["apple"]);
</script>
Checkbox.vue
<template>
<label>
<input
type="checkbox"
:value="value"
v-model="checked"
/>
{{ value }}
</label>
</template>
<script setup>
import { watch, ref } from 'vue';
const checkbox = ref(null);
const props = defineProps({
modelValue: Array,
value: String
});
const checked = ref(props.modelValue.includes(props.value));
watch(checked, (v) => {
if(!v && props.modelValue.includes(props.value)) {
const toRemove = props.modelValue.indexOf(props.value);
props.modelValue.splice(toRemove, 1);
} else if(v && !props.modelValue.includes(props.value)) {
props.modelValue.push(props.value);
}
})
watch(props.modelValue, (mv) => {
if(checked.value && !mv.includes(props.value)) {
checked.value = false;
} else if(!checked.value && mv.includes(props.value)) {
checked.value = true;
}
})
</script>
In the example I used a ref
to store internal true/false value. Then there are two watches that handle the discrepancy between checked
and modelValue
(mapped to the group array).