Reputation: 995
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 class="mt-6">
<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 class="mt-3">{{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 class="mt-1 relative">
<input
type="checkbox"
:id="id ?? null"
:name="name"
:value="value"
:checked="modelValue ?? false"
class="bg-gray-200 text-gold-500 rounded border-0 w-5 h-5 mr-2 focus:ring-2 focus:ring-gold-500"
@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 class="mt-6">
<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 class="mt-3">{{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.
Upvotes: 1
Views: 2773
Reputation: 995
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 class="mt-6">
<Checkbox v-for="box in ['EVO', 'Solist', 'SPL']" v-model="status" :value="box" :label="box" name="status" />
</div>
<div class="mt-3">{{status}}</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
let status = ref([]);
</script>
Checkbox component:
<template>
<div class="mt-1 relative">
<input
type="checkbox"
:id="id ?? null"
:name="name"
:value="value"
:checked="modelValue.includes(value)"
class="bg-gray-200 text-gold-500 rounded border-0 w-5 h-5 mr-2 focus:ring-2 focus:ring-gold-500"
@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)
}
Upvotes: 2
Reputation: 23480
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 class="mt-1 relative">
<input
type="checkbox"
:id="id ?? null"
:name="name"
:value="value"
:checked="modelValue ?? false"
class="bg-gray-200 text-gold-500 rounded border-0 w-5 h-5 mr-2 focus:ring-2 focus:ring-gold-500"
@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 class="mt-6" v-for="box in status">
<Checkbox v-model="box.status" :value="box.label" :label="box.label" name="status"></Checkbox>
</div>
<div class="mt-3">{{status}}</div>
</div>
</div>
Upvotes: 2