Added `dynamic form fields` example.
This commit is contained in:
parent
f67f8317c9
commit
fbd09119d3
|
|
@ -34,3 +34,7 @@ If it is not helped try update your node.js and npm.
|
|||
3. [**Reset Values**](reset-values)
|
||||
|
||||
Reset text input, checkbox and select to their pristine values.
|
||||
|
||||
4. [**Dynamic Form Fields**](dynamic-form-fields)
|
||||
|
||||
Dynamically adding and removing fields to form.
|
||||
|
|
|
|||
|
|
@ -18,14 +18,15 @@ const MyInput = React.createClass({
|
|||
// when the value is empty and the required prop is
|
||||
// passed to the input. showError() is true when the
|
||||
// value typed is invalid
|
||||
const className = this.props.className + ' ' + (this.showRequired() ? 'required' : this.showError() ? 'error' : null);
|
||||
const className = 'form-group' + (this.props.className || ' ') +
|
||||
(this.showRequired() ? 'required' : this.showError() ? 'error' : '');
|
||||
|
||||
// An error message is returned ONLY if the component is invalid
|
||||
// or the server has returned an error message
|
||||
const errorMessage = this.getErrorMessage();
|
||||
|
||||
return (
|
||||
<div className='form-group'>
|
||||
<div className={className}>
|
||||
<label htmlFor={this.props.name}>{this.props.title}</label>
|
||||
<input
|
||||
type={this.props.type || 'text'}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
import React from 'react';
|
||||
import Formsy from 'formsy-react';
|
||||
|
||||
function contains(container, item, cmp) {
|
||||
for (const it of container) {
|
||||
if (cmp(it, item)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const MyRadioGroup = React.createClass({
|
||||
mixins: [Formsy.Mixin],
|
||||
getInitialState() {
|
||||
return { value: [], cmp: (a, b) => a === b };
|
||||
},
|
||||
componentDidMount() {
|
||||
const value = this.props.value || [];
|
||||
this.setValue(value);
|
||||
this.setState({ value: value, cmp: this.props.cmp || this.state.cmp });
|
||||
},
|
||||
|
||||
changeValue(value, event) {
|
||||
const checked = event.currentTarget.checked;
|
||||
|
||||
let newValue = [];
|
||||
if (checked) {
|
||||
newValue = this.state.value.concat(value);
|
||||
} else {
|
||||
newValue = this.state.value.filter(it => !this.state.cmp(it, value));
|
||||
}
|
||||
|
||||
this.setValue(newValue);
|
||||
this.setState({ value: newValue });
|
||||
},
|
||||
|
||||
render() {
|
||||
const className = 'form-group' + (this.props.className || ' ') +
|
||||
(this.showRequired() ? 'required' : this.showError() ? 'error' : '');
|
||||
const errorMessage = this.getErrorMessage();
|
||||
|
||||
const { name, title, items } = this.props;
|
||||
return (
|
||||
<div className={className}>
|
||||
<label htmlFor={name}>{title}</label>
|
||||
{items.map((item, i) => (
|
||||
<div key={i}>
|
||||
<input
|
||||
type="checkbox"
|
||||
name={name}
|
||||
onChange={this.changeValue.bind(this, item)}
|
||||
checked={contains(this.state.value, item, this.state.cmp)}
|
||||
/>
|
||||
<span>{JSON.stringify(item)}</span>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
<span className='validation-error'>{errorMessage}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
export default MyRadioGroup;
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
import React from 'react';
|
||||
import Formsy from 'formsy-react';
|
||||
|
||||
const MyRadioGroup = React.createClass({
|
||||
mixins: [Formsy.Mixin],
|
||||
|
||||
componentDidMount() {
|
||||
const value = this.props.value;
|
||||
this.setValue(value);
|
||||
this.setState({ value });
|
||||
},
|
||||
|
||||
changeValue(value) {
|
||||
this.setValue(value);
|
||||
this.setState({ value });
|
||||
},
|
||||
|
||||
render() {
|
||||
const className = 'form-group' + (this.props.className || ' ') +
|
||||
(this.showRequired() ? 'required' : this.showError() ? 'error' : '');
|
||||
const errorMessage = this.getErrorMessage();
|
||||
|
||||
const { name, title, items } = this.props;
|
||||
return (
|
||||
<div className={className}>
|
||||
<label htmlFor={name}>{title}</label>
|
||||
{items.map((item, i) => (
|
||||
<div key={i}>
|
||||
<input
|
||||
type="radio"
|
||||
name={name}
|
||||
onChange={this.changeValue.bind(this, item)}
|
||||
checked={this.state.value === item}
|
||||
/>
|
||||
<span>{item.toString()}</span>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
<span className='validation-error'>{errorMessage}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
export default MyRadioGroup;
|
||||
|
|
@ -9,7 +9,8 @@ const MySelect = React.createClass({
|
|||
},
|
||||
|
||||
render() {
|
||||
const className = this.props.className + ' ' + (this.showRequired() ? 'required' : this.showError() ? 'error' : null);
|
||||
const className = 'form-group' + (this.props.className || ' ') +
|
||||
(this.showRequired() ? 'required' : this.showError() ? 'error' : '');
|
||||
const errorMessage = this.getErrorMessage();
|
||||
|
||||
const options = this.props.options.map((option, i) => (
|
||||
|
|
@ -19,7 +20,7 @@ const MySelect = React.createClass({
|
|||
));
|
||||
|
||||
return (
|
||||
<div className='form-group'>
|
||||
<div className={className}>
|
||||
<label htmlFor={this.props.name}>{this.props.title}</label>
|
||||
<select name={this.props.name} onChange={this.changeValue} value={this.getValue()}>
|
||||
{options}
|
||||
|
|
|
|||
|
|
@ -58,17 +58,18 @@ const DynamicInput = React.createClass({
|
|||
},
|
||||
validate() {
|
||||
const value = this.getValue();
|
||||
return value !== '' ? validators[this.state.validationType].regexp.test(value) : true;
|
||||
console.log(value, this.state.validationType);
|
||||
return value ? validators[this.state.validationType].regexp.test(value) : true;
|
||||
},
|
||||
getCustomErrorMessage() {
|
||||
return this.showError() ? validators[this.state.validationType].message : '';
|
||||
},
|
||||
render() {
|
||||
const className = this.props.className + ' ' + (this.showError() ? 'error' : null);
|
||||
const className = 'form-group' + (this.props.className || ' ') + (this.showRequired() ? 'required' : this.showError() ? 'error' : null);
|
||||
const errorMessage = this.getCustomErrorMessage();
|
||||
|
||||
return (
|
||||
<div className='form-group'>
|
||||
<div className={className}>
|
||||
<label htmlFor={this.props.name}>{this.props.title}</label>
|
||||
<input type='text' name={this.props.name} onChange={this.changeValue} value={this.getValue()}/>
|
||||
<span className='validation-error'>{errorMessage}</span>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
.many-fields-conf {
|
||||
width: 400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.many-fields {
|
||||
width: 600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.field {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.many-fields .form-group {
|
||||
width: calc(100% - 20px);
|
||||
float: left;
|
||||
}
|
||||
.many-fields .remove-field {
|
||||
margin-top: 30px;
|
||||
margin-left: 8px;
|
||||
display: inline-block;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { Form } from 'formsy-react';
|
||||
|
||||
import MyInput from './../components/Input';
|
||||
import MySelect from './../components/Select';
|
||||
import MyRadioGroup from './../components/RadioGroup';
|
||||
import MyMultiCheckboxSet from './../components/MultiCheckboxSet';
|
||||
|
||||
const Fields = props => {
|
||||
function onRemove(pos) {
|
||||
return event => {
|
||||
event.preventDefault();
|
||||
props.onRemove(pos);
|
||||
};
|
||||
}
|
||||
const foo = 'required';
|
||||
return (
|
||||
<div className="fields">
|
||||
{props.data.map((field, i) => (
|
||||
<div className="field" key={field.id}>
|
||||
{
|
||||
field.type === 'input' ?
|
||||
(
|
||||
<MyInput
|
||||
name={`fields[${i}]`}
|
||||
title={field.validations ? JSON.stringify(field.validations) : 'No validations'}
|
||||
required={field.required}
|
||||
validations={field.validations}
|
||||
/>
|
||||
) :
|
||||
(
|
||||
<MySelect
|
||||
name={`fields[${i}]`}
|
||||
title={field.validations ? JSON.stringify(field.validations) : 'No validations'}
|
||||
required={field.required}
|
||||
validations={field.validations}
|
||||
options={[
|
||||
{title: '123', value: '123'},
|
||||
{title: 'some long text', value: 'some long text'},
|
||||
{title: '`empty string`', value: ''},
|
||||
{title: 'alpha42', value: 'alpha42'},
|
||||
{title: 'test@mail.com', value: 'test@mail.com'}
|
||||
]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
<a href="#" className="remove-field" onClick={onRemove(i)}>X</a>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const App = React.createClass({
|
||||
getInitialState() {
|
||||
return { fields: [], canSubmit: false };
|
||||
},
|
||||
submit(data) {
|
||||
alert(JSON.stringify(data, null, 4));
|
||||
},
|
||||
addField(fieldData) {
|
||||
fieldData.validations = fieldData.validations.length ?
|
||||
fieldData.validations.reduce((a, b) => Object.assign({}, a, b)) :
|
||||
null;
|
||||
fieldData.id = Date.now();
|
||||
this.setState({ fields: this.state.fields.concat(fieldData) });
|
||||
},
|
||||
removeField(pos) {
|
||||
const fields = this.state.fields;
|
||||
this.setState({ fields: fields.slice(0, pos).concat(fields.slice(pos+1)) })
|
||||
},
|
||||
enableButton() {
|
||||
this.setState({ canSubmit: true });
|
||||
},
|
||||
disableButton() {
|
||||
this.setState({ canSubmit: false });
|
||||
},
|
||||
render() {
|
||||
const { fields, canSubmit } = this.state;
|
||||
return (
|
||||
<div>
|
||||
<Form onSubmit={this.addField} className="many-fields-conf">
|
||||
<MyMultiCheckboxSet name="validations" title="Validations"
|
||||
cmp={(a, b) => JSON.stringify(a) === JSON.stringify(b)}
|
||||
items={[
|
||||
{isEmail: true},
|
||||
{isEmptyString: true},
|
||||
{isNumeric: true},
|
||||
{isAlphanumeric: true},
|
||||
{equals: 5},
|
||||
{minLength: 3},
|
||||
{maxLength: 7}
|
||||
]}
|
||||
/>
|
||||
<MyRadioGroup name="required" value={false} title="Required"
|
||||
items={[true, false]} />
|
||||
<MyRadioGroup name="type" value="input" title="Type"
|
||||
items={['input', 'select']} />
|
||||
<button type="submit">Add</button>
|
||||
</Form>
|
||||
<Form onSubmit={this.submit} onValid={this.enableButton} onInvalid={this.disableButton} className="many-fields">
|
||||
<Fields data={fields} onRemove={this.removeField} />
|
||||
<button type="submit" disabled={!canSubmit}>Submit</button>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
ReactDOM.render(<App/>, document.getElementById('example'));
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Dynamic Form Fields</title>
|
||||
<link href="../global.css" rel="stylesheet"/>
|
||||
<link href="app.css" rel="stylesheet"/>
|
||||
</head>
|
||||
<body>
|
||||
<h1 class="breadcrumbs"><a href="../index.html">Formsy React Examples</a> / Dynamic Form Fields</h1>
|
||||
<div id="example"/>
|
||||
<script src="/__build__/shared.js"></script>
|
||||
<script src="/__build__/dynamic-form-fields.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -49,11 +49,28 @@ form {
|
|||
color: #555;
|
||||
background-color: #FFF;
|
||||
background-image: none;
|
||||
border: 1px solid #CCC;
|
||||
border: 2px solid #CCC;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.form-group.error input[type='text'],
|
||||
.form-group.error input[type='email'],
|
||||
.form-group.error input[type='number'],
|
||||
.form-group.error input[type='password'],
|
||||
.form-group.error select {
|
||||
border-color: red;
|
||||
color: red;
|
||||
}
|
||||
|
||||
.form-group.required input[type='text'],
|
||||
.form-group.required input[type='email'],
|
||||
.form-group.required input[type='number'],
|
||||
.form-group.required input[type='password'],
|
||||
.form-group.required select {
|
||||
border-color: #FF9696;
|
||||
}
|
||||
|
||||
.validation-error {
|
||||
color: red;
|
||||
margin: 5px 0;
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
<li><a href="login/index.html">Login Page</a></li>
|
||||
<li><a href="custom-validation/index.html">Custom Validation</a></li>
|
||||
<li><a href="reset-values/index.html">Reset Values</a></li>
|
||||
<li><a href="dynamic-form-fields/index.html">Dynamic Form Fields</a></li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
Loading…
Reference in New Issue