@mobx-ecosystem/mobx-form
Version:
provides the ability to use forms with validation in MobX stores
1,006 lines (993 loc) • 34.8 kB
JavaScript
import { makeAutoObservable } from 'mobx';
const isObject = (obj) => obj !== null && typeof obj === 'object';
function isEqual(value, other) {
// Если оба значения одинаковы (включая примитивы и ссылки на один объект)
if (value === other) {
return true;
}
// Если одно из значений null или не объект, и они не равны (уже проверено выше)
if (value === null || other === null || typeof value !== 'object' || typeof other !== 'object') {
return false;
}
// Если один из аргументов — Date, сравниваем их временные метки
if (value instanceof Date && other instanceof Date) {
return value.getTime() === other.getTime();
}
// Если один из аргументов — RegExp, сравниваем их строковые представления
if (value instanceof RegExp && other instanceof RegExp) {
return value.toString() === other.toString();
}
// Если один из аргументов — Map, преобразуем их в массивы и сравниваем
if (value instanceof Map && other instanceof Map) {
if (value.size !== other.size)
return false;
for (const [key, val] of value) {
if (!other.has(key) || !isEqual(val, other.get(key))) {
return false;
}
}
return true;
}
// Если один из аргументов — Set, преобразуем их в массивы и сравниваем
if (value instanceof Set && other instanceof Set) {
if (value.size !== other.size)
return false;
for (const val of value) {
if (!other.has(val)) {
return false;
}
}
return true;
}
// Если это массивы, сравниваем их длины и элементы
if (Array.isArray(value) && Array.isArray(other)) {
if (value.length !== other.length)
return false;
for (let i = 0; i < value.length; i++) {
if (!isEqual(value[i], other[i])) {
return false;
}
}
return true;
}
// Если это объекты, сравниваем их ключи и значения
const valueKeys = Object.keys(value);
const otherKeys = Object.keys(other);
// Если количество ключей разное, объекты не равны
if (valueKeys.length !== otherKeys.length) {
return false;
}
// Рекурсивно сравниваем все свойства
for (const key of valueKeys) {
if (!Object.hasOwnProperty.call(other, key) || !isEqual(value[key], other[key])) {
return false;
}
}
return true;
}
class FieldService {
constructor(initValue, options) {
Object.defineProperty(this, "validate", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "_serviceType", {
enumerable: true,
configurable: true,
writable: true,
value: 'field-service'
});
Object.defineProperty(this, "_initValue", {
enumerable: true,
configurable: true,
writable: true,
value: undefined
});
Object.defineProperty(this, "_value", {
enumerable: true,
configurable: true,
writable: true,
value: undefined
});
Object.defineProperty(this, "_error", {
enumerable: true,
configurable: true,
writable: true,
value: undefined
});
Object.defineProperty(this, "_disabled", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
Object.defineProperty(this, "_isBlurred", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
Object.defineProperty(this, "options", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "setOptions", {
enumerable: true,
configurable: true,
writable: true,
value: (options) => {
this.options = options;
}
});
Object.defineProperty(this, "setValue", {
enumerable: true,
configurable: true,
writable: true,
value: (value, { validate } = {}) => {
var _a;
this.value = value;
if (validate) {
(_a = this.validate) === null || _a === void 0 ? void 0 : _a.call(this);
}
}
});
Object.defineProperty(this, "onChange", {
enumerable: true,
configurable: true,
writable: true,
value: (_, value) => {
var _a;
this.value = value;
(_a = this.validate) === null || _a === void 0 ? void 0 : _a.call(this);
}
});
Object.defineProperty(this, "onBlur", {
enumerable: true,
configurable: true,
writable: true,
value: (_) => {
var _a;
this.isBlurred = true;
(_a = this.validate) === null || _a === void 0 ? void 0 : _a.call(this);
}
});
Object.defineProperty(this, "reset", {
enumerable: true,
configurable: true,
writable: true,
value: () => {
this.value = this.initValue;
this.isBlurred = false;
}
});
Object.defineProperty(this, "setAsInit", {
enumerable: true,
configurable: true,
writable: true,
value: () => {
this.initValue = this.value;
this.isBlurred = false;
}
});
Object.defineProperty(this, "touch", {
enumerable: true,
configurable: true,
writable: true,
value: () => {
this.isBlurred = true;
}
});
Object.defineProperty(this, "disable", {
enumerable: true,
configurable: true,
writable: true,
value: () => {
this.disabled = true;
}
});
Object.defineProperty(this, "enable", {
enumerable: true,
configurable: true,
writable: true,
value: () => {
this.disabled = false;
}
});
makeAutoObservable(this);
this.initValue = initValue;
this.options = options;
}
get initValue() {
return this._initValue;
}
set initValue(initValue) {
var _a;
this._initValue = initValue;
this._value = initValue;
(_a = this.validate) === null || _a === void 0 ? void 0 : _a.call(this);
}
get value() {
return this._value;
}
set value(value) {
var _a, _b, _c, _d;
const result = (_b = (_a = this.options) === null || _a === void 0 ? void 0 : _a.beforeOnChange) === null || _b === void 0 ? void 0 : _b.call(_a, value);
if (result === 'abort') {
return;
}
const oldValue = this._value;
this._value = value;
if (oldValue !== value) {
(_d = (_c = this.options) === null || _c === void 0 ? void 0 : _c.onChange) === null || _d === void 0 ? void 0 : _d.call(_c, value);
}
}
get error() {
return this._error;
}
set error(error) {
this._error = error;
}
get disabled() {
var _a, _b;
return this._disabled || Boolean((_b = (_a = this.options) === null || _a === void 0 ? void 0 : _a.disabledFn) === null || _b === void 0 ? void 0 : _b.call(_a));
}
set disabled(disabled) {
this._disabled = disabled;
}
get isValid() {
return !this._error;
}
get isInit() {
if (isObject(this.value)) {
return isEqual(this.value, this._initValue);
}
return this._value === this._initValue;
}
get isBlurred() {
return this._isBlurred;
}
set isBlurred(isBlurred) {
this._isBlurred = isBlurred;
}
get isTouched() {
return !this.isInit || this.isBlurred;
}
// TODO: Rethink...
get props() {
var _a;
let commonProps = {
value: this.value,
error: this.error,
disabled: this.disabled,
};
if ((_a = this.options) === null || _a === void 0 ? void 0 : _a.onError) {
commonProps = Object.assign(Object.assign({}, commonProps), { onError: (err) => {
this.error = this.error || (err === null || err === void 0 ? void 0 : err.toString());
} });
}
return Object.assign(Object.assign({}, commonProps), { onChange: this.onChange, onBlur: this.onBlur });
}
}
/******************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
function __awaiter(thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
}
let validate;
let preSubmitValidationError;
const configureForm = (configuration) => {
validate = configuration.validation.validate;
preSubmitValidationError = configuration.validation.preSubmitValidationError;
};
const _checkConfiguration = () => {
if (!validate || !preSubmitValidationError)
throw new Error("You must define configureForm to configure mobx-form");
};
class CombinedFormFieldService {
constructor(initValue) {
Object.defineProperty(this, "_touched", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
Object.defineProperty(this, "_disabled", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
Object.defineProperty(this, "_error", {
enumerable: true,
configurable: true,
writable: true,
value: undefined
});
Object.defineProperty(this, "_initValue", {
enumerable: true,
configurable: true,
writable: true,
value: []
});
Object.defineProperty(this, "_value", {
enumerable: true,
configurable: true,
writable: true,
value: []
});
Object.defineProperty(this, "_validate", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "setTouched", {
enumerable: true,
configurable: true,
writable: true,
value: (touched) => {
this._touched = touched;
}
});
Object.defineProperty(this, "add", {
enumerable: true,
configurable: true,
writable: true,
value: (value) => {
var _a;
this.value.push(value);
this.setTouched(true);
(_a = this.validate) === null || _a === void 0 ? void 0 : _a.call(this, 'only-touched');
}
});
Object.defineProperty(this, "removeByIndex", {
enumerable: true,
configurable: true,
writable: true,
value: (index) => {
var _a;
this.value.splice(index, 1);
this.setTouched(true);
(_a = this.validate) === null || _a === void 0 ? void 0 : _a.call(this, 'only-touched');
}
});
Object.defineProperty(this, "reset", {
enumerable: true,
configurable: true,
writable: true,
value: () => {
this._value = this.initValue.slice(0); // copy array without objects
this._value.forEach(it => it.formService.reset());
this.setTouched(false);
}
});
Object.defineProperty(this, "setAsInit", {
enumerable: true,
configurable: true,
writable: true,
value: () => {
this.initValue = this.value;
this._value.forEach(it => it.formService.setValuesAsInit());
this.setTouched(false);
}
});
Object.defineProperty(this, "getValues", {
enumerable: true,
configurable: true,
writable: true,
value: () => {
return this.value.map(it => it.formService.getValues());
}
});
Object.defineProperty(this, "touch", {
enumerable: true,
configurable: true,
writable: true,
value: () => {
this.value.forEach(it => it.formService.touch());
this.setTouched(true);
}
});
Object.defineProperty(this, "validateFields", {
enumerable: true,
configurable: true,
writable: true,
value: (type = 'everything') => {
return Promise.all(this.value.map(it => it.formService.validate(type)));
}
});
Object.defineProperty(this, "validate", {
enumerable: true,
configurable: true,
writable: true,
value: (type = 'everything') => __awaiter(this, void 0, void 0, function* () {
var _a;
return Promise.all([(_a = this._validate) === null || _a === void 0 ? void 0 : _a.call(this), this.validateFields(type)]);
})
});
Object.defineProperty(this, "disable", {
enumerable: true,
configurable: true,
writable: true,
value: () => {
this.disabled = true;
}
});
Object.defineProperty(this, "enable", {
enumerable: true,
configurable: true,
writable: true,
value: () => {
this.disabled = false;
}
});
makeAutoObservable(this);
this.initValue = initValue || [];
}
get initValue() {
return this._initValue;
}
set initValue(_initValue) {
var _a;
this._initValue = _initValue;
this._value = _initValue.slice(0); // copy array without objects
this.setTouched(false);
(_a = this.validate) === null || _a === void 0 ? void 0 : _a.call(this, 'only-touched');
}
get value() {
return this._value;
}
set value(array) {
this._value = array;
this.setTouched(true);
}
get disabled() {
return this._disabled;
}
set disabled(disabled) {
this._disabled = disabled;
if (disabled) {
this.value.forEach(it => it.formService.disable());
}
else {
this.value.forEach(it => it.formService.enable());
}
}
get error() {
return this._error;
}
set error(error) {
this._error = error;
}
get isValid() {
return !this._error && this.value.every(it => it.formService.isValid);
}
get isTouched() {
return this._touched || this.value.some(it => it.formService.isTouched);
}
get isInit() {
return !this.isTouched;
}
get hasItems() {
return Boolean(this.value.length);
}
}
class AutocompleteFieldService {
constructor(initValue, options) {
Object.defineProperty(this, "field", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "inputField", {
enumerable: true,
configurable: true,
writable: true,
value: new FieldService("")
});
Object.defineProperty(this, "options", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "setValue", {
enumerable: true,
configurable: true,
writable: true,
value: (value, { inputValue = "", withNotification = true, withBlur = true }) => {
var _a, _b, _c, _d;
if (!withNotification) {
this.field.value = value;
this.inputField.value = inputValue;
(_b = (_a = this.field).validate) === null || _b === void 0 ? void 0 : _b.call(_a);
(_d = (_c = this.inputField).validate) === null || _d === void 0 ? void 0 : _d.call(_c);
}
else {
this.field.value = value;
this.inputField.value = inputValue;
}
if (withBlur) {
return this.field.isBlurred = true;
}
}
});
Object.defineProperty(this, "reset", {
enumerable: true,
configurable: true,
writable: true,
value: () => {
this.field.reset();
}
});
Object.defineProperty(this, "setAsInit", {
enumerable: true,
configurable: true,
writable: true,
value: () => {
this.field.setAsInit();
}
});
Object.defineProperty(this, "onInputChange", {
enumerable: true,
configurable: true,
writable: true,
value: (e, value) => {
var _a, _b, _c, _d;
const result = (_b = (_a = this.options) === null || _a === void 0 ? void 0 : _a.onInputBeforeChange) === null || _b === void 0 ? void 0 : _b.call(_a, value);
if (result === 'abort') {
return;
}
const oldValue = this.inputField.value;
this.inputField.value = value;
if (oldValue !== value) {
(_d = (_c = this.options) === null || _c === void 0 ? void 0 : _c.onInputChange) === null || _d === void 0 ? void 0 : _d.call(_c, value);
}
}
});
Object.defineProperty(this, "touch", {
enumerable: true,
configurable: true,
writable: true,
value: () => {
this.field.touch();
}
});
Object.defineProperty(this, "disable", {
enumerable: true,
configurable: true,
writable: true,
value: () => {
this.field.disabled = true;
}
});
Object.defineProperty(this, "enable", {
enumerable: true,
configurable: true,
writable: true,
value: () => {
this.field.disabled = false;
}
});
makeAutoObservable(this);
this.options = options;
this.field = new FieldService(null, { onChange: options === null || options === void 0 ? void 0 : options.onChange, beforeOnChange: options === null || options === void 0 ? void 0 : options.beforeOnChange });
this.field.initValue = initValue;
}
get validate() {
return this.field.validate;
}
set validate(validate) {
this.field.validate = validate;
}
get value() {
return this.field.value;
}
set value(value) {
this.field.value = value;
}
get error() {
return this.field.error;
}
set error(error) {
this.field.error = error;
}
get disabled() {
return this.field.disabled;
}
set disabled(disabled) {
this.field.disabled = disabled;
}
get isValid() {
return this.field.isValid;
}
get isInit() {
return this.field.isInit;
}
set initValue(initValue) {
this.field.initValue = initValue;
}
get isTouched() {
return this.field.isTouched;
}
get initValue() {
return this.field.initValue;
}
get props() {
return Object.assign(Object.assign({}, this.field.props), {
// inputValue: this.inputField.value,
onSearchChange: this.onInputChange });
}
}
class FormService {
constructor(fields, validationSchema) {
Object.defineProperty(this, "fields", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "validationSchema", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "onSubmit", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "setOnSubmit", {
enumerable: true,
configurable: true,
writable: true,
value: (onSubmit) => {
this.onSubmit = onSubmit;
}
});
Object.defineProperty(this, "submit", {
enumerable: true,
configurable: true,
writable: true,
value: () => __awaiter(this, void 0, void 0, function* () {
var _a;
yield this.validate('everything');
if (this.canBeSubmitted) {
return (_a = this.onSubmit) === null || _a === void 0 ? void 0 : _a.call(this);
}
else {
preSubmitValidationError === null || preSubmitValidationError === void 0 ? void 0 : preSubmitValidationError();
}
})
});
/***
* Validate the form
*
* *Configure this method with configureForm from mobx-form
*/
Object.defineProperty(this, "validate", {
enumerable: true,
configurable: true,
writable: true,
value: (type = 'only-touched') => __awaiter(this, void 0, void 0, function* () {
const fieldValues = this.getValues();
// валидация для сложных форм снизу -> вверх
yield this.bypassFields(this.fields, (field) => __awaiter(this, void 0, void 0, function* () {
var _b;
if (field instanceof CombinedFormFieldService) {
return yield ((_b = field.validateFields) === null || _b === void 0 ? void 0 : _b.call(field, type));
}
}));
// валидация для простейших полей сверху -> вниз
const errors = yield (validate === null || validate === void 0 ? void 0 : validate(fieldValues, this.validationSchema));
if (errors && Object.keys(errors || []).length != 0) {
this.setErrors(errors, type);
}
else {
this.resetErrors();
}
})
});
Object.defineProperty(this, "setValidationSchema", {
enumerable: true,
configurable: true,
writable: true,
value: (validationSchema) => {
this.validationSchema = validationSchema;
this.setValidationToFields();
}
});
/**
*
* @returns Object of field values
*/
Object.defineProperty(this, "getValues", {
enumerable: true,
configurable: true,
writable: true,
value: () => {
const values = {};
for (const key of this.keys) {
values[key] = this.getValue(this.fields[key]);
}
return values;
}
});
Object.defineProperty(this, "getValue", {
enumerable: true,
configurable: true,
writable: true,
value: (value) => {
if (value instanceof FieldService || value instanceof CombinedFormFieldService || value instanceof AutocompleteFieldService) {
return value === null || value === void 0 ? void 0 : value.value;
}
else if (typeof value === 'object') {
const values = {};
for (const key of Object.keys(value)) {
values[key] = this.getValue(value === null || value === void 0 ? void 0 : value[key]);
}
return values;
}
return value;
}
});
/**
* Set fields by this
*/
Object.defineProperty(this, "setFieldsByThis", {
enumerable: true,
configurable: true,
writable: true,
value: (obj) => {
const fields = {};
Object.keys(obj).forEach(key => {
if (obj[key] && obj[key] instanceof FieldService || obj[key] instanceof CombinedFormFieldService || obj[key] instanceof AutocompleteFieldService) {
fields[key] = obj[key];
}
});
this.fields = fields;
this.setValidationToFields();
}
});
Object.defineProperty(this, "bypassFields", {
enumerable: true,
configurable: true,
writable: true,
value: (fields, action, levelParams) => {
if (fields instanceof FieldService || fields instanceof CombinedFormFieldService || fields instanceof AutocompleteFieldService) {
// if(typeof fields.value === 'object') {
// this.bypassFields(fields.value, action, levelParams)
// }
return action(fields, levelParams);
}
else if (typeof fields === 'object') {
return Promise.all(Object.keys(fields || {}).map(key => {
return this.bypassFields(fields === null || fields === void 0 ? void 0 : fields[key], action, levelParams === null || levelParams === void 0 ? void 0 : levelParams[key]);
}));
}
}
});
Object.defineProperty(this, "setValidationToFields", {
enumerable: true,
configurable: true,
writable: true,
value: () => {
this.bypassFields(this.fields, (field) => {
if (!(field instanceof CombinedFormFieldService)) {
field.validate = this.validate;
}
else {
field._validate = this.validate;
}
});
}
});
/**
* Set object to init values by form service keys
*/
Object.defineProperty(this, "setInitValues", {
enumerable: true,
configurable: true,
writable: true,
value: (values, { validate } = {}) => {
this.bypassFields(this.fields, (field, levelParams) => field.initValue = levelParams, values);
if (validate) {
this.validate();
}
}
});
/**
* Set object to values by form service keys
*/
Object.defineProperty(this, "setValues", {
enumerable: true,
configurable: true,
writable: true,
value: (values, { validate } = {}) => {
this.bypassFields(this.fields, (field, levelParams) => field.value = levelParams, values);
if (validate) {
this.validate('everything');
}
}
});
/**
* Set field errors to undefined
*/
Object.defineProperty(this, "resetErrors", {
enumerable: true,
configurable: true,
writable: true,
value: () => {
this.bypassFields(this.fields, (field) => field.error = undefined);
}
});
/**
* Set field values to init values
*/
Object.defineProperty(this, "setValuesAsInit", {
enumerable: true,
configurable: true,
writable: true,
value: () => {
this.bypassFields(this.fields, (field) => {
field.setAsInit();
});
}
});
Object.defineProperty(this, "getKeys", {
enumerable: true,
configurable: true,
writable: true,
value: ({ keyType = 'include', keys = [] }) => {
if (keyType === 'include') {
return keys;
}
else {
return Object.keys(this.fields).filter(fieldKey => !keys.includes(fieldKey));
}
}
});
/**
* Reset fields to their own initial values
*/
Object.defineProperty(this, "reset", {
enumerable: true,
configurable: true,
writable: true,
value: (keyParams) => {
if (keyParams === null || keyParams === void 0 ? void 0 : keyParams.keys) {
const _keys = this.getKeys(keyParams);
_keys.forEach(key => {
const field = this.fields[key];
field.reset();
});
}
else {
this.bypassFields(this.fields, (field) => {
field.reset();
});
}
this.validate();
}
});
/**
* Pass true to the property 'disabled'
*/
Object.defineProperty(this, "disable", {
enumerable: true,
configurable: true,
writable: true,
value: () => {
this.bypassFields(this.fields, (field) => field.disable());
}
});
/**
* Pass false to the property 'disabled'
*/
Object.defineProperty(this, "enable", {
enumerable: true,
configurable: true,
writable: true,
value: () => {
this.bypassFields(this.fields, (field) => field.enable());
}
});
Object.defineProperty(this, "touch", {
enumerable: true,
configurable: true,
writable: true,
value: () => {
this.bypassFields(this.fields, (field) => field.touch());
}
});
_checkConfiguration();
makeAutoObservable(this);
this.fields = fields;
this.validationSchema = validationSchema;
this.setValidationToFields();
}
/**
* Return field keys
*/
get keys() {
return Object.keys(this.fields);
}
/**
* Check each field if its isValid = true
*/
get isValid() {
let isValid = true;
this.bypassFields(this.fields, (field) => {
if (!field.isValid) {
isValid = false;
}
});
return isValid;
}
/**
* Check each field if its isTouched = true
*/
get isTouched() {
let isTouched = false;
this.bypassFields(this.fields, (field) => {
if (field.isTouched) {
isTouched = true;
}
});
return isTouched;
}
/**
* Check if isTouched = true && isValid = true
*/
get canBeSubmitted() {
return this.isTouched && this.isValid;
}
/**
* always true if the form service is empty
*/
get disabled() {
let disabled = true;
this.bypassFields(this.fields, (field) => {
if (!field.disabled) {
disabled = false;
}
});
return disabled;
}
/**
* Set errors for fields
* @param errors object of string which provides errors for fields
*/
setErrors(error, validationType = 'only-touched') {
this.bypassFields(this.fields, (field, levelParams) => {
if (field.isTouched || validationType === 'everything') { // set error only if it's changed
field.error = levelParams;
}
}, error);
}
}
export { AutocompleteFieldService, CombinedFormFieldService, FieldService, FormService, _checkConfiguration, configureForm, preSubmitValidationError, validate };
//# sourceMappingURL=index.dev.js.map