@talend/react-forms
Version:
React forms library based on json schema form.
457 lines (448 loc) • 16.2 kB
JavaScript
"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