refactors form components to handle any number of nested field components
This commit is contained in:
@@ -24,6 +24,7 @@ class Button extends Component {
|
||||
this.buttonProps = {
|
||||
className: classes,
|
||||
onClick: this.props.onClick,
|
||||
disabled: this.props.disabled,
|
||||
...this.props
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
20
src/client/components/forms/Submit/index.js
Normal file
20
src/client/components/forms/Submit/index.js
Normal 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;
|
||||
Reference in New Issue
Block a user