First shot on form validation.
This commit is contained in:
parent
096b792caa
commit
4ed749eee3
3 changed files with 151 additions and 0 deletions
|
@ -58,6 +58,7 @@ h2 {
|
|||
font-size: $h2-font-size;
|
||||
margin: $h2-margin;
|
||||
}
|
||||
|
||||
input {
|
||||
padding: $input-padding;
|
||||
border: none;
|
||||
|
|
45
frontend/src/components/form/ValidationForm.vue
Normal file
45
frontend/src/components/form/ValidationForm.vue
Normal file
|
@ -0,0 +1,45 @@
|
|||
<script setup lang="ts">
|
||||
import {type ComponentInternalInstance, ref, type Slot} from "vue";
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "submit"): void
|
||||
}>();
|
||||
|
||||
const validationComponents = ref<ComponentInternalInstance[]>([]);
|
||||
|
||||
defineExpose({
|
||||
registerValidationComponent(component: ComponentInternalInstance): void {
|
||||
validationComponents.value.push(component);
|
||||
}
|
||||
});
|
||||
|
||||
function validate(): boolean {
|
||||
let valid = true;
|
||||
for (const component of validationComponents.value) {
|
||||
if (!(component.exposed?.validate)) {
|
||||
throw new Error(`Component has no exposed validate() method: ${component.type.__name}`);
|
||||
}
|
||||
valid = component.exposed.validate() && valid;
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
function onSubmit() {
|
||||
const valid = validate();
|
||||
if (valid) {
|
||||
emit("submit");
|
||||
}
|
||||
// TODO: Else scroll to first error and focus input.
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form @submit.prevent="onSubmit">
|
||||
<slot/>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
</style>
|
105
frontend/src/components/form/ValidationFormInput.vue
Normal file
105
frontend/src/components/form/ValidationFormInput.vue
Normal file
|
@ -0,0 +1,105 @@
|
|||
<script setup lang="ts">
|
||||
import {getCurrentInstance, onMounted, ref} from "vue";
|
||||
import {type Constraint, forConstraint} from "@/shared/validation/validator";
|
||||
|
||||
interface Props {
|
||||
modelValue?: string;
|
||||
label: string;
|
||||
type?: string;
|
||||
placeholder: string;
|
||||
constraint: Constraint;
|
||||
validationError: string;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
const emit = defineEmits<{
|
||||
(e: "update:modelValue", value: string): void;
|
||||
}>();
|
||||
|
||||
const input = ref<HTMLInputElement>();
|
||||
const valid = ref(true);
|
||||
const validated = ref(false);
|
||||
|
||||
function registerValidationComponent() {
|
||||
const instance = getCurrentInstance();
|
||||
let parent = instance?.parent;
|
||||
while (parent) {
|
||||
if (parent.exposed?.registerValidationComponent) {
|
||||
parent.exposed.registerValidationComponent(instance);
|
||||
return;
|
||||
}
|
||||
parent = parent.parent;
|
||||
}
|
||||
throw new Error("Could not find matching ValidationForm for ValidationFormInpunt.");
|
||||
}
|
||||
|
||||
function onInput() {
|
||||
if (validated.value) {
|
||||
validate();
|
||||
}
|
||||
const element = input.value;
|
||||
if (!element) {
|
||||
console.warn("Could not get referenced input element.")
|
||||
return;
|
||||
}
|
||||
emit("update:modelValue", element.value);
|
||||
}
|
||||
|
||||
function validate(): boolean {
|
||||
const element = input.value;
|
||||
if (!element) {
|
||||
console.warn("Could not get referenced input element.")
|
||||
return false;
|
||||
}
|
||||
valid.value = forConstraint(props.constraint, false)(element.value);
|
||||
validated.value = true;
|
||||
return valid.value;
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
validate
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
registerValidationComponent();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<label>
|
||||
{{label}}:
|
||||
<input
|
||||
ref="input"
|
||||
:value="modelValue"
|
||||
@input="onInput"
|
||||
:type="type || 'text'"
|
||||
:placeholder="placeholder"
|
||||
/>
|
||||
</label>
|
||||
<div class="validation-error" v-if="!valid">
|
||||
{{validationError}}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "../../scss/variables";
|
||||
|
||||
label {
|
||||
display: block;
|
||||
font-weight: $label-font-weight;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
margin: 0.25em 0;
|
||||
}
|
||||
|
||||
.validation-error {
|
||||
color: $variant-color-danger;
|
||||
margin: 0.25em 0;
|
||||
}
|
||||
</style>
|
Loading…
Reference in a new issue