Skip to content

Instantly share code, notes, and snippets.

@StevePotter
Created September 3, 2014 13:30
Show Gist options
  • Save StevePotter/603b366873bb99fd8afd to your computer and use it in GitHub Desktop.
Save StevePotter/603b366873bb99fd8afd to your computer and use it in GitHub Desktop.
A mixin for bootstrap react that adds validation and value conversion to forms.
//use this mixin to get validation support and some helpful input functions
FormMixin = {
autoHandleChange: function(field, e)
{
var value = (this.refs[field] && this.refs[field].getValue) ? this.refs[field].getValue() : e.target.value;
stateUpdate = {}
stateUpdate[field] = value
this.setState(stateUpdate, function() {
stateUpdate = {}
stateUpdate[field + "Error"] = this.validateSingle(field)
this.setState(stateUpdate);
});
},
validate: function()
{
if (_.isObject(this.validationRules))
{
return validate(this.state, this.validationRules);
}
},
highlightInvalidFields: function()
{
var invalidFields = this.validate();
if (_.isEmpty(invalidFields))
return true;
var stateUpdate = {}
for(var field in invalidFields)
{
stateUpdate[field + "Error"] = invalidFields[field][0] //always arrays
}
this.setState(stateUpdate);
return false;
},
isValid: function()
{
return _.isEmpty(this.validate());
},
validateSingle: function(field)
{
if (!_.isObject(this.validationRules))
return;
var rule = _.pick(this.validationRules,field);
if (!rule[field]) //no validation for this field
return;
var result = validate(this.state, rule);
if (!result)
return result;
if (_.isArray(result[field]) && result[field].length > 0)
{
return result[field][0];
}
},
autoValues: function()
{
return _.pick(this.state,_.keys(this.refs));
},
renderAutoInput: function(properties) {
name = properties.name
properties = _.extend({ ref:name, value:this.state[name], onChange:this.autoHandleChange.bind(this,name), validationError: this.state[name + "Error"], wrapperClassName: "col-sm-10", labelClassName: "col-sm-2", type:"text" }, properties);
return V.Input(properties);
},
};
function classSet(classNames) {
if (typeof classNames == 'object') {
return Object.keys(classNames).filter(function(className) {
return classNames[className];
}).join(' ');
} else {
return Array.prototype.join.call(arguments, ' ');
}
}
V.Input = React.createClass({displayName: 'Input',
propTypes: {
type: React.PropTypes.string,
label: React.PropTypes.renderable,
help: React.PropTypes.renderable,
addonBefore: React.PropTypes.renderable,
addonAfter: React.PropTypes.renderable,
bsStyle: React.PropTypes.oneOf(['success', 'warning', 'error']),
hasFeedback: React.PropTypes.bool,
groupClassName: React.PropTypes.string,
wrapperClassName: React.PropTypes.string,
labelClassName: React.PropTypes.string
},
getInputDOMNode: function () {
return this.refs.input.getDOMNode();
},
getValue: function () {
if (this.props.type === 'static') {
return this.props.value;
}
else if (this.props.type) {
var value = this.getInputDOMNode().value;
if (this.props.converter)
{
if (this.props.converter === "int")
{
converted = parseInt(value);
value = isNaN(converted) ? value : converted; //example for failure would be "-". just let it be
}
else if (this.props.converter === "float")
{
converted = parseFloat(value);
value = isNaN(converted) ? value : converted; //example for failure would be ".". just let it be
}
else
{
value = this.props.converter(value);
}
}
return value;
}
else {
throw Error('Cannot use getValue without specifying input type.');
}
},
getChecked: function () {
return this.getInputDOMNode().checked;
},
isCheckboxOrRadio: function () {
return this.props.type === 'radio' || this.props.type === 'checkbox';
},
renderInput: function () {
var input = null;
if (!this.props.type) {
return this.props.children
}
switch (this.props.type) {
case 'select':
input = (
React.DOM.select( {className:"form-control", ref:"input", key:"input"},
this.props.children
)
);
break;
case 'textarea':
input = React.DOM.textarea( {className:"form-control", ref:"input", key:"input"} );
break;
case 'static':
input = (
React.DOM.p( {className:"form-control-static", ref:"input", key:"input"},
this.props.value
)
);
break;
default:
var className = this.isCheckboxOrRadio() ? '' : 'form-control';
input = React.DOM.input( {className:className, ref:"input", key:"input"} );
}
return this.transferPropsTo(input);
},
renderInputGroup: function (children) {
var addonBefore = this.props.addonBefore ? (
React.DOM.span( {className:"input-group-addon", key:"addonBefore"},
this.props.addonBefore
)
) : null;
var addonAfter = this.props.addonAfter ? (
React.DOM.span( {className:"input-group-addon", key:"addonAfter"},
this.props.addonAfter
)
) : null;
return addonBefore || addonAfter ? (
React.DOM.div( {className:"input-group", key:"input-group"},
addonBefore,
children,
addonAfter
)
) : children;
},
renderIcon: function () {
var classes = {
'glyphicon': true,
'form-control-feedback': true,
'glyphicon-ok': this.props.bsStyle === 'success',
'glyphicon-warning-sign': this.props.bsStyle === 'warning',
'glyphicon-remove': this.props.bsStyle === 'error' || this.invalid()
};
return this.props.hasFeedback || this.invalid() ? (
React.DOM.span( {className:classSet(classes), key:"icon"} )
) : null;
},
invalid: function () {
return (_.isString(this.props.validationError) && this.props.validationError.length > 0);
},
renderHelp: function () {
if (this.invalid())
{
return <span className="help-block" key="help">{this.props.validationError}</span>
}
return this.props.help ? (
React.DOM.span( {className:"help-block", key:"help"},
this.props.help
)
) : null;
},
renderCheckboxandRadioWrapper: function (children) {
var classes = {
'checkbox': this.props.type === 'checkbox',
'radio': this.props.type === 'radio'
};
return (
React.DOM.div( {className:classSet(classes), key:"checkboxRadioWrapper"},
children
)
);
},
renderWrapper: function (children) {
return this.props.wrapperClassName ? (
React.DOM.div( {className:this.props.wrapperClassName, key:"wrapper"},
children
)
) : children;
},
renderLabel: function (children) {
var classes = {
'control-label': !this.isCheckboxOrRadio()
};
classes[this.props.labelClassName] = this.props.labelClassName;
return this.props.label ? (
React.DOM.label( {htmlFor:this.props.id, className:classSet(classes), key:"label"},
children,
this.props.label
)
) : children;
},
renderFormGroup: function (children) {
var classes = {
'form-group': true,
'has-feedback': this.props.hasFeedback,
'has-success': this.props.bsStyle === 'success',
'has-warning': this.props.bsStyle === 'warning',
'has-error': this.props.bsStyle === 'error'
};
if (this.invalid())
{
classes['has-error'] = true
classes['has-feedback'] = true
}
classes[this.props.groupClassName] = this.props.groupClassName;
return (
React.DOM.div( {className:classSet(classes)},
children
)
);
},
render: function () {
if (this.isCheckboxOrRadio()) {
return this.renderFormGroup(
this.renderWrapper([
this.renderCheckboxandRadioWrapper(
this.renderLabel(
this.renderInput()
)
),
this.renderHelp()
])
);
}
else {
return this.renderFormGroup([
this.renderLabel(),
this.renderWrapper([
this.renderInputGroup(
this.renderInput()
),
this.renderIcon(),
this.renderHelp()
])
]);
}
}
});
//example usage:
var example= React.createClass({
mixins: [FormMixin],
validationRules:
{
sampleId: { presence: true },
degreesBetweenTests: { presence: true, numericality: { message: "Must be a positive whole number.", onlyInteger: true, greaterThan: 0 } }
},
render: function() {
return <div>
{ this.renderAutoInput({name:"sampleId", label:"Sample ID" })}
{ this.renderAutoInput({name:"operator", label:"Operator" })}
{ this.renderAutoInput({name:"notes", label:"Notes" })}
</div>
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment