Skip to content

Instantly share code, notes, and snippets.

@dtothefp
Last active March 5, 2016 16:29
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dtothefp/a8bed4dc372fc2766fcb to your computer and use it in GitHub Desktop.
Save dtothefp/a8bed4dc372fc2766fcb to your computer and use it in GitHub Desktop.
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">
Email
<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>
);
}
}
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}
/>
);
}
}
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>
);
}
}
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
);
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>
);
}
}
import keyMirror from 'react/lib/keyMirror';
export default keyMirror({
REGISTER_FORM: null,
SET_FORM_VALUE: null,
SUBMIT_FORM_DATA: null,
UNREGISTER_FORM: null
});
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
};
}
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
};
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;
}
];
import { Reactor } from 'nuclear-js';
const reactor = new Reactor({
debug: process.env.NODE_ENV === 'development'
});
export default reactor;
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