@zensen/form-service
Version:
A reactive form service framework
650 lines (523 loc) • 23.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = void 0;
var _fastDeepEqual = _interopRequireDefault(require("fast-deep-equal"));
var _error = require("./error");
var _utils = require("./utils");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": 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 _objectSpread(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 _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 _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
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."); }
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 _iterableToArray(iter) { if (typeof Symbol !== "undefined" && Symbol.iterator in Object(iter)) return Array.from(iter); }
function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }
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 _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
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; }
var errCb = function errCb(selector) {
return Array.isArray(selector) || selector.validators;
};
var pristineCb = function pristineCb(selector) {
return selector.clipPristine;
};
function pathToKeyPath(path) {
var str = "".concat(path);
return str ? str.split('.') : [];
}
var Service = /*#__PURE__*/function () {
_createClass(Service, [{
key: "isDirty",
get: function get() {
return !(0, _fastDeepEqual["default"])(this.__state, this.__initialState);
}
}, {
key: "isPristine",
get: function get() {
var fn = function fn(obj) {
return !Object.values(obj).filter(function (v) {
return _typeof(v) === 'object' ? fn(v) : v;
}).length;
};
return _typeof(this.__pristine) === 'object' ? fn(this.__pristine) : this.__pristine;
}
}, {
key: "hasErrors",
get: function get() {
var fn = function fn(obj) {
return Object.values(obj).filter(function (v) {
return _typeof(v) === 'object' ? fn(v) : v;
}).length > 0;
};
return _typeof(this.__errors) === 'object' ? fn(this.__errors) : Boolean(this.__errors);
}
}, {
key: "state",
get: function get() {
return this.__state;
}
}, {
key: "errors",
get: function get() {
return this.__errors;
}
}]);
function Service(model, selectors, onChange) {
_classCallCheck(this, Service);
this.__state = {};
this.__errors = {};
this.__pristine = {};
this.__selectors = selectors;
this.__onChange = onChange;
this.refresh(model);
this.__verifySelectors();
}
_createClass(Service, [{
key: "refresh",
value: function refresh(model) {
this.__state = (0, _utils.deepCopy)(model);
this.__state = this.convert(model, 'format');
this.__initialState = (0, _utils.deepCopy)(this.__state);
this.__refreshErrors();
this.__refreshPristine();
this.__change();
}
}, {
key: "reset",
value: function reset() {
this.__state = (0, _utils.deepCopy)(this.__initialState);
this.__refreshErrors();
this.__refreshPristine();
this.__change();
}
}, {
key: "apply",
value: function apply(path, value) {
var keyPath = pathToKeyPath(path);
if (path && value === this.__state) {
throw new _error.MutationError(keyPath, value, this.__state);
}
var pristine = (0, _utils.getValueByPath)(this.__pristine, keyPath);
if (_typeof(pristine) === 'object') {
throw new _error.PristineError(keyPath);
}
this.__verifyValue(keyPath, value);
(0, _utils.setValueByPath)(this.__state, keyPath, value);
this.validateKey(keyPath);
(0, _utils.setValueByPath)(this.__pristine, keyPath, false);
this.__spreadSchema('__state', keyPath);
this.__modify(keyPath);
}
}, {
key: "addItem",
value: function addItem(path) {
var index = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : -1;
var keyPath = pathToKeyPath(path);
var items = (0, _utils.getValueByPath)(this.__state, keyPath);
var shiftedIndex = index !== -1 ? index : items.length;
var selector = this.getSelector(keyPath);
var model = this.convert(this.__state, 'unformat');
var rawItem = selector.createItem(keyPath, shiftedIndex, model, this);
var item = this.__convertItem(rawItem, keyPath);
items.splice(shiftedIndex, 0, item);
this.__spreadSchema('__state', [].concat(_toConsumableArray(keyPath), [shiftedIndex]));
this.__addItemToSchema('__errors', keyPath, shiftedIndex, item, '', errCb);
this.__addItemToSchema('__pristine', keyPath, shiftedIndex, item, true, pristineCb);
this.__modifyPristineItem([].concat(_toConsumableArray(keyPath), [shiftedIndex]));
this.__modify(keyPath);
this.__change();
}
}, {
key: "removeItem",
value: function removeItem(path) {
var index = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : -1;
var keyPath = pathToKeyPath(path);
var items = (0, _utils.getValueByPath)(this.__state, keyPath);
var shiftedIndex = index === -1 ? items.length - 1 : index;
items.splice(shiftedIndex, 1);
this.__spreadSchema('__state', [].concat(_toConsumableArray(keyPath), ["".concat(shiftedIndex)]));
this.__removeItemFromSchema('__errors', keyPath, shiftedIndex);
this.__removeItemFromSchema('__pristine', keyPath, shiftedIndex);
this.__modify(keyPath);
this.__change();
}
}, {
key: "moveItem",
value: function moveItem(path, fromIndex, toIndex) {
var keyPath = pathToKeyPath(path);
this.__moveItemInSchema('__state', keyPath, fromIndex, toIndex);
this.__moveItemInSchema('__errors', keyPath, fromIndex, toIndex);
this.__moveItemInSchema('__pristine', keyPath, fromIndex, toIndex);
this.__change();
}
}, {
key: "swapItems",
value: function swapItems(path, index1, index2) {
var keyPath = pathToKeyPath(path);
this.__swapItemsInSchema('__state', keyPath, index1, index2);
this.__swapItemsInSchema('__errors', keyPath, index1, index2);
this.__swapItemsInSchema('__pristine', keyPath, index1, index2);
(0, _utils.setValueByPath)(this.__pristine, [].concat(_toConsumableArray(keyPath), [index1]), false);
(0, _utils.setValueByPath)(this.__pristine, [].concat(_toConsumableArray(keyPath), [index2]), false);
this.__change();
}
}, {
key: "convert",
value: function convert(data, op) {
var _this = this;
var rootPath = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : [];
var rootSelector = this.getSelector([], true);
var action = rootSelector && rootSelector[op];
var copy = _typeof(data) === 'object' ? (0, _utils.deepCopy)(data) : data;
var result = action ? action(copy, rootPath, data) : copy;
(0, _utils.traverse)(result, function (keyPath, value) {
var fullPath = [].concat(_toConsumableArray(rootPath), _toConsumableArray(keyPath));
var selector = _this.getSelector(fullPath, true);
if (selector && selector[op]) {
var selVal = selector[op](value, fullPath, data);
if (selVal !== null && _typeof(selVal) === 'object') {
var _copy = selVal instanceof Date ? new Date(selVal.getTime()) : (0, _utils.deepCopy)(selVal);
(0, _utils.setValueByPath)(result, keyPath, _copy);
} else {
(0, _utils.setValueByPath)(result, keyPath, selVal);
}
}
});
return result;
}
}, {
key: "build",
value: function build() {
return this.convert(this.__state, 'unformat');
}
}, {
key: "validate",
value: function validate() {
var _this2 = this;
this.__pristine = (0, _utils.map)(this.__pristine, function () {
return false;
});
(0, _utils.traverse)(this.__state, function (keyPath) {
var pristine = (0, _utils.getValueByPath)(_this2.__pristine, keyPath);
if (pristine !== undefined && _typeof(pristine) !== 'object') {
_this2.validateKey(keyPath, true);
}
});
return !this.hasErrors;
}
}, {
key: "validateKey",
value: function validateKey(keyPath) {
var _this3 = this;
var force = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
var clippedPathIndex = keyPath.findIndex(function (_, index) {
var subPath = keyPath.slice(0, index);
return _this3.getValidators(subPath);
});
var validatorPathLength = clippedPathIndex !== -1 ? clippedPathIndex : keyPath.length;
var validatorPath = keyPath.slice(0, validatorPathLength);
var validators = this.getValidators(validatorPath);
var pristine = (0, _utils.getValueByPath)(this.__pristine, keyPath);
var prevErrors = this.__errors;
if (validators && (!pristine || force)) {
var selector = this.getSelector(validatorPath);
var useRaw = selector.validateRaw || false;
if (!selector.validateManually || force) {
this.__processValidator(validatorPath, validators, useRaw);
}
}
if (prevErrors !== this.__errors) {
this.__change();
}
}
}, {
key: "unsetPristine",
value: function unsetPristine(keyPath) {
if (typeof (0, _utils.getValueByPath)(this.__pristine, keyPath) !== 'boolean') {
throw new TypeError("Invalid path: ".concat(keyPath.join('.')));
}
(0, _utils.setValueByPath)(this.__pristine, keyPath, false);
}
}, {
key: "getSelectorPath",
value: function getSelectorPath(keyPath) {
var _this4 = this;
var ignoreCheck = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
if (!ignoreCheck) {
var value = (0, _utils.getValueByPath)(this.__state, keyPath);
if (value === undefined) {
throw new _error.PathError(keyPath);
}
}
var initialValue = keyPath.length ? ['children'] : [];
return keyPath.reduce(function (accum, curr, index) {
var parentPath = keyPath.slice(0, index);
var parent = (0, _utils.getValueByPath)(_this4.__state, parentPath);
var key = Array.isArray(parent) ? '$' : curr;
return index < keyPath.length - 1 ? [].concat(_toConsumableArray(accum), [key, 'children']) : [].concat(_toConsumableArray(accum), [key]);
}, initialValue);
}
}, {
key: "getSelector",
value: function getSelector(keyPath) {
var ignoreCheck = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
var selectorPath = this.getSelectorPath(keyPath, ignoreCheck);
return (0, _utils.getValueByPath)(this.__selectors, selectorPath);
}
}, {
key: "getValidators",
value: function getValidators(keyPath) {
var selector = this.getSelector(keyPath);
if (selector) {
return Array.isArray(selector) ? selector : selector.validators;
}
return null;
}
}, {
key: "__change",
value: function __change() {
this.__onChange(this.isDirty, this.__state, this.__errors, this.__pristine);
}
}, {
key: "__verifySelectors",
value: function __verifySelectors() {
var _this5 = this;
(0, _utils.traverse)(this.__state, function (keyPath, v) {
var validators = _this5.getValidators(keyPath);
if (validators) {
var parentPath = keyPath.slice(0, keyPath.length - 1);
parentPath.forEach(function (_, index) {
var ancestorPath = parentPath.slice(0, index + 1);
var ancestorValidators = _this5.getValidators(ancestorPath);
if (ancestorValidators) {
throw new _error.VerificationError("Selector (".concat(keyPath.join('.'), ") has ancestor selector with validators: ").concat(ancestorPath.join('.')));
}
});
}
if (_typeof(v) === 'object') {
var selectors = _this5.getSelector(keyPath);
if (selectors && selectors.ignorePristine && !selectors.clipPristine) {
var msg = "ignorePristine set object-type key for path: ".concat(keyPath.join('.'), ". Perhaps you meant to use clipPristine?");
throw new _error.VerificationError(msg);
}
}
});
}
}, {
key: "__verifyValue",
value: function __verifyValue(keyPath, value) {
var selector = this.getSelector(keyPath, true);
var oldValue = (0, _utils.getValueByPath)(this.__state, keyPath);
if (!selector || !selector.unsafe) {
if (oldValue === undefined) {
throw new TypeError("Invalid path: ".concat(keyPath.join('.')));
}
if (oldValue !== null && value !== null) {
if (_typeof(oldValue) === 'object') {
var oldPathMap = (0, _utils.getKeyPaths)(oldValue);
if (_typeof(value) === 'object') {
var pathMap = (0, _utils.getKeyPaths)(value);
if (!(0, _fastDeepEqual["default"])(oldPathMap, pathMap)) {
throw new _error.MutationError(keyPath, oldValue, value);
}
} else {
throw new _error.MutationError(keyPath, oldValue, value);
}
}
}
}
}
}, {
key: "__buildSchema",
value: function __buildSchema(refSchema, initialValue, fn) {
var _this6 = this;
var rootPath = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : [];
var result = Array.isArray(refSchema) ? [] : {};
var rootSelector = this.getSelector(rootPath);
if (rootSelector && fn(rootSelector)) {
return initialValue;
}
(0, _utils.traverse)(refSchema, function (keyPath, value) {
var dateType = value instanceof Date;
if (!dateType && value !== null && _typeof(value) === 'object') {
var fullPath = [].concat(_toConsumableArray(rootPath), _toConsumableArray(keyPath));
var selector = _this6.getSelector(fullPath);
if (selector && fn(selector)) {
(0, _utils.setValueByPath)(result, keyPath, initialValue);
return false;
}
(0, _utils.setValueByPath)(result, keyPath, Array.isArray(value) ? [] : {});
} else {
(0, _utils.setValueByPath)(result, keyPath, initialValue);
}
}, true);
return result;
}
}, {
key: "__convertItem",
value: function __convertItem(data) {
var rootPath = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
var item = _typeof(data) === 'object' ? (0, _utils.deepCopy)(data) : data;
var result = this.convert([item], 'format', rootPath);
return result[0];
}
}, {
key: "__spreadSchema",
value: function __spreadSchema(schemaKey, keyPath) {
var _this7 = this;
var ref = this[schemaKey];
this[schemaKey] = Array.isArray(ref) ? _toConsumableArray(ref) : _objectSpread({}, ref);
if (keyPath.length > 1) {
keyPath.slice(0, keyPath.length - 1).forEach(function (_, index) {
var subPath = keyPath.slice(0, index + 1);
var subObj = (0, _utils.getValueByPath)(_this7[schemaKey], subPath);
var result = Array.isArray(subObj) ? _toConsumableArray(subObj) : _objectSpread({}, subObj);
(0, _utils.setValueByPath)(_this7[schemaKey], subPath, result);
});
}
}
}, {
key: "__addItemToSchema",
value: function __addItemToSchema(schemaKey, keyPath, index, item, defaultValue, fn) {
var value = (0, _utils.getValueByPath)(this[schemaKey], keyPath);
if (_typeof(value) === 'object') {
var selector = this.getSelector(keyPath);
var clipElement = selector && selector.children && selector.children.$ && fn(selector.children.$);
var subObj = !clipElement && _typeof(item) === 'object' ? this.__buildSchema(item, defaultValue, fn, [].concat(_toConsumableArray(keyPath), ["".concat(index)])) : defaultValue;
value.splice(index, 0, subObj);
this.__spreadSchema(schemaKey, [].concat(_toConsumableArray(keyPath), ["".concat(index)]));
}
}
}, {
key: "__removeItemFromSchema",
value: function __removeItemFromSchema(schemaKey, keyPath, index) {
var item = (0, _utils.getValueByPath)(this[schemaKey], keyPath);
if (Array.isArray(item)) {
item.splice(index, 1);
this.__spreadSchema(schemaKey, [].concat(_toConsumableArray(keyPath), ["".concat(index)]));
}
}
}, {
key: "__moveItemInSchema",
value: function __moveItemInSchema(schemaKey, keyPath, fromIndex, toIndex) {
var items = (0, _utils.getValueByPath)(this[schemaKey], keyPath);
if (Array.isArray(items)) {
var result = (0, _utils.moveItem)(items, fromIndex, toIndex).map(function (item) {
if (_typeof(item) === 'object') {
return Array.isArray(item) ? _toConsumableArray(item) : _objectSpread({}, item);
}
return item;
});
if (keyPath.length) {
(0, _utils.setValueByPath)(this[schemaKey], keyPath, result);
} else {
this[schemaKey] = result;
}
this.__spreadSchema(schemaKey, keyPath);
}
}
}, {
key: "__swapItemsInSchema",
value: function __swapItemsInSchema(schemaKey, keyPath, index1, index2) {
var items = (0, _utils.getValueByPath)(this[schemaKey], keyPath);
if (Array.isArray(items)) {
var result = (0, _utils.swap)(items, index1, index2);
if (keyPath.length) {
(0, _utils.setValueByPath)(this[schemaKey], keyPath, result);
} else {
this[schemaKey] = result;
}
this.__spreadSchema(schemaKey, [].concat(_toConsumableArray(keyPath), [index1]));
this.__spreadSchema(schemaKey, [].concat(_toConsumableArray(keyPath), [index2]));
}
}
}, {
key: "__modify",
value: function __modify(keyPath) {
var pristine = (0, _utils.getValueByPath)(this.__pristine, keyPath);
if (pristine && _typeof(pristine) !== 'object') {
(0, _utils.setValueByPath)(this.__pristine, keyPath, false);
}
this.__change();
}
}, {
key: "__processValidator",
value: function __processValidator(keyPath, validators, useRaw) {
var _this8 = this;
var data = useRaw ? this.convert(this.__state, 'unformat') : this.__state;
var value = (0, _utils.getValueByPath)(data, keyPath);
try {
validators.forEach(function (validator) {
if (!validator.validate(value, keyPath, data, _this8)) {
throw new _error.ValidationError(validator.error);
}
});
this.__setError(keyPath, '');
} catch (e) {
if (e instanceof _error.ValidationError) {
this.__setError(keyPath, e.message);
} else {
throw e;
}
}
}
}, {
key: "__refreshErrors",
value: function __refreshErrors() {
this.__errors = this.__buildSchema(this.__state, '', function (selector) {
return Array.isArray(selector) || selector.validators;
});
}
}, {
key: "__setError",
value: function __setError(keyPath, message) {
if (keyPath.length) {
(0, _utils.setValueByPath)(this.__errors, keyPath, message);
this.__spreadSchema('__errors', keyPath);
} else {
this.__errors = message;
}
}
}, {
key: "__refreshPristine",
value: function __refreshPristine() {
var _this9 = this;
this.__pristine = this.__buildSchema(this.__state, true, function (selector) {
return selector.clipPristine;
});
(0, _utils.traverse)(this.__pristine, function (keyPath) {
var selector = _this9.getSelector(keyPath);
if (selector && selector.ignorePristine) {
(0, _utils.setValueByPath)(_this9.__pristine, keyPath, false);
}
});
}
}, {
key: "__modifyPristineItem",
value: function __modifyPristineItem(keyPath) {
var _this10 = this;
var pristine = (0, _utils.getValueByPath)(this.__pristine, keyPath);
if (_typeof(pristine) === 'object') {
(0, _utils.traverse)(pristine, function (subPath) {
var fullPath = [].concat(_toConsumableArray(keyPath), _toConsumableArray(subPath));
var selector = _this10.getSelector(fullPath);
if (selector && selector.ignorePristine) {
(0, _utils.setValueByPath)(_this10.__pristine, fullPath, false);
}
});
} else {
var selector = this.getSelector(keyPath);
if (selector && selector.ignorePristine) {
(0, _utils.setValueByPath)(this.__pristine, keyPath, false);
}
}
}
}]);
return Service;
}();
exports["default"] = Service;