UNPKG

@bynder/react-formulation

Version:
342 lines (305 loc) 12 kB
// @flow import React from 'react'; import PropTypes from 'prop-types'; import autobind from 'autobind-decorator'; import getComponentName from './utils/getComponentName'; import { getValidationErrors, getAllValidationErrors, } from './utils/validateSchema'; export default function withValidation(configuration: Object | ReactClass<any>) { const Validation = function Validation(WrappedComponent: ReactClass<any>) { class ValidationWrapper extends React.Component { constructor(props: Props) { super(props); this.schema = {}; if (configuration && configuration.schema) { this.schema = configuration.schema; } else { this.schema = { [props.name]: props.rules, }; } let validateOn = 'blur'; if (configuration && configuration.validateOn) { validateOn = configuration.validateOn; } else if (props.validateOn) { validateOn = props.validateOn; } let customMessages; if (configuration && configuration.messages) { customMessages = configuration.messages; } else if (props.messages) { customMessages = props.messages; } const initialModel = props.initialModel || {}; if (!props.initialModel) { Object.keys(this.schema).forEach((key) => { initialModel[key] = { value: null, isTouched: false, }; }); } this.state = { model: initialModel || {}, isTouched: false, schema: getAllValidationErrors(this.schema, initialModel), initialModel, validateOn, customMessages, isButtonDisabled: true, }; } getChildContext() { return { validatorBindInput: this.bindInput, validatorAttributes: this.getSchema, validatorCanSubmit: this.state.schema.isValid && this.state.isTouched, validatorMessages: this.state.customMessages, validatorGetAllErrors: this.getAllValidationErrors, validateOn: this.state.validateOn, }; } componentWillUpdate(nextProps: Props, nextState: Object) { if (this.state.model !== nextState.model) { const model = nextState.model; const schema = getAllValidationErrors(this.schema, model); // disable the button if there are no changes const isChanged = Object.keys(model).some(key => ( model[key].value !== this.state.initialModel[key].value )); const isButtonDisabled = (isChanged) ? !schema.isValid || !nextState.isTouched : true; this.setState({ isButtonDisabled, }); } } @autobind setInitialModel(model: Object) { const initialModel = this.state.model; Object.entries(model).forEach(([key, value]) => { initialModel[key] = { value, isTouched: false, }; }); this.setState({ model: initialModel, initialModel }); this.resetValidation(initialModel); } @autobind setModel(model: Object) { this.setState({ model }); return model; } @autobind setProperty(prop: string, value: any) { const newModel = this.setModel({ ...this.state.model, [prop]: { ...this.state.model[prop], value, }, }); this.getAllValidationErrors(newModel); return newModel; } @autobind setInputProperty(prop: string, value: any) { return this.setModel({ ...this.state.model, [prop]: { ...this.state.model[prop], value, isTouched: true, }, }); } @autobind setPropertyAndValidate(prop: string, value: any) { const model = this.setInputProperty(prop, value); const validationErrors = getValidationErrors( this.schema, prop, value, this.state.schema, model, ); this.setState({ schema: validationErrors, }); } @autobind setTouched() { this.setState({ isTouched: true }); } @autobind setUntouched() { this.setState({ isTouched: false }); } @autobind getAllValidationErrors(initialModel: Object) { const model = initialModel || this.state.model; const schema = getAllValidationErrors(this.schema, model); this.setState({ schema, }); return schema; } @autobind getSchema(name: string) { return this.state.schema.fields[name]; } @autobind validateInput(e: Event) { const { name, type, value: targetValue } = e.target; const value = (type === 'checkbox') ? e.target.checked : targetValue; this.setState({ schema: getValidationErrors( this.schema, name, value, this.state.schema, this.state.model, ), }); } @autobind validateField(name: string) { const value = this.state.model[name].value; this.setState({ schema: getValidationErrors( this.schema, name, value, this.state.schema, this.state.model, ), }); } @autobind bindToChangeEvent(e: Event) { const { name, type, value } = e.target; if (type === 'checkbox') { this.setInputProperty(name, e.target.checked); } else { this.setInputProperty(name, value); } if (this.state.validateOn === 'change') { this.validateInput(e); } else if (this.state.validateOn === 'submit' && this.state.schema.fields[name].isValid !== null) { this.resetValidation(); } else if (this.state.validateOn === 'blur' && this.state.schema.fields[name].isValid !== null) { this.state.schema.fields[name].isValid = null; } if (!this.state.isTouched) { this.setState({ isTouched: true }); } } @autobind bindInput(name: string, type: string) { const model = this.state.model[name]; const props = { name, onChange: this.bindToChangeEvent, onBlur: this.state.validateOn === 'blur' ? this.validateInput : null, }; if (type === 'checkbox') { return { ...props, checked: (model && model.value) ? model.value : false, }; } return { ...props, value: (model && model.value) ? model.value : '', }; } @autobind resetValidation() { const fields = {}; Object.entries(this.state.schema.fields).forEach(([name, attributes]) => { fields[name] = { ...attributes, isTouched: false, isValid: null, errors: [], }; }); this.setState({ schema: { isValid: null, fields, }, isButtonDisabled: true, }); } @autobind resetForm() { this.resetValidation(); this.setState({ model: this.state.initialModel, isButtonDisabled: true, }); } @autobind clearForm() { const model = {}; Object.entries(this.state.model).forEach(([name, attributes]) => { model[name] = { ...attributes, value: '', isTouched: '', }; }); this.resetValidation(); this.setState({ model, isTouched: false, }); } render() { const nextProps = { ...this.props, bindInput: this.bindInput, bindToChangeEvent: this.bindToChangeEvent, model: this.state.model, setPropertyAndValidate: this.setPropertyAndValidate, setProperty: this.setProperty, setModel: this.setModel, setInitialModel: this.setInitialModel, isTouched: this.state.isTouched, schema: this.state.schema, validateForm: this.getAllValidationErrors, validateField: this.validateField, resetValidation: this.resetValidation, resetForm: this.resetForm, clearForm: this.clearForm, getSchema: this.getSchema, isButtonDisabled: this.state.isButtonDisabled, setTouched: this.setTouched, setUntouched: this.setUntouched, }; return ( <WrappedComponent {...nextProps} /> ); } } ValidationWrapper.childContextTypes = { validatorBindInput: PropTypes.func, validatorAttributes: PropTypes.func, validatorCanSubmit: PropTypes.bool, validatorMessages: PropTypes.object, validatorGetAllErrors: PropTypes.func, validateOn: PropTypes.string, }; ValidationWrapper.displayName = `Validator(${getComponentName(WrappedComponent)})`; return ValidationWrapper; }; if (typeof configuration === 'object') { return Validation; } return Validation(configuration); }