cspace-ui
Version:
CollectionSpace user interface for browsers
234 lines (199 loc) • 6.38 kB
JSX
import React, { isValidElement, PureComponent } from 'react';
import PropTypes from 'prop-types';
import Immutable from 'immutable';
import get from 'lodash/get';
import classNames from 'classnames';
import warning from 'warning';
import { getCsid } from '../../helpers/recordDataHelpers';
import styles from '../../../styles/cspace-ui/RecordForm.css';
function renderTemplate(component, messages, handlers) {
const overrideProps = {};
const type = get(component, 'type');
if (type) {
// FIXME: Do this without looking at propTypes, so that propTypes may be removed in the
// production build.
// eslint-disable-next-line react/forbid-foreign-prop-types
const { propTypes } = type;
if (propTypes) {
Object.keys(handlers).forEach((handlerName) => {
if (propTypes[handlerName] && !component.props[handlerName]) {
overrideProps[handlerName] = handlers[handlerName];
}
});
}
return React.cloneElement(
component,
overrideProps,
React.Children.map(
component.props.children,
(child) => renderTemplate(child, messages, handlers),
),
);
}
return component;
}
const propTypes = {
config: PropTypes.shape({
recordTypes: PropTypes.object,
}),
recordTypeConfig: PropTypes.shape({
defaultForm: PropTypes.string,
fields: PropTypes.object,
forms: PropTypes.object,
messages: PropTypes.object,
}),
recordType: PropTypes.string.isRequired,
vocabulary: PropTypes.string,
csid: PropTypes.string,
data: PropTypes.instanceOf(Immutable.Map),
formName: PropTypes.string,
readOnly: PropTypes.bool,
roleNames: PropTypes.instanceOf(Immutable.List),
subrecordData: PropTypes.instanceOf(Immutable.Map),
onAddInstance: PropTypes.func,
onCommit: PropTypes.func,
onMoveInstance: PropTypes.func,
onRemoveInstance: PropTypes.func,
onSortInstances: PropTypes.func,
};
const defaultProps = {
data: Immutable.Map(),
};
const childContextTypes = {
config: PropTypes.shape({
recordTypes: PropTypes.object,
}),
formName: PropTypes.string,
recordData: PropTypes.instanceOf(Immutable.Map),
recordType: PropTypes.string,
recordTypeConfig: PropTypes.PropTypes.shape({
fields: PropTypes.object,
}),
roleNames: PropTypes.instanceOf(Immutable.List),
subrecordData: PropTypes.instanceOf(Immutable.Map),
vocabulary: PropTypes.string,
csid: PropTypes.string,
readOnly: PropTypes.bool,
};
export default class RecordForm extends PureComponent {
getChildContext() {
const {
config,
csid,
data,
formName,
recordType,
recordTypeConfig,
roleNames,
subrecordData,
vocabulary,
readOnly,
} = this.props;
// Get the csid from the data. This may differ from the csid in props, for example if a
// urn-style csid was entered in the address bar. We always want to supply the guid-style csid
// in the context.
const dataCsid = getCsid(data);
return {
config,
formName,
readOnly,
recordType,
recordTypeConfig,
roleNames,
subrecordData,
vocabulary,
csid: dataCsid || csid,
recordData: data,
};
}
render() {
const {
config,
csid,
data,
formName,
readOnly,
recordType,
recordTypeConfig,
onAddInstance,
onCommit,
onMoveInstance,
onRemoveInstance,
onSortInstances,
} = this.props;
if (!recordTypeConfig) {
return null;
}
const {
fields,
forms,
messages,
} = recordTypeConfig;
const handlers = {
onAddInstance: (path, position) => {
onAddInstance(recordTypeConfig, csid, path, position);
},
onCommit: (path, value) => {
onCommit(recordTypeConfig, csid, path, value);
},
onSortInstances: (path, byField) => {
onSortInstances(config, recordTypeConfig, csid, path, byField);
},
onMoveInstance: (path, newPosition) => {
onMoveInstance(recordTypeConfig, csid, path, newPosition);
},
onRemoveInstance: (path) => {
onRemoveInstance(recordTypeConfig, csid, path);
},
};
let formTemplate;
if (formName) {
formTemplate = get(forms, [formName, 'template']);
}
if (!formTemplate) {
// Try to get the configured default form.
const defaultFormName = recordTypeConfig.defaultForm || 'default';
if (defaultFormName) {
formTemplate = get(forms, [defaultFormName, 'template']);
}
warning(formTemplate, `No form template found for form name ${formName} or default form name ${defaultFormName} in record type ${recordType}. Check the record type plugin configuration.`);
}
if (typeof formTemplate === 'function') {
const result = formTemplate(data, config);
if (!result) {
return null;
}
if (typeof result === 'string') {
// The form template function returned a string. This will be the name of another form
// template to use.
formTemplate = get(forms, [result, 'template']);
warning(formTemplate, `No form template found for computed form name ${result} for form name ${formName} in record type ${recordType}. Check the record type plugin configuration.`);
} else {
if (isValidElement(result)) {
// The form template function returned a React element to use.
formTemplate = result;
}
warning(formTemplate, `The computed form template for form name ${formName} in record type ${recordType} did not return a string or a React element. Check the record type plugin configuration.`);
}
}
const rootPropertyName = Object.keys(fields)[0];
const formContent = React.cloneElement(formTemplate, {
readOnly,
name: rootPropertyName,
value: data.get(rootPropertyName),
children: React.Children.map(
formTemplate.props.children,
(child) => renderTemplate(child, messages, handlers),
),
});
const className = classNames(styles.common, `cspace-ui-RecordForm--${recordType}`);
return (
<div className={className}>
{formContent}
</div>
);
}
}
RecordForm.propTypes = propTypes;
RecordForm.defaultProps = defaultProps;
RecordForm.childContextTypes = childContextTypes;