Last active
March 5, 2016 16:29
-
-
Save dtothefp/a8bed4dc372fc2766fcb to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, {Component, PropTypes} from 'react'; | |
import {provideReactor} from 'nuclear-js-react-addons'; | |
import cx from 'classnames'; | |
import Form from './form'; | |
import Input from './form-input'; | |
import Label from './form-label'; | |
@provideReactor({ | |
Actions: PropTypes.object.isRequired | |
}) | |
export default class SimpleForm extends Component { | |
render() { | |
let nameValidator = { | |
keypress: true, | |
customType: 'email' | |
}; | |
return ( | |
<Form> | |
<Label action="action"> | |
Text | |
<Input type="text" name="first_name" placeholder="First Name" ref="first_name" validation={nameValidator} /> | |
</Label> | |
<Label action="action"> | |
Text Area | |
<Input type="textarea" name="textarea" placeholder="Textarea" /> | |
</Label> | |
<Label action="action"> | |
<Input type="email" name="email" placeholder="EMail" /> | |
</Label> | |
<Label action="action"> | |
First Radio | |
<Input type="radio" name="first_radio" /> | |
</Label> | |
<Label action="action"> | |
Second Radio | |
<Input type="radio" name="second_radio" /> | |
</Label> | |
</Form> | |
); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import _ from 'lodash'; | |
import React, {Component, PropTypes} from 'react'; | |
import {nuclearComponent} from 'nuclear-js-react-addons'; | |
import {Getters} from './nuclear-getters'; | |
import cx from 'classnames'; | |
@nuclearComponent({ | |
formErrors: Getters.formErrors | |
}) | |
export default class Input extends Component { | |
constructor(props) { | |
let {attachToForm, detachFromForm, formId, value} = props; | |
super(props); | |
this.attachToForm = attachToForm; | |
this.detachFromForm = detachFromForm; | |
this.formId = formId; | |
this.state = {value: value || ''}; | |
} | |
componentWillMount() { | |
this.attachToForm(this); | |
} | |
componentWillUnmount() { | |
this.detachFromForm(this); | |
} | |
handleChange = (e) => { | |
let value = e.target.value; | |
if(this.props.validation && this.props.validation.keypress) { | |
this.props.Actions.setFieldValue(this.formId, _.extend({}, {value}, this.props)); | |
} | |
this.setState({value}); | |
} | |
render() { | |
let errors = this.props.formErrors && this.props.formErrors.getIn([this.formId, 'currentValues']).filter((data) => { | |
let error = data.get('name') === this.props.name && data.get('error'); | |
return error; | |
}, this); | |
let errorMsg = errors && errors.size && errors.first().get('error'); | |
let classes = cx({ | |
error: errors.size | |
}); | |
return ( | |
<input | |
className={classes} | |
name={this.props.name} | |
type={this.props.type} | |
onChange={this.handleChange} | |
required={this.props.required} | |
validation={this.props.validation} | |
/> | |
); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import _ from 'lodash'; | |
import React, {Component} from 'react'; | |
import {nuclearComponent} from 'nuclear-js-react-addons'; | |
import {Getters} from './nuclear-getters'; | |
import cx from 'classnames'; | |
@nuclearComponent({ | |
formErrors: Getters.formErrors | |
}) | |
export default class Label extends Component { | |
render() { | |
let errors = this.props.formErrors && this.props.formErrors.getIn([this.props.formId, 'currentValues']).filter((data) => { | |
let error = _.isArray(this.props.inputNames) && this.props.inputNames.indexOf(data.get('name')) !== -1 && data.get('error'); | |
return error; | |
}, this); | |
let errorMsg = errors && errors.size && errors.first().get('error'); | |
let classes = cx({ | |
error: errors.size | |
}); | |
return ( | |
<label className={classes}> | |
<span className="error-msg">{errorMsg ? errorMsg : ''}</span> | |
{this.props.children} | |
</label> | |
); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, {Component, PropTypes} from 'react'; | |
import SimpleForm from './component/form-simple'; | |
import {Stores as ValidationStores, makeActions} from './nuclear-bootstrap'; | |
import reactor from './nuclear-reactor'; | |
import './index.html'; | |
reactor.registerStores({ | |
validationState: ValidationStores.inputStateStore | |
}); | |
React.render( | |
<SimpleForm reactor={reactor} Actions={makeActions(reactor)} />, | |
document.body | |
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import _ from 'lodash'; | |
import cx from 'classnames'; | |
import React, {Component, PropTypes} from 'react'; | |
import {nuclearComponent} from 'nuclear-js-react-addons'; | |
import {Getters} from './nuclear-bootstrap'; | |
function _recurseChildren(child, i) { | |
let elm; | |
let {children} = child.props || {}; | |
let isParentWithChildren = children && _.isArray(children) && _.isFunction(child.type); | |
let isLabel = (child.type && (child.type.name.toLowerCase() === 'label' || child.type.displayName === 'NuclearComponent(Label)')); | |
if(isParentWithChildren && isLabel) { | |
let clonedChildren = children.map(_recurseChildren, this); | |
let inputNames = children.map((child) => child.props && child.props.name); | |
let props = _.extend( | |
{}, | |
child.props, | |
{ | |
children: clonedChildren, | |
key: i, | |
formId: this.formId, | |
Actions: this.props.Actions, | |
inputNames | |
} | |
); | |
elm = React.cloneElement(child, props); | |
} else if (typeof child !== 'string'){ | |
let props = _.extend( | |
{}, | |
child.props, | |
{ | |
attachToForm: this.attachToForm.bind(this), | |
detachFromForm: this.detachFromForm.bind(this), | |
formId: this.formId, | |
Actions: this.props.Actions, | |
key: i | |
} | |
); | |
let {name, type, validation, required, value} = props; | |
elm = React.cloneElement(child, props); | |
this.initialData.push({ | |
name, | |
type, | |
required, | |
value, | |
validation | |
}); | |
} else { | |
elm = child; | |
} | |
return elm; | |
} | |
function _getComponentData(components, ctx) { | |
return components.reduce((list, component) => { | |
let {name, type, validation, required, value} = _.merge({}, component.props, component.state); | |
list.push({ | |
name, | |
type, | |
required, | |
value, | |
validation | |
}); | |
return list; | |
}, [], ctx); | |
} | |
@nuclearComponent({ | |
formHasErrors: Getters.formHasErrors | |
}) | |
export default class Form extends Component { | |
static contextTypes = { | |
Actions: PropTypes.object.isRequired | |
}; | |
constructor(props) { | |
super(props); | |
this.inputs = []; | |
this.initialData = []; | |
this.formId = _.uniqueId('form_'); | |
this.clones = React.Children.map(this.props.children, _recurseChildren, this); | |
} | |
componentWillMount() { | |
this.props.Actions.register(this.formId, this.initialData); | |
} | |
attachToForm(component) { | |
this.inputs.push(component); | |
} | |
handleSubmit = (e) => { | |
e.preventDefault(); | |
this.props.Actions.setFormData(this.formId, _getComponentData(this.inputs, this)); | |
} | |
detachFromForm(component) { | |
this.inputs = this.inputs.filter((cachedComponent) => { | |
let bool = true; | |
if(cachedComponent && cachedComponent.props && component === cachedComponent) { | |
bool = false; | |
} else if(component.name === cachedComponent.name) { | |
bool = false; | |
} | |
return bool; | |
}); | |
} | |
render() { | |
let classes = cx({ | |
'form-error': this.props.formHasErrors | |
}); | |
return ( | |
<form onSubmit={this.handleSubmit} className={classes}> | |
{this.clones} | |
<button type="submit">Submit</button> | |
</form> | |
); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import keyMirror from 'react/lib/keyMirror'; | |
export default keyMirror({ | |
REGISTER_FORM: null, | |
SET_FORM_VALUE: null, | |
SUBMIT_FORM_DATA: null, | |
UNREGISTER_FORM: null | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import actionTypes from './nuclear-action-types'; | |
export default function(reactor) { | |
/** | |
* Registers a form to be tracked in the form store | |
* @param {String} formId | |
* @param {Object} initialValues | |
*/ | |
const register = function register(formId, initialValues) { | |
reactor.dispatch(actionTypes.REGISTER_FORM, { | |
formId, | |
initialValues | |
}); | |
}; | |
/** | |
* Registers a form to be tracked in the form store | |
* @param {String} formId | |
* @param {Object} inputData | |
*/ | |
const setFieldValue = function setFieldValue(formId, inputData) { | |
reactor.dispatch(actionTypes.SET_FORM_VALUE, { formId, inputData }); | |
}; | |
/** | |
* Registers a form to be tracked in the form store | |
* @param {String} formId | |
* @param {String} fieldName | |
* @param {String|Number} value | |
*/ | |
const setFormData = function setFormData(formId, formData) { | |
reactor.dispatch(actionTypes.SUBMIT_FORM_DATA, { formId, formData }); | |
}; | |
const unregister = function unregister(formId) { | |
reactor.dispatch(actionTypes.UNREGISTER_FORM, { | |
formId | |
}); | |
}; | |
return { | |
register, | |
setFieldValue, | |
setFormData, | |
unregister | |
}; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import path from 'path'; | |
import makeActions from './nuclear-actions'; | |
import * as Getters from './nuclear-getters'; | |
import * as Stores from './nuclear-stores'; | |
export default { | |
makeActions, | |
Getters, | |
Stores | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { toImmutable } from 'nuclear-js'; | |
export const validationState = ['validationState']; | |
export const formHasErrors = [ | |
validationState, | |
(state) => { | |
let dataVals = state.valueSeq(); | |
let hasErrors = false; | |
if(dataVals.size) { | |
hasErrors = dataVals.some((data) => { | |
return data.get('currentValues').filter( component => component.get('error') ).size; | |
}); | |
} | |
return hasErrors; | |
} | |
]; | |
export const formErrors = [ | |
validationState, | |
formHasErrors, | |
(state, hasErrors) => { | |
if(hasErrors) { | |
return state; | |
} | |
return hasErrors; | |
} | |
]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Reactor } from 'nuclear-js'; | |
const reactor = new Reactor({ | |
debug: process.env.NODE_ENV === 'development' | |
}); | |
export default reactor; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Store, toImmutable } from 'nuclear-js'; | |
import actionTypes from './nuclear-action-types'; | |
import validate from 'hrc-data-model-validate'; | |
export const inputStateStore = Store({ | |
getInitialState() { | |
return toImmutable({}); | |
}, | |
initialize() { | |
this.on(actionTypes.REGISTER_FORM, registerForm); | |
this.on(actionTypes.SET_FORM_VALUE, setFormValue); | |
this.on(actionTypes.SUBMIT_FORM_DATA, submitFormData); | |
this.on(actionTypes.UNREGISTER_FORM, unregisterForm); | |
} | |
}); | |
/** | |
* @param {Immutable.Map} state | |
* @param {Object} payload | |
* @param {String} payload.formId | |
* @param {Array} payload.initialValues | |
* @return {Immutable.Map} | |
*/ | |
function registerForm(state, { formId, initialValues }) { | |
var formEntry = toImmutable({ | |
initialValues: initialValues, | |
currentValues: initialValues | |
}); | |
return state.set(formId, formEntry); | |
} | |
/** | |
* @param {Immutable.Map} state | |
* @param {Object} payload | |
* @param {String} payload.formId | |
* @param {Object} payload.inputData | |
* @return {Immutable.Map} | |
*/ | |
function setFormValue(state, { formId, inputData }) { | |
let { name, value, error } = validate([inputData])[0]; | |
var formEntry = state.get(formId); | |
if (!formEntry) { | |
throw new Error(`FormStore: cannot find form by formId=${formId}`); | |
} | |
let newState = state.getIn([formId, 'currentValues']).map((data) => { | |
if(data.get('name') === name) { | |
return data.merge({ value, error }); | |
} | |
return data; | |
}); | |
return state.setIn([formId, 'currentValues'], newState); | |
} | |
/** | |
* @param {Immutable.Map} state | |
* @param {Object} payload | |
* @param {String} payload.formId | |
* @param {Array} payload.formData | |
* @return {Immutable.Map} | |
*/ | |
function submitFormData(state, { formId, formData }) { | |
let validatedData = validate(formData); | |
var formEntry = state.get(formId); | |
if (!formEntry) { | |
throw new Error(`FormStore: cannot find form by formId=${formId}`); | |
} | |
return state.setIn([formId, 'currentValues'], toImmutable(validatedData)); | |
} | |
/** | |
* @param {Immutable.Map} state | |
* @param {Object} payload | |
* @param {String} payload.formId | |
* @return {Immutable.Map} | |
*/ | |
function unregisterForm(state, { formId }) { | |
return state.delete(formId); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment