I've been trying to create a simple component with a styled checkbox and a corresponding label. The values (strings) of all selected checkboxes should be stored in an array. This works well with plain html checkboxes:
<template>
<div>
<div >
<div>
<input type="checkbox" value="EVO" v-model="status" /> <label for="EVO">EVO</label>
</div>
<div>
<input type="checkbox" value="Solist" v-model="status" /> <label for="Solist">Solist</label>
</div>
<div>
<input type="checkbox" value="SPL" v-model="status" /> <label for="SPL">SPL</label>
</div>
</div>
<div >{{status}}</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
let status = ref([]);
</script>
It results in the following, desired situation:
Now if I replace those checkboxes with my custom checkbox component, I can't get it to work. If I check a box, it's emitted value seems to replace the status
array instead of it being added to or removed from it, resulting in the following:
So all checkboxes are checked by default for some reason, when I click on one of them they all get unchecked and the status
value goes to false
and clicking any of the checkboxes again will check them all and make status
true
.
Now I get that returning whether the box is checked or not in the emit returns a true or false value, but I don't get how Vue does this with native checkboxes and how to implement this behaviour with my component.
Here's the code of my checkbox component:
<template>
<div >
<input
type="checkbox"
:id="id ?? null"
:name="name"
:value="value"
:checked="modelValue ?? false"
@input="updateValue"
/>
{{ label }}
</div>
</template>
<script setup>
const props = defineProps({
id: String,
label: String,
name: String,
value: String,
errors: Object,
modelValue: Boolean,
})
const emit = defineEmits(['update:modelValue'])
const updateValue = function(event) {
emit('update:modelValue', event.target.checked)
}
</script>
And the parent component only uses a different template:
<template>
<div>
<div >
<Checkbox v-model="status" value="EVO" label="EVO" name="status" />
<Checkbox v-model="status" value="Solist" label="Solist" name="status" />
<Checkbox v-model="status" value="SPL" label="SPL" name="status" />
</div>
<div >{{status}}</div>
</div>
</template>
I've tried to look at this answer from StevenSiebert, but it uses an object and I want to replicate the original Vue behaviour with native checkboxes.
I've also referred the official Vue docs on v-model
, but can't see why this would work different with native checkboxes than with components.
CodePudding user response:
Your v-model is the same for every checkbox, maybe like following snippet:
const { ref } = Vue
const app = Vue.createApp({
setup() {
const status = ref([{label: 'EVO', status: false}, {label: 'Solist', status: false}, {label: 'SPL', status: false}])
return {
status
}
},
})
app.component('Checkbox', {
template: `
<div >
<input
type="checkbox"
:id="id ?? null"
:name="name"
:value="value"
:checked="modelValue ?? false"
@input="updateValue"
/>
{{ label }}
</div>
`,
props:{
id: String,
label: String,
name: String,
value: String,
errors: Object,
modelValue: Boolean,
},
setup(props, {emit}) {
const updateValue = function(event) {
emit('update:modelValue', event.target.checked)
}
return {
updateValue
}
}
})
app.mount('#demo')
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.18/tailwind.min.css" integrity="sha512-JfKMGsgDXi8aKUrNctVLIZO1k1iMC80jsnMBLHIJk8104g/8WTaoYFNXWxFGV859NY6CMshjktRFklrcWJmt3g==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
<div id="demo">
<div>
<div v-for="box in status">
<Checkbox v-model="box.status" :value="box.label" :label="box.label" name="status"></Checkbox>
</div>
<div >{{status}}</div>
</div>
</div>
CodePudding user response:
I can pass an array as the v-model
to the Checkbox
component and mark is as checked if the value
is within that array. When the checkbox gets toggles, I add or remove the value to/from the array depending if it's already in there.
Parent component:
<template>
<div>
<div >
<Checkbox v-for="box in ['EVO', 'Solist', 'SPL']" v-model="status" :value="box" :label="box" name="status" />
</div>
<div >{{status}}</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
let status = ref([]);
</script>
Checkbox component:
<template>
<div >
<input
type="checkbox"
:id="id ?? null"
:name="name"
:value="value"
:checked="modelValue.includes(value)"
@input="updateValue"
/>
{{ label }}
</div>
</template>
<script setup>
const props = defineProps({
id: String,
label: String,
name: String,
value: String,
errors: Object,
modelValue: Boolean,
})
const emit = defineEmits(['update:modelValue'])
const updateValue = function(event) {
const arr = props.modelValue;
if(arr.includes(props.value)) { // Remove if present
arr.splice(arr.indexOf(props.value), 1)
}
else { // Add if not present
arr.push(props.value)
}
emit('update:modelValue', arr)
}
</script>
Or to accomodate for booleans as well (like native checkboxes):
<template>
<!-- ... -->
<input
type="checkbox"
:value="value"
:checked="isChecked"
@input="updateValue"
<!-- ... -->
/>
<!-- ... -->
</template>
<script setup>
// ...
const isChecked = computed(() => {
if(props.modelValue instanceof Array) {
return props.modelValue.includes(props.value)
}
return props.modelValue;
})
const updateValue = function(event) {
let model = props.modelValue;
if(model instanceof Array) {
if(isChecked()) {
model.splice(model.indexOf(props.value), 1)
}
else {
model.push(props.value)
}
}
else {
model = !model
}
emit('update:modelValue', model)
}