1
0
mirror of synced 2024-11-22 05:16:05 +03:00

refactor: Got rid of error observer to push errors into fields

This commit is contained in:
Zaytsev Kirill 2021-05-22 22:15:47 +03:00
parent 7aca27a010
commit 6afe5f0a08
6 changed files with 41 additions and 118 deletions

View File

@ -31,7 +31,7 @@ const VALIDATION_BEHAVIOR = {
SUBMIT: 'submit', SUBMIT: 'submit',
} }
type Context<U> = { type FormularioFieldContext<U> = {
model: U; model: U;
name: string; name: string;
runValidation(): Promise<Violation[]>; runValidation(): Promise<Violation[]>;
@ -52,14 +52,14 @@ type Empty = null | undefined
@Component({ name: 'FormularioField', inheritAttrs: false }) @Component({ name: 'FormularioField', inheritAttrs: false })
export default class FormularioField extends Vue { export default class FormularioField extends Vue {
@Inject({ default: '' }) __Formulario_path!: string
@Inject({ default: undefined }) __FormularioForm_set!: Function|undefined @Inject({ default: undefined }) __FormularioForm_set!: Function|undefined
@Inject({ default: () => (): void => {} }) __FormularioForm_emitValidation!: Function @Inject({ default: () => (): void => {} }) __FormularioForm_emitValidation!: Function
@Inject({ default: undefined }) __FormularioForm_register!: Function|undefined @Inject({ default: undefined }) __FormularioForm_register!: Function|undefined
@Inject({ default: undefined }) __FormularioForm_unregister!: Function|undefined @Inject({ default: undefined }) __FormularioForm_unregister!: Function|undefined
@Inject({ default: () => (): Record<string, unknown> => ({}) }) __FormularioForm_getValue!: () => Record<string, unknown>
@Inject({ default: undefined }) __FormularioForm_addErrorObserver!: Function|undefined @Inject({ default: () => (): Record<string, unknown> => ({}) })
@Inject({ default: undefined }) __FormularioForm_removeErrorObserver!: Function|undefined __FormularioForm_getValue!: () => Record<string, unknown>
@Inject({ default: '' }) path!: string
@Model('input', { default: '' }) value!: unknown @Model('input', { default: '' }) value!: unknown
@ -90,8 +90,8 @@ export default class FormularioField extends Vue {
private validationRun: Promise<Violation[]> = Promise.resolve([]) private validationRun: Promise<Violation[]> = Promise.resolve([])
private get fullQualifiedName (): string { public get fullQualifiedName (): string {
return this.path !== '' ? `${this.path}.${this.name}` : this.name return this.__Formulario_path !== '' ? `${this.__Formulario_path}.${this.name}` : this.name
} }
private get model (): unknown { private get model (): unknown {
@ -113,7 +113,7 @@ export default class FormularioField extends Vue {
} }
} }
private get context (): Context<unknown> { private get context (): FormularioFieldContext<unknown> {
return Object.defineProperty({ return Object.defineProperty({
name: this.fullQualifiedName, name: this.fullQualifiedName,
runValidation: this.runValidation.bind(this), runValidation: this.runValidation.bind(this),
@ -175,18 +175,12 @@ export default class FormularioField extends Vue {
if (typeof this.__FormularioForm_register === 'function') { if (typeof this.__FormularioForm_register === 'function') {
this.__FormularioForm_register(this.fullQualifiedName, this) this.__FormularioForm_register(this.fullQualifiedName, this)
} }
if (typeof this.__FormularioForm_addErrorObserver === 'function' && !this.errorsDisabled) {
this.__FormularioForm_addErrorObserver({ callback: this.setErrors, type: 'field', field: this.fullQualifiedName })
}
if (this.validationBehavior === VALIDATION_BEHAVIOR.LIVE) { if (this.validationBehavior === VALIDATION_BEHAVIOR.LIVE) {
this.runValidation() this.runValidation()
} }
} }
beforeDestroy (): void { beforeDestroy (): void {
if (!this.errorsDisabled && typeof this.__FormularioForm_removeErrorObserver === 'function') {
this.__FormularioForm_removeErrorObserver(this.setErrors)
}
if (typeof this.__FormularioForm_unregister === 'function') { if (typeof this.__FormularioForm_unregister === 'function') {
this.__FormularioForm_unregister(this.fullQualifiedName) this.__FormularioForm_unregister(this.fullQualifiedName)
} }
@ -245,10 +239,18 @@ export default class FormularioField extends Vue {
}) })
} }
/**
* @internal
*/
setErrors (errors: string[]): void { setErrors (errors: string[]): void {
this.localErrors = arrayify(errors) if (!this.errorsDisabled) {
this.localErrors = arrayify(errors)
}
} }
/**
* @internal
*/
resetValidation (): void { resetValidation (): void {
this.localErrors = [] this.localErrors = []
this.violations = [] this.violations = []

View File

@ -15,7 +15,7 @@ import {
@Component({ name: 'FormularioFieldGroup' }) @Component({ name: 'FormularioFieldGroup' })
export default class FormularioFieldGroup extends Vue { export default class FormularioFieldGroup extends Vue {
@Inject({ default: '' }) path!: string @Inject({ default: '' }) __Formulario_path!: string
@Prop({ required: true }) @Prop({ required: true })
readonly name!: string readonly name!: string
@ -23,12 +23,13 @@ export default class FormularioFieldGroup extends Vue {
@Prop({ default: false }) @Prop({ default: false })
readonly isArrayItem!: boolean readonly isArrayItem!: boolean
@Provide('path') get groupPath (): string { @Provide('__Formulario_path')
get groupPath (): string {
if (this.isArrayItem) { if (this.isArrayItem) {
return `${this.path}[${this.name}]` return `${this.__Formulario_path}[${this.name}]`
} }
if (this.path === '') { if (this.__Formulario_path === '') {
return this.name return this.name
} }

View File

@ -26,12 +26,6 @@ import FormularioFormRegistry from '@/FormularioFormRegistry'
import FormularioField from '@/FormularioField.vue' import FormularioField from '@/FormularioField.vue'
import {
ErrorHandler,
ErrorObserver,
ErrorObserverRegistry,
} from '@/validation/ErrorObserver'
import { Violation } from '@/validation/validator' import { Violation } from '@/validation/validator'
type ValidationEventPayload = { type ValidationEventPayload = {
@ -49,14 +43,10 @@ export default class FormularioForm extends Vue {
// Form errors only used on FormularioForm default slot // Form errors only used on FormularioForm default slot
@Prop({ default: () => ([]) }) readonly formErrors!: string[] @Prop({ default: () => ([]) }) readonly formErrors!: string[]
@Provide()
public path = ''
public proxy: Record<string, unknown> = {} public proxy: Record<string, unknown> = {}
private registry: FormularioFormRegistry = new FormularioFormRegistry(this) private registry: FormularioFormRegistry = new FormularioFormRegistry(this)
private errorObserverRegistry = new ErrorObserverRegistry()
// Local error messages are temporal, they wiped each resetValidation call // Local error messages are temporal, they wiped each resetValidation call
private localFormErrors: string[] = [] private localFormErrors: string[] = []
private localFieldErrors: Record<string, string[]> = {} private localFieldErrors: Record<string, string[]> = {}
@ -87,27 +77,24 @@ export default class FormularioForm extends Vue {
} }
@Watch('formularioValue', { deep: true }) @Watch('formularioValue', { deep: true })
onFormularioValueChanged (values: Record<string, unknown>): void { onFormularioValueChange (values: Record<string, unknown>): void {
if (this.hasModel && values && typeof values === 'object') { if (this.hasModel && values && typeof values === 'object') {
this.setValues(values) this.setValues(values)
} }
} }
@Watch('mergedFormErrors')
onMergedFormErrorsChanged (errors: string[]): void {
this.errorObserverRegistry.filter(o => o.type === 'form').observe(errors)
}
@Watch('mergedFieldErrors', { deep: true, immediate: true }) @Watch('mergedFieldErrors', { deep: true, immediate: true })
onMergedFieldErrorsChanged (errors: Record<string, string[]>): void { onMergedFieldErrorsChange (errors: Record<string, string[]>): void {
this.errorObserverRegistry.filter(o => o.type === 'field').observe(errors) this.registry.forEach((vm, path) => {
vm.setErrors(errors[path] || [])
})
} }
created (): void { created (): void {
this.initProxy() this.initProxy()
} }
@Provide() @Provide('__FormularioForm_getValue')
getFormValues (): Record<string, unknown> { getFormValues (): Record<string, unknown> {
return this.proxy return this.proxy
} }
@ -129,29 +116,20 @@ export default class FormularioForm extends Vue {
this.$emit('validation', payload) this.$emit('validation', payload)
} }
@Provide('__FormularioForm_addErrorObserver')
addErrorObserver (observer: ErrorObserver): void {
this.errorObserverRegistry.add(observer)
if (observer.type === 'form') {
observer.callback(this.mergedFormErrors)
} else if (observer.field && has(this.mergedFieldErrors, observer.field)) {
observer.callback(this.mergedFieldErrors[observer.field])
}
}
@Provide('__FormularioForm_removeErrorObserver')
removeErrorObserver (observer: ErrorHandler): void {
this.errorObserverRegistry.remove(observer)
}
@Provide('__FormularioForm_register') @Provide('__FormularioForm_register')
private register (field: string, component: FormularioField): void { private register (field: string, vm: FormularioField): void {
this.registry.add(field, component) this.registry.add(field, vm)
if (has(this.mergedFieldErrors, field)) {
vm.setErrors(this.mergedFieldErrors[field] || [])
}
} }
@Provide('__FormularioForm_unregister') @Provide('__FormularioForm_unregister')
private unregister (field: string): void { private unregister (field: string): void {
this.registry.remove(field) if (this.registry.has(field)) {
this.registry.remove(field)
}
} }
initProxy (): void { initProxy (): void {

View File

@ -115,7 +115,7 @@ export default class FormularioFormRegistry {
/** /**
* Iterate over the registry. * Iterate over the registry.
*/ */
forEach (callback: Function): void { forEach (callback: (component: FormularioField, field: string) => void): void {
this.registry.forEach((component, field) => { this.registry.forEach((component, field) => {
callback(component, field) callback(component, field)
}) })

View File

@ -1,7 +1,9 @@
export type Empty = undefined | null
export type RecordKey = string | number export type RecordKey = string | number
export type RecordLike<T> = T[] | Record<RecordKey, T> export type RecordLike<T> = T[] | Record<RecordKey, T>
export type Scalar = boolean | number | string | symbol | undefined | null export type Scalar = boolean | number | string | symbol | Empty
export function isScalar (value: unknown): boolean { export function isScalar (value: unknown): boolean {
switch (typeof value) { switch (typeof value) {

View File

@ -1,60 +0,0 @@
import { has } from '@/utils'
export interface ErrorHandler {
(errors: Record<string, any> | any[]): void;
}
export interface ErrorObserver {
callback: ErrorHandler;
type: ErrorObserverType;
field?: string;
}
export type ErrorObserverType = 'form' | 'field'
export interface ErrorObserverPredicate {
(value: ErrorObserver, index: number, array: ErrorObserver[]): unknown;
}
export class ErrorObserverRegistry {
private observers: ErrorObserver[] = []
constructor (observers: ErrorObserver[] = []) {
this.observers = observers
}
public add (observer: ErrorObserver): void {
if (!this.observers.some(o => o.callback === observer.callback)) {
this.observers.push(observer)
}
}
public remove (handler: ErrorHandler): void {
this.observers = this.observers.filter(o => o.callback !== handler)
}
public filter (predicate: ErrorObserverPredicate): ErrorObserverRegistry {
return new ErrorObserverRegistry(this.observers.filter(predicate))
}
public some (predicate: ErrorObserverPredicate): boolean {
return this.observers.some(predicate)
}
public observe (errors: Record<string, string[]>|string[]): void {
this.observers.forEach(observer => {
if (observer.type === 'form') {
observer.callback(errors)
} else if (
observer.field &&
!Array.isArray(errors)
) {
if (has(errors, observer.field)) {
observer.callback(errors[observer.field])
} else {
observer.callback([])
}
}
})
}
}