@bynder/react-formulation
Version:
Simple form validation
287 lines (245 loc) • 7.97 kB
JSX
// @flow
import React from 'react';
import PropTypes from 'prop-types';
import autobind from 'autobind-decorator';
import type { Children } from 'react';
import withValidation from './withValidation';
import defaultMessages from './utils/validationMessages';
type Props = {
name: string,
setInitialModel: () => void,
initialValue: string,
children: Children,
bindInput: () => void,
getSchema: () => void,
isButtonDisabled: () => void,
onSubmit: () => void,
model: Object,
resetForm: () => void,
className: string,
validateForm: () => void,
validateOn: string,
};
const defaultStyle = {
display: 'inline',
};
class InlineFormValidator extends React.Component {
static props: Props;
getChildContext() {
return {
validatorName: this.props.name,
validatorBindInput: this.props.bindInput,
validatorAttributes: this.props.getSchema,
validatorCanSubmit: !this.props.isButtonDisabled,
validatorResetForm: this.props.resetForm,
};
}
componentWillMount() {
const { name, initialValue } = this.props;
this.props.setInitialModel({
[name]: initialValue,
});
}
componentWillReceiveProps(nextProps: Props) {
if (nextProps.name && nextProps.initialValue !== this.props.initialValue) {
nextProps.setInitialModel({
[nextProps.name]: nextProps.initialValue,
});
}
}
onSubmit(e: Event) {
e.preventDefault();
const submitValue = {};
const validatedForm = this.props.validateForm();
Object.entries(this.props.model).forEach(([model, attributes]) => {
submitValue.name = model;
submitValue.value = attributes.value;
});
if ((this.props.validateOn === 'submit' && validatedForm.fields[this.props.name].isValid) ||
(this.props.validateOn !== 'submit' && !this.props.isButtonDisabled)) {
this.props.onSubmit(submitValue);
}
}
render() {
return (
<form onSubmit={this.onSubmit} className={this.props.className}>
{this.props.children}
</form>
);
}
}
InlineFormValidator.childContextTypes = {
validatorName: PropTypes.string,
validatorBindInput: PropTypes.func,
validatorAttributes: PropTypes.func,
validatorCanSubmit: PropTypes.bool,
validatorResetForm: PropTypes.func,
};
class InlineFormFieldInput extends React.Component { // eslint-disable-line react/no-multi-comp
onKeyUp(e) {
if (this.props.resetOnEscape && e.keyCode === 27) {
this.context.validatorResetForm();
}
if (typeof this.props.child.props.onKeyUp === 'function') {
this.props.child.props.onKeyUp(e);
}
}
render() {
return React.cloneElement(this.props.child, {
...this.context.validatorBindInput(this.context.validatorName),
onKeyUp: this.onKeyUp,
});
}
}
InlineFormFieldInput.propTypes = {
child: PropTypes.node,
resetOnEscape: PropTypes.bool,
};
InlineFormFieldInput.contextTypes = {
validatorName: PropTypes.string,
validatorBindInput: PropTypes.func,
validatorResetForm: PropTypes.func,
};
const InlineFormField = ({ resetOnEscape, children, ...props }) => (
<div {...props}>
{React.Children.map(children, (child, i) => (
<InlineFormFieldInput key={i} child={child} resetOnEscape={resetOnEscape} />
))}
</div>
);
InlineFormField.propTypes = {
resetOnEscape: PropTypes.bool,
children: PropTypes.node,
};
const InlineFormErrors = ({
children,
...props
}, context) => {
let errors = [];
const schema = context.validatorAttributes(context.validatorName);
if (schema.errors && schema.errors.length) {
errors = schema.errors;
}
return (
<div {...props}>
{errors.map((error) => {
let displayError = null;
const messages = context.validatorMessages;
if (typeof error === 'string') {
displayError = error;
} else if (messages && messages[error.rule]) {
const customError = messages[error.rule];
if (typeof customError === 'function') {
displayError = customError(error.condition);
} else if (typeof customError === 'string') {
displayError = customError;
}
} else if (defaultMessages[error.rule]) {
displayError = defaultMessages[error.rule](error.condition);
} else {
displayError = error.rule;
}
return displayError;
})}
</div>
);
};
InlineFormErrors.propTypes = {
children: PropTypes.node,
};
InlineFormErrors.contextTypes = {
validatorName: PropTypes.string,
validatorAttributes: PropTypes.func,
validatorMessages: PropTypes.object,
};
const InlineFormSubmit = ({
style,
children,
...props
}, context) => {
if (children && children.length > 1) {
return (
<div style={style || defaultStyle} {...props}>
{React.Children.map(children,
(child) => {
if (typeof child === 'object') {
return React.cloneElement(child, {
disabled: !context.validatorCanSubmit,
});
}
return null;
},
)}
</div>
);
}
const child = React.Children.only(children);
return React.cloneElement(child, {
disabled: !context.validatorCanSubmit,
});
};
InlineFormSubmit.propTypes = {
style: PropTypes.objectOf(
PropTypes.string,
),
children: PropTypes.node,
};
InlineFormSubmit.contextTypes = {
validatorCanSubmit: PropTypes.bool,
};
const InlineFormCancel = ({
style,
children,
...props
}, context) => {
if (children && children.length > 1) {
return (
<div style={style || defaultStyle} {...props}>
{React.Children.map(children,
(child) => {
const onClick = () => {
context.validatorResetForm();
if (child.props.onClick) {
child.props.onClick();
}
};
return React.cloneElement(child, {
onClick,
});
},
)}
</div>
);
}
const child = React.Children.only(children);
const onClick = () => {
context.validatorResetForm();
if (child.props.onClick) {
child.props.onClick();
}
};
return React.cloneElement(child, {
onClick,
});
};
InlineFormCancel.propTypes = {
style: PropTypes.objectOf(
PropTypes.string,
),
children: PropTypes.node,
};
InlineFormCancel.contextTypes = {
validatorResetForm: PropTypes.func,
};
const InlineForm = withValidation(InlineFormValidator);
InlineForm.Field = InlineFormField;
InlineForm.Errors = InlineFormErrors;
InlineForm.Submit = InlineFormSubmit;
InlineForm.Cancel = InlineFormCancel;
Object.defineProperty(InlineForm.Field, 'name', { value: 'InlineForm.Field' });
Object.defineProperty(InlineForm.Errors, 'name', { value: 'InlineForm.Errors' });
Object.defineProperty(InlineForm.Submit, 'name', { value: 'InlineForm.Submit' });
Object.defineProperty(InlineForm.Cancel, 'name', { value: 'InlineForm.Cancel' });
export default InlineForm;