2016-05-02 12:45:42 +05:30
|
|
|
import React, { Component, PropTypes } from 'react';
|
|
|
|
|
|
|
|
import classNames from 'classnames';
|
|
|
|
|
2016-05-02 15:08:45 +05:30
|
|
|
import FormModel from './FormModel';
|
2016-05-02 14:50:50 +05:30
|
|
|
|
2016-05-02 12:45:42 +05:30
|
|
|
import styles from './form.scss';
|
|
|
|
|
|
|
|
export default class Form extends Component {
|
|
|
|
static displayName = 'Form';
|
|
|
|
|
|
|
|
static propTypes = {
|
|
|
|
id: PropTypes.string, // and id, that uniquely identifies form contents
|
|
|
|
isLoading: PropTypes.bool,
|
2016-05-02 14:50:50 +05:30
|
|
|
form: PropTypes.instanceOf(FormModel),
|
2016-05-02 12:45:42 +05:30
|
|
|
onSubmit: PropTypes.func,
|
|
|
|
onInvalid: PropTypes.func,
|
|
|
|
children: PropTypes.oneOfType([
|
|
|
|
PropTypes.arrayOf(PropTypes.node),
|
|
|
|
PropTypes.node
|
|
|
|
])
|
|
|
|
};
|
|
|
|
|
|
|
|
static defaultProps = {
|
|
|
|
id: 'default',
|
|
|
|
isLoading: false,
|
|
|
|
onSubmit() {},
|
|
|
|
onInvalid() {}
|
|
|
|
};
|
|
|
|
|
|
|
|
state = {
|
2016-05-28 01:34:17 +05:30
|
|
|
isTouched: false,
|
|
|
|
isLoading: this.props.isLoading || false
|
2016-05-02 12:45:42 +05:30
|
|
|
};
|
|
|
|
|
2016-05-28 01:34:17 +05:30
|
|
|
componentWillMount() {
|
|
|
|
if (this.props.form) {
|
|
|
|
this.props.form.addLoadingListener(this.onLoading);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-02 12:45:42 +05:30
|
|
|
componentWillReceiveProps(nextProps) {
|
|
|
|
if (nextProps.id !== this.props.id) {
|
|
|
|
this.setState({
|
|
|
|
isTouched: false
|
|
|
|
});
|
|
|
|
}
|
2016-05-28 01:34:17 +05:30
|
|
|
|
|
|
|
if (typeof nextProps.isLoading !== 'undefined') {
|
|
|
|
this.setState({
|
|
|
|
isLoading: nextProps.isLoading
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (nextProps.form && nextProps.form !== this.props.form) {
|
|
|
|
throw new Error('The FormModel instance should not be changed during component lifetime');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
componentWillUnmount() {
|
|
|
|
if (this.props.form) {
|
|
|
|
this.props.form.removeLoadingListener(this.onLoading);
|
|
|
|
}
|
2016-05-02 12:45:42 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
render() {
|
2016-05-28 01:34:17 +05:30
|
|
|
const {isLoading} = this.state;
|
2016-05-02 12:45:42 +05:30
|
|
|
|
|
|
|
return (
|
|
|
|
<form
|
|
|
|
className={classNames(
|
|
|
|
styles.form,
|
|
|
|
{
|
|
|
|
[styles.isFormLoading]: isLoading,
|
|
|
|
[styles.formTouched]: this.state.isTouched
|
|
|
|
}
|
|
|
|
)}
|
|
|
|
onSubmit={this.onFormSubmit}
|
|
|
|
noValidate
|
|
|
|
>
|
|
|
|
{this.props.children}
|
|
|
|
</form>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
onFormSubmit = (event) => {
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
|
|
if (!this.state.isTouched) {
|
|
|
|
this.setState({
|
|
|
|
isTouched: true
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
const form = event.currentTarget;
|
|
|
|
|
|
|
|
if (form.checkValidity()) {
|
|
|
|
this.props.onSubmit();
|
|
|
|
} else {
|
2016-05-02 14:50:50 +05:30
|
|
|
const invalidEls = form.querySelectorAll(':invalid');
|
|
|
|
const errors = {};
|
|
|
|
invalidEls[0].focus(); // focus on first error
|
|
|
|
|
|
|
|
Array.from(invalidEls).reduce((errors, el) => {
|
|
|
|
if (!el.name) {
|
|
|
|
console.warn('Found an element without name', el);
|
|
|
|
return errors;
|
|
|
|
}
|
|
|
|
|
|
|
|
let errorMessage = el.validationMessage;
|
|
|
|
if (el.validity.valueMissing) {
|
|
|
|
errorMessage = `error.${el.name}_required`;
|
|
|
|
} else if (el.validity.typeMismatch) {
|
|
|
|
errorMessage = `error.${el.name}_invalid`;
|
|
|
|
}
|
|
|
|
|
|
|
|
errors[el.name] = errorMessage;
|
2016-05-02 12:45:42 +05:30
|
|
|
|
2016-05-02 14:50:50 +05:30
|
|
|
return errors;
|
|
|
|
}, errors);
|
2016-05-02 12:45:42 +05:30
|
|
|
|
2016-05-02 14:50:50 +05:30
|
|
|
this.props.form && this.props.form.setErrors(errors);
|
|
|
|
this.props.onInvalid(errors);
|
2016-05-02 12:45:42 +05:30
|
|
|
}
|
|
|
|
};
|
2016-05-28 01:34:17 +05:30
|
|
|
|
|
|
|
onLoading = (isLoading) => this.setState({isLoading});
|
2016-05-02 12:45:42 +05:30
|
|
|
}
|