react-form-generator
Version:
React Form Generator.
320 lines (268 loc) • 10.6 kB
JSX
/** @jsx React.DOM */
var React = require( 'react' )
, R = React;
var t = require( '../../tools' )
, merge = t.merge
, getOrNull = t.getOrNull
, getOrDefault = t.getOrDefault;
/**
* Provides GeneratedForm component.
* Can be configured by `conf` arguments:
* @param {Object} conf - settings for component builder.
* @param {Object[]} conf.validators - map of custom validators.
* @param {Object[]} conf.mixins - map of React's mixins.
* @param {Object[]} conf.primitives - map of custom primitive's renderers.
* @param {Object[]} conf.layouts - map of custom layout's renderers.
* @return {ReactElement}
*/
function formGenerator ( conf ) {
conf = conf || {};
var v = require( '../../validation' )( conf.validators )
, validate = v.validateField;
var MIXINS = [].concat( conf.mixins || [] );
var RENDERERS = merge({
text: require( './primitives/text' )( R, t ),
textarea: require( './primitives/textarea' )( R, t ),
checkbox: require( './primitives/checkbox' )( R, t ),
radiogroup: require( './primitives/radiogroup' )( R, t ),
select: require( './primitives/select' )( R, t ),
button: require( './primitives/button' )( R, t )
}, getOrDefault( conf, 'primitives', {} ));
var LAYOUTS = merge({
default: require( './layouts/simpleFormField' )( R, t ),
unwrapped: require( './layouts/unwrapped' )( R, t ),
label: require( './layouts/label' )( R, t ),
header: require( './layouts/header' )( R, t )
}, getOrDefault( conf, 'layouts', {} ));
/**
* # GeneratedForm component.
* Builds form by metadata.
*
* ## Metadata format:
*
* ```js
* var meta = {
* key: "unique key for React",
* settings: {
* isDisabled: true
* },
* fields: []
* };
* ```
*
* ## Data format:
* ```js
* var value = {
* testField: "test",
* testField2: ...
* };
* ```
*
* ## Usage
* ```
* var conf = {};
* var GeneratedForm = require( 'react-form-generator' )( conf );
*
* var MyForm = React.createClass({
* render: function () {
* return (
* <GeneratedForm meta={meta}
* value={value}
* onChange={this.handleChange}
* />
* );
* }
* });
* ```
*/
var GeneratedForm = /*RENDERERS['group'] =*/ React.createClass({
propTypes: {
/**
* ## Properties:
*/
meta: React.PropTypes.object.isRequired,
value: React.PropTypes.object,
/**
* ### errors
* ```js
* {
* %fieldID%: [
* {
* rule: %rule%,
* value: %value%,
* message: %message%,
* }
* ]
* }
* ```
*/
errors: React.PropTypes.object,
/**
* ## Methods:
*/
/**
* ### onChange
* Fires after user input into any field of generated form.
* Callback will get the next data:
* @param {Object} formValue - forms's new value.
* @param {Object} change - value of the changed field.
* @param {Object} formErrors - forms's validation errors.
* ```js
* {
* %fieldID%: %fieldNewValue%
* }
* ```
*/
onChange: React.PropTypes.func,
/**
* ### onEvent
* Fires when field triggers some of standard DOM events.
* Callback will get the next data:
* @param {string} fieldID - ID of field that triggers
* DOM event
* @param {string} eventName - name of event (one of
* stadart DOM events).
* @param {Object} eventInfo - useful info about event.
* @param {SyntheticEvent} eventInfo.originalEvent - original React event
* @param {string} eventInfo.path - %fiendID%:%eventName%
* (useful for event routing).
* ```
*/
onEvent: React.PropTypes.func
},
mixins: MIXINS,
/* =========================================================== */
/* =================== Component Life Cycle ================== */
/* =========================================================== */
getDefaultProps: function () {
return {
key: 'generated-form-' + Date.now(),
onChange: function () {},
onEvent: function () {}
};
},
/* =========================================================== */
/* ========================= Helpers ========================= */
/* =========================================================== */
getFieldValue: function ( fieldID ) {
return getOrNull( this.props.value, fieldID );
},
getFieldErrors: function ( fieldID ) {
var errors = getOrDefault( this, 'props.errors', {} );
return getOrNull( errors, fieldID );
},
/* =========================================================== */
/* ======================== Handlers ========================= */
/* =========================================================== */
handleFieldChanged: function ( fieldData ) {
var fieldID = fieldData.id
, fieldMeta = fieldData.meta
, formValue = this.props.value
, fieldValue = getOrNull( fieldData.value, fieldID )
, fieldErrs = validate( fieldID, fieldMeta, fieldValue );
this.props.onChange( merge( formValue, fieldData.value ),
fieldData.value,
fieldErrs );
},
handleFieldEvent: function ( fieldID, eventName, eventInfo ) {
this.props.onEvent( fieldID, eventName, eventInfo );
},
/* =========================================================== */
/* ======================== Renderers ======================== */
/* =========================================================== */
render: function () {
var meta = getOrDefault( this, 'props.meta', {} )
, fields = meta.fields;
if ( !t.isDefined( fields ) )
return null;
else
return this.renderLayout( meta.layout.grid );
},
renderLayout: function ( grid, key ) {
return (
<div className={grid.css} key={key}>
{this.renderLayoutRows( grid.rows || [] )}
</div>
);
},
renderLayoutRows: function ( rows ) {
var self = this;
return rows.map(function ( row, idx ) {
return (
<div key={'generated-layout-row'+idx}
className={row.css}>
{self.renderLayoutCells( row )}
</div>
);
});
},
renderLayoutCells: function ( row ) {
var self = this
, cells = getOrDefault( row, 'cells', [] );
return cells.map(function ( cell, idx ) {
return (
<div key={'generated-layout-cell'+idx}
className={cell.css}>
{self.renderCellContent( cell )}
</div>
);
});
},
renderCellContent: function ( cell ) {
var self = this
, meta = this.props.meta || {}
, contents = getOrDefault( cell, 'content', [] )
, Layout;
return contents.map(function ( cnt, idx ) {
var cntSpec = getOrDefault( cnt, 'rendererSpecific' )
, fldID = cntSpec.fieldID
, fldMeta = getOrNull( meta.fields, fldID );
var config = {
fieldID: fldID,
meta: fldMeta,
css: cntSpec.css,
value: self.getFieldValue( fldID ),
errors: self.getFieldErrors( fldID ),
onChange: self.handleFieldChanged,
onEvent: self.handleFieldEvent
};
var renderer = getOrNull(cnt, 'renderer')
, key = 'generated-field-' + idx;
if ( 'field' === renderer )
return (
<GeneratedField key={key} config={config} />
);
else if ( 'grid' === renderer ) {
return self.renderLayout( cnt.rendererSpecific,
key );
}
else {
Layout = LAYOUTS[ cnt.renderer || 'default' ];
return Layout
? <Layout key={key} meta={cnt} field={config}>
{fldID
? (<GeneratedField config={config}/>)
: null}
</Layout>
: null;
}
});
}
});
var GeneratedField = React.createClass({
mixins: [],
render: function () {
var props = this.props
, rendererId = getOrNull( props, 'config.meta.renderer' )
, Renderer = getOrNull( RENDERERS, rendererId );
return Renderer
? (<Renderer config={props.config} />)
: null;
}
});
GeneratedForm.validateForm = v.validateForm;
GeneratedForm.isFormValid = v.isFormValid;
return GeneratedForm;
}
formGenerator.tools = t;
module.exports = formGenerator;