refactors form components to handle any number of nested field components

This commit is contained in:
James
2018-09-24 22:07:01 -04:00
parent 70e76d80ef
commit 8badbf9329
16 changed files with 347 additions and 204 deletions

View File

@@ -24,6 +24,7 @@ class Button extends Component {
this.buttonProps = {
className: classes,
onClick: this.props.onClick,
disabled: this.props.disabled,
...this.props
};
}

View File

@@ -1,5 +1,5 @@
import React, { Component } from 'react';
import { Tooltip } from 'payload/components';
import { FormConsumer, Tooltip } from 'payload/components';
import './index.css';
@@ -15,18 +15,18 @@ class Input extends Component {
this.validate = this.validate.bind(this);
}
validate() {
validate(value) {
let emailTest = /\S+@\S+\.\S+/;
switch (this.props.type) {
case 'text':
return this.el.value.length > 0;
return value.length > 0;
case 'password':
return this.el.value.length > 0;
return value.length > 0;
case 'email':
return emailTest.test(this.el.value);
return emailTest.test(value);
case 'hidden':
return true;
@@ -36,12 +36,6 @@ class Input extends Component {
}
}
componentDidUpdate() {
if (this.props.autoFocus) {
this.el.focus();
}
}
render() {
const Required = this.props.required
? () => <span className="required">*</span>
@@ -60,21 +54,27 @@ class Input extends Component {
? className
: `${className} error`;
const validate = this.props.required ? this.validate : () => true;
return (
<div className={className} style={this.props.style}>
<Error />
<Label />
<input
ref={el => { this.el = el; } }
disabled={this.props.disabled}
placeholder={this.props.placeholder}
onChange={this.props.change}
onFocus={this.props.onFocus}
type={this.props.type}
id={this.props.id ? this.props.id : this.props.name}
name={this.props.name}
value={this.props.value} />
</div>
<FormConsumer>
{context => {
return (
<div className={className} style={this.props.style}>
<Error />
<Label />
<input
value={context.fields[this.props.name] ? context.fields[this.props.name].value : ''}
onChange={(e) => { context.handleChange(e, validate) }}
disabled={this.props.disabled}
placeholder={this.props.placeholder}
type={this.props.type}
id={this.props.id ? this.props.id : this.props.name}
name={this.props.name} />
</div>
)
}}
</FormConsumer>
);
}
}

View File

@@ -1,5 +1,5 @@
import React, { Component } from 'react';
import { Tooltip } from 'payload/components';
import { FormConsumer, Tooltip } from 'payload/components';
import './index.css';
@@ -14,12 +14,12 @@ class Textarea extends Component {
this.validate = this.validate.bind(this);
}
validate() {
validate(value) {
switch (this.props.type) {
case 'honeypot':
return this.el.value.length === 0;
return value.length === 0;
default:
return this.el.value.length > 0;
return value.length > 0;
}
}
@@ -49,21 +49,28 @@ class Textarea extends Component {
className = 'interact';
}
const validate = this.props.required ? this.validate : () => true;
return (
<div className={className} style={style}>
<Error />
<Label />
<textarea
ref={el => { this.el = el; } }
onBlur={this.validate}
onFocus={this.props.onFocus}
onChange={this.props.change}
id={this.props.id ? this.props.id : this.props.name}
name={this.props.name}
rows={this.props.rows ? this.props.rows : '5'}
value={this.props.value}>
</textarea>
</div>
<FormConsumer>
{context => {
return (
<div className={className} style={style}>
<Error />
<Label />
<textarea
value={context.fields[this.props.name] ? context.fields[this.props.name].value : ''}
onChange={(e) => { context.handleChange(e, validate) }}
disabled={this.props.disabled}
placeholder={this.props.placeholder}
type={this.props.type}
id={this.props.id ? this.props.id : this.props.name}
name={this.props.name}>
</textarea>
</div>
)
}}
</FormConsumer>
);
}
}

View File

@@ -1,70 +1,48 @@
import React, { Component } from 'react';
import { withRouter } from 'react-router-dom';
import React, { Component, createContext } from 'react';
import { ajax } from 'payload';
import './index.css';
const FormContext = createContext({});
class Form extends Component {
constructor(props) {
super(props);
this.state = {
fields: this.buildFields(),
fields: {},
status: undefined,
processing: false
};
// Fill from renderChildren
this.childRefs = {};
this.buildFields = this.buildFields.bind(this);
this.handleChange = this.handleChange.bind(this);
this.submit = this.submit.bind(this);
this.renderChildren = this.renderChildren.bind(this);
}
buildFields() {
let fields = {};
handleChange(e, validate) {
let valid = validate(e.target.value);
React.Children.map(this.props.children, (child) => {
if (child.props.name) {
fields[child.props.name] = {
value: child.props.value ? child.props.value : '',
required: child.props.required
};
}
});
return fields;
}
handleChange(e) {
let newState = { ...this.state };
newState.fields[e.target.name].value = e.target.value;
this.setState(newState);
}
submit(e) {
let isValid = true;
let newState = { ...this.state };
Object.keys(this.childRefs).forEach((field) => {
if (this.childRefs[field].props.required) {
let current = this.childRefs[field];
let validated = current.validate();
newState.fields[field].valid = validated;
if (!validated) {
isValid = false;
this.setState({
fields: {
...this.state.fields,
[e.target.name]: {
value: e.target.value,
valid: valid
}
}
});
}
// Update validated fields
this.setState(newState);
submit(e) {
e.preventDefault();
let isValid = true;
Object.keys(this.state.fields).forEach((field) => {
if (!this.state.fields[field].valid) {
isValid = false;
}
});
// If not valid, prevent submission
if (!isValid) {
@@ -76,7 +54,7 @@ class Form extends Component {
this.props.onSubmit(this.state.fields);
// If form is AJAX, fetch data
} else if (this.props.ajax) {
} else if (this.props.ajax !== false) {
e.preventDefault();
let data = {};
@@ -118,52 +96,10 @@ class Form extends Component {
);
}
if (this.props.clearAfterSubmit && isValid) {
// Loop through fields - if not valid, set to invalid, rerender with error
Object.keys(this.state.fields).forEach((field) => {
newState.fields[field].value = '';
});
}
// If valid and not AJAX submit as usual
return;
}
renderChildren() {
let children = React.Children.map(this.props.children, (child) => {
if (child.props.name) {
// Initialize validation as true - only show error class if error after blur
let valid = true;
// If a valid value has been passed from field, set valid equal to that
if (typeof this.state.fields[child.props.name].valid !== 'undefined') {
valid = this.state.fields[child.props.name].valid;
}
return React.cloneElement(child, {
ref: (el) => {
this.childRefs[child.props.name] = el;
},
change: this.handleChange,
validate: this.validate,
valid: valid,
value: this.state.fields[child.props.name].value
});
}
if (child.props.type === 'submit') {
return React.cloneElement(child, {
disabled: this.state.processing || child.props.disabled === 'disabled' ? 'disabled' : false,
children: this.state.processing ? 'Processing...' : child.props.children
});
}
return child;
});
return children;
}
render() {
let Status = () => {
return null;
@@ -186,10 +122,24 @@ class Form extends Component {
action={this.props.action}
className={this.props.className}>
<Status />
{this.renderChildren()}
<FormContext.Provider value={{
handleChange: this.handleChange.bind(this),
fields: this.state.fields,
processing: this.state.processing
}}>
{this.props.children}
</FormContext.Provider>
</form>
);
}
}
export default withRouter(Form);
export const FormConsumer = (props => {
return (
<FormContext.Consumer>
{props.children}
</FormContext.Consumer>
);
});
export default Form;

View File

@@ -0,0 +1,20 @@
import React, { Component } from 'react';
import { FormConsumer, Button } from 'payload/components';
class FormSubmit extends Component {
render() {
return (
<FormConsumer>
{context => {
return (
<Button disabled={context.processing ? 'disabled' : ''}>
{this.props.children}
</Button>
)
}}
</FormConsumer>
);
}
}
export default FormSubmit;