formsy-react/src/Wrapper.js

256 lines
6.8 KiB
JavaScript

import PropTypes from 'prop-types';
import React from 'react';
import utils from './utils';
const convertValidationsToObject = (validations) => {
if (typeof validations === 'string') {
return validations.split(/,(?![^{[]*[}\]])/g).reduce((validationsAccumulator, validation) => {
let args = validation.split(':');
const validateMethod = args.shift();
args = args.map((arg) => {
try {
return JSON.parse(arg);
} catch (e) {
return arg; // It is a string if it can not parse it
}
});
if (args.length > 1) {
throw new Error('Formsy does not support multiple args on string validations. Use object format of validations instead.');
}
// Avoid parameter reassignment
const validationsAccumulatorCopy = Object.assign({}, validationsAccumulator);
validationsAccumulatorCopy[validateMethod] = args.length ? args[0] : true;
return validationsAccumulatorCopy;
}, {});
}
return validations || {};
};
const propTypes = {
innerRef: PropTypes.func,
name: PropTypes.string.isRequired,
required: PropTypes.bool,
validations: PropTypes.oneOfType([
PropTypes.object,
PropTypes.string,
]),
value: PropTypes.oneOfType([
PropTypes.bool,
PropTypes.string,
]),
};
export {
propTypes,
};
export default (Component) => {
class WrappedComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
value: props.value,
isRequired: false,
isValid: true,
isPristine: true,
pristineValue: props.value,
validationError: [],
externalError: null,
formSubmitted: false,
};
this.getErrorMessage = this.getErrorMessage.bind(this);
this.getErrorMessages = this.getErrorMessages.bind(this);
this.getValue = this.getValue.bind(this);
this.isFormDisabled = this.isFormDisabled.bind(this);
this.isPristine = this.isPristine.bind(this);
this.isRequired = this.isRequired.bind(this);
this.isValid = this.isValid.bind(this);
this.resetValue = this.resetValue.bind(this);
this.setValue = this.setValue.bind(this);
this.showRequired = this.showRequired.bind(this);
}
componentWillMount() {
const configure = () => {
this.setValidations(this.props.validations, this.props.required);
// Pass a function instead?
this.context.formsy.attachToForm(this);
};
if (!this.props.name) {
throw new Error('Form Input requires a name property when used');
}
configure();
}
// We have to make sure the validate method is kept when new props are added
componentWillReceiveProps(nextProps) {
this.setValidations(nextProps.validations, nextProps.required);
}
componentDidUpdate(prevProps) {
// If the value passed has changed, set it. If value is not passed it will
// internally update, and this will never run
if (!utils.isSame(this.props.value, prevProps.value)) {
this.setValue(this.props.value);
}
// If validations or required is changed, run a new validation
if (!utils.isSame(this.props.validations, prevProps.validations) ||
!utils.isSame(this.props.required, prevProps.required)) {
this.context.formsy.validate(this);
}
}
// Detach it when component unmounts
componentWillUnmount() {
this.context.formsy.detachFromForm(this);
}
getErrorMessage() {
const messages = this.getErrorMessages();
return messages.length ? messages[0] : null;
}
getErrorMessages() {
return !this.isValid() || this.showRequired() ?
(this.state.externalError || this.state.validationError || []) : [];
}
getValue() {
return this.state.value;
}
hasValue() {
return this.state.value !== '';
}
isFormDisabled() {
return this.context.formsy.isFormDisabled();
}
isFormSubmitted() {
return this.state.formSubmitted;
}
isPristine() {
return this.state.isPristine;
}
isRequired() {
return !!this.props.required;
}
isValid() {
return this.state.isValid;
}
isValidValue(value) {
return this.context.formsy.isValidValue.call(null, this, value);
// return this.props.isValidValue.call(null, this, value);
}
resetValue() {
this.setState({
value: this.state.pristineValue,
isPristine: true,
}, () => {
this.context.formsy.validate(this);
});
}
setValidations(validations, required) {
// Add validations to the store itself as the props object can not be modified
this.validations = convertValidationsToObject(validations) || {};
this.requiredValidations = required === true ? { isDefaultRequiredValue: true } :
convertValidationsToObject(required);
}
// By default, we validate after the value has been set.
// A user can override this and pass a second parameter of `false` to skip validation.
setValue(value, validate = true) {
if (!validate) {
this.setState({
value,
});
} else {
this.setState({
value,
isPristine: false,
}, () => {
this.context.formsy.validate(this);
});
}
}
showError() {
return !this.showRequired() && !this.isValid();
}
showRequired() {
return this.state.isRequired;
}
render() {
const { innerRef } = this.props;
const propsForElement = {
getErrorMessage: this.getErrorMessage,
getErrorMessages: this.getErrorMessages,
getValue: this.getValue,
hasValue: this.hasValue,
isFormDisabled: this.isFormDisabled,
isValid: this.isValid,
isPristine: this.isPristine,
isFormSubmitted: this.isFormSubmitted,
isRequired: this.isRequired,
isValidValue: this.isValidValue,
resetValue: this.resetValue,
setValidations: this.setValidations,
setValue: this.setValue,
showRequired: this.showRequired,
showError: this.showError,
...this.props,
};
if (innerRef) {
propsForElement.ref = innerRef;
}
return React.createElement(Component, propsForElement);
}
}
function getDisplayName(component) {
return (
component.displayName ||
component.name ||
(typeof component === 'string' ? component : 'Component')
);
}
WrappedComponent.displayName = `Formsy(${getDisplayName(Component)})`;
WrappedComponent.contextTypes = {
formsy: PropTypes.object, // What about required?
};
WrappedComponent.defaultProps = {
innerRef: () => {},
required: false,
validationError: '',
validationErrors: {},
validations: null,
value: Component.defaultValue,
};
WrappedComponent.propTypes = propTypes;
return WrappedComponent;
};