<!DOCTYPE html>
<html>
<head>
<link data-require="bootstrap-css@*" data-semver="3.3.6" rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.css" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div id="app"></div>
<script data-require="react@*" data-semver="0.14.2" src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.2/react.min.js"></script>
<script data-require="react-with-addons@0.14.0" data-semver="0.14.0" src="http://fb.me/react-with-addons-0.14.0.js"></script>
<script data-require="react-dom@*" data-semver="0.14.0" src="http://fb.me/react-dom-0.14.0.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js"></script>
<script type="text/babel" src="script.js"></script>
</body>
</html>
/* Constants */
const FIELD_TYPE = {
text: 'text',
email: 'email',
password: 'password',
radiobutton: 'radiobutton'
};
const getSizeTypes = function() {
let sizeTypes = [];
const prefixes = ['xs-', 'sm-', 'md-', 'lg-', 'xs-offset-', 'sm-offset-', 'md-offset-', 'lg-offset-'];
for (var i = 0; i < prefixes.length; i++) {
for (var k = 0; k < 12; k++) {
sizeTypes.push(prefixes[i] + (k + 1));
}
}
return sizeTypes;
}
const validateSizeTypes = function(props, propName, componentName) {
var values = props[propName].split(' ');
var sizeTypes = getSizeTypes();
for (var i = 0; i < values.length; i++) {
if (!sizeTypes.includes(values[i])) {
return new Error('Invalid prop `' + propName + '` supplied to' +
' `' + componentName + '`. Validation failed. The value must be one or more of types: ' +
sizeTypes.join(', '));
}
}
}
/* Bootstrap elements wrapped in React components */
class Container extends React.Component {
render() {
return <div className="container">{this.props.children}</div>;
}
}
class Row extends React.Component {
render() {
return <div className="row">{this.props.children}</div>
}
}
// need fix: can exists another classNames (see FormGroup component)
class Col extends React.Component {
render() {
return <div className={this.renderClassName()}>{this.props.children}</div>;
}
renderClassName() {
if (!this.props.size) {
return 'col-sm-12';
}
const col = ' col-';
const className = this.props.size.split(' ').map(clazz => col + clazz).join(' ');
return className;
}
}
Col.propTypes = {
size: validateSizeTypes
};
class Form extends React.Component {
render() {
const className = (this.props.horizontal) ? 'form-horizontal' : '';
return <form className={className}>{this.props.children}</form>;
}
}
Form.propTypes = {
horizontal: React.PropTypes.bool
};
class FormGroup extends React.Component {
render() {
return <div {...this.props} className={this.getCssClasses()}>{this.props.children}</div>;
}
getCssClasses() {
let classNames = [];
if (this.props.hasError) {
classNames.push('has-error');
}
if (this.props.className) {
classNames = [...classNames, ...this.props.className.split(' ')];
}
return [...classNames, 'form-group'].join(' ');
}
};
FormGroup.propTypes = {
hasError: React.PropTypes.bool
};
const Button = (props) => {
let classNames = ['btn'];
classNames = classNames.join(' ');
return <button type="submit" className={classNames}>{props.children}</button>
};
class Validation extends React.Component {
constructor(props) {
super(props);
this.validationRules = [
{ required: (rule) => (typeof rule.value === 'undefined' || rule.value === '') },
{ minLength: (rule) => rule.value.length < rule.minLength }
];
}
render() {
return <span className="help-block text-danger">{this.getMessage()}</span>;
}
getMessage() {
let message = '';
this.validationRules.forEach((rule, index) => {
if (!this.isRulePresent(rule)) {
return;
}
let messageIsEmpty = !message;
if (messageIsEmpty && this.isInvalid(this.props.value, rule)) {
message = this.getMessageForRule(rule);
}
});
return message;
}
isRulePresent(rule) {
const ruleKey = Object.keys(rule)[0];
const isRulePresentInProps = this.props.rules.some(rule => {
const key = Object.keys(rule)[0];
return key === ruleKey;
});
return isRulePresentInProps;
}
isInvalid(value, rule) {
const ruleKey = Object.keys(rule)[0];
const ruleFunction = rule[ruleKey];
const propObject = this.props.rules.find(rule => Object.keys(rule)[0] === ruleKey)[ruleKey];
console.log({ ...propObject, value: value });
return ruleFunction({ ...propObject, value: value });
}
getMessageForRule(rule) {
let message = '';
const ruleKey = Object.keys(rule)[0];
this.props.rules.forEach((rule) => {
const key = Object.keys(rule)[0];
if (ruleKey === key) {
message = rule[key].message;
}
});
return message;
}
}
Validation.propTypes = {
value: React.PropTypes.string.isRequired,
rules: React.PropTypes.array.isRequired
};
/*
class FormField extends React.Component {
render() {
return (
<FormGroup>
{this.renderLabel()}
{this.renderInputContainer()}
</FormGroup>
);
}
renderLabel() {
if (!this.props.label) {
return;
}
return <label className={this.getLabelCssClass()} htmlFor={this.props.name}>{this.props.label}</label>;
}
getLabelCssClass() {
let cssClasses = ['control-label'];
if (this.props.horizontal) {
cssClasses.push('col-sm-2');
}
return cssClasses.join(' ');
}
renderInputContainer() {
if (!this.props.horizontal) {
return this.renderInput();
}
return (
<div className="col-sm-10">
{this.renderInput()}
</div>
);
}
renderInput() {
const type = this.props.type || 'text';
return (
<input
className="form-control"
id={this.props.name}
type={type}
placeholder={this.props.placeholder}
/>
);
}
}
FormField.propTypes = {
type: React.PropTypes.oneOf(Object.keys(FIELD_TYPE)),
horizontal: React.PropTypes.bool,
name: React.PropTypes.string.isRequired,
label: React.PropTypes.string,
placeholder: React.PropTypes.string
};
*/
/* React components related to the App */
class PageHeader extends React.Component {
render() {
return (
<Row>
<Col size="sm-6 md-12">
<div className="page-header">
<h1>React Form</h1>
</div>
</Col>
</Row>
);
}
}
class AppForm extends React.Component {
render() {
return (
<Form horizontal>
<FormGroup hasError>
<label htmlFor="inputEmail3" className="col-sm-2 control-label">Email</label>
<Col size="sm-10">
<input
className="form-control"
id="inputEmail3"
type="email"
placeholder="Email"
onInput={(e) => this.onInput('email', e.target.value )}
/>
<Validation
value={this.props.model.email}
rules={[
{ required: { message: 'Email is required' } },
{ minLength: { minLength: 5, message: 'At least 5 characters' } }
]}
/>
</Col>
</FormGroup>
<FormGroup>
<label htmlFor="inputPassword3" className="col-sm-2 control-label">Password</label>
<Col size="sm-10">
<input
className="form-control"
id="inputPassword3"
type="password"
placeholder="Password"
onInput={(e) => this.onInput('password', e.target.value )}
/>
</Col>
</FormGroup>
<FormGroup>
<Col size="sm-offset-2 sm-10">
<div className="checkbox">
<label>
<input
type="checkbox"
onChange={(e) => { this.onInput('rememberMe', e.target.checked ); }}
/> Remember me
</label>
</div>
</Col>
</FormGroup>
<FormGroup>
<Col size="sm-offset-2 sm-10">
<Button>Sign in</Button>
</Col>
</FormGroup>
</Form>
);
}
onInput(key, value) {
this.props.model[key] = value;
const modelUpdated = this.props.model;
this.props.onModelChange({ ...modelUpdated });
}
}
AppForm.propTypes = {
model: React.PropTypes.object.isRequired,
onModelChange: React.PropTypes.func.isRequired
};
class App extends React.Component {
constructor() {
super();
this.state = {
model: {
email: '',
password: '',
rememberMe: false
}
};
}
render() {
return (
<Container>
<PageHeader />
<Row>
<Col size="md-12">
<AppForm model={this.state.model} onModelChange={this.onChangeFormData.bind(this)} />
</Col>
</Row>
</Container>
);
}
onChangeFormData(data) {
this.setState({ ...data });
}
}
ReactDOM.render(<App />, document.getElementById('app'));
/* Styles go here */