1
0
mirror of synced 2025-02-16 20:53:13 +03:00
vue-formulario/src/FormularioForm.vue
Zaytsev Kirill d39ca17e45 feat!: Added property "unregisterBehavior" to FormularioField to control value unset behavior on field component removal
Defaults to "none" which means value will not be unset and path will not be remove
2021-09-30 12:33:54 +03:00

215 lines
6.2 KiB
Vue

<template>
<form @submit.prevent="onSubmit">
<slot :errors="formErrorsComputed" />
</form>
</template>
<script lang="ts">
import Vue from 'vue'
import {
Component,
Model,
Prop,
Provide,
Watch,
} from 'vue-property-decorator'
import {
id,
clone,
deepEquals,
get,
has,
merge,
set,
unset,
} from '@/utils'
import { FormularioField } from '@/types'
import { Violation } from '@/validation/validator'
import { UNREGISTER_BEHAVIOR } from '@/enum'
const update = (state: Record<string, unknown>, path: string, value: unknown): Record<string, unknown> => {
if (value === undefined) {
return unset(state, path) as Record<string, unknown>
}
return set(state, path, value) as Record<string, unknown>
}
@Component({ name: 'FormularioForm' })
export default class FormularioForm extends Vue {
@Model('input', { default: () => ({}) })
public readonly state!: Record<string, unknown>
@Prop({ default: () => id('formulario-form') })
public readonly id!: string
// Describes validation errors of whole form
@Prop({ default: () => ({}) }) readonly fieldsErrors!: Record<string, string[]>
// Only used on FormularioForm default slot
@Prop({ default: () => ([]) }) readonly formErrors!: string[]
private proxy: Record<string, unknown> = {}
private registry: Map<string, FormularioField> = new Map()
// Local error messages are temporal, they wiped each resetValidation call
private localFieldsErrors: Record<string, string[]> = {}
private localFormErrors: string[] = []
private get fieldsErrorsComputed (): Record<string, string[]> {
return merge(this.fieldsErrors || {}, this.localFieldsErrors)
}
private get formErrorsComputed (): string[] {
return [...this.formErrors, ...this.localFormErrors]
}
@Provide('__FormularioForm_register')
private register (path: string, field: FormularioField): void {
if (!this.registry.has(path)) {
this.registry.set(path, field)
}
const value = get(this.proxy, path)
if (!field.hasModel) {
if (value !== undefined) {
field.proxy = value
} else {
this.setFieldValue(path, null)
this.emitInput()
}
} else if (!deepEquals(field.proxy, value)) {
this.setFieldValue(path, field.proxy)
this.emitInput()
}
if (has(this.fieldsErrorsComputed, path)) {
field.setErrors(this.fieldsErrorsComputed[path])
}
}
@Provide('__FormularioForm_unregister')
private unregister (path: string, behavior: string): void {
if (this.registry.has(path)) {
this.registry.delete(path)
if (behavior === UNREGISTER_BEHAVIOR.UNSET) {
this.proxy = unset(this.proxy, path) as Record<string, unknown>
this.emitInput()
}
}
}
@Provide('__FormularioForm_getState')
private getState (): Record<string, unknown> {
return this.proxy
}
@Provide('__FormularioForm_set')
private setFieldValue (path: string, value: unknown): void {
this.proxy = update(this.proxy, path, value)
}
@Provide('__FormularioForm_emitInput')
private emitInput (): void {
this.$emit('input', clone(this.proxy))
}
@Provide('__FormularioForm_emitValidation')
private emitValidation (path: string, violations: Violation[]): void {
this.$emit('validation', { path, violations })
}
@Watch('state', { deep: true })
private onStateChange (newState: Record<string, unknown>): void {
const newProxy = clone(newState)
const oldProxy = this.proxy
let proxyHasChanges = false
this.registry.forEach((field, path) => {
const newValue = get(newState, path, null)
const oldValue = get(oldProxy, path, null)
field.proxy = newValue
if (!deepEquals(newValue, oldValue)) {
field.$emit('input', newValue)
update(newProxy, path, newValue)
proxyHasChanges = true
}
})
this.proxy = newProxy
if (proxyHasChanges) {
this.emitInput()
}
}
@Watch('fieldsErrorsComputed', { deep: true, immediate: true })
private onFieldsErrorsChange (fieldsErrors: Record<string, string[]>): void {
this.registry.forEach((field, path) => {
field.setErrors(fieldsErrors[path] || [])
})
}
public created (): void {
this.$formulario.register(this.id, this)
if (typeof this.state === 'object') {
this.proxy = clone(this.state)
}
}
public beforeDestroy (): void {
this.$formulario.unregister(this.id)
}
public runValidation (): Promise<Record<string, Violation[]>> {
const runs: Promise<void>[] = []
const violations: Record<string, Violation[]> = {}
this.registry.forEach((field, path) => {
runs.push(field.runValidation().then(v => { violations[path] = v }))
})
return Promise.all(runs).then(() => violations)
}
public hasValidationErrors (): Promise<boolean> {
return this.runValidation().then(violations => {
return Object.keys(violations).some(path => violations[path].length > 0)
})
}
public setErrors ({ fieldsErrors, formErrors }: {
fieldsErrors?: Record<string, string[]>;
formErrors?: string[];
}): void {
this.localFieldsErrors = fieldsErrors || {}
this.localFormErrors = formErrors || []
}
public resetValidation (): void {
this.localFieldsErrors = {}
this.localFormErrors = []
this.registry.forEach((field: FormularioField) => {
field.resetValidation()
})
}
private onSubmit (): Promise<void> {
return this.runValidation().then(violations => {
const hasErrors = Object.keys(violations).some(path => violations[path].length > 0)
if (!hasErrors) {
this.$emit('submit', clone(this.proxy))
} else {
this.$emit('error', violations)
}
})
}
}
</script>