UNPKG

mobx-react-form

Version:
210 lines (173 loc) 5.75 kB
import { action, computed, observable, makeObservable, autorun, runInAction } from "mobx"; import _ from "lodash"; import Base from "./Base"; import Validator from "./Validator"; import State from "./State"; import Field from "./Field"; import ValidatorInterface from "./models/ValidatorInterface"; import { FieldInterface, FieldConstructor } from "./models/FieldInterface"; import { FormInterface, FieldsDefinitions, FormConfig } from "./models/FormInterface"; import { FieldPropsEnum } from "./models/FieldProps"; import { OptionsEnum } from "./models/OptionsModel"; export default class Form extends Base implements FormInterface { name: string; path = null; validator: ValidatorInterface; debouncedValidation: any = null; constructor( setup: FieldsDefinitions = {}, { name = "", options = {}, plugins = {}, bindings = {}, hooks = {}, handlers = {}, }: FormConfig = {} ) { super(); makeObservable(this, { fields: observable, validatedValues: computed, error: computed, hasError: computed, isValid: computed, isPristine: computed, isDirty: computed, isDefault: computed, isEmpty: computed, focused: computed, touched: computed, disabled: computed, // init: action, invalidate: action, clear: action, reset: action, resetValidation: action, }); this.name = name; runInAction(() => (this.$hooks = hooks)); runInAction(() => (this.$handlers = handlers)); // load data from initializers methods const initial = _.each( { setup, options, plugins, bindings, }, (val, key) => (typeof (this as any)[key] === 'function') ? _.merge(val, (this as any)[key].apply(this, [this])) : val ); // setup hooks & handlers from initialization methods runInAction(() => Object.assign(this.$hooks, (this as any).hooks?.apply(this, [this]))); runInAction(() => Object.assign(this.$handlers, (this as any).handlers?.apply(this, [this]))); this.state = new State({ form: this, initial: initial.setup, options: initial.options, bindings: initial.bindings, }); this.validator = new Validator({ form: this, plugins: initial.plugins, }); this.initFields(initial.setup); this.debouncedValidation = _.debounce( this.validate, this.state.options.get(OptionsEnum.validationDebounceWait), this.state.options.get(OptionsEnum.validationDebounceOptions) ); // execute validation on form initialization this.state.options.get(OptionsEnum.validateOnInit) && this.validator.validate({ showErrors: this.state.options.get(OptionsEnum.showErrorsOnInit), }); this.execHook(FieldPropsEnum.onInit); // handle Form onChange Hook autorun(() => this.$changed && this.execHook(FieldPropsEnum.onChange)); } /* ------------------------------------------------------------------ */ /* COMPUTED */ get validatedValues(): object { const data: any = {}; this.each(($field: any) => (data[$field.path] = $field.validatedValue)); return data; } get error(): string | null { return this.validator.error; } get hasError(): boolean { return !!this.validator.error || this.check(FieldPropsEnum.hasError, true); } get isValid(): boolean { return !this.validator.error && this.check(FieldPropsEnum.isValid, true); } get isPristine(): boolean { return this.check(FieldPropsEnum.isPristine, true); } get isDirty(): boolean { return this.check(FieldPropsEnum.isDirty, true); } get isDefault(): boolean { return this.check(FieldPropsEnum.isDefault, true); } get isEmpty(): boolean { return this.check(FieldPropsEnum.isEmpty, true); } get focused(): boolean { return this.check(FieldPropsEnum.focused, true); } get touched(): boolean { return this.check(FieldPropsEnum.touched, true); } get disabled(): boolean { return this.check(FieldPropsEnum.disabled, true); } makeField(data: FieldConstructor, FieldClass: typeof Field = Field) { return new FieldClass(data); } /** DEPRECATED Init Form Fields and Nested Fields init($fields: any = null): void { _.set(this, "fields", observable.map({})); this.state.initial.props.values = $fields; // eslint-disable-line this.state.current.props.values = $fields; // eslint-disable-line this.initFields({ fields: $fields || this.state.struct(), }); } */ invalidate(message: string | null = null, deep: boolean = true): void { this.debouncedValidation.cancel(); this.validator.error = message || this.state.options.get(OptionsEnum.defaultGenericError) || true; deep && this.each((field: FieldInterface) => field.debouncedValidation.cancel()); } showErrors(show: boolean = true): void { this.each((field: FieldInterface) => field.showErrors(show)); } resetValidation(deep: boolean = true): void { this.validator.error = null; deep && this.each((field: FieldInterface) => field.resetValidation(deep)); } /** Clear Form Fields */ clear(deep: boolean = true, execHook: boolean = true): void { execHook && this.execHook(FieldPropsEnum.onClear); this.$touched = false; this.$changed = 0; deep && this.each((field: FieldInterface) => field.clear(deep)); } /** Reset Form Fields */ reset(deep: boolean = true, execHook: boolean = true): void { execHook && this.execHook(FieldPropsEnum.onReset); this.$touched = false; this.$changed = 0; deep && this.each((field: FieldInterface) => field.reset(deep)); } }