UNPKG

@talend/react-forms

Version:

React forms library based on json schema form.

457 lines (448 loc) 16.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = exports.UIFormComponent = void 0; var _react = require("react"); var _reactI18next = require("react-i18next"); var _propTypes = _interopRequireDefault(require("prop-types")); var _designSystem = require("@talend/design-system"); var _jsonSchemaFormCore = require("@talend/json-schema-form-core"); var _constants = require("../constants"); require("../translate"); var _context = require("./context"); var _customFormats = _interopRequireDefault(require("./customFormats")); var _Buttons = _interopRequireDefault(require("./fields/Button/Buttons.component")); var _FormTemplate = require("./FormTemplate"); var _lang = _interopRequireDefault(require("./lang")); var _merge = _interopRequireDefault(require("./merge")); var _errors = require("./utils/errors"); var _properties = require("./utils/properties"); var _propTypes2 = require("./utils/propTypes"); var _validation = require("./utils/validation"); var _widgets = _interopRequireDefault(require("./utils/widgets")); var _Widget = _interopRequireDefault(require("./Widget")); var _UIFormModule = _interopRequireDefault(require("./UIForm.module.scss")); var _jsxRuntime = require("react/jsx-runtime"); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); } function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; } function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } class UIFormComponent extends _react.Component { constructor(props) { super(props); const { jsonSchema, uiSchema } = props; const state = {}; if (Object.keys(jsonSchema).length) { _extends(state, (0, _merge.default)(jsonSchema, uiSchema)); } state.widgets = { ...state.widgets, ...props.widgets }; this.state = state; this.onChange = this.onChange.bind(this); this.onFinish = this.onFinish.bind(this); this.onSubmit = this.onSubmit.bind(this); this.onTrigger = this.onTrigger.bind(this); this.onActionClick = this.onActionClick.bind(this); this.focusFirstError = this.focusFirstError.bind(this); this.setFormRef = this.setFormRef.bind(this); // control the tv4 language here. const language = (0, _lang.default)(props.t); const languageId = props.id || '@talend'; if (props.language != null) { _extends(language, props.language); // Force update of language by id even if already set _jsonSchemaFormCore.tv4.addLanguage(languageId, language); _jsonSchemaFormCore.tv4.language(languageId); } if (!_jsonSchemaFormCore.tv4.language(languageId)) { _jsonSchemaFormCore.tv4.addLanguage(languageId, language); _jsonSchemaFormCore.tv4.language(languageId); // set it } const allFormats = _extends((0, _customFormats.default)(props.t), props.customFormats); _jsonSchemaFormCore.tv4.addFormat(allFormats); } /** * Update the state with the new schema. * @param jsonSchema * @param uiSchema */ componentDidUpdate(prevProps) { if (!this.props.jsonSchema || !this.props.uiSchema || this.props.jsonSchema === prevProps.jsonSchema && this.props.uiSchema === prevProps.uiSchema) { return; } const { jsonSchema, uiSchema } = this.props; if (Object.keys(jsonSchema).length) { const merged = (0, _merge.default)(jsonSchema, uiSchema); this.setState({ ...merged, widgets: { ...merged.widgets, ...this.props.widgets } }); } } /** * Fire onChange callback while interacting with form fields * @param event The event that triggered the callback * @param schema The payload field schema * @param value The payload new value */ onChange(event, { schema, value }) { const newProperties = (0, _properties.mutateValue)(this.props.properties, schema, value); this.props.onChange(event, { schema, value, oldProperties: this.props.properties, properties: newProperties, formData: newProperties }); } /** * Perform validation and triggers when user has finished to change a value * @param event The event that triggered the callback * @param schema The payload field schema * @param value The new value, provided if not already taken into account * @param deepValidation Validate the subItems * Most of the time, this value is not provided. It will be taken from props.properties * This allows to perform triggers/validation while changing a value * (ex: add an element in array) * @param widgetChangeErrors Change errors hook, allows any widget to manipulate the errors map * (ex: shift the errors in array elements on remove) */ onFinish(event, { schema, value }, { deepValidation = false, widgetChangeErrors } = {}) { // get property value let newValue; if (value !== undefined) { newValue = value; } else { newValue = (0, _properties.getValue)(this.props.properties, schema); } // validate value. This validation can be deep if schema is an object or an array const widgetErrors = (0, _validation.validateSingle)(schema, newValue, this.props.properties, this.props.customValidation, deepValidation, event); const hasErrors = Object.values(widgetErrors).find(Boolean); const { t } = this.props; // update errors map let errors = Object.entries(widgetErrors).reduce((accu, [errorKey, errorValue]) => { const errorSchema = { key: errorKey }; let errorMsg = errorValue; if (errorValue && errorValue.startsWith('CUSTOM_ERROR')) { errorMsg = t(errorValue, { defaultValue: (0, _lang.default)(t)[errorValue] }); } return errorMsg ? (0, _errors.addError)(accu, errorSchema, errorMsg) : (0, _errors.removeError)(accu, errorSchema); }, this.props.errors); // widget error modifier if (widgetChangeErrors) { errors = widgetChangeErrors(errors); } // commit errors this.props.setErrors(event, errors); if (!hasErrors && schema.triggers && schema.triggers.length) { let formData = this.props.properties; if (value !== undefined) { formData = (0, _properties.mutateValue)(formData, schema, value); } let propertyName = schema.key.join('.'); if (this.props.moz) { schema.key.forEach((key, index) => { if (index !== schema.key.length - 1) { formData = formData[key]; } }); propertyName = schema.key[schema.key.length - 1]; this.onTrigger(event, { formData, formId: this.props.id, propertyName, value }); } else { const trigger = schema.triggers.find(triggerItem => triggerItem.onEvent === undefined); if (trigger) { this.onTrigger(event, { trigger, schema, properties: formData, errors, value }); } } } } /** * Triggers an onTrigger callback that is allowed to modify the form * @param event The event that triggered the callback * @param payload The trigger payload * trigger The type of trigger * schema The field schema */ onTrigger(event, payload) { const { onTrigger } = this.props; if (!onTrigger) { return null; } if (this.props.moz) { return onTrigger(payload.formData, payload.formId, payload.propertyName, payload.value); } if (!payload.trigger) { throw new Error('onTrigger payload do not have required trigger property'); } return onTrigger(event, { properties: this.props.properties, errors: this.props.errors, ...payload }); } onActionClick(actionOnClick) { if (typeof actionOnClick === 'function') { return (event, data) => actionOnClick(event, { ...data, formData: this.props.properties, properties: this.props.properties }); } return () => {}; } /** * Triggers submit callback if form is valid * @param event the submit event */ onSubmit(event) { if (this.props.onSubmit) { event.preventDefault(); } const { mergedSchema } = this.state; const { properties, customValidation } = this.props; const newErrors = (0, _validation.validateAll)(mergedSchema, properties, customValidation); Object.entries(this.props.errors).filter(entry => entry[0] in newErrors).reduce((accu, [key, value]) => { // eslint-disable-next-line no-param-reassign accu[key] = value; return accu; }, newErrors); const errors = Object.entries(newErrors).filter(entry => entry[1]).reduce((accu, [key, value]) => { // eslint-disable-next-line no-param-reassign accu[key] = value; return accu; }, {}); const isValid = !Object.keys(errors).length; this.props.setErrors(event, errors, !isValid ? this.focusFirstError : undefined); if (this.props.onSubmit && isValid) { if (this.props.moz) { this.props.onSubmit(null, { formData: properties }); } else { this.props.onSubmit(event, properties, mergedSchema); } } return isValid; } setFormRef(element) { this.formRef = element; } focusFirstError() { if (!this.formRef) { return; } const elementWithError = this.formRef.querySelector('[aria-invalid="true"]'); if (elementWithError) { elementWithError.focus(); } } render() { const { onSubmitEnter, onSubmitLeave, properties } = this.props; let actions = this.props.actions || [{ bsStyle: 'primary', label: 'Submit', type: 'submit', widget: 'button', position: 'right' }]; if (!this.state.mergedSchema) { return null; } if (onSubmitEnter) { actions = actions.map(action => { if (action.type === 'submit') { return { ...action, onMouseEnter: event => onSubmitEnter(event, properties), onMouseLeave: onSubmitLeave }; } return action; }); } const formTemplate = this.props.displayMode === 'text' ? _FormTemplate.TextModeFormTemplate : _FormTemplate.DefaultFormTemplate; const widgetsRenderer = () => this.state.mergedSchema.map((nextSchema, index) => /*#__PURE__*/(0, _jsxRuntime.jsx)(_Widget.default, { id: this.props.id, idSeparator: this.props.idSeparator, onChange: this.onChange, onFinish: this.onFinish, onTrigger: this.onTrigger, schema: nextSchema, properties: this.props.properties, errors: this.props.errors, templates: this.props.templates, widgets: this.state.widgets, displayMode: this.props.displayMode, updating: this.props.updating }, index)); const buttonsRenderer = () => { if (actions.length === 0) { return null; } const buttons = /*#__PURE__*/(0, _jsxRuntime.jsx)(_Buttons.default, { id: `${this.props.id}-actions`, onTrigger: this.onTrigger, schema: { items: actions }, onClick: this.onActionClick, getComponent: this.props.getComponent }); if (this.props.anchorButtonsToFooter) { return /*#__PURE__*/(0, _jsxRuntime.jsx)("div", { "data-drawer-absolute-footer-buttons": true, className: _UIFormModule.default['drawer-absolute-footer-buttons'], children: buttons }); } return buttons; }; const Element = this.props.as; return /*#__PURE__*/(0, _jsxRuntime.jsx)(Element, { acceptCharset: this.props.acceptCharset, action: this.props.action, autoComplete: this.props.autoComplete, encType: this.props.encType, id: this.props.id, method: this.props.method, name: this.props.name, noValidate: this.props.as === _designSystem.Form ? this.props.noHtml5Validate : undefined, onReset: this.props.onReset, onSubmit: this.onSubmit, target: this.props.target, ref: this.setFormRef, children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_context.WidgetContext.Provider, { value: { ..._widgets.default, ...this.state.widgets }, children: formTemplate({ children: this.props.children, widgetsRenderer, buttonsRenderer }) }) }); } } exports.UIFormComponent = UIFormComponent; _defineProperty(UIFormComponent, "displayName", 'TalendUIForm'); const I18NUIForm = (0, _reactI18next.withTranslation)(_constants.I18N_DOMAIN_FORMS)(UIFormComponent); if (process.env.NODE_ENV !== 'production') { I18NUIForm.propTypes = { ..._propTypes2.formPropTypes, /** Form definition: Json schema that specify the data model */ jsonSchema: _propTypes.default.object.isRequired, // eslint-disable-line react/forbid-prop-types /** Form definition: UI schema that specify how to render the fields */ uiSchema: _propTypes.default.array.isRequired, // eslint-disable-line react/forbid-prop-types /** * Form definition: Form fields values. * Note that it should contains @definitionName for triggers. */ properties: _propTypes.default.object.isRequired, // eslint-disable-line react/forbid-prop-types /** Form definition: The forms errors { [fieldKey]: errorMessage } */ errors: _propTypes.default.object.isRequired, // eslint-disable-line react/forbid-prop-types /** * Actions buttons to display at the bottom of the form. * If not provided, a single submit button is displayed. */ actions: _propTypes.default.arrayOf(_Buttons.default.propTypes.schema), /** * Tag used to render the form (defaults to "form") */ as: _propTypes.default.string, /** * User callback: Custom validation function. * Prototype: function customValidation(schema, value, properties) * Return format : errorMessage String | falsy * This is triggered on fields that has their uiSchema > customValidation : true */ customValidation: _propTypes.default.func, /* * Form definition: Custom formats */ customFormats: _propTypes.default.object, /** * User callback: Trigger * Prototype: function onTrigger(event, { trigger, schema, properties }) */ onTrigger: _propTypes.default.func, /** Custom templates */ templates: _propTypes.default.object, /** Custom widgets */ widgets: _propTypes.default.object, /** Display mode: example 'text' */ displayMode: _propTypes.default.string, /** State management impl: The change callback */ onChange: _propTypes.default.func.isRequired, /** State management impl: Set All fields validations errors */ setErrors: _propTypes.default.func, getComponent: _propTypes.default.func, onSubmitEnter: _propTypes.default.func, onSubmitLeave: _propTypes.default.func }; UIFormComponent.propTypes = I18NUIForm.propTypes; } I18NUIForm.defaultProps = { ...I18NUIForm.defaultProps, noHtml5Validate: true, properties: {}, as: _designSystem.Form }; UIFormComponent.defaultProps = I18NUIForm.defaultProps; var _default = exports.default = I18NUIForm; //# sourceMappingURL=UIForm.component.js.map