UNPKG

react-ocean-forms

Version:
1,257 lines (1,229 loc) 76.1 kB
import React, { useContext } from 'react'; /** * Copyright (c) 2018-present, Umweltbundesamt GmbH * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /** * Returns TRUE if a string s has a value. * FALSE is returned, if s is undefined, null or empty. */ function stringHasValue(s) { if (s === undefined || s === null) { return false; } return s.length > 0; } /** * Returns the display name of the wrapped component. */ // tslint:disable-next-line:naming-convention var getDisplayName = function (wrappedComponent) { if (stringHasValue(wrappedComponent.displayName)) { return wrappedComponent.displayName; } if (stringHasValue(wrappedComponent.name)) { return wrappedComponent.name; } return 'Component'; }; /** * Copyright (c) 2018-present, Umweltbundesamt GmbH * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /** * Returns the 'deep' value of an object. For example * you can provide the string 'foo.bar.baz' to get * the value of the third nested object. * @param name Name / property path * @param object Object */ // tslint:disable-next-line:naming-convention var getDeepValue = function (name, object) { return name.split('.').reduce(function (o, i) { // Workaround for deep objects and // 'cannot read property of undefined' // - is there a better way? if (o === undefined) { return undefined; } if (o[i] === null) { return undefined; } return o[i]; }, object); }; /** * Copyright (c) 2018-present, Umweltbundesamt GmbH * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /** * Returns an array with the parameter as its only element if * the parameter is not an array. Otherwise it returns an array */ // tslint:disable-next-line:naming-convention var toArray = function (param) { if (!Array.isArray(param)) { return [param]; } return param; }; /** * Parses the validation error and returns either * a validation object or undefined * @param name field name * @param error error message */ // tslint:disable-next-line:naming-convention var parseValidationError = function (name, error) { if (typeof error === 'object') { if (error.message_id === undefined || error.params === undefined) { // Error object is invalid return null; } return error; } if (typeof error === 'string') { // Convert the strin to a validation object return { message_id: error, params: {}, }; } if (error !== undefined) { // tslint:disable-next-line:no-console console.error("[Form] Validation result for the field " + name + " was unexpected. Result: " + error); } return null; }; /*! ***************************************************************************** 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. ***************************************************************************** */ /* global Reflect, Promise */ var extendStatics = function(d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); }; function __extends(d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); } 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 __rest(s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) if (e.indexOf(p[i]) < 0) t[p[i]] = s[p[i]]; return t; } 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 }; } } /** * Copyright (c) 2018-present, Umweltbundesamt GmbH * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ // tslint:disable-next-line:naming-convention var DEFAULT_MESSAGES = { ojs_error_required: 'This field is mandatory.', ojs_error_alphaNumeric: 'Only alpha-numeric input is allowed.', ojs_error_minLength: 'The value must be at least {length} characters long.', ojs_error_maxLength: 'The value must be less than {length} characters long.', ojs_field_required: 'Required', ojs_form_validationSummaryHeader: 'Errors', }; // tslint:disable-next-line:naming-convention var TEST_MESSAGES = DEFAULT_MESSAGES; /** * Uses the parameterized string and replaces the * templates with the values passed as the second * parameter. * @param id Message id to be formatted * @param values Parameter values */ // tslint:disable-next-line:naming-convention var stringFormatter = function (id, values) { // Do nothing if the id is not a string if (typeof id !== 'string') { return id; } // Either use one of the default messages or // the string as a literal var formattedString = id; if (DEFAULT_MESSAGES[id] !== undefined) { formattedString = DEFAULT_MESSAGES[id]; } // If no values are specified do nothing if (!values) { return formattedString; } if (typeof values !== 'object') { return formattedString; } if (Array.isArray(values)) { return formattedString; } // Iterate through each value and replace the // value inside the string Object.keys(values).forEach(function (key) { var search = new RegExp("{" + key + "}", 'g'); formattedString = formattedString.replace(search, values[key]); }); return formattedString; }; /** * Adds custom messages to be supported by the * stringFormatter * @param messages Object containing messages */ // tslint:disable-next-line:naming-convention var addCustomMessages = function (messages) { DEFAULT_MESSAGES = __assign({}, DEFAULT_MESSAGES, messages); }; /** * Copyright (c) 2018-present, Umweltbundesamt GmbH * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /** * Copyright (c) 2018-present, Umweltbundesamt GmbH * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /** * Component for displaying bootstrap * form feedbacks if there are any errors */ var FieldError = function (props) { var id = props.id, invalid = props.invalid, error = props.error, stringFormatter = props.stringFormatter; // If the field isn't invalid do nothing if (invalid !== true || error === null) { return null; } // Error could be either an string or an array of strings var errorArray = toArray(error); return (React.createElement(React.Fragment, null, errorArray.map(function (item) { var errorString = stringFormatter(item.message_id, item.params); return React.createElement("span", { key: id + "_" + item.message_id }, errorString); }))); }; FieldError.displayName = 'FieldError'; /** * Copyright (c) 2018-present, Umweltbundesamt GmbH * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /** * Context for the communication between the form * and its fields. */ var FormContext = React.createContext(undefined); /** * Copyright (c) 2018-present, Umweltbundesamt GmbH * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ // tslint:disable-next-line:export-name naming-convention var useFormContext = function () { var context = useContext(FormContext); if (context === undefined) { throw new Error('[useFormContext]: Could not find form context. This component must be used inside a <Form> tag.'); } return context; }; /** * Copyright (c) 2018-present, Umweltbundesamt GmbH * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /** * Enum representing the message id's for field errors. */ var FieldErrorMessageId; (function (FieldErrorMessageId) { FieldErrorMessageId["AlphaNumeric"] = "ojs_error_alphaNumeric"; FieldErrorMessageId["MaxLength"] = "ojs_error_maxLength"; FieldErrorMessageId["MinLength"] = "ojs_error_minLength"; FieldErrorMessageId["Required"] = "ojs_error_required"; })(FieldErrorMessageId || (FieldErrorMessageId = {})); /** * Returns true if the given object implements * IFIeldErrorObject * @param object Object to test */ // tslint:disable-next-line:no-any function isIFieldErrorObject(object) { return object && typeof object.message_id === 'string'; } /** * Returns true if the given object is a IDefaultValidator * @param object Function to test */ // tslint:disable-next-line:no-any function isDefaultValidator(object) { return object && typeof object === 'function' && object.isDefaultValidator === true; } /** * Copyright (c) 2018-present, Umweltbundesamt GmbH * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ var _this = undefined; /** * Wrapper function to call validators with parameters * @param validator function to call * @param context form context * @param args parameters for the validator */ var withParam = function (validator) { var args = []; for (var _i = 1; _i < arguments.length; _i++) { args[_i - 1] = arguments[_i]; } return function (value, context) { return validator(value, context, args); }; }; /** * Wrapper function to call async validators with parameters * @param validator function to call * @param context form context * @param args parameters for the validator */ var withAsyncParam = function (validator) { var args = []; for (var _i = 1; _i < arguments.length; _i++) { args[_i - 1] = arguments[_i]; } return function (value, context) { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) { return [2 /*return*/, validator(value, context, args)]; }); }); }; }; /** * Checks if there is any value * @param value field value */ var required = function (value) { // Special check for empty arrays if (Array.isArray(value)) { return value.length === 0 ? FieldErrorMessageId.Required : undefined; } if (typeof value === 'number') { return undefined; } if (typeof value === 'boolean') { return value === false ? FieldErrorMessageId.Required : undefined; } if (typeof value === 'string') { return value.length === 0 ? FieldErrorMessageId.Required : undefined; } return value !== null && value !== undefined ? undefined : FieldErrorMessageId.Required; }; required.isDefaultValidator = true; /** * Checks if the value is alpha numeric */ var alphaNumeric = function (value) { if (typeof value !== 'string') { return undefined; } return /[^a-zA-Z0-9 ]/i.test(value) ? FieldErrorMessageId.AlphaNumeric : undefined; }; /** * Checks if the given value has the minimum * length * @param value field value * @param context form context * @param length minimum length */ var minLength = function (value, context, _a) { var length = _a[0]; if (!isILength(value)) { return undefined; } if (value.length >= length) { return undefined; } return { message_id: FieldErrorMessageId.MinLength, params: { length: String(length), }, }; }; /** * Checks if the given value has the maximum * length * @param value field value * @param context form context * @param length maximum length */ var maxLength = function (value, context, _a) { var length = _a[0]; if (!isILength(value)) { return undefined; } if (value.length <= length) { return undefined; } return { message_id: FieldErrorMessageId.MaxLength, params: { length: String(length), }, }; }; // tslint:disable-next-line:no-any function isILength(object) { return object !== null && object !== undefined && typeof object.length === 'number'; } // tslint:disable-next-line:naming-convention var validators = { withParam: withParam, withAsyncParam: withAsyncParam, required: required, alphaNumeric: alphaNumeric, minLength: minLength, maxLength: maxLength, }; /** * Copyright (c) 2018-present, Umweltbundesamt GmbH * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /** * High order component for consuming the form context * @deprecated Deprecated in favour of `useFormContext` hook */ var withForm = function (Component) { /** * We need to cast the component back to a * React.ComponentType in order to use it in * the FormContext.Consumer. Otherwise * typescript will cry about the component * not having a constructor / call signatures. */ // tslint:disable-next-line:naming-convention var CastedComponent = Component; /** * Component that injects the form context prop * to the wrapped component */ // tslint:disable-next-line:naming-convention var FormComponent = function (props) { var context = useFormContext(); // @ts-ignore return React.createElement(CastedComponent, __assign({}, props, { context: context })); }; FormComponent.displayName = "FormComponent(" + getDisplayName(Component) + ")"; return FormComponent; }; /** * Component that handles validation of the * wrapped component. */ var BaseValidationWrapper = /** @class */ (function (_super) { __extends(BaseValidationWrapper, _super); function BaseValidationWrapper(props) { var _this = _super.call(this, props) || this; _this.unmounted = false; /** * Creates the initial / default validation * state of a validated component */ _this.createInitialValidationState = function () { return { valid: true, error: null, isValidating: false, isRequired: _this.checkIsRequired(), }; }; /** * Returns true if this component contains * a required field validator */ _this.checkIsRequired = function () { var validators = _this.props.validators; return Array.isArray(validators) && validators.some(isDefaultValidator); }; /** * Resets the validation state to the default */ _this.reset = function () { _this.clearValidationTimeout(); _this.updateAndNotify(_this.createInitialValidationState()); }; /** * Updates the validation state * @param state New state */ _this.updateValidationState = function (state) { var oldState = _this.state; var newState = __assign({}, oldState, state); _this.updateAndNotify(newState); }; /** * Triggers the validation of a field. * @param value Value of the field * @param options Validation params @see checkAsync @see immediateAsync * @param checkAsync True if the async validators should be triggered as well * @param immediateAsync True if the async validators should fire immediately */ _this.validate = function (value, _a) { var _b = _a === void 0 ? {} : _a, _c = _b.checkAsync, checkAsync = _c === void 0 ? true : _c, _d = _b.immediateAsync, immediateAsync = _d === void 0 ? false : _d; return __awaiter(_this, void 0, void 0, function () { var _e, validators, asyncValidators, formContext, validationState, performAsyncValidation, propAsyncValidationWait, asyncValidationWait; var _this = this; return __generator(this, function (_f) { _e = this.props, validators = _e.validators, asyncValidators = _e.asyncValidators, formContext = _e.context; validationState = { valid: true, error: null, isValidating: false, isRequired: this.checkIsRequired(), }; // Clear the old timeout so we only run the // async validators after the waiting period // when the value didn't change in the meantime this.clearValidationTimeout(); // No validators - nothing to do here if (!Array.isArray(validators) && !Array.isArray(asyncValidators)) { this.setState(validationState); return [2 /*return*/, validationState]; } // Synchronous validators if (Array.isArray(validators)) { validationState.valid = validators.every(function (validator) { var result = validator(value, formContext); var parsedResult = parseValidationError(_this.fullName, result); if (isIFieldErrorObject(parsedResult)) { validationState.error = parsedResult; return false; } return true; }); } // Ignore async validation if sync validation is already false if (validationState.valid === false) { this.updateAndNotify(validationState); return [2 /*return*/, validationState]; } // Only run async validation if needed if (!checkAsync || !Array.isArray(asyncValidators)) { this.updateAndNotify(validationState); return [2 /*return*/, validationState]; } performAsyncValidation = function () { return __awaiter(_this, void 0, void 0, function () { var validatorFunctions, errors, parsedErrors; var _this = this; return __generator(this, function (_a) { switch (_a.label) { case 0: validatorFunctions = asyncValidators.map(function (validator) { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) { return [2 /*return*/, validator(value, formContext)]; }); }); }); return [4 /*yield*/, Promise.all(validatorFunctions)]; case 1: errors = _a.sent(); parsedErrors = errors.map(function (error) { return parseValidationError(_this.fullName, error); }); validationState.error = parsedErrors.filter(isIFieldErrorObject); validationState.valid = validationState.error.length === 0; if (validationState.error.length === 0) { validationState.error = null; } validationState.isValidating = false; this.asyncTimeout = undefined; this.updateAndNotify(validationState); return [2 /*return*/, validationState]; } }); }); }; validationState.isValidating = true; if (immediateAsync === true) { this.updateAndNotify(validationState); return [2 /*return*/, performAsyncValidation()]; } propAsyncValidationWait = this.props.asyncValidationWait; asyncValidationWait = propAsyncValidationWait === undefined ? formContext.asyncValidationWait : propAsyncValidationWait; this.asyncTimeout = window.setTimeout(performAsyncValidation, asyncValidationWait); this.updateAndNotify(validationState); return [2 /*return*/, validationState]; }); }); }; _this.state = _this.createInitialValidationState(); return _this; } Object.defineProperty(BaseValidationWrapper.prototype, "fullName", { get: function () { var _a = this.props, name = _a.name, context = _a.context; return context.fieldPrefix !== null ? context.fieldPrefix.concat('.', name) : name; }, enumerable: true, configurable: true }); /** * Unregisters the field from the form */ BaseValidationWrapper.prototype.componentWillUnmount = function () { this.clearValidationTimeout(); this.unmounted = true; }; /** * Returns the current async timeout */ BaseValidationWrapper.prototype.getAsyncTimeout = function () { return this.asyncTimeout; }; /** * Clears the validation timeout if currently * running */ BaseValidationWrapper.prototype.clearValidationTimeout = function () { if (this.asyncTimeout !== undefined) { clearTimeout(this.asyncTimeout); this.asyncTimeout = undefined; } }; /** * Updates the validation state and notifies the form * @param newState New validation state */ BaseValidationWrapper.prototype.updateAndNotify = function (newState) { var context = this.props.context; // Don't do anything if the component has already been // unmounted. This can happen when the validated Field // is already removed while there are async validators // running in the background. if (this.unmounted) { return; } this.setState(newState); context.notifyFieldEvent(this.fullName, 'validation', newState); }; // tslint:disable-next-line:member-ordering BaseValidationWrapper.prototype.render = function () { var _a = this.state, isValidating = _a.isValidating, isRequired = _a.isRequired, valid = _a.valid, error = _a.error; var context = this.props.context; var validation = { isValidating: isValidating, isRequired: isRequired, valid: valid, error: error, validate: this.validate, reset: this.reset, update: this.updateValidationState, }; return this.props.render(this.fullName, validation, context); }; BaseValidationWrapper.displayName = 'ValidationWrapper'; return BaseValidationWrapper; }(React.Component)); var ValidationWrapper = withForm(BaseValidationWrapper); /** * Copyright (c) 2018-present, Umweltbundesamt GmbH * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /** * Higher order component for validation */ var withValidation = function (component) { // tslint:disable-next-line:naming-convention var CastedComponent = component; var validatedComponent = function (props) { var renderComponent = function (fullName, validation, context) { // @ts-ignore return React.createElement(CastedComponent, __assign({ context: context, fullName: fullName, validation: validation }, props)); }; return (React.createElement(ValidationWrapper, __assign({}, props, { render: renderComponent }))); }; validatedComponent.displayName = "ValidatedComponent(" + getDisplayName(component) + ")"; return validatedComponent; }; /** * Copyright (c) 2018-present, Umweltbundesamt GmbH * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /** * Wrapper for groups of input fields * managed by the form component */ var BaseFieldGroup = /** @class */ (function (_super) { __extends(BaseFieldGroup, _super); function BaseFieldGroup(props) { var _this = _super.call(this, props) || this; /** * Resets the validation state */ _this.reset = function () { var reset = _this.props.validation.reset; reset(); }; /** * Listens to child field events, triggers validation if * needed and passes them to the higher context * @param name Field name * @param event Event name * @param args Event args */ _this.notifyFieldEvent = function (name, event, args) { var _a; var _b = _this.props, fullName = _b.fullName, context = _b.context, validate = _b.validation.validate; context.notifyFieldEvent(name, event, args); if (event !== 'change' && event !== 'blur') { return; } var asyncValidateOnChange = _this.getAsyncValidateOnChangeSetting(); if (event === 'change') { var localName = name.substring(fullName.length + 1); var currentGroupValue = _this.getGroupValue(); var intermediateGroupValue = __assign({}, (currentGroupValue === undefined ? {} : currentGroupValue), (_a = {}, // Override the value of the event sender, because // the Field didn't update its state yet, making the // Form.getValues() returning an old Field value. _a[localName] = args, _a)); void validate(intermediateGroupValue, { checkAsync: asyncValidateOnChange }); } else if (!asyncValidateOnChange) { void validate(_this.getGroupValue()); } }; /** * Triggers the validation of the group * @param args Options for the validate call */ _this.validate = function (args) { return __awaiter(_this, void 0, void 0, function () { var validate, value; return __generator(this, function (_a) { validate = this.props.validation.validate; value = this.getGroupValue(); return [2 /*return*/, validate(value, args)]; }); }); }; var fullName = props.fullName, label = props.label, context = props.context, updateValidation = props.validation.update; _this.checkFormContext(); // Our state will overwrite the parts of the formContext _this.state = { fieldPrefix: fullName, notifyFieldEvent: _this.notifyFieldEvent, }; // Register the group in the formContext, so the group // validation can be called on form submit. context.registerField(fullName, { label: label, updateValidation: updateValidation, validate: _this.validate, reset: _this.reset, getValue: function () { return ({}); }, isGroup: true, }); return _this; } /** * Unregisters the field from the form */ BaseFieldGroup.prototype.componentWillUnmount = function () { var _a = this.props, context = _a.context, fullName = _a.fullName; context.unregisterField(fullName); }; /** * Helper function to get the correct value * of the group (including all values of the nested fields) */ BaseFieldGroup.prototype.getGroupValue = function () { var _a = this.props, context = _a.context, fullName = _a.fullName; var formValues = context.getValues(); var formValue = formValues[fullName]; if (formValue === '' || formValue === undefined) { return undefined; } return formValue; }; /** * Returns the correct asyncValidateOnChange setting, * where the field setting takes priorty over the * form setting */ BaseFieldGroup.prototype.getAsyncValidateOnChangeSetting = function () { var _a = this.props, propChange = _a.asyncValidateOnChange, contextChange = _a.context.asyncValidateOnChange; return propChange === undefined ? contextChange : propChange; }; /** * Generates the form context for the FieldGroup children * Overrides fieldPrefix and notifyFieldEvent from the * parent context, and overrides defaultValues and values * for the group items if needed */ BaseFieldGroup.prototype.getSubContext = function () { var _a = this.props, context = _a.context, disabled = _a.disabled, plaintext = _a.plaintext; return __assign({}, context, this.state, { disabled: disabled === undefined ? context.disabled : disabled, plaintext: plaintext === undefined ? context.plaintext : plaintext, defaultValues: this.overrideContextValues('defaultValues'), values: this.overrideContextValues('values') }); }; /** * Checks if the FieldGroup is inside a valid form context * and throws an user friendly error if not */ BaseFieldGroup.prototype.checkFormContext = function () { var _a = this.props, context = _a.context, fullName = _a.fullName; if (context === undefined || typeof context.registerField !== 'function') { throw new Error("Could not find a form context for field group \"" + fullName + "\". " + 'Fields can only be used inside a Form tag.'); } }; /** * Checks if the FieldGroup has a prop with the given name * and overrides the according value in the parent form context. * @param name Property name */ BaseFieldGroup.prototype.overrideContextValues = function (name) { var _a; var contextValue; var propValue; var fullName = this.props.fullName; if (name === 'defaultValues') { contextValue = this.props.context.defaultValues; propValue = this.props.defaultValues; } else if (name === 'values') { contextValue = this.props.context.values; propValue = this.props.values; } if (propValue === undefined) { return contextValue; } var returnValue = __assign({}, contextValue, (_a = {}, _a[fullName] = propValue, _a)); return returnValue; }; // tslint:disable-next-line:member-ordering BaseFieldGroup.prototype.render = function () { var _a = this.props, fullName = _a.fullName, _b = _a.validation, isValidating = _b.isValidating, isRequired = _b.isRequired, valid = _b.valid, error = _b.error, renderProp = _a.render; var groupState = { fullName: fullName, isValidating: isValidating, isRequired: isRequired, valid: valid, error: error, }; var subContext = this.getSubContext(); return (React.createElement(FormContext.Provider, { value: subContext }, renderProp(groupState))); }; BaseFieldGroup.displayName = 'FieldGroup'; return BaseFieldGroup; }(React.Component)); var FieldGroup = withValidation(BaseFieldGroup); /** * Copyright (c) 2018-present, Umweltbundesamt GmbH * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /** * Wrapper component for passing strings to the * context.stringFormatter method */ var FormText = function (_a) { var text = _a.text, values = _a.values; if (text === '' || text === null) { return null; } var context = useFormContext(); return (React.createElement(React.Fragment, null, context.stringFormatter(text, values))); }; FormText.displayName = 'FormText'; /** * Copyright (c) 2018-present, Umweltbundesamt GmbH * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /** * Create a required * * @param validators Validator array */ function createRequiredMarker(isRequired) { if (isRequired) { return React.createElement("span", { className: "field-required" }, " *"); } return null; } /** * Component for displaying bootstrap * form groups with any children */ var FieldLine = function (props) { var field = props.field, meta = props.meta, label = props.label, children = props.children; var groupClass = meta.valid ? 'field-group' : 'field-group is-invalid'; return (React.createElement("div", { className: groupClass }, React.createElement("label", { htmlFor: field.id, className: "text-right" }, React.createElement(FormText, { text: label }), createRequiredMarker(meta.isRequired)), React.createElement("div", { className: "input-container" }, children, React.createElement(FieldError, { id: field.id + "_errors", invalid: !meta.valid, error: meta.error, stringFormatter: meta.stringFormatter })))); }; FieldLine.displayName = 'FieldLine'; /** * Wrapper for managed forms */ var Form = /** @class */ (function (_super) { __extends(Form, _super); function Form(props) { var _this = _super.call(this, props) || this; _this.fields = new Map(); _this.eventListeners = new Map(); /** * Returns the current state of the given field * @param name Field name * @returns Current field state or default field state */ _this.getFieldState = function (name) { var fieldState = _this.fields.get(name); if (fieldState === undefined) { throw new Error("[Form] getFieldState: Could not find state of field '" + name + "'"); } return fieldState; }; /** * Generates and returns an object that contains * all values from all the fields. * @returns Current values in form of { name: value, name2: value2, ... } */ _this.getValues = function () { var values = {}; _this.fields.forEach(function (state, name) { if (state.isGroup === true) { return; } var nameParts = name.split('.'); var valueRef = values; nameParts.forEach(function (key, index) { if (nameParts.length === 1 || index === nameParts.length - 1) { valueRef[key] = state.getValue(); } else { if (valueRef[key] === undefined) { valueRef[key] = {}; } valueRef = valueRef[key]; } }); }); return values; }; /** * Gets called when a field triggers an event * @param name Field name * @param event Event name * @param args Event args */ _this.notifyFieldEvent = function (name, event, args) { if (event === 'validation') { var label = _this.getFieldState(name).label; _this.notifyListeners(name, event, __assign({}, args, { label: label })); } else { _this.notifyListeners(name, event, args); } }; /** * Handles the submit event of the form - prevents * the default and runs the submit logic * @param event Event object */ _this.handleSubmit = function (event) { event.preventDefault(); if (event.stopPropagation !== undefined) { event.stopPropagation(); } void _this.submit(); }; /** * Submits the form - triggers any validations if needed * and raises the onFormSubmit prop callback if the * form is currently valid. * @param submitArgs Arguments that will be passed * to the onSubmit callback */ _this.submit = function (submitArgs) { return __awaiter(_this, void 0, void 0, function () { var validations, validationStates, allValid, formValid, callOnSubmitResult, resetOnSubmit, cleanup; var _this = this; return __generator(this, function (_a) { switch (_a.label) { case 0: this.updateBusyState(true); validations = []; this.fields.forEach(function (field) { validations.push(field.validate({ checkAsync: true, immediateAsync: true, })); }); return [4 /*yield*/, Promise.all(validations)]; case 1: validationStates = _a.sent(); allValid = validationStates.every(function (state) { return state.valid === true; }); if (allValid === false) { this.notifyFieldEvent('_form', 'submit-invalid'); this.updateBusyState(false); return [2 /*return*/]; } formValid = this.triggerFormValidation(); if (formValid === false) { this.notifyFieldEvent('_form', 'submit-invalid'); this.updateBusyState(false); return [2 /*return*/]; } callOnSubmitResult = this.callOnSubmit(submitArgs); resetOnSubmit = this.props.resetOnSubmit; cleanup = function (resetForm) { _this.updateBusyState(false); if (resetForm) { _this.reset(); } }; if (callOnSubmitResult instanceof Promise) { void callOnSubmitResult.then(function () { cleanup(resetOnSubmit); }); } else { cleanup(resetOnSubmit); } return [2 /*return*/]; } }); }); }; /** * Handles the reset event of the form - prevents * the default and sets the state of all fields back * to the default state. * @param event Event object */ _this.handleReset = function (event) { event.preventDefault(); if (event.stopPropagation !== undefined) { event.stopPropagation(); } _this.reset(); }; /** * Sets the state of all fields back * to the default state. */ _this.reset = function () { _this.fields.forEach(function (state) { state.reset(); }); var onReset = _this.props.onReset; if (onReset !== undefined) { onReset(); } }; /** * Registers a new field to the form. * @param name Field name * @param fieldState Field state */ _this.registerField = function (name, fieldState) { if (typeof name !== 'string' || name.length === 0) { throw new Error('[Form] registerField: name is required'); } if (typeof fieldState !== 'object') { throw new Error('[Form] registerField: field state is required'); } if (typeof fieldState.label !== 'string' || typeof fieldState.validate !== 'function' || typeof fieldState.updateValidation !== 'function' || typeof fieldState.reset !== 'function' || typeof fieldState.getValue !== 'function') { throw new Error('[Form] registerField: invalid field state given'); } _this.fields.set(name, fieldState); }; /** * Unregisters a field from the form. * @param name Field name */ _this.unregisterField = function (name) { var label = _this.getFieldState(name).label; _this.notifyListeners(name, 'validation', { label: label, valid: true, }); _this.fields.delete(name); }; _this.state = { context: { fieldPrefix: null, registerField: _this.registerField, unregisterField: _this.unregisterField, notifyFieldEvent: _this.notifyFieldEvent, registerListener: function (name, callback) { _this.eventListeners.set(name, callback); }, unregisterListener: function (name) { _this.eventListeners.delete(name); }, getFieldState: _this.getFieldState, getValues: _this.getValues, submit: _this.submit, reset: _this.reset, busy: false, }, }; return _this; } /** * Notifies the event listeners about an event * @param name Field name * @param event Event name * @param args Event args */ Form.prototype.notifyListeners = function (name, event, args) { this.eventListeners.forEach(function (callback) { callback(name, event, args); }); }; /** * Updates the busy state in the form context * @param busy Busy state */ Form.prototype.updateBusyState = function (busy) { this.setState(function (prevState) { return ({ context: __assign({}, prevState.context, { busy: busy }), }); }); }; /** * Triggers the form wide validation callback if given * @returns Validation state of the form */ Form.prototype.triggerFormValidation = function () { var onValidate = this.props.onValidate; if (onValidate === undefined) { return true; } var values = this.getValues(); var result = onValidate(values); // If the callback returned null then the form is valid if (result === null) { return true; } // Otherwise parse the result object and update the // field states. var allFieldsValid = true; this.fields.forEach(function (state, name) { var fieldError = parseValidationError(name, getDeepValue(name, result)); var isValid = fieldError === null || typeof fieldError !== 'object'; if (isValid) { return; } state.updateValidation({ valid: false, error: fieldError, }); allFieldsValid = false; }); return allFieldsValid; }; /** * Gathers all the data and calls the onSubmit callback * if provided. * @param submitArgs Arguments that will be passed * to the onSubmit callback */ Form.prototype.callOnSubmit = function (submitArgs) { var onSubmit = this.props.onSubmit; if (onSubmit === undefined) { return; } var values = this.getValues(); return onSubmit(values, submitArgs); }; /** * Combines the local form context with * the values from the props to form the * full form context passed to the form * components. */ Form.prototype.prepareFormContext = function () { var context = this.state.context; var _a = this.props, defaultValues = _a.defaultValues, values = _a.values, asyncValidateOnChange = _a.asyncValidateOnChange, asyncValidationWait = _a.asyncValidationWait, stringFormatter = _a.formatString, disabled = _a.disabled, plaintext = _a.plaintext, busyProp = _a.busy; // Override the busy state with the busy prop if it is set to true var busy = busyProp === true ? busyProp : context.busy; return __assign({}, context, { defaultValues: defaultValues, values: values, asyncValidationWait: asyncValid