UNPKG

formular

Version:
880 lines (853 loc) 32.8 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } var equal = _interopDefault(require('fast-deep-equal')); var react = require('react'); /*! ***************************************************************************** Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT. See the Apache Version 2.0 License for specific language governing permissions and limitations under the License. ***************************************************************************** */ var __assign = function() { __assign = Object.assign || function __assign(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; function __awaiter(thisArg, _arguments, P, generator) { 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) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); } function __generator(thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } } var Event = /** @class */ (function () { function Event(name) { this.name = name; this.handlers = []; } /** * Add handler to current Event */ Event.prototype.addHandler = function (handler) { this.handlers.push(handler); }; /** * Remove handler from current Event */ Event.prototype.removeHandler = function (handler) { this.handlers = this.handlers.filter(function (h) { return h !== handler; }); }; /** * Call all handlers in all priorities of current Event */ Event.prototype.call = function () { var eventArgs = []; for (var _i = 0; _i < arguments.length; _i++) { eventArgs[_i] = arguments[_i]; } this.handlers.forEach(function (handler) { try { handler.apply(void 0, eventArgs); } catch (err) { console.error(err); } }); }; return Event; }()); var EventAggregator = /** @class */ (function () { function EventAggregator() { this.events = {}; } /** * Get Event by name */ EventAggregator.prototype.getEvent = function (name) { var event = this.events[name]; if (!event) { event = new Event(name); this.events[name] = event; } return event; }; EventAggregator.prototype.subscribe = function (name, handler) { var _this = this; var event = this.getEvent(name); event.addHandler(handler); return function () { _this.unsubscribe(name, handler); }; }; EventAggregator.prototype.unsubscribe = function (name, handler) { var event = this.getEvent(name); event.removeHandler(handler); }; EventAggregator.prototype.dispatch = function (name) { var eventArgs = []; for (var _i = 1; _i < arguments.length; _i++) { eventArgs[_i - 1] = arguments[_i]; } var event = this.getEvent(name); if (event) { event.call.apply(event, eventArgs); } }; /** * Subscribe to Event and unsubscribe after call */ EventAggregator.prototype.once = function (name, handler) { var event = this.getEvent(name); var handlerWrapper = function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } var result = handler.apply(void 0, args); if (result) { event.removeHandler(handlerWrapper); } }; event.addHandler(handlerWrapper); return { event: event, handlerWrapper: handlerWrapper }; }; return EventAggregator; }()); /* https://github.com/alkemics/CancelablePromise */ var handleCallback = function (resolve, reject, callback, result) { try { resolve(callback(result)); } catch (e) { reject(e); } }; var CancelablePromise = /** @class */ (function () { function CancelablePromise(executor) { // @ts-ignore this._promise = new Promise(executor); this._canceled = false; } CancelablePromise.all = function (iterable) { return new CancelablePromise(function (y, n) { Promise.all(iterable).then(y, n); }); }; CancelablePromise.race = function (iterable) { return new CancelablePromise(function (y, n) { Promise.race(iterable).then(y, n); }); }; CancelablePromise.reject = function (value) { return new CancelablePromise(function (y, n) { Promise.reject(value).then(y, n); }); }; CancelablePromise.resolve = function (value) { return new CancelablePromise(function (y, n) { Promise.resolve(value).then(y, n); }); }; CancelablePromise.prototype.then = function (success, error) { var _this = this; var promise = new CancelablePromise(function (resolve, reject) { _this._promise.then(function (result) { if (_this._canceled) { promise.cancel(); } if (success && !_this._canceled) { handleCallback(resolve, reject, success, result); } else { resolve(result); } }, function (result) { if (_this._canceled) { promise.cancel(); } if (error && !_this._canceled) { handleCallback(resolve, reject, error, result); } else { reject(result); } }); }); return promise; }; CancelablePromise.prototype.catch = function (error) { return this.then(undefined, error); }; CancelablePromise.prototype.cancel = function (errorCallback) { this._canceled = true; if (errorCallback) { // @ts-ignore this._promise.catch(errorCallback); } return this; }; return CancelablePromise; }()); var asyncSome = function (arr, calle) { return __awaiter(void 0, void 0, void 0, function () { var item, restItems, isMatch; return __generator(this, function (_a) { switch (_a.label) { case 0: if (!arr.length) return [3 /*break*/, 2]; item = arr[0], restItems = arr.slice(1); return [4 /*yield*/, calle(item)]; case 1: isMatch = _a.sent(); if (isMatch) { return [2 /*return*/, true]; } return [2 /*return*/, asyncSome(restItems, calle)]; case 2: return [2 /*return*/, false]; } }); }); }; function debounce(func, wait, immediate) { var timeout; return function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } var context = this; var later = function () { timeout = undefined; if (!immediate) { func.apply(context, args); } }; var callNow = immediate && timeout === undefined; if (timeout !== undefined) { clearTimeout(timeout); } timeout = setTimeout(later, wait); if (callNow) { func.apply(context, args); } }; } var eventNames = { stateChange: 'state change', valueChange: 'value change', change: 'change', unset: 'unset', focus: 'focus', blur: 'blur', startValidate: 'start validate', validate: 'validate', }; var Field = /** @class */ (function () { function Field(opts, form) { var _this = this; this.handleFocus = function (event) { _this._events.dispatch(eventNames.focus, event); }; this.handleBlur = function (event) { _this._events.dispatch(eventNames.blur, event); }; this.validate = function () { if (!_this.validators || !_this.validators.length) { // existing error state should be cleared bcs it could be set from server validation via field.setError(err) _this.setState({ error: null, isValid: true, isValidating: false, isValidated: true, }); return CancelablePromise.resolve(); } var error; _this._events.dispatch(eventNames.startValidate); var setError = function (error) { _this.setState({ error: error, isValid: !error, isValidating: false, isValidated: true, }); _this._events.dispatch(eventNames.validate, _this.state.error); }; _this.setState({ isValidating: true, }); if (_this._cancelablePromise) { _this._cancelablePromise.cancel(); } _this._cancelablePromise = new CancelablePromise(function (resolve) { return __awaiter(_this, void 0, void 0, function () { var _this = this; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, asyncSome(this.validators, function (validator) { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, validator(this.state.value, this.form && this.form.fields)]; // error here is String error message case 1: error = _a.sent(); // error here is String error message return [2 /*return*/, Boolean(error)]; } }); }); })]; case 1: _a.sent(); resolve(); return [2 /*return*/]; } }); }); }); return _this._cancelablePromise.then(function () { setError(error); return error; }); }; this.form = form; this.opts = opts || {}; this.name = this.opts.name; this.node = this.opts.node; this.readOnly = this.opts.readOnly || false; this.validators = this.opts.validate || []; this.debounceValidate = this.opts.validationDelay ? debounce(this.validate, this.opts.validationDelay) : this.validate; this.state = { value: Field.modifyValue(this.opts.value), error: null, isChanged: false, isValidating: false, isValidated: false, isValid: true, }; this.props = { ref: function (node) { return _this.node = node; }, onChange: function (event) { return _this.set(event.currentTarget.value); }, onFocus: this.handleFocus, onBlur: this.handleBlur, }; this._events = new EventAggregator(); this._initialValue = this.state.value; this._cancelablePromise = null; // TODO how to detect this? // this.hasAsyncValidators = this.validators.some((validator) => ( // validator.constructor.name === 'AsyncFunction' // )) } Field.modifyValue = function (value) { return value === undefined || value === null ? '' : value; }; // Common methods Field.prototype.setState = function (values) { var newState = __assign(__assign({}, this.state), values); var isEqual = equal(this.state, newState); if (!isEqual) { this.state = newState; this._events.dispatch(eventNames.stateChange, this.state); } }; Field.prototype.setRef = function (node) { this.node = node; if (this.node) { this.node.addEventListener(eventNames.focus, this.handleFocus); this.node.addEventListener(eventNames.blur, this.handleBlur); } }; Field.prototype.unsetRef = function () { if (this.node) { this.node.removeEventListener(eventNames.focus, this.handleFocus); this.node.removeEventListener(eventNames.blur, this.handleBlur); } this.node = undefined; }; Field.prototype.set = function (value, opts) { var silent = (opts || {}).silent; var modifiedValue = Field.modifyValue(value); if (modifiedValue !== this.state.value && !this.readOnly) { this.setState({ value: modifiedValue, isChanged: true, }); if (!silent) { this._events.dispatch(eventNames.valueChange, this.state.value); this._events.dispatch(eventNames.change, this.state.value); } } }; Field.prototype.unset = function () { this.setState({ value: this._initialValue, error: null, isChanged: false, isValidating: false, isValidated: false, isValid: true, }); this._events.dispatch(eventNames.unset); }; Field.prototype.setError = function (error) { if (error === this.state.error) { return; } this.setState({ error: error, isChanged: true, isValid: false, isValidating: false, isValidated: true, }); this._events.dispatch(eventNames.change, this.state.value); }; Field.prototype.on = function (eventName, handler) { this._events.subscribe(eventName, handler); }; Field.prototype.off = function (eventName, handler) { this._events.unsubscribe(eventName, handler); }; return Field; }()); var eventNames$1 = { stateChange: 'state change', attachFields: 'attach fields', detachFields: 'detach fields', forceUpdate: 'force update', change: 'change', focus: 'focus', blur: 'blur', submit: 'submit', }; var defaultOptions = { initialValues: {}, }; var Form = /** @class */ (function () { function Form(opts) { this.name = opts.name; this.opts = __assign(__assign({}, defaultOptions), opts); // @ts-ignore this.fields = {}; this.state = { isValid: true, isChanged: false, isValidating: false, isSubmitting: false, isSubmitted: false, }; this._events = new EventAggregator(); this._attachFields(this.opts.fields); } Form.prototype._attachFields = function (fieldOpts) { var _this = this; var fieldNames = Object.keys(fieldOpts); fieldNames.forEach(function (fieldName) { var initialValue = _this.opts.initialValues && _this.opts.initialValues[fieldName]; var opts = fieldOpts[fieldName]; opts = Array.isArray(opts) ? { validate: opts } : opts; opts.name = fieldName; if (typeof initialValue !== 'undefined') { opts.value = initialValue; } var field = new Field(opts, _this); _this.fields[fieldName] = field; var eventKeys = ['change', 'focus', 'blur']; eventKeys.forEach(function (key) { field.on(eventNames[key], function () { _this._events.dispatch(eventNames$1[key], field); }); }); }); }; Form.prototype.attachFields = function (fieldOpts) { this._attachFields(fieldOpts); this._events.dispatch(eventNames$1.attachFields); this.forceUpdate(); }; Form.prototype.detachFields = function (fieldNames) { var _this = this; fieldNames.forEach(function (fieldName) { delete _this.fields[fieldName]; }); this._events.dispatch(eventNames$1.detachFields); this.forceUpdate(); }; Form.prototype.forceUpdate = function () { this._events.dispatch(eventNames$1.forceUpdate); }; Form.prototype.setState = function (values) { this.state = __assign(__assign({}, this.state), values); this._events.dispatch(eventNames$1.stateChange, this.state); }; Form.prototype.setValues = function (values) { var _this = this; var fieldNames = Object.keys(values); // TODO should we mark form as changed and validate it? fieldNames.forEach(function (fieldName) { var field = _this.fields[fieldName]; if (field) { field.set(values[fieldName]); } }); }; Form.prototype.getValues = function () { var _this = this; var fieldNames = Object.keys(this.fields); var values = {}; fieldNames.forEach(function (fieldName) { values[fieldName] = _this.fields[fieldName].state.value; }); return values; }; Form.prototype.unsetValues = function () { var _this = this; this.setState({ isChanged: false, isValid: true, }); Object.keys(this.fields).forEach(function (fieldName) { _this.fields[fieldName].unset(); }); }; Form.prototype.setErrors = function (errors) { var _this = this; var fieldNames = Object.keys(errors); fieldNames.forEach(function (fieldName) { var field = _this.fields[fieldName]; if (field) { field.setError(errors[fieldName]); } }); }; Form.prototype.getErrors = function () { var _this = this; var fieldNames = Object.keys(this.fields); var errors = {}; fieldNames.forEach(function (fieldName) { var error = _this.fields[fieldName].state.error; if (error) { errors[fieldName] = error; } }); return Object.keys(errors).length ? errors : null; }; Form.prototype.validate = function () { return __awaiter(this, void 0, void 0, function () { var promises, errors, isValid; var _this = this; return __generator(this, function (_a) { switch (_a.label) { case 0: promises = Object.keys(this.fields).map(function (fieldName) { return _this.fields[fieldName].validate(); }); return [4 /*yield*/, Promise.all(promises)]; case 1: errors = _a.sent(); isValid = errors.every(function (error) { return !error; }); this.setState({ isValid: isValid }); return [2 /*return*/, isValid]; } }); }); }; Form.prototype.submit = function () { return __awaiter(this, void 0, void 0, function () { var values, errors; return __generator(this, function (_a) { switch (_a.label) { case 0: values = this.getValues(); // TODO don't validate if all fields are changed and valid return [4 /*yield*/, this.validate()]; case 1: // TODO don't validate if all fields are changed and valid _a.sent(); errors = this.getErrors(); this._events.dispatch(eventNames$1.submit, errors, values); return [2 /*return*/, { values: values, errors: errors, }]; } }); }); }; Form.prototype.on = function (eventName, handler) { this._events.subscribe(eventName, handler); }; Form.prototype.off = function (eventName, handler) { this._events.unsubscribe(eventName, handler); }; return Form; }()); var eventNames$2 = { replace: 'replace', setValues: 'set values', unsetValues: 'unset values', attachForms: 'attach forms', detachForms: 'detach forms', forceUpdate: 'force update', submit: 'submit', }; var FormGroup = /** @class */ (function () { function FormGroup(forms) { var _this = this; this._handleFormEvent = function (eventName) { return debounce(function () { _this._events.dispatch(eventName); }, 100); }; this._events = new EventAggregator(); // @ts-ignore this.forms = forms || {}; this._subscribe(); } FormGroup.prototype._subscribe = function () { var _this = this; var forms = Object.values(this.forms); forms.forEach(function (form) { var eventNames = Object.keys(eventNames$1); eventNames.forEach(function (eventName) { form.on(eventName, _this._handleFormEvent(eventName)); }); }); }; FormGroup.prototype._unsubscribe = function () { var _this = this; var forms = Object.values(this.forms); forms.forEach(function (form) { var eventNames = Object.keys(eventNames$1); eventNames.forEach(function (eventName) { form.off(eventName, _this._handleFormEvent(eventName)); }); }); }; FormGroup.prototype.attachForms = function (forms) { var _this = this; var formNames = Object.keys(forms); formNames.forEach(function (formName) { if (formName in _this.forms) { console.error("Form with name \"" + formName + "\" already exists in FormGroup"); } else { _this.forms[formName] = forms[formName]; } }); this._events.dispatch(eventNames$2.attachForms); this.forceUpdate(); }; FormGroup.prototype.detachForms = function (formNames) { var _this = this; formNames.forEach(function (fieldName) { delete _this.forms[fieldName]; }); this._events.dispatch(eventNames$2.detachForms); this.forceUpdate(); }; FormGroup.prototype.replace = function (newForms) { this._unsubscribe(); this.forms = newForms; this._subscribe(); this._events.dispatch(eventNames$2.replace); this.forceUpdate(); }; FormGroup.prototype.forceUpdate = function () { this._events.dispatch(eventNames$2.forceUpdate); }; FormGroup.prototype.validate = function () { return __awaiter(this, void 0, void 0, function () { var forms, statuses, isValid; return __generator(this, function (_a) { switch (_a.label) { case 0: forms = Object.values(this.forms); return [4 /*yield*/, Promise.all(forms.map(function (form) { return form.validate(); }))]; case 1: statuses = _a.sent(); isValid = statuses.every(function (isValid) { return isValid; }); return [2 /*return*/, isValid]; } }); }); }; FormGroup.prototype.setValues = function (values) { var _this = this; var formNames = Object.keys(this.forms); formNames.forEach(function (formName) { var form = _this.forms[formName]; var formValues = values[formName]; if (formValues) { form.setValues(formValues); } }); this._events.dispatch(eventNames$2.setValues); }; FormGroup.prototype.getValues = function () { var _this = this; var formNames = Object.keys(this.forms); var values = {}; formNames.forEach(function (formName) { var form = _this.forms[formName]; values[formName] = form.getValues(); }); return values; }; FormGroup.prototype.unsetValues = function () { var _this = this; var formNames = Object.keys(this.forms); formNames.forEach(function (formName) { var form = _this.forms[formName]; form.unsetValues(); }); this._events.dispatch(eventNames$2.unsetValues); }; // TODO looks like getValues() if we need rewrite it? Write getKeyValues(key) FormGroup.prototype.getErrors = function () { var _this = this; var formNames = Object.keys(this.forms); var errors = {}; formNames.forEach(function (formName) { var form = _this.forms[formName]; var formErrors = form.getErrors(); if (formErrors) { errors[formName] = formErrors; } }); return Object.keys(errors).length ? errors : null; }; FormGroup.prototype.submit = function () { return __awaiter(this, void 0, void 0, function () { var values, errors; return __generator(this, function (_a) { switch (_a.label) { case 0: values = this.getValues(); return [4 /*yield*/, this.validate()]; case 1: _a.sent(); errors = this.getErrors(); this._events.dispatch(eventNames$2.submit, errors, values); return [2 /*return*/, { values: values, errors: errors, }]; } }); }); }; FormGroup.prototype.on = function (eventName, handler) { this._events.subscribe(eventName, handler); }; FormGroup.prototype.off = function (eventName, handler) { this._events.unsubscribe(eventName, handler); }; return FormGroup; }()); var useForceUpdate = function () { var _a = react.useState(0), _ = _a[0], setState = _a[1]; return react.useCallback(function () { return setState(function (v) { return v + 1; }); }, []); }; var useFieldState = function (field) { var forceUpdate = useForceUpdate(); react.useEffect(function () { field.on('state change', forceUpdate); return function () { field.off('state change', forceUpdate); }; }, [field]); return field.state; }; var FieldState = function (_a) { var children = _a.children, field = _a.field; var fieldState = useFieldState(field); return children(fieldState); }; var useForm = function (opts, deps) { var _a = react.useState(0), _ = _a[0], update = _a[1]; var form = react.useMemo(function () { return new Form(opts); }, deps || []); react.useEffect(function () { var handleUpdate = function () { update(function (v) { return ++v; }); }; form.on('force update', handleUpdate); return function () { form.off('force update', handleUpdate); }; }, []); return form; }; var useField = function (opts, deps) { return react.useMemo(function () { return new Field(opts); }, deps || []); }; var useFormGroup = function (forms, deps) { var _a = react.useState(0), _ = _a[0], update = _a[1]; var formGroup = react.useMemo(function () { return new FormGroup(forms); }, deps || []); react.useEffect(function () { var handleUpdate = function () { update(function (v) { return ++v; }); }; formGroup.on('force update', handleUpdate); return function () { formGroup.off('force update', handleUpdate); }; }, []); return formGroup; }; var useFormState = function (form) { var forceUpdate = useForceUpdate(); react.useEffect(function () { form.on('change', forceUpdate); form.on('state change', forceUpdate); return function () { form.off('change', forceUpdate); form.off('state change', forceUpdate); }; }, [form]); var state = form.state; var values = form.getValues(); var errors = form.getErrors(); return __assign(__assign({}, state), { values: values, errors: errors }); }; exports.Field = Field; exports.FieldState = FieldState; exports.Form = Form; exports.FormGroup = FormGroup; exports.useField = useField; exports.useFieldState = useFieldState; exports.useForm = useForm; exports.useFormGroup = useFormGroup; exports.useFormState = useFormState;