fix(netlify-cms-core): validate nested fields (#1873)

This commit is contained in:
Bartholomew
2019-02-05 23:27:34 +01:00
committed by Shawn Erquhart
parent ade5809dff
commit 627e600d29
10 changed files with 183 additions and 22 deletions

View File

@ -68,13 +68,17 @@ const valueTypes = {
};
export default class ListControl extends React.Component {
validations = [];
static propTypes = {
metadata: ImmutablePropTypes.map,
onChange: PropTypes.func.isRequired,
onChangeObject: PropTypes.func.isRequired,
onValidateObject: PropTypes.func.isRequired,
value: ImmutablePropTypes.list,
field: PropTypes.object,
forID: PropTypes.string,
controlRef: PropTypes.func,
mediaPaths: ImmutablePropTypes.map.isRequired,
getAsset: PropTypes.func.isRequired,
onOpenMediaLibrary: PropTypes.func.isRequired,
@ -85,6 +89,8 @@ export default class ListControl extends React.Component {
setInactiveStyle: PropTypes.func.isRequired,
editorControl: PropTypes.func.isRequired,
resolveWidget: PropTypes.func.isRequired,
clearFieldErrors: PropTypes.func.isRequired,
fieldsErrors: ImmutablePropTypes.map.isRequired,
};
static defaultProps = {
@ -168,6 +174,17 @@ export default class ListControl extends React.Component {
onChange((value || List()).push(parsedValue));
};
processControlRef = ref => {
if (!ref) return;
this.validations.push(ref.validate);
};
validate = () => {
this.validations.forEach(validateListItem => {
validateListItem();
});
};
/**
* In case the `onChangeObject` function is frozen by a child widget implementation,
* e.g. when debounced, always get the latest object value instead of using
@ -196,16 +213,25 @@ export default class ListControl extends React.Component {
handleRemove = (index, event) => {
event.preventDefault();
const { itemsCollapsed } = this.state;
const { value, metadata, onChange, field } = this.props;
const { value, metadata, onChange, field, clearFieldErrors } = this.props;
const collectionName = field.get('name');
const isSingleField = this.getValueType() === valueTypes.SINGLE;
const metadataRemovePath = isSingleField ? value.get(index) : value.get(index).valueSeq();
const parsedMetadata = metadata && { [collectionName]: metadata.removeIn(metadataRemovePath) };
// Removed item object index is the last item in the list
const removedItemIndex = value.count() - 1;
this.setState({ itemsCollapsed: itemsCollapsed.delete(index) });
onChange(value.remove(index), parsedMetadata);
clearFieldErrors();
// Remove deleted item object validation
if (this.validations) {
this.validations.splice(removedItemIndex, 1);
}
};
handleItemCollapseToggle = (index, event) => {
@ -253,7 +279,16 @@ export default class ListControl extends React.Component {
};
renderItem = (item, index) => {
const { classNameWrapper, editorControl, resolveWidget } = this.props;
const {
classNameWrapper,
editorControl,
onValidateObject,
clearFieldErrors,
fieldsErrors,
controlRef,
resolveWidget,
} = this.props;
const { itemsCollapsed } = this.state;
const collapsed = itemsCollapsed.get(index);
let field = this.props.field;
@ -286,6 +321,11 @@ export default class ListControl extends React.Component {
editorControl={editorControl}
resolveWidget={resolveWidget}
forList
onValidateObject={onValidateObject}
clearFieldErrors={clearFieldErrors}
fieldsErrors={fieldsErrors}
ref={this.processControlRef}
controlRef={controlRef}
/>
</SortableListItem>
);