mobx-react-form
Version:
Reactive MobX Form State Management
1,131 lines (1,118 loc) • 145 kB
JavaScript
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory(require("_"), require("mobx"));
else if(typeof define === 'function' && define.amd)
define(["_", "mobx"], factory);
else if(typeof exports === 'object')
exports["MobxReactForm"] = factory(require("_"), require("mobx"));
else
root["MobxReactForm"] = factory(root["_"], root["mobx"]);
})(self, (__WEBPACK_EXTERNAL_MODULE_lodash__, __WEBPACK_EXTERNAL_MODULE_mobx__) => {
return /******/ (() => { // webpackBootstrap
/******/ "use strict";
/******/ var __webpack_modules__ = ({
/***/ "./src/Base.ts":
/*!*********************!*\
!*** ./src/Base.ts ***!
\*********************/
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
const mobx_1 = __webpack_require__(/*! mobx */ "mobx");
const lodash_1 = __importDefault(__webpack_require__(/*! lodash */ "lodash"));
const utils_1 = __webpack_require__(/*! ./utils */ "./src/utils.ts");
const parser_1 = __webpack_require__(/*! ./parser */ "./src/parser.ts");
const FieldProps_1 = __webpack_require__(/*! ./models/FieldProps */ "./src/models/FieldProps.ts");
const OptionsModel_1 = __webpack_require__(/*! ./models/OptionsModel */ "./src/models/OptionsModel.ts");
const ValidatorInterface_1 = __webpack_require__(/*! ./models/ValidatorInterface */ "./src/models/ValidatorInterface.ts");
class Base {
constructor() {
Object.defineProperty(this, "noop", {
enumerable: true,
configurable: true,
writable: true,
value: () => { }
});
Object.defineProperty(this, "state", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "fields", {
enumerable: true,
configurable: true,
writable: true,
value: mobx_1.observable.map({})
});
Object.defineProperty(this, "path", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "$submitted", {
enumerable: true,
configurable: true,
writable: true,
value: 0
});
Object.defineProperty(this, "$submitting", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
Object.defineProperty(this, "$validated", {
enumerable: true,
configurable: true,
writable: true,
value: 0
});
Object.defineProperty(this, "$validating", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
Object.defineProperty(this, "$clearing", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
Object.defineProperty(this, "$resetting", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
Object.defineProperty(this, "$touched", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
Object.defineProperty(this, "$changed", {
enumerable: true,
configurable: true,
writable: true,
value: 0
});
Object.defineProperty(this, "$hooks", {
enumerable: true,
configurable: true,
writable: true,
value: {}
});
Object.defineProperty(this, "$handlers", {
enumerable: true,
configurable: true,
writable: true,
value: {}
});
Object.defineProperty(this, "execHook", {
enumerable: true,
configurable: true,
writable: true,
value: (name, fallback = {}) => (0, utils_1.$try)(fallback[name], this.$hooks[name], this.noop).apply(this, [this])
});
Object.defineProperty(this, "execHandler", {
enumerable: true,
configurable: true,
writable: true,
value: (name, args, fallback = undefined, hook = null, execHook = true) => [
(0, utils_1.$try)(this.$handlers[name] && this.$handlers[name].apply(this, [this]), fallback, this.noop).apply(this, [...args]),
execHook && this.execHook(hook || name),
]
});
/**
Interceptor
*/
Object.defineProperty(this, "intercept", {
enumerable: true,
configurable: true,
writable: true,
value: (opt) => this.MOBXEvent((typeof opt === 'function')
? { type: "interceptor", call: opt }
: Object.assign({ type: "interceptor" }, opt))
});
/**
Observer
*/
Object.defineProperty(this, "observe", {
enumerable: true,
configurable: true,
writable: true,
value: (opt) => this.MOBXEvent((typeof opt === 'function')
? { type: "observer", call: opt }
: Object.assign({ type: "observer" }, opt))
});
/**
Event Handler: On Clear
*/
Object.defineProperty(this, "onClear", {
enumerable: true,
configurable: true,
writable: true,
value: (...args) => this.execHandler(FieldProps_1.FieldPropsEnum.onClear, args, (e) => {
(0, utils_1.isEvent)(e) && e.preventDefault();
this.clear(true, false);
})
});
/**
Event Handler: On Reset
*/
Object.defineProperty(this, "onReset", {
enumerable: true,
configurable: true,
writable: true,
value: (...args) => this.execHandler(FieldProps_1.FieldPropsEnum.onReset, args, (e) => {
(0, utils_1.isEvent)(e) && e.preventDefault();
this.reset(true, false);
})
});
/**
Event Handler: On Submit
*/
Object.defineProperty(this, "onSubmit", {
enumerable: true,
configurable: true,
writable: true,
value: (...args) => this.execHandler(FieldProps_1.FieldPropsEnum.onSubmit, args, (e, o = {}) => {
(0, utils_1.isEvent)(e) && e.preventDefault();
this.submit(o);
}, null, false)
});
/**
Event Handler: On Add
*/
Object.defineProperty(this, "onAdd", {
enumerable: true,
configurable: true,
writable: true,
value: (...args) => this.execHandler(FieldProps_1.FieldPropsEnum.onAdd, args, (e, val) => {
(0, utils_1.isEvent)(e) && e.preventDefault();
this.add((0, utils_1.isEvent)(val) ? null : val, false);
})
});
/**
Event Handler: On Del
*/
Object.defineProperty(this, "onDel", {
enumerable: true,
configurable: true,
writable: true,
value: (...args) => this.execHandler(FieldProps_1.FieldPropsEnum.onDel, args, (e, path) => {
(0, utils_1.isEvent)(e) && e.preventDefault();
this.del((0, utils_1.isEvent)(path) ? this.path : path, false);
})
});
(0, mobx_1.makeObservable)(this, {
$submitted: mobx_1.observable,
$submitting: mobx_1.observable,
$validated: mobx_1.observable,
$validating: mobx_1.observable,
$clearing: mobx_1.observable,
$resetting: mobx_1.observable,
$touched: mobx_1.observable,
$changed: mobx_1.observable,
$hooks: mobx_1.observable,
$handlers: mobx_1.observable,
changed: mobx_1.computed,
submitted: mobx_1.computed,
submitting: mobx_1.computed,
validated: mobx_1.computed,
validating: mobx_1.computed,
clearing: mobx_1.computed,
resetting: mobx_1.computed,
hasIncrementalKeys: mobx_1.computed,
hasNestedFields: mobx_1.computed,
size: mobx_1.computed,
// initialization
initField: mobx_1.action,
// actions
submit: mobx_1.action,
deepUpdate: mobx_1.action,
set: mobx_1.action,
add: mobx_1.action,
del: mobx_1.action,
});
}
get resetting() {
return this.hasNestedFields ? this.check(FieldProps_1.FieldPropsEnum.resetting, true) : this.$resetting;
}
get clearing() {
return this.hasNestedFields ? this.check(FieldProps_1.FieldPropsEnum.clearing, true) : this.$clearing;
}
get submitted() {
return (0, mobx_1.toJS)(this.$submitted);
}
get submitting() {
return (0, mobx_1.toJS)(this.$submitting);
}
get validated() {
return (0, mobx_1.toJS)(this.$validated);
}
get validating() {
return (0, mobx_1.toJS)(this.$validating);
}
get hasIncrementalKeys() {
return !!this.fields.size && (0, utils_1.hasIntKeys)(this.fields);
}
get hasNestedFields() {
return this.fields.size !== 0;
}
get size() {
return this.fields.size;
}
get changed() {
return !lodash_1.default.isNil(this.path) && this.hasNestedFields
? (this.reduce((acc, field) => (acc + field.changed), 0) + this.$changed)
: this.$changed;
}
/******************************************************************
Initializer
*/
initFields(initial, update = false) {
const fallback = this.state.options.get(OptionsModel_1.OptionsEnum.fallback);
const $path = (key) => lodash_1.default.trimStart([this.path, key].join("."), ".");
let fields;
fields = (0, parser_1.prepareFieldsData)(initial, this.state.strict, fallback);
fields = (0, parser_1.mergeSchemaDefaults)(fields, this.validator);
// create fields
lodash_1.default.forIn(fields, (field, key) => {
const path = $path(key);
const $f = this.select(path, null, false);
if (lodash_1.default.isNil($f)) {
if (fallback) {
this.initField(key, path, field, update);
}
else {
const structPath = (0, utils_1.pathToStruct)(path);
const struct = this.state.struct();
const found = struct
.filter((s) => s.startsWith(structPath))
.find((s) => s.charAt(structPath.length) === "." ||
s.substring(structPath.length, structPath.length + 2) === "[]" ||
s === structPath);
if (found)
this.initField(key, path, field, update);
}
}
});
}
initField(key, path, data, update = false) {
const initial = this.state.get("current", "props");
const struct = (0, utils_1.pathToStruct)(path);
// try to get props from separated objects
const _try = (prop) => {
const t = lodash_1.default.get(initial[prop], struct);
if ([
FieldProps_1.FieldPropsEnum.input,
FieldProps_1.FieldPropsEnum.output,
FieldProps_1.FieldPropsEnum.converter,
].includes(prop) && typeof t !== "function")
return undefined;
return t;
};
const props = {
$value: lodash_1.default.get(initial[FieldProps_1.SeparatedPropsMode.values], path),
$computed: _try(FieldProps_1.SeparatedPropsMode.computed),
$label: _try(FieldProps_1.SeparatedPropsMode.labels),
$placeholder: _try(FieldProps_1.SeparatedPropsMode.placeholders),
$default: _try(FieldProps_1.SeparatedPropsMode.defaults),
$initial: _try(FieldProps_1.SeparatedPropsMode.initials),
$disabled: _try(FieldProps_1.SeparatedPropsMode.disabled),
$deleted: _try(FieldProps_1.SeparatedPropsMode.deleted),
$type: _try(FieldProps_1.SeparatedPropsMode.types),
$related: _try(FieldProps_1.SeparatedPropsMode.related),
$rules: _try(FieldProps_1.SeparatedPropsMode.rules),
$options: _try(FieldProps_1.SeparatedPropsMode.options),
$bindings: _try(FieldProps_1.SeparatedPropsMode.bindings),
$extra: _try(FieldProps_1.SeparatedPropsMode.extra),
$hooks: _try(FieldProps_1.SeparatedPropsMode.hooks),
$handlers: _try(FieldProps_1.SeparatedPropsMode.handlers),
$validatedWith: _try(FieldProps_1.SeparatedPropsMode.validatedWith),
$validators: _try(FieldProps_1.SeparatedPropsMode.validators),
$observers: _try(FieldProps_1.SeparatedPropsMode.observers),
$interceptors: _try(FieldProps_1.SeparatedPropsMode.interceptors),
$converters: _try(FieldProps_1.SeparatedPropsMode.converters),
$input: _try(FieldProps_1.SeparatedPropsMode.input),
$output: _try(FieldProps_1.SeparatedPropsMode.output),
$autoFocus: _try(FieldProps_1.SeparatedPropsMode.autoFocus),
$ref: _try(FieldProps_1.SeparatedPropsMode.refs),
$nullable: _try(FieldProps_1.SeparatedPropsMode.nullable),
$autoComplete: _try(FieldProps_1.SeparatedPropsMode.autoComplete),
};
const field = this.state.form.makeField({
key,
path,
struct,
data,
props,
update,
state: this.state,
}, data && data[FieldProps_1.FieldPropsEnum.class] || _try(FieldProps_1.SeparatedPropsMode.classes));
this.fields.merge({ [key]: field });
return field;
}
/******************************************************************
Actions
*/
validate(opt, obj) {
const $opt = lodash_1.default.merge(opt, { path: this.path });
return this.state.form.validator.validate($opt, obj);
}
/**
Submit
*/
submit(hooks = {}, { execOnSubmitHook = true, execValidationHooks = true, validate = true } = {}) {
const execOnSubmit = () => this.execHook(FieldProps_1.FieldPropsEnum.onSubmit, hooks);
const submit = execOnSubmitHook ? execOnSubmit() : undefined;
this.$submitting = true;
this.$submitted += 1;
if (!validate || !this.state.options.get(OptionsModel_1.OptionsEnum.validateOnSubmit, this)) {
return Promise
.resolve(submit)
.then((0, mobx_1.action)(() => (this.$submitting = false)))
.catch((0, mobx_1.action)((err) => {
this.$submitting = false;
throw err;
}))
.then(() => this);
}
const exec = (isValid) => isValid
? this.execHook(ValidatorInterface_1.ValidationHooks.onSuccess, hooks)
: this.execHook(ValidatorInterface_1.ValidationHooks.onError, hooks);
return (this.validate({
showErrors: this.state.options.get(OptionsModel_1.OptionsEnum.showErrorsOnSubmit, this),
})
.then(({ isValid }) => {
const handler = execValidationHooks ? exec(isValid) : undefined;
if (isValid)
return Promise.all([submit, handler]);
const $err = this.state.options.get(OptionsModel_1.OptionsEnum.defaultGenericError, this);
const $throw = this.state.options.get(OptionsModel_1.OptionsEnum.submitThrowsError, this);
if ($throw && $err)
this.invalidate();
return Promise.all([submit, handler]);
})
.then((0, mobx_1.action)(() => (this.$submitting = false)))
.catch((0, mobx_1.action)((err) => {
this.$submitting = false;
throw err;
}))
.then(() => this));
}
/**
Check Field Computed Values
*/
check(prop, deep = false) {
(0, utils_1.allowedProps)(FieldProps_1.AllowedFieldPropsTypes.computed, [prop]);
return deep
? (0, utils_1.checkPropOccurrence)({
type: utils_1.props.occurrences[prop],
data: this.deepCheck(utils_1.props.occurrences[prop], prop, this.fields),
})
: this[prop];
}
deepCheck(type, prop, fields) {
const $fields = (0, utils_1.getObservableMapValues)(fields);
return lodash_1.default.transform($fields, (check, field) => {
if (!field.fields.size || !Array.isArray(field.initial)) {
check.push(field[prop]);
}
check.push((0, utils_1.checkPropOccurrence)({
data: this.deepCheck(type, prop, field.fields),
type,
}));
return check;
}, []);
}
/**
Update Field Values recurisvely
OR Create Field if 'undefined'
*/
update(fields) {
if (!lodash_1.default.isPlainObject(fields)) {
throw new Error("The update() method accepts only plain objects.");
}
this.deepUpdate((0, parser_1.prepareFieldsData)({ fields }, this.state.strict), undefined, undefined, fields);
}
deepUpdate(fields, path = "", recursion = true, raw) {
lodash_1.default.each(fields, (field, key) => {
var _a;
const $key = lodash_1.default.has(field, FieldProps_1.FieldPropsEnum.name) ? field.name : key;
const $path = lodash_1.default.trimStart(`${path}.${$key}`, ".");
const strictUpdate = this.state.options.get(OptionsModel_1.OptionsEnum.strictUpdate, this);
const $field = this.select($path, null, strictUpdate);
const $container = this.select(path, null, false) || this.state.form.select(this.path, null, false);
const applyInputConverterOnUpdate = this.state.options.get(OptionsModel_1.OptionsEnum.applyInputConverterOnUpdate, this);
if (!lodash_1.default.isNil($field) && !lodash_1.default.isUndefined(field)) {
if (Array.isArray($field.values())) {
const n = (_a = lodash_1.default.max(lodash_1.default.map(field.fields, (f, i) => Number(i)))) !== null && _a !== void 0 ? _a : -1;
(0, utils_1.getObservableMapValues)($field.fields).forEach(($f) => {
if (Number($f.name) > n) {
$field.$changed++;
$field.state.form.$changed++;
$field.fields.delete($f.name);
}
});
}
if (field === null || field === void 0 ? void 0 : field.fields) {
const fallback = this.state.options.get(OptionsModel_1.OptionsEnum.fallback);
const x = this.state.struct().findIndex(s => s.startsWith($field.path.replace(/\.\d+\./, '[].') + '[]'));
if (!fallback && $field.fields.size === 0 && x < 0) {
$field.value = (0, parser_1.parseInput)(applyInputConverterOnUpdate ? $field.$input : (val) => val, {
fallbackValueOption: this.state.options.get(OptionsModel_1.OptionsEnum.fallbackValue, this),
separated: lodash_1.default.get(raw, $path),
});
return;
}
}
if (lodash_1.default.isNull(field) || lodash_1.default.isNil(field.fields)) {
$field.value = (0, parser_1.parseInput)(applyInputConverterOnUpdate ? $field.$input : (val) => val, {
fallbackValueOption: this.state.options.get(OptionsModel_1.OptionsEnum.fallbackValue, this),
separated: field,
});
return;
}
}
if (!lodash_1.default.isNil($container) && lodash_1.default.isNil($field)) {
// get full path when using update() with select() - FIX: #179
const $newFieldPath = lodash_1.default.trimStart([this.path, $path].join("."), ".");
// init field into the container field
$container.$changed++;
$container.state.form.$changed++;
$container.initField($key, $newFieldPath, field, true);
}
else if (recursion) {
if (lodash_1.default.has(field, FieldProps_1.FieldPropsEnum.fields) && !lodash_1.default.isNil(field.fields)) {
// handle nested fields if defined
this.deepUpdate(field.fields, $path);
}
else {
// handle nested fields if undefined or null
const $fields = (0, parser_1.pathToFieldsTree)(this.state.struct(), $path);
this.deepUpdate($fields, $path, false);
}
}
});
}
/**
Get Fields Props
*/
get(prop = null, strict = true) {
if (lodash_1.default.isNil(prop)) {
return this.deepGet([...utils_1.props.computed, ...utils_1.props.editable, ...utils_1.props.validation], this.fields, strict);
}
(0, utils_1.allowedProps)(FieldProps_1.AllowedFieldPropsTypes.all, Array.isArray(prop) ? prop : [prop]);
if (lodash_1.default.isString(prop)) {
if ([
FieldProps_1.FieldPropsEnum.hooks,
FieldProps_1.FieldPropsEnum.handlers
].includes(prop)) {
return this[`$${prop}`];
}
if (strict && this.fields.size === 0) {
const retrieveNullifiedEmptyStrings = this.state.options.get(OptionsModel_1.OptionsEnum.retrieveNullifiedEmptyStrings, this);
return (0, parser_1.parseCheckOutput)(this, prop, strict ? retrieveNullifiedEmptyStrings : false);
}
const value = this.deepGet(prop, this.fields, strict);
const removeNullishValuesInArrays = this.state.options.get(OptionsModel_1.OptionsEnum.removeNullishValuesInArrays, this);
return (0, parser_1.parseCheckArray)(this, value, prop, strict ? removeNullishValuesInArrays : false);
}
return this.deepGet(prop, this.fields, strict);
}
/**
Get Fields Props Recursively
*/
deepGet(prop, fields, strict = true) {
return lodash_1.default.transform((0, utils_1.getObservableMapValues)(fields), (obj, field) => {
const $nested = ($fields) => $fields.size !== 0
? this.deepGet(prop, $fields, strict)
: undefined;
Object.assign(obj, {
[field.key]: { fields: $nested(field.fields) },
});
if (lodash_1.default.isString(prop)) {
const opt = this.state.options;
const removeProp = ((opt.get(OptionsModel_1.OptionsEnum.retrieveOnlyDirtyFieldsValues, this) && prop === FieldProps_1.FieldPropsEnum.value && field.isPristine) ||
(opt.get(OptionsModel_1.OptionsEnum.retrieveOnlyEnabledFieldsValues, this) && prop === FieldProps_1.FieldPropsEnum.value && field.disabled) ||
(opt.get(OptionsModel_1.OptionsEnum.retrieveOnlyEnabledFieldsErrors, this) && prop === FieldProps_1.FieldPropsEnum.error && field.disabled && field.isValid && (!field.error || !field.hasError)) ||
(opt.get(OptionsModel_1.OptionsEnum.softDelete, this) && prop === FieldProps_1.FieldPropsEnum.value && field.deleted));
if (field.fields.size === 0) {
delete obj[field.key];
if (removeProp)
return obj;
const retrieveNullifiedEmptyStrings = this.state.options.get(OptionsModel_1.OptionsEnum.retrieveNullifiedEmptyStrings, this);
return Object.assign(obj, {
[field.key]: (0, parser_1.parseCheckOutput)(field, prop, strict ? retrieveNullifiedEmptyStrings : false),
});
}
let value = this.deepGet(prop, field.fields, strict);
if (prop === FieldProps_1.FieldPropsEnum.value)
value = field.$output(value);
delete obj[field.key];
if (removeProp)
return obj;
const removeNullishValuesInArrays = this.state.options.get(OptionsModel_1.OptionsEnum.removeNullishValuesInArrays, this);
return Object.assign(obj, {
[field.key]: (0, parser_1.parseCheckArray)(field, value, prop, strict ? removeNullishValuesInArrays : false),
});
}
lodash_1.default.each(prop, ($prop) => Object.assign(obj[field.key], {
[$prop]: field[$prop],
}));
return obj;
}, {});
}
/**
Set Fields Props
*/
set(prop, data) {
// UPDATE CUSTOM PROP
if (lodash_1.default.isString(prop) && !lodash_1.default.isUndefined(data)) {
(0, utils_1.allowedProps)(FieldProps_1.AllowedFieldPropsTypes.editable, [prop]);
const isPlain = [
FieldProps_1.FieldPropsEnum.hooks,
FieldProps_1.FieldPropsEnum.handlers,
].includes(prop);
const deep = (lodash_1.default.isObject(data) && prop === FieldProps_1.FieldPropsEnum.value) || (lodash_1.default.isPlainObject(data) && !isPlain);
if (deep && this.hasNestedFields)
return this.deepSet(prop, data, "", true);
if (prop === FieldProps_1.FieldPropsEnum.value) {
const applyInputConverterOnSet = this.state.options.get(OptionsModel_1.OptionsEnum.applyInputConverterOnSet, this);
this.value = (0, parser_1.parseInput)(applyInputConverterOnSet ? this.$input : (val) => val, {
fallbackValueOption: this.state.options.get(OptionsModel_1.OptionsEnum.fallbackValue, this),
separated: data,
});
}
else if (isPlain) {
Object.assign(this[`$${prop}`], data);
}
else {
lodash_1.default.set(this, `$${prop}`, data);
}
return;
}
// NO PROP NAME PROVIDED ("prop" is value)
if (lodash_1.default.isNil(data)) {
if (this.hasNestedFields)
this.deepSet(FieldProps_1.FieldPropsEnum.value, prop, "", true);
else
this.set(FieldProps_1.FieldPropsEnum.value, prop);
}
}
/**
Set Fields Props Recursively
*/
deepSet(prop, data, path = "", recursion = false) {
const err = "You are updating a not existent field:";
const isStrict = this.state.options.get(OptionsModel_1.OptionsEnum.strictSet, this);
if (lodash_1.default.isNil(data)) {
this.each((field) => field.$value = (0, parser_1.defaultValue)({
fallbackValueOption: this.state.options.get(OptionsModel_1.OptionsEnum.fallbackValue, this),
value: field.$value,
nullable: field.$nullable,
type: field.type,
}));
return;
}
lodash_1.default.each(data, ($val, $key) => {
const $path = lodash_1.default.trimStart(`${path}.${$key}`, ".");
// get the field by path joining keys recursively
const field = this.select($path, null, isStrict);
// if no field found when is strict update, throw error
if (isStrict)
(0, utils_1.throwError)($path, field, err);
// update the field/fields if defined
if (!lodash_1.default.isUndefined(field)) {
// update field values or others props
if (!lodash_1.default.isUndefined($val)) {
field.set(prop, $val, recursion);
}
// update values recursively only if field has nested
if (field.fields.size && lodash_1.default.isObject($val)) {
this.deepSet(prop, $val, $path, recursion);
}
}
});
}
/**
Add Field
*/
add(obj, execEvent = true) {
if ((0, utils_1.isArrayOfObjects)(obj)) {
lodash_1.default.each(obj, (values) => this.update({
[(0, utils_1.maxKey)(this.fields)]: values,
}));
this.$changed++;
this.state.form.$changed++;
execEvent && this.execHook(FieldProps_1.FieldPropsEnum.onAdd);
return this;
}
let key;
if (lodash_1.default.has(obj, FieldProps_1.FieldPropsEnum.key))
key = obj.key;
if (lodash_1.default.has(obj, FieldProps_1.FieldPropsEnum.name))
key = obj.name;
if (!key)
key = (0, utils_1.maxKey)(this.fields);
const $path = ($key) => lodash_1.default.trimStart([this.path, $key].join("."), ".");
const tree = (0, parser_1.pathToFieldsTree)(this.state.struct(), this.path, 0, true);
const field = this.initField(key, $path(key), lodash_1.default.merge(tree[0], obj));
const hasValues = lodash_1.default.has(obj, FieldProps_1.FieldPropsEnum.value) || lodash_1.default.has(obj, FieldProps_1.FieldPropsEnum.fields);
if (!hasValues && !this.state.options.get(OptionsModel_1.OptionsEnum.preserveDeletedFieldsValues, this)) {
const fallbackValueOption = this.state.options.get(OptionsModel_1.OptionsEnum.fallbackValue, this);
field.$value = (0, parser_1.defaultValue)({ fallbackValueOption, value: field.$value, nullable: field.$nullable, type: field.type });
field.each((field) => field.$value = (0, parser_1.defaultValue)({
fallbackValueOption,
value: field.$value,
nullable: field.$nullable,
type: field.type
}));
}
this.$changed++;
this.state.form.$changed++;
execEvent && this.execHook(FieldProps_1.FieldPropsEnum.onAdd);
return field;
}
/**
Del Field
*/
del($path = null, execEvent = true) {
const isStrict = this.state.options.get(OptionsModel_1.OptionsEnum.strictDelete, this);
const path = (0, parser_1.parsePath)($path !== null && $path !== void 0 ? $path : this.path);
const fullpath = lodash_1.default.trim([this.path, path].join("."), ".");
const container = this.container($path);
const keys = lodash_1.default.split(path, ".");
const last = lodash_1.default.last(keys);
if (isStrict && !container.fields.has(last)) {
const msg = `Key "${last}" not found when trying to delete field`;
(0, utils_1.throwError)(fullpath, null, msg);
}
container.$changed++;
container.state.form.$changed++;
if (this.state.options.get(OptionsModel_1.OptionsEnum.softDelete, this)) {
return this.select(fullpath).set(FieldProps_1.FieldPropsEnum.deleted, true);
}
container.each((field) => field.debouncedValidation.cancel());
execEvent && this.execHook(FieldProps_1.FieldPropsEnum.onDel);
return container.fields.delete(last);
}
/******************************************************************
Events
*/
/**
MobX Event (observe/intercept)
*/
MOBXEvent({ prop = FieldProps_1.FieldPropsEnum.value, key = null, path = null, call, type }) {
let $prop = key || prop;
(0, utils_1.allowedProps)(FieldProps_1.AllowedFieldPropsTypes.observable, [$prop]);
const $instance = this.select(path || this.path, null, null) || this;
const $call = (change) => call.apply(null, [
{
change,
form: this.state.form,
path: $instance.path || null,
field: $instance.path ? $instance : null,
},
]);
let fn;
let ffn;
if (type === "observer") {
fn = mobx_1.observe;
ffn = (cb) => (0, mobx_1.observe)($instance.fields, cb); // fields
}
if (type === "interceptor") {
$prop = `$${prop}`;
fn = mobx_1.intercept;
ffn = $instance.fields.intercept; // fields
}
const $dkey = $instance.path ? `${$prop}@${$instance.path}` : $prop;
lodash_1.default.merge(this.state.disposers[type], {
[$dkey]: $prop === FieldProps_1.FieldPropsEnum.fields
? ffn.apply((change) => $call(change))
: fn($instance, $prop, (change) => $call(change)),
});
}
/**
Dispose MOBX Events
*/
dispose(opt = null) {
if (this.path && opt)
return this.disposeSingle(opt);
return this.disposeAll();
}
/**
Dispose All Events (observe/intercept)
*/
disposeAll() {
const dispose = (disposer) => disposer.apply();
lodash_1.default.each(this.state.disposers.interceptor, dispose);
lodash_1.default.each(this.state.disposers.observer, dispose);
this.state.disposers = { interceptor: {}, observer: {} };
return null;
}
/**
Dispose Single Event (observe/intercept)
*/
disposeSingle({ type, key = FieldProps_1.FieldPropsEnum.value, path = null }) {
const $path = (0, parser_1.parsePath)(path !== null && path !== void 0 ? path : this.path);
// eslint-disable-next-line
if (type === "interceptor")
key = `$${key}`; // target observables
this.state.disposers[type][`${key}@${$path}`].apply();
delete this.state.disposers[type][`${key}@${$path}`];
}
/******************************************************************
Utils
*/
/**
Fields Selector
*/
select(path, fields = null, isStrict = true) {
const $path = (0, parser_1.parsePath)(path);
const keys = lodash_1.default.split($path, ".");
const head = lodash_1.default.head(keys);
keys.shift();
let $fields = lodash_1.default.isNil(fields)
? this.fields.get(head)
: fields.get(head);
let stop = false;
lodash_1.default.each(keys, ($key) => {
if (stop)
return;
if (lodash_1.default.isNil($fields)) {
$fields = undefined;
stop = true;
}
else {
$fields = $fields.fields.get($key);
}
});
if (isStrict && this.state.options.get(OptionsModel_1.OptionsEnum.strictSelect, this)) {
(0, utils_1.throwError)(path, $fields);
}
return $fields;
}
/**
Get Container
*/
container($path) {
const path = (0, parser_1.parsePath)($path !== null && $path !== void 0 ? $path : this.path);
const cpath = lodash_1.default.trim(path.replace(new RegExp("[^./]+$"), ""), ".");
if (!!this.path && lodash_1.default.isNil($path)) {
return cpath !== ""
? this.state.form.select(cpath, null, false)
: this.state.form;
}
return cpath !== "" ? this.select(cpath, null, false) : this;
}
/**
Has Field
*/
has(path) {
return this.fields.has(path);
}
/**
Map Fields
*/
map(cb) {
return (0, utils_1.getObservableMapValues)(this.fields).map(cb);
}
/**
* Iterates deeply over fields and invokes `iteratee` for each element.
* The iteratee is invoked with three arguments: (value, index|key, depth).
*
* @param {Function} iteratee The function invoked per iteration.
* @param {Array|Object} [fields=form.fields] fields to iterate over.
* @param {number} [depth=1] The recursion depth for internal use.
* @returns {Array} Returns [fields.values()] of input [fields] parameter.
* @example
*
* JSON.stringify(form)
* // => {
* "fields": {
* "state": {
* "fields": {
* "city": {
* "fields": { "places": {
* "fields": {},
* "key": "places", "path": "state.city.places", "$value": "NY Places"
* }
* },
* "key": "city", "path": "state.city", "$value": "New York"
* }
* },
* "key": "state", "path": "state", "$value": "USA"
* }
* }
* }
*
* const data = {};
* form.each(field => data[field.path] = field.value);
* // => {
* "state": "USA",
* "state.city": "New York",
* "state.city.places": "NY Places"
* }
*
*/
each(iteratee, fields = null, depth = 0) {
const $fields = fields || this.fields;
(0, utils_1.getObservableMapValues)($fields).forEach((field, index) => {
iteratee(field, index, depth);
if (field.fields.size !== 0) {
this.each(iteratee, field.fields, depth + 1);
}
});
}
reduce(iteratee, acc) {
return (0, utils_1.getObservableMapValues)(this.fields).reduce(iteratee, acc);
}
/******************************************************************
Helpers
*/
/**
Fields Selector (alias of select)
*/
$(key) {
return this.select(key);
}
/**
Fields Values (recursive with Nested Fields)
*/
values() {
return this.get(FieldProps_1.FieldPropsEnum.value);
}
/**
Fields Errors (recursive with Nested Fields)
*/
errors() {
return this.get(FieldProps_1.FieldPropsEnum.error);
}
/**
Fields Labels (recursive with Nested Fields)
*/
labels() {
return this.get(FieldProps_1.FieldPropsEnum.label);
}
/**
Fields Placeholders (recursive with Nested Fields)
*/
placeholders() {
return this.get(FieldProps_1.FieldPropsEnum.placeholder);
}
/**
Fields Default Values (recursive with Nested Fields)
*/
defaults() {
return this.get(FieldProps_1.FieldPropsEnum.default);
}
/**
Fields Initial Values (recursive with Nested Fields)
*/
initials() {
return this.get(FieldProps_1.FieldPropsEnum.initial);
}
/**
Fields Types (recursive with Nested Fields)
*/
types() {
return this.get(FieldProps_1.FieldPropsEnum.type);
}
}
exports["default"] = Base;
/***/ }),
/***/ "./src/Bindings.ts":
/*!*************************!*\
!*** ./src/Bindings.ts ***!
\*************************/
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
const lodash_1 = __importDefault(__webpack_require__(/*! lodash */ "lodash"));
const utils_1 = __webpack_require__(/*! ./utils */ "./src/utils.ts");
const FieldProps_1 = __webpack_require__(/*! ./models/FieldProps */ "./src/models/FieldProps.ts");
class Bindings {
constructor() {
Object.defineProperty(this, "templates", {
enumerable: true,
configurable: true,
writable: true,
value: {
// default: ({ field, form, props, keys, $try }) => ({
// [keys.id]: $try(props.id, field.id),
// }),
}
});
Object.defineProperty(this, "rewriters", {
enumerable: true,
configurable: true,
writable: true,
value: {
default: {
id: FieldProps_1.FieldPropsEnum.id,
name: FieldProps_1.FieldPropsEnum.name,
type: FieldProps_1.FieldPropsEnum.type,
value: FieldProps_1.FieldPropsEnum.value,
checked: FieldProps_1.FieldPropsEnum.checked,
label: FieldProps_1.FieldPropsEnum.label,
placeholder: FieldProps_1.FieldPropsEnum.placeholder,
disabled: FieldProps_1.FieldPropsEnum.disabled,
autoComplete: FieldProps_1.FieldPropsEnum.autoComplete,
onChange: FieldProps_1.FieldPropsEnum.onChange,
onBlur: FieldProps_1.FieldPropsEnum.onBlur,
onFocus: FieldProps_1.FieldPropsEnum.onFocus,
autoFocus: FieldProps_1.FieldPropsEnum.autoFocus,
inputMode: FieldProps_1.FieldPropsEnum.inputMode,
onKeyUp: FieldProps_1.FieldPropsEnum.onKeyUp,
onKeyDown: FieldProps_1.FieldPropsEnum.onKeyDown,
},
}
});
}
register(bindings) {
lodash_1.default.each(bindings, (val, key) => {
if ((typeof val === 'function'))
lodash_1.default.merge(this.templates, { [key]: val });
if (lodash_1.default.isPlainObject(val))
lodash_1.default.merge(this.rewriters, { [key]: val });
});
return this;
}
load(field, name = FieldProps_1.FieldPropsEnum.default, props) {
const args = ({
keys: lodash_1.default.get(this.rewriters, FieldProps_1.FieldPropsEnum.default),
form: field.state.form,
field,
props,
$try: utils_1.$try,
});
if (lodash_1.default.has(this.templates, FieldProps_1.FieldPropsEnum.default)) {
return lodash_1.default.get(this.templates, name)(args);
}
if (lodash_1.default.has(this.rewriters, name)) {
const $bindings = {};
lodash_1.default.each(lodash_1.default.get(this.rewriters, name), ($v, $k) => lodash_1.default.merge($bindings, { [$v]: (0, utils_1.$try)(props[$k], field[$k]) }));
return $bindings;
}
return lodash_1.default.get(this.templates, name)(args);
}
}
exports["default"] = Bindings;
/***/ }),
/***/ "./src/Field.ts":
/*!**********************!*\
!*** ./src/Field.ts ***!
\**********************/
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
const mobx_1 = __webpack_require__(/*! mobx */ "mobx");
const lodash_1 = __importDefault(__webpack_require__(/*! lodash */ "lodash"));
const Base_1 = __importDefault(__webpack_require__(/*! ./Base */ "./src/Base.ts"));
const utils_1 = __webpack_require__(/*! ./utils */ "./src/utils.ts");
const parser_1 = __webpack_require__(/*! ./parser */ "./src/parser.ts");
const OptionsModel_1 = __webpack_require__(/*! ./models/OptionsModel */ "./src/models/OptionsModel.ts");
const FieldProps_1 = __webpack_require__(/*! ./models/FieldProps */ "./src/models/FieldProps.ts");
const applyFieldPropFunc = (instance, prop) => {
if (typeof prop !== 'function')
return prop;
return prop.apply(instance, [{
field: instance,
form: instance.state.form
}]);
};
const retrieveFieldPropFunc = (prop) => (typeof prop === 'function') ? prop : undefined;
const propGetter = (instance, prop) => (typeof instance[`_${prop}`] === 'function')
? instance[`_${prop}`].apply(instance, [{
form: instance.state.form,
field: instance,
}]) : instance[`$${prop}`];
const setupFieldProps = (instance, props, data) => Object.assign(instance, {
// retrieve functions
_label: retrieveFieldPropFunc(props.$label || (data === null || data === void 0 ? void 0 : data.label)),
_placeholder: retrieveFieldPropFunc(props.$placeholder || (data === null || data === void 0 ? void 0 : data.placeholder)),
_disabled: retrieveFieldPropFunc(props.$disabled || (data === null || data === void 0 ? void 0 : data.disabled)),
_rules: retrieveFieldPropFunc(props.$rules || (data === null || data === void 0 ? void 0 : data.rules)),
_related: retrieveFieldPropFunc(props.$related || (data === null || data === void 0 ? void 0 : data.related)),
_deleted: retrieveFieldPropFunc(props.$deleted || (data === null || data === void 0 ? void 0 : data.deleted)),
_validators: retrieveFieldPropFunc(props.$validators || (data === null || data === void 0 ? void 0 : data.validators)),
_validatedWith: retrieveFieldPropFunc(props.$validatedWith || (data === null || data === void 0 ? void 0 : data.validatedWith)),
_bindings: retrieveFieldPropFunc(props.$bindings || (data === null || data === void 0 ? void 0 : data.bindings)),
_extra: retrieveFieldPropFunc(props.$extra || (data === null || data === void 0 ? void 0 : data.extra)),
_options: retrieveFieldPropFunc(props.$options || (data === null || data === void 0 ? void 0 : data.options)),
_autoFocus: retrieveFieldPropFunc(props.$autoFocus || (data === null || data === void 0 ? void 0 : data.autoFocus)),
_inputMode: retrieveFieldPropFunc(props.$inputMode || (data === null || data === void 0 ? void 0 : data.inputMode)),
// apply functions or value
$label: applyFieldPropFunc(instance, props.$label || (data === null || data === void 0 ? void 0 : data.label) || ""),
$placeholder: applyFieldPropFunc(instance, props.$placeholder || (data === null || data === void 0 ? void 0 : data.placeholder) || ""),
$disabled: applyFieldPropFunc(instance, props.$disabled || (data === null || data === void 0 ? void 0 : data.disabled) || false),
$rules: applyFieldPropFunc(instance, props.$rules || (data === null || data === void 0 ? void 0 : data.rules) || null),
$related: applyFieldPropFunc(instance, props.$related || (data === null || data === void 0 ? void 0 : data.related) || []),
$deleted: applyFieldPropFunc(instance, props.$deleted || (data === null || data === void 0 ? void 0 : data.deleted) || false),
$validatedWith: applyFieldPropFunc(instance, props.$validatedWith || (data === null || data === void 0 ? void 0 : data.validatedWith) || FieldProps_1.FieldPropsEnum.value),
$bindings: applyFieldPropFunc(instance, props.$bindings || (data === null || data === void 0 ? void 0 : data.bindings) || FieldProps_1.FieldPropsEnum.default),
$extra: applyFieldPropFunc(instance, props.$extra || (data === null || data === void 0 ? void 0 : data.extra) || null),
$options: applyFieldPropFunc(instance, props.$options || (data === null || data === void 0 ? void 0 : data.options) || {}),
$autoFocus: applyFieldPropFunc(instance, props.$autoFocus || (data === null || data === void 0 ? void 0 : data.autoFocus) || false),
$inputMode: applyFieldPropFunc(instance, props.$inputMode || (data === null || data === void 0 ? void 0 : data.inputMode) || undefined),
$validators: applyFieldPropFunc(instance, props.$validators || (data === null || data === void 0 ? void 0 : data.validators) || null),
// other props
$hooks: props.$hooks || (data === null || data === void 0 ? void 0 : data.hooks) || {},
$handlers: props.$handlers || (data === null || data === void 0 ? void 0 : data.handlers) || {},
$observers: props.$observers || (data === null || data === void 0 ? void 0 : data.observers) || null,
$interceptors: props.$interceptors || (data === null || data === void 0 ? void 0 : data.interceptors) || null,
$ref: props.$ref || (data === null || data === void 0 ? void 0 : data.ref) || undefined,
$nullable: props.$nullable || (data === null || data === void 0 ? void 0 : data.nullable) || false,
$autoComplete: props.$autoComplete || (data === null || data === void 0 ? void 0 : data.autoComplete) || undefined,
});
const setupDefaultProp = (instance, data, props, update, { isEmptyArray, fallbackValueOption }) => (0, parser_1.parseInput)((val) => val, {
isEmptyArray,
type: instance.type,
unified: update
? (0, parser_1.defaultValue)({
fallbackValueOption,
type: instance.type,
value: instance.value
})
: data === null || data === void 0 ? void 0 : data.default,
separated: props.$default,
fallback: instance.$initial,
});
class Field extends Base_1.default {
constructor({ key, path, struct, data = {}, props = {}, update = false, state, }) {
super();
Object.defineProperty(this, "hasInitialNestedFields", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
Object.defineProperty(this, "incremental", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
Object.defineProperty(this, "id", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "key", {
enumerable: true,
configurable: true,
writable: true,
value: v