mobx-form
Version:
A simple form helper for mobx
1,333 lines (1,130 loc) • 39.6 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('mobx')) :
typeof define === 'function' && define.amd ? define(['exports', 'mobx'], factory) :
(global = global || self, factory(global.MobxForm = {}, global.mobx));
}(this, (function (exports, mobx) { 'use strict';
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
try {
var info = gen[key](arg);
var value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
Promise.resolve(value).then(_next, _throw);
}
}
function _asyncToGenerator(fn) {
return function () {
var self = this,
args = arguments;
return new Promise(function (resolve, reject) {
var gen = fn.apply(self, args);
function _next(value) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
}
function _throw(err) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
}
_next(undefined);
});
};
}
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
function _defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
return Constructor;
}
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
function ownKeys(object, enumerableOnly) {
var keys = Object.keys(object);
if (Object.getOwnPropertySymbols) {
var symbols = Object.getOwnPropertySymbols(object);
if (enumerableOnly) symbols = symbols.filter(function (sym) {
return Object.getOwnPropertyDescriptor(object, sym).enumerable;
});
keys.push.apply(keys, symbols);
}
return keys;
}
function _objectSpread2(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i] != null ? arguments[i] : {};
if (i % 2) {
ownKeys(Object(source), true).forEach(function (key) {
_defineProperty(target, key, source[key]);
});
} else if (Object.getOwnPropertyDescriptors) {
Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
} else {
ownKeys(Object(source)).forEach(function (key) {
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
});
}
}
return target;
}
function _objectWithoutPropertiesLoose(source, excluded) {
if (source == null) return {};
var target = {};
var sourceKeys = Object.keys(source);
var key, i;
for (i = 0; i < sourceKeys.length; i++) {
key = sourceKeys[i];
if (excluded.indexOf(key) >= 0) continue;
target[key] = source[key];
}
return target;
}
function _objectWithoutProperties(source, excluded) {
if (source == null) return {};
var target = _objectWithoutPropertiesLoose(source, excluded);
var key, i;
if (Object.getOwnPropertySymbols) {
var sourceSymbolKeys = Object.getOwnPropertySymbols(source);
for (i = 0; i < sourceSymbolKeys.length; i++) {
key = sourceSymbolKeys[i];
if (excluded.indexOf(key) >= 0) continue;
if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue;
target[key] = source[key];
}
}
return target;
}
function _toConsumableArray(arr) {
return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread();
}
function _arrayWithoutHoles(arr) {
if (Array.isArray(arr)) return _arrayLikeToArray(arr);
}
function _iterableToArray(iter) {
if (typeof Symbol !== "undefined" && Symbol.iterator in Object(iter)) return Array.from(iter);
}
function _unsupportedIterableToArray(o, minLen) {
if (!o) return;
if (typeof o === "string") return _arrayLikeToArray(o, minLen);
var n = Object.prototype.toString.call(o).slice(8, -1);
if (n === "Object" && o.constructor) n = o.constructor.name;
if (n === "Map" || n === "Set") return Array.from(o);
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
}
function _arrayLikeToArray(arr, len) {
if (len == null || len > arr.length) len = arr.length;
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
return arr2;
}
function _nonIterableSpread() {
throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
var jqTrim = function ( str ) {
return ((str || '') + '').trim();
};
var now = function () {
return Date.now();
};
/**
* returns a new function than will be called after "ms" number of milliseconds
* after the last call to it
*
* This is useful to execute a function that might occur too often
*
* @method debounce
* @static
* @param f {Function} the function to debounce
* @param ms {Number} the number of milliseconds to wait. If any other call
* is made before that threshold the waiting will be restarted
* @param [ctx=undefined] {Object} the context on which this function will be executed
* (the 'this' object inside the function wil be set to context)
* @param [immediate=undefined] {Boolean} if the function should be executed in the leading edge or the trailing edge
* ```
*/
var debouncy = function debounce( f, ms, ctx, immediate ) {
var ts, fn;
var timeout = null;
var args;
fn = function () {
ctx = ctx || this;
args = arguments;
ts = now();
var later = function () {
var diff = now() - ts;
if ( diff < ms ) {
timeout = setTimeout( later, ms - diff );
return;
}
timeout = null;
if ( !immediate ) {
f.apply( ctx, args );
}
};
if ( timeout === null ) {
if ( immediate ) {
f.apply( ctx, args );
}
timeout = setTimeout( later, ms );
}
};
fn.cancel = function () {
clearTimeout( timeout );
timeout = null; // properly clear the timeout so the next call to fn works as expected
};
return fn;
};
var isNullishOrEmpty = function isNullishOrEmpty(value) {
return typeof value === 'undefined' || value === null || value === '';
};
/**
* Field class provides abstract the validation of a single field
*/
var Field = /*#__PURE__*/function () {
_createClass(Field, [{
key: "resetInteractedFlag",
value: function resetInteractedFlag() {
this._interacted = false;
}
}, {
key: "markAsInteracted",
value: function markAsInteracted() {
this._interacted = true;
}
}, {
key: "resetValidatedOnce",
value: function resetValidatedOnce() {
this._validatedOnce = false;
}
}, {
key: "_setValueOnly",
value: function _setValueOnly(val) {
if (!this._interacted) {
this._interacted = true;
}
if (this._value === val) {
return;
}
this._value = val;
}
}, {
key: "_setValue",
value: function _setValue(val) {
if (this._value !== val && this._clearErrorOnValueChange && !this.valid) {
this.resetError();
}
this._setValueOnly(val);
if (this._autoValidate) {
this._debouncedValidation();
}
}
/**
* setter for the value of the field
*/
}, {
key: "setValue",
/**
* set the value of the field, optionaly
* reset the errorMessage and interacted flags
*
* @param {any} value
* @param { object} params the options object
* @param {Boolean} params.resetInteractedFlag whether or not to reset the interacted flag
*
*/
value: function setValue(value) {
var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
resetInteractedFlag = _ref.resetInteractedFlag,
commit = _ref.commit;
if (resetInteractedFlag) {
this._setValueOnly(value);
this.rawError = undefined;
this._interacted = false;
} else {
this._setValue(value);
}
if (commit) {
this.commit();
}
}
/**
* Restore the initial value of the field
*/
}, {
key: "restoreInitialValue",
value: function restoreInitialValue() {
var _ref2 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
_ref2$resetInteracted = _ref2.resetInteractedFlag,
resetInteractedFlag = _ref2$resetInteracted === void 0 ? true : _ref2$resetInteracted,
_ref2$commit = _ref2.commit,
commit = _ref2$commit === void 0 ? true : _ref2$commit;
this.setValue(this._initialValue, {
resetInteractedFlag: resetInteractedFlag,
commit: commit
});
}
}, {
key: "commit",
value: function commit() {
this._initialValue = this.value;
}
/**
* clear the valid state of the field by
* removing the errorMessage string. A field is
* considered valid if the errorMessage is not empty
*/
}, {
key: "resetError",
value: function resetError() {
this.rawError = undefined;
}
}, {
key: "clearValidation",
value: function clearValidation() {
this.resetError();
}
/**
* mark the field as already blurred so validation can
* start to be applied to the field.
*/
}, {
key: "_doValidate",
value: function () {
var _doValidate2 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {
var _validateFn, model, ret, i, vfn, _ret;
return regeneratorRuntime.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_validateFn = this._validateFn, model = this.model;
if (_validateFn) {
_context.next = 3;
break;
}
return _context.abrupt("return", Promise.resolve(true));
case 3:
if (!Array.isArray(_validateFn)) {
_context.next = 25;
break;
}
i = 0;
case 5:
if (!(i < _validateFn.length)) {
_context.next = 23;
break;
}
vfn = _validateFn[i];
if (!(typeof vfn !== 'function')) {
_context.next = 9;
break;
}
throw new Error('Validator must be a function or a function[]');
case 9:
_context.prev = 9;
_context.next = 12;
return vfn(this, model.fields, model);
case 12:
ret = _context.sent;
if (!(ret === false || ((_ret = ret) === null || _ret === void 0 ? void 0 : _ret.error))) {
_context.next = 15;
break;
}
return _context.abrupt("return", ret);
case 15:
_context.next = 20;
break;
case 17:
_context.prev = 17;
_context.t0 = _context["catch"](9);
return _context.abrupt("return", Promise.reject(_context.t0));
case 20:
i++;
_context.next = 5;
break;
case 23:
_context.next = 32;
break;
case 25:
_context.prev = 25;
ret = _validateFn(this, model.fields, model);
_context.next = 32;
break;
case 29:
_context.prev = 29;
_context.t1 = _context["catch"](25);
return _context.abrupt("return", Promise.reject(_context.t1));
case 32:
return _context.abrupt("return", ret);
case 33:
case "end":
return _context.stop();
}
}
}, _callee, this, [[9, 17], [25, 29]]);
}));
function _doValidate() {
return _doValidate2.apply(this, arguments);
}
return _doValidate;
}()
}, {
key: "setDisabled",
value: function setDisabled(disabled) {
if (disabled) {
this.resetError();
}
this._disabled = disabled;
}
}, {
key: "_validate",
/**
* validate the field. If force is true the validation will be perform
* even if the field was not initially interacted or blurred
*
* @param params {object} arguments object
* @param params.force {boolean} [force=false]
*/
value: function _validate() {
var _this = this;
var _ref3 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
_ref3$force = _ref3.force,
force = _ref3$force === void 0 ? false : _ref3$force;
var required = this.required;
if (!this._validatedOnce) {
this._validatedOnce = true;
}
var shouldSkipValidation = this.disabled || !required && !this._validateFn;
if (shouldSkipValidation) return;
if (!force) {
var userDidntInteractedWithTheField = !this._interacted;
if (userDidntInteractedWithTheField && !this.hasValue) {
// if we're not forcing the validation
// and we haven't interacted with the field
// we asume this field pass the validation status
this.resetError();
return;
} // if the field requires the user to lost focus before starting the validation
// we wait until the field is marked as blurredOnce. Except in the case the
// field has an error already in which case we do want to execute the validation
if (this.waitForBlur && !this._blurredOnce && !this.errorMessage) {
return;
}
} else {
this._blurredOnce = true;
}
if (required) {
if (!this.hasValue) {
// we can indicate that the field is required by passing the error message as the value of
// the required field. If we pass a boolean or a function then the value of the error message
// can be set in the requiredMessage field of the validator descriptor
this.setError({
message: typeof this._required === 'string' ? this._required : "Field: \"".concat(this.name, "\" is required")
});
return;
}
this.resetError();
}
this.setValidating(true);
var validationTs = this._validationTs = Date.now();
var res = this._doValidate(); // eslint-disable-next-line consistent-return
return new Promise(function (resolve) {
res.then(mobx.action(function (res_) {
if (validationTs !== _this._validationTs) return; // ignore stale validations
_this.setValidating(false); // if the function returned a boolean we assume it is
// the flag for the valid state
if (typeof res_ === 'boolean') {
_this.setErrorMessage(res_ ? undefined : _this.originalErrorMessage);
resolve();
return;
}
if (res_ && res_.error) {
_this.setErrorMessage(res_.error);
resolve();
return;
}
_this.resetError();
resolve(); // we use this to chain validators
}), mobx.action(function () {
var errorArg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
if (validationTs !== _this._validationTs) return; // ignore stale validations
_this.setValidating(false);
var error = errorArg.error,
message = errorArg.message;
var errorToSet = errorArg;
if (!message) {
errorToSet = _objectSpread2(_objectSpread2({}, errorToSet), {}, {
message: message || _this.originalErrorMessage
});
}
if (error) {
errorToSet = _objectSpread2(_objectSpread2({}, errorToSet), {}, {
message: error
});
}
_this.setError(errorToSet);
resolve(); // we use this to chain validators
}));
});
}
}, {
key: "setErrorMessage",
value: function setErrorMessage(msg) {
if (jqTrim(msg) === '') {
msg = undefined;
}
if (!msg) {
this.resetError();
} else {
this.setError({
message: msg
});
}
}
}, {
key: "setError",
value: function setError(error) {
this.rawError = error;
}
}, {
key: "validatedAtLeastOnce",
get: function get() {
return this._validatedOnce;
}
}, {
key: "waitForBlur",
get: function get() {
return !!this._waitForBlur;
}
}, {
key: "disabled",
get: function get() {
return !!this._disabled;
}
}, {
key: "required",
get: function get() {
if (this.disabled) return false;
return !!this._required;
}
}, {
key: "hasValue",
get: function get() {
if (this._hasValueFn) {
return this._hasValueFn(this.value);
} // consider the case where the value is an array
// we consider it actually has a value if the value is defined
// and the array is not empy
if (Array.isArray(this.value)) {
return this.value.length > 0;
}
return !isNullishOrEmpty(this.value);
}
/**
* flag to know if a validation is in progress on this field
*/
}, {
key: "blurred",
get: function get() {
return !!this._blurredOnce;
}
/** the raw error in caes validator throws a real error */
}, {
key: "errorMessage",
/**
* the error message associated with this field.
* This is used to indicate what error happened during
* the validation process
*/
get: function get() {
var _this$rawError;
return (_this$rawError = this.rawError) === null || _this$rawError === void 0 ? void 0 : _this$rawError.message;
}
/**
* whether the validation should be launch after a
* new value is set in the field. This is usually associated
* to forms that set the value on the fields after each
* onChange event
*/
}, {
key: "autoValidate",
get: function get() {
return this._autoValidate;
}
/**
* used to keep track of the original message
*/
}, {
key: "valid",
/**
* whether the field is valid or not
*/
get: function get() {
return !this.errorMessage;
}
/**
* whether the user has interacted or not with the field
*/
}, {
key: "interacted",
get: function get() {
return this._interacted;
}
/**
* get the value set on the field
*/
}, {
key: "value",
get: function get() {
return this._value;
},
set: function set(val) {
this._setValue(val);
}
}, {
key: "dirty",
get: function get() {
return this._initialValue !== this.value;
}
}, {
key: "originalErrorMessage",
get: function get() {
return this._originalErrorMessage || "Validation for \"".concat(this.name, "\" failed");
}
}, {
key: "validating",
get: function get() {
return this._validating;
}
}, {
key: "error",
get: function get() {
return this.errorMessage;
}
}]);
function Field(model, value) {
var _this2 = this;
var validatorDescriptor = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
var fieldName = arguments.length > 3 ? arguments[3] : undefined;
_classCallCheck(this, Field);
this._disabled = void 0;
this._required = void 0;
this._validatedOnce = false;
this._validating = false;
this._initialValue = void 0;
this._value = void 0;
this._interacted = void 0;
this._blurredOnce = false;
this.rawError = void 0;
this._autoValidate = false;
this._originalErrorMessage = void 0;
this.markBlurredAndValidate = function () {
if (!_this2._blurredOnce) {
_this2._blurredOnce = true;
}
_this2.validate();
};
this.validate = function (opts) {
_this2._debouncedValidation.cancel();
return _this2._validate(opts);
};
this.setValidating = function (validating) {
_this2._validating = validating;
};
this.setRequired = function (val) {
_this2._required = val;
};
mobx.makeObservable(this, {
resetValidatedOnce: mobx.action,
_validatedOnce: mobx.observable,
validatedAtLeastOnce: mobx.computed,
_disabled: mobx.observable,
_required: mobx.observable,
waitForBlur: mobx.computed,
disabled: mobx.computed,
required: mobx.computed,
resetInteractedFlag: mobx.action,
markAsInteracted: mobx.action,
hasValue: mobx.computed,
_autoValidate: mobx.observable,
_value: mobx.observable,
_initialValue: mobx.observable,
_interacted: mobx.observable,
_blurredOnce: mobx.observable,
dirty: mobx.computed,
blurred: mobx.computed,
errorMessage: mobx.computed,
rawError: mobx.observable.ref,
setError: mobx.action,
resetError: mobx.action,
error: mobx.computed,
autoValidate: mobx.computed,
valid: mobx.computed,
validating: mobx.computed,
_validating: mobx.observable,
setValidating: mobx.action,
interacted: mobx.computed,
_setValueOnly: mobx.action,
_setValue: mobx.action,
setValue: mobx.action,
restoreInitialValue: mobx.action,
commit: mobx.action,
clearValidation: mobx.action,
markBlurredAndValidate: mobx.action,
_doValidate: mobx.action,
setDisabled: mobx.action,
validate: mobx.action,
originalErrorMessage: mobx.computed,
_validate: mobx.action,
setRequired: mobx.action,
setErrorMessage: mobx.action
});
var DEBOUNCE_THRESHOLD = 300;
this._value = value;
this.model = model;
this.name = fieldName;
this._initialValue = value;
var waitForBlur = validatorDescriptor.waitForBlur,
disabled = validatorDescriptor.disabled,
errorMessage = validatorDescriptor.errorMessage,
validator = validatorDescriptor.validator,
hasValue = validatorDescriptor.hasValue,
required = validatorDescriptor.required,
_validatorDescriptor$ = validatorDescriptor.autoValidate,
autoValidate = _validatorDescriptor$ === void 0 ? true : _validatorDescriptor$,
meta = validatorDescriptor.meta,
_validatorDescriptor$2 = validatorDescriptor.validationDebounceThreshold,
validationDebounceThreshold = _validatorDescriptor$2 === void 0 ? DEBOUNCE_THRESHOLD : _validatorDescriptor$2,
clearErrorOnValueChange = validatorDescriptor.clearErrorOnValueChange;
this._debouncedValidation = debouncy(this._validate, validationDebounceThreshold);
this._waitForBlur = waitForBlur;
this._originalErrorMessage = errorMessage;
this._validateFn = validator;
this._clearErrorOnValueChange = clearErrorOnValueChange; // useful to determine if the field has a value set
// only used if provided
this._hasValueFn = hasValue;
this._required = required;
this._autoValidate = autoValidate;
this._disabled = disabled;
this.meta = meta; // store other props passed on the fields
}
return Field;
}();
var toString = Object.prototype.toString;
var isObject = function isObject(o) {
return o && toString.call(o) === '[object Object]';
};
/**
* a helper class to generate a dynamic form
* provided some keys and validators descriptors
*
* @export
* @class FormModel
*/
var FormModel = /*#__PURE__*/function () {
_createClass(FormModel, [{
key: "restoreInitialValues",
/**
* Restore the initial values set at the creation time of the model
* */
value: function restoreInitialValues(opts) {
this._eachField(function (field) {
return field.restoreInitialValue(opts);
});
}
}, {
key: "commit",
value: function commit() {
this._eachField(function (field) {
return field.commit();
});
}
}, {
key: "updateFrom",
/**
* Set multiple values to more than one field a time using an object
* where each key is the name of a field. The value will be set to each
* field and from that point on the values set are considered the new
* initial values. Validation and interacted flags are also reset if the second argument is true
* */
value: function updateFrom(obj) {
var _this = this;
var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var _ref$resetInteractedF = _ref.resetInteractedFlag,
resetInteractedFlag = _ref$resetInteractedF === void 0 ? true : _ref$resetInteractedF,
opts = _objectWithoutProperties(_ref, ["resetInteractedFlag"]);
Object.keys(obj).forEach(function (key) {
return _this.updateField(key, obj[key], _objectSpread2({
resetInteractedFlag: resetInteractedFlag
}, opts));
});
}
/**
* return the array of errors found. The array is an Array<String>
* */
}, {
key: "validate",
/**
* Manually perform the form validation
* */
value: function validate() {
var _this2 = this;
this._validating = true;
return Promise.all(this._fieldKeys().map(function (key) {
var field = _this2.fields[key];
return Promise.resolve(field.validate({
force: true
}));
})).then(function () {
_this2.setValidating(false);
}).catch(function () {
_this2.setValidating(false);
});
}
/**
* Update the value of the field identified by the provided name.
* Optionally if reset is set to true, interacted and
* errorMessage are cleared in the Field.
* */
}, {
key: "updateField",
value: function updateField(name, value) {
var opts = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
var throwIfMissingField = opts.throwIfMissingField,
restOpts = _objectWithoutProperties(opts, ["throwIfMissingField"]);
var theField = this._getField(name, {
throwIfMissingField: throwIfMissingField
});
theField === null || theField === void 0 ? void 0 : theField.setValue(value, restOpts);
}
/**
* return the data as plain Javascript object (mobx magic removed from the fields)
* */
}, {
key: "validatedAtLeastOnce",
get: function get() {
var _this3 = this;
var keys = Object.keys(this.fields);
return keys.every(function (key) {
return _this3.fields[key].validatedAtLeastOnce;
});
}
}, {
key: "dataIsReady",
get: function get() {
return this.interacted && this.requiredAreFilled && this.valid;
}
}, {
key: "requiredFields",
get: function get() {
var _this4 = this;
var keys = Object.keys(this.fields);
return keys.filter(function (key) {
return _this4.fields[key].required;
});
}
}, {
key: "requiredAreFilled",
get: function get() {
var _this5 = this;
var keys = Object.keys(this.fields);
return keys.every(function (key) {
var field = _this5.fields[key];
if (field.required) {
return !!field.hasValue;
}
return true;
});
}
}, {
key: "valid",
// flag to indicate whether the form is valid or not
// since some of the validators might be async validators
// this value might be false until the validation process finish
get: function get() {
var _this6 = this;
if (this._validating) {
return false; // consider the form invalid until the validation process finish
}
var keys = Object.keys(this.fields);
return keys.every(function (key) {
var field = _this6.fields[key];
return !!field.valid;
});
}
/**
* whether or not the form has been "interacted", meaning that at
* least a value has set on any of the fields after the model
* has been created
*/
}, {
key: "interacted",
get: function get() {
var _this7 = this;
var keys = this._fieldKeys();
return keys.some(function (key) {
var field = _this7.fields[key];
return !!field.interacted;
});
}
}, {
key: "dirty",
get: function get() {
var _this8 = this;
return this._fieldKeys().some(function (key) {
var f = _this8._getField(key);
return f.dirty;
});
}
}, {
key: "summary",
get: function get() {
var _this9 = this;
return this._fieldKeys().reduce(function (seq, key) {
var field = _this9.fields[key];
if (field.errorMessage) {
seq.push(field.errorMessage);
}
return seq;
}, []);
}
}, {
key: "validating",
get: function get() {
var _this10 = this;
return this._validating || this._fieldKeys().some(function (key) {
var f = _this10._getField(key);
return f.validating;
});
}
}, {
key: "serializedData",
get: function get() {
var _this11 = this;
var keys = Object.keys(this.fields);
return mobx.toJS(keys.reduce(function (seq, key) {
var field = _this11.fields[key];
var value = mobx.toJS(field.value); // this is required to make sure forms that use the serializedData object
// have the values without leading or trailing spaces
seq[key] = typeof value === 'string' ? jqTrim(value) : value;
return seq;
}, {}));
}
/**
* Creates an instance of FormModel.
*
* @param {Object|Array} [descriptors={}]
* @param {Object} [initialState={}]
*
* initialState => an object which keys are the names of the fields and the values the initial values for the form.
* validators => an object which keys are the names of the fields and the values are the descriptors for the validators
*/
}]);
function FormModel() {
var _this12 = this;
var _ref2 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
_ref2$descriptors = _ref2.descriptors,
descriptors = _ref2$descriptors === void 0 ? {} : _ref2$descriptors,
initialState = _ref2.initialState,
_ref2$options = _ref2.options,
options = _ref2$options === void 0 ? {} : _ref2$options;
_classCallCheck(this, FormModel);
this.fields = {};
this._validating = false;
this.setValidating = function (validating) {
_this12._validating = validating;
};
this.addFields = function (fieldsDescriptor) {
if (fieldsDescriptor == null || !isObject(fieldsDescriptor) && !Array.isArray(fieldsDescriptor)) {
throw new Error('fieldDescriptor has to be an Object or an Array');
}
if (Array.isArray(fieldsDescriptor)) {
fieldsDescriptor.forEach(function (field) {
var value = field.value,
name = field.name,
descriptor = _objectWithoutProperties(field, ["value", "name"]);
_this12._createField({
value: value,
name: name,
descriptor: descriptor
});
});
return;
}
var fieldsToAdd = Object.keys(fieldsDescriptor);
fieldsToAdd.forEach(function (key) {
var _fieldsDescriptor$key = fieldsDescriptor[key],
value = _fieldsDescriptor$key.value,
descriptor = _objectWithoutProperties(_fieldsDescriptor$key, ["value"]);
_this12._createField({
value: value,
name: key,
descriptor: descriptor
});
});
};
mobx.makeObservable(this, {
resetValidatedOnce: mobx.action,
validatedAtLeastOnce: mobx.computed,
dataIsReady: mobx.computed,
requiredFields: mobx.computed,
requiredAreFilled: mobx.computed,
fields: mobx.observable,
_validating: mobx.observable,
setValidating: mobx.action,
validating: mobx.computed,
valid: mobx.computed,
interacted: mobx.computed,
restoreInitialValues: mobx.action,
updateFrom: mobx.action,
summary: mobx.computed,
validate: mobx.action,
updateField: mobx.action,
serializedData: mobx.computed,
resetInteractedFlag: mobx.action,
disableFields: mobx.action,
addFields: mobx.action,
enableFields: mobx.action,
commit: mobx.action,
dirty: mobx.computed
});
this.addFields(descriptors);
initialState && this.updateFrom(initialState, {
throwIfMissingField: options.throwIfMissingField,
commit: true
});
}
_createClass(FormModel, [{
key: "_getField",
value: function _getField(name) {
var _ref3 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
_ref3$throwIfMissingF = _ref3.throwIfMissingField,
throwIfMissingField = _ref3$throwIfMissingF === void 0 ? true : _ref3$throwIfMissingF;
var theField = this.fields[name];
if (!theField && throwIfMissingField) {
throw new Error("Field \"".concat(name, "\" not found"));
}
return theField;
}
}, {
key: "_eachField",
value: function _eachField(cb) {
var _this13 = this;
Object.keys(this.fields).forEach(function (key) {
return cb(_this13.fields[key]);
});
}
}, {
key: "_fieldKeys",
value: function _fieldKeys() {
return Object.keys(this.fields);
}
}, {
key: "resetInteractedFlag",
value: function resetInteractedFlag() {
this._eachField(function (field) {
return field.resetInteractedFlag();
});
}
}, {
key: "disableFields",
value: function disableFields(fieldKeys) {
var _this14 = this;
if (!Array.isArray(fieldKeys)) throw new TypeError('fieldKeys should be an array with the names of the fields to disable');
fieldKeys.forEach(function (key) {
var field = _this14._getField(key);
field.setDisabled(true);
});
}
}, {
key: "_createField",
value: function _createField(_ref4) {
var value = _ref4.value,
name = _ref4.name,
descriptor = _ref4.descriptor;
mobx.extendObservable(this.fields, _defineProperty({}, name, new Field(this, value, descriptor, name)));
}
}, {
key: "enableFields",
value: function enableFields(fieldKeys) {
var _this15 = this;
if (!Array.isArray(fieldKeys)) throw new TypeError('fieldKeys should be an array with the names of the fields to disable');
fieldKeys.forEach(function (key) {
var field = _this15._getField(key);
field.setDisabled(false);
});
}
}, {
key: "resetValidatedOnce",
value: function resetValidatedOnce() {
var _this16 = this;
this._fieldKeys().forEach(function (key) {
_this16.fields[key].resetValidatedOnce();
});
}
}]);
return FormModel;
}();
/**
* return an instance of a FormModel refer to the constructor
*
* @param {Object|Array} fieldDescriptors
* @param {Object} initialState
* @param {Object} options
*/
var createModel = function createModel(_ref5) {
var descriptors = _ref5.descriptors,
initialState = _ref5.initialState,
options = _ref5.options;
return new FormModel({
descriptors: descriptors,
initialState: initialState,
options: options
});
};
var createModelFromState = function createModelFromState() {
var initialState = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var validators = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
var stateKeys = Object.keys(initialState);
var validatorsKeys = Object.keys(validators);
var descriptors = Array.from(new Set([].concat(_toConsumableArray(stateKeys), _toConsumableArray(validatorsKeys))), function (key) {
return _objectSpread2(_objectSpread2({}, validators[key] || {}), {}, {
value: initialState[key],
name: key
});
});
return createModel({
descriptors: descriptors,
options: options
});
};
exports.FormModel = FormModel;
exports.createModel = createModel;
exports.createModelFromState = createModelFromState;
Object.defineProperty(exports, '__esModule', { value: true });
})));