nestedreact
Version:
Advanced models, state management, and data binding solution for React
140 lines (139 loc) • 6.01 kB
JSX
/**
* Linked React components for building forms implementing React 0.14 valueLink semantic.
*
* WTFPL License, (c) 2016 Vlad Balin, Volicon.
*/
import * as React from 'react';
const setValue = (x, e) => e.target.value;
const setBoolValue = (x, e) => Boolean(e.target.checked);
/**
* Wrapper for standard <input/> to be compliant with React 0.14 valueLink semantic.
* Simple supports for link validation - adds 'invalid' class if link has an error.
*
* <input type="checkbox" checkedLink={ linkToBool } />
* <input type="radio" valueLink={ linkToSelectedValue } value="option1value" />
* <input type="text" valueLink={ linkToString } />
*/
function validationClasses(props, value, error) {
let classNames = props.className ? [props.className] : [];
if (error) {
classNames.push(props.invalidClass || 'invalid');
if (value === '') {
classNames.push(props.requiredClass || 'required');
}
}
return classNames.join(' ');
}
export function Input(props) {
const { valueLink, checkedLink, ...rest } = props, type = props.type, link = valueLink || checkedLink;
switch (type) {
case 'checkbox':
return <input {...rest} checked={Boolean(link.value)} onChange={link.action(setBoolValue)}/>;
case 'radio':
return <input {...rest} checked={link.value === props.value} onChange={e => { e.target.checked && link.set(props.value); }}/>;
default:
return <input {...rest} className={validationClasses(rest, valueLink.value, valueLink.error)} value={String(valueLink.value)} onChange={valueLink.action(setValue)}/>;
}
}
;
export const isRequired = x => x != null && x !== '';
isRequired.error = 'Required';
const emailPattern = /^[-a-z0-9~!$%^&*_=+}{\'?]+(\.[-a-z0-9~!$%^&*_=+}{\'?]+)*@([a-z0-9_][-a-z0-9_]*(\.[-a-z0-9_]+)*\.(aero|arpa|biz|com|coop|edu|gov|info|int|mil|museum|name|net|org|pro|travel|mobi|[a-z][a-z])|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(:[0-9]{1,5})?$/i;
export const isEmail = x => Boolean(x.match(emailPattern));
isEmail.error = 'Should be valid email';
export class NumberInput extends React.Component {
constructor() {
super(...arguments);
this.onKeyPress = e => {
const { charCode } = e, { integer, positive } = this.props, allowed = (positive ? [] : [45]).concat(integer ? [] : [46]);
if (e.ctrlKey)
return;
if (charCode && // allow control characters
(charCode < 48 || charCode > 57) && // char is number
allowed.indexOf(charCode) < 0) {
e.preventDefault();
}
};
this.onChange = e => {
// Update local state...
const { value } = e.target;
this.setValue(value);
const asNumber = Number(value);
if (value && !isNaN(asNumber)) {
this.props.valueLink.update(x => {
// Update link if value is changed
if (asNumber !== Number(x)) {
return asNumber;
}
});
}
};
}
componentWillMount() {
// Initialize component state
this.setAndConvert(this.props.valueLink.value);
}
setValue(x) {
// We're not using native state in order to avoid race condition.
this.value = String(x);
this.error = this.value === '' || isNaN(Number(x));
this.forceUpdate();
}
setAndConvert(x) {
let value = Number(x);
if (this.props.positive) {
value = Math.abs(x);
}
if (this.props.integer) {
value = Math.round(value);
}
this.setValue(value);
}
componentWillReceiveProps(nextProps) {
const { valueLink: next } = nextProps;
if (Number(next.value) !== Number(this.value)) {
this.setAndConvert(next.value); // keep state being synced
}
}
render() {
const { valueLink, positive, integer, ...props } = this.props, error = valueLink.error || this.error;
return <input {...props} type="text" className={validationClasses(props, this.value, error)} value={this.value} onKeyPress={this.onKeyPress} onChange={this.onChange}/>;
}
}
/**
* Wrapper for standard <textarea/> to be compliant with React 0.14 valueLink semantic.
* Simple supports for link validation - adds 'invalid' class if link has an error.
*
* <TextArea valueLink={ linkToText } />
*/
export const TextArea = ({ valueLink, ...props }) => (<textarea {...props} className={validationClasses(props, valueLink.value, valueLink.error)} value={valueLink.value} onChange={valueLink.action(setValue)}/>);
/**
* Wrapper for standard <select/> to be compliant with React 0.14 valueLink semantic.
* Regular <option/> tags must be used:
*
* <Select valueLink={ linkToSelectedValue }>
* <option value="a">A</option>
* <option value="b">B</option>
* </Select>
*/
export const Select = ({ valueLink, children, ...props }) => (<select {...props} value={valueLink.value} onChange={valueLink.action(setValue)}>
{children}
</select>);
/**
* Simple custom <Radio/> tag implementation. Can be easily styled.
* Intended to be used with offhand bool link:
*
* <Radio checkedLink={ linkToValue.equals( optionValue ) />
*/
export const Radio = ({ className = 'radio', checkedLink, children }) => (<div className={className + (checkedLink.value ? ' selected' : '')} onClick={checkedLink.action(() => true)}>
{children}
</div>);
/**
* Simple custom <Checkbox /> tag implementation.
* Takes any type of boolean link. Can be easily styled.
*
* <Checkbox checkedLink={ boolLink } />
*/
export const Checkbox = ({ className = 'checkbox', checkedLink, children }) => (<div className={className + (checkedLink.value ? ' selected' : '')} onClick={checkedLink.action(x => !x)}>
{children}
</div>);