Added `dynamic form fields` example.

This commit is contained in:
Semigradsky 2015-12-01 17:00:43 +03:00
parent f67f8317c9
commit fbd09119d3
11 changed files with 295 additions and 8 deletions

View File

@ -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.

View File

@ -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'}

View File

@ -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;

View File

@ -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;

View File

@ -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}

View File

@ -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>

View File

@ -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;
}

View File

@ -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'));

View File

@ -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>

View File

@ -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;

View File

@ -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>