react-ocean-forms
Version:
Forms components for react based on the context api.
1,255 lines (1,224 loc) • 77.3 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
var React = require('react');
var React__default = _interopDefault(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__default.createElement(React__default.Fragment, null, errorArray.map(function (item) {
var errorString = stringFormatter(item.message_id, item.params);
return React__default.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__default.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 = React.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.
*/
(function (FieldErrorMessageId) {
FieldErrorMessageId["AlphaNumeric"] = "ojs_error_alphaNumeric";
FieldErrorMessageId["MaxLength"] = "ojs_error_maxLength";
FieldErrorMessageId["MinLength"] = "ojs_error_minLength";
FieldErrorMessageId["Required"] = "ojs_error_required";
})(exports.FieldErrorMessageId || (exports.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 ? exports.FieldErrorMessageId.Required : undefined;
}
if (typeof value === 'number') {
return undefined;
}
if (typeof value === 'boolean') {
return value === false ? exports.FieldErrorMessageId.Required : undefined;
}
if (typeof value === 'string') {
return value.length === 0 ? exports.FieldErrorMessageId.Required : undefined;
}
return value !== null && value !== undefined ? undefined : exports.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) ? exports.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: exports.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: exports.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__default.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__default.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__default.createElement(CastedComponent, __assign({ context: context, fullName: fullName, validation: validation }, props));
};
return (React__default.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__default.createElement(FormContext.Provider, { value: subContext }, renderProp(groupState)));
};
BaseFieldGroup.displayName = 'FieldGroup';
return BaseFieldGroup;
}(React__default.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__default.createElement(React__default.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__default.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__default.createElement("div", { className: groupClass },
React__default.createElement("label", { htmlFor: field.id, className: "text-right" },
React__default.createElement(FormText, { text: label }),
createRequiredMarker(meta.isRequired)),
React__default.createElement("div", { className: "input-container" },
children,
React__default.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