2017-08-20 21:15:21 +05:30
|
|
|
// @flow
|
|
|
|
import React, { Component } from 'react';
|
2016-05-02 12:45:42 +05:30
|
|
|
|
|
|
|
import classNames from 'classnames';
|
|
|
|
|
2017-06-13 01:02:59 +05:30
|
|
|
import logger from 'services/logger';
|
2016-05-02 14:50:50 +05:30
|
|
|
|
2016-05-02 12:45:42 +05:30
|
|
|
import styles from './form.scss';
|
|
|
|
|
2017-08-20 21:15:21 +05:30
|
|
|
import type FormModel from './FormModel';
|
|
|
|
|
|
|
|
type Props = {
|
|
|
|
id: string,
|
|
|
|
isLoading: bool,
|
|
|
|
form?: FormModel,
|
|
|
|
onSubmit: Function,
|
|
|
|
onInvalid: (errors: {[errorKey: string]: string}) => void,
|
|
|
|
children: *
|
|
|
|
};
|
2017-08-23 02:01:41 +05:30
|
|
|
type State = {
|
|
|
|
isTouched: bool,
|
|
|
|
isLoading: bool
|
|
|
|
};
|
2017-08-20 21:15:21 +05:30
|
|
|
type InputElement = HTMLInputElement|HTMLTextAreaElement;
|
|
|
|
|
2017-08-23 02:01:41 +05:30
|
|
|
export default class Form extends Component<Props, State> {
|
2016-05-02 12:45:42 +05:30
|
|
|
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
|
|
|
};
|
|
|
|
|
2017-08-20 21:15:21 +05:30
|
|
|
formEl: ?HTMLFormElement;
|
|
|
|
|
2016-05-28 01:34:17 +05:30
|
|
|
componentWillMount() {
|
|
|
|
if (this.props.form) {
|
|
|
|
this.props.form.addLoadingListener(this.onLoading);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-20 21:15:21 +05:30
|
|
|
componentWillReceiveProps(nextProps: Props) {
|
2016-05-02 12:45:42 +05:30
|
|
|
if (nextProps.id !== this.props.id) {
|
|
|
|
this.setState({
|
|
|
|
isTouched: false
|
|
|
|
});
|
|
|
|
}
|
2016-05-28 01:34:17 +05:30
|
|
|
|
2016-08-14 14:31:04 +05:30
|
|
|
if (typeof nextProps.isLoading !== 'undefined'
|
|
|
|
&& nextProps.isLoading !== this.state.isLoading
|
|
|
|
) {
|
2016-05-28 01:34:17 +05:30
|
|
|
this.setState({
|
|
|
|
isLoading: nextProps.isLoading
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-08-20 21:15:21 +05:30
|
|
|
const nextForm = nextProps.form;
|
|
|
|
if (nextForm
|
|
|
|
&& this.props.form
|
|
|
|
&& nextForm !== this.props.form
|
|
|
|
) {
|
2016-05-28 03:06:10 +05:30
|
|
|
this.props.form.removeLoadingListener(this.onLoading);
|
2017-08-20 21:15:21 +05:30
|
|
|
nextForm.addLoadingListener(this.onLoading);
|
2016-05-28 01:34:17 +05:30
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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}
|
2017-08-20 21:15:21 +05:30
|
|
|
ref={(el: ?HTMLFormElement) => this.formEl = el}
|
2016-05-02 12:45:42 +05:30
|
|
|
noValidate
|
|
|
|
>
|
|
|
|
{this.props.children}
|
|
|
|
</form>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2017-08-20 21:15:21 +05:30
|
|
|
submit() {
|
2016-05-02 12:45:42 +05:30
|
|
|
if (!this.state.isTouched) {
|
|
|
|
this.setState({
|
|
|
|
isTouched: true
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-08-20 21:15:21 +05:30
|
|
|
const form = this.formEl;
|
|
|
|
|
|
|
|
if (!form) {
|
|
|
|
return;
|
|
|
|
}
|
2016-05-02 12:45:42 +05:30
|
|
|
|
|
|
|
if (form.checkValidity()) {
|
2017-09-09 19:52:19 +05:30
|
|
|
this.setState({isLoading: true});
|
|
|
|
|
2017-08-20 21:15:21 +05:30
|
|
|
Promise.resolve(this.props.onSubmit(
|
|
|
|
this.props.form ? this.props.form : new FormData(form)
|
|
|
|
))
|
2017-08-23 02:30:10 +05:30
|
|
|
.catch((errors: {[key: string]: string}) => {
|
|
|
|
this.setErrors(errors);
|
2017-09-09 19:52:19 +05:30
|
|
|
})
|
|
|
|
.finally(() => this.setState({isLoading: false}));
|
2016-05-02 12:45:42 +05:30
|
|
|
} else {
|
2016-05-02 14:50:50 +05:30
|
|
|
const invalidEls = form.querySelectorAll(':invalid');
|
|
|
|
const errors = {};
|
|
|
|
invalidEls[0].focus(); // focus on first error
|
|
|
|
|
2017-08-20 21:15:21 +05:30
|
|
|
Array.from(invalidEls).reduce((errors, el: InputElement) => {
|
2016-05-02 14:50:50 +05:30
|
|
|
if (!el.name) {
|
2017-06-13 01:02:59 +05:30
|
|
|
logger.warn('Found an element without name', {el});
|
|
|
|
|
2016-05-02 14:50:50 +05:30
|
|
|
return errors;
|
|
|
|
}
|
|
|
|
|
|
|
|
let errorMessage = el.validationMessage;
|
2017-09-09 19:52:19 +05:30
|
|
|
|
2016-05-02 14:50:50 +05:30
|
|
|
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
|
|
|
|
2017-08-20 21:15:21 +05:30
|
|
|
this.setErrors(errors);
|
2016-05-02 12:45:42 +05:30
|
|
|
}
|
2017-08-20 21:15:21 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
setErrors(errors: {[key: string]: string}) {
|
|
|
|
this.props.form && this.props.form.setErrors(errors);
|
|
|
|
this.props.onInvalid(errors);
|
|
|
|
}
|
|
|
|
|
|
|
|
onFormSubmit = (event: Event) => {
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
|
|
this.submit();
|
2016-05-02 12:45:42 +05:30
|
|
|
};
|
2016-05-28 01:34:17 +05:30
|
|
|
|
2017-08-20 21:15:21 +05:30
|
|
|
onLoading = (isLoading: bool) => this.setState({isLoading});
|
2016-05-02 12:45:42 +05:30
|
|
|
}
|