I have a checkbox form question component I made in Vue 2 and i’m migrating to Vue 3 so want to simplify it as much as possible and make sure i’ve taken advantage of all the new feature of Vue 3.
After a lot of trial and error i’ve managed to get it to work, but have I over complicated it?
The component should take a question using v-slot and answers in the ‘options’ array prop.
It should let the user select as many answers as applicable but if they select ‘none’ it should clear all other answers.
If they have ‘none’ selected then they select another answer it should clear ‘none’ so it isn’t checked anymore.
Parent component
<template>
<div>
<h2>title</h2>
<InputCheckboxGroup
name="question_one"
:required="checkIsRequired('question_one')"
:error="formRef.errors.get('question_one')"
v-model="formRef.question_one"
:options="getField('question_one')['options']"
@update:modelValue="handleAffectedByInput"
>
<template v-slot:question>
{{ getField("question_one").question }}
</template>
</InputCheckboxGroup>
</div>
</template>
<script setup>
import InputCheckboxGroup from "../Form/InputCheckboxGroup";
import Form from "../../../../Form";
import { isRequired } from "./formHelper.js";
import { ref, markRaw, defineExpose } from "vue";
const props = defineProps({
step: Object,
fields: Object,
formData: Object
})
let formRef = markRaw(ref(new Form({
question_one: []
}, props.formData)))
const getField = (fieldName) => {
return props.fields[fieldName];
}
const checkIsRequired = (fieldName) => {
var fieldData = getField(fieldName);
return isRequired(fieldData, formRef.value);
}
function handleAffectedByInput(values) {
if (values.length && (values[values.length - 1] === 'none')) {
formRef.question_one = [values[values.length - 1]];
return;
}
clearCheckboxValues(['none'], values, 'question_one');
}
function clearCheckboxValues(previousAnswers, values, formField) {
for (const answer of previousAnswers) {
if (values.includes(answer)) {
formRef.value[formField] = values.filter(value => value !== answer);
}
}
}
defineExpose({
formRef
})
</script>
Child/Checkbox questionn componet
<template>
<div class="form-group">
<fieldset :aria-describedby="name">
<legend>
<p v-if="$slots.question" class="input__question">
<slot name="question"></slot>
</p>
</legend>
<div
class="input_checkbox"
v-for="opt in options"
v-bind:key="opt.value"
>
<label v-if="opt.value == 'none'">
<input
type="checkbox"
value="none"
v-model="model"
@click="onCheckboxChange"
/>
<span>None</span>
</label>
<div v-else class="form-group form-check">
<input
type="checkbox"
class="form-check-input"
:value="opt.value"
@click="onCheckboxChange"
v-model="model"
/>
<label :for="opt.value" class="form-check-label">
{{ opt.label }}
</label>
</div>
</div>
</fieldset>
</div>
</template>
<script setup>
import { defineModel, defineEmits } from "vue";
const props = defineProps({
error: String,
name: String,
options: Array,
required: Boolean
});
const model = defineModel()
const emit = defineEmits(['update:modelValue'])
function optionIsChecked (value) {
return model.value.includes(value);
}
function onCheckboxChange($event) {
var previouslySelected = model.value || [];
var newValue = [];
if ($event.target.checked) {
newValue = [...previouslySelected, $event.target.value];
} else {
newValue = previouslySelected.filter(
(x) => x != $event.target.value
);
}
if ($event.target.value === 'none' || $event.target.value === 'involved_none_above') {
newValue = [$event.target.value];
}
model.value = newValue
emit("update:modelValue", newValue);
}
</script>