Vulcan/packages/vulcan-forms/lib/components/FormComponent.jsx

206 lines
6.7 KiB
React
Raw Normal View History

2017-05-19 14:42:43 -06:00
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
2017-06-01 11:42:30 +09:00
import { intlShape } from 'meteor/vulcan:i18n';
import classNames from 'classnames';
import { Components } from 'meteor/vulcan:core';
import { registerComponent } from 'meteor/vulcan:core';
import { isEmptyValue } from '../modules/utils';
2017-05-19 14:42:43 -06:00
class FormComponent extends PureComponent {
constructor(props) {
super(props);
this.handleBlur = this.handleBlur.bind(this);
this.updateCharacterCount = this.updateCharacterCount.bind(this);
this.renderErrors = this.renderErrors.bind(this);
if (props.limit) {
this.state = {
limit: props.value ? props.limit - props.value.length : props.limit
}
}
}
componentWillReceiveProps(nextProps) {
this.updateCharacterCount(nextProps.name, nextProps.value)
}
handleBlur() {
// see https://facebook.github.io/react/docs/more-about-refs.html
if (this.formControl !== null) {
this.props.updateCurrentValues({[this.props.name]: this.formControl.getValue()});
}
}
updateCharacterCount(name, value) {
if (this.props.limit) {
2017-08-16 16:24:50 +09:00
const characterCount = value ? value.length : 0;
this.setState({
2017-08-16 16:24:50 +09:00
limit: this.props.limit - characterCount
});
}
}
renderComponent() {
// see https://facebook.github.io/react/warnings/unknown-prop.html
const { control, group, updateCurrentValues, document, beforeComponent, afterComponent, limit, errors, ...rest } = this.props; // eslint-disable-line
2017-08-19 16:17:52 +09:00
// const base = typeof this.props.control === 'function' ? this.props : rest;
const properties = {
2017-08-16 16:24:50 +09:00
value: '', // default value, will be overridden by `rest` if real value has been passed down through props
...rest,
onBlur: this.handleBlur,
2017-09-16 22:26:52 +02:00
refFunction: (ref) => this.formControl = ref,
};
2017-09-16 22:26:52 +02:00
// for text fields, update character count on change
if (!this.props.control || ['number', 'url', 'email', 'textarea', 'text'].includes(this.props.control)) {
properties.onChange = this.updateCharacterCount;
}
2016-04-07 15:24:38 +09:00
// if control is a React component, use it
2017-08-19 16:17:52 +09:00
if (typeof this.props.control === 'function') {
2016-04-07 14:20:44 +09:00
2016-11-15 10:44:01 +01:00
return <this.props.control {...properties} document={document} />
2016-04-04 16:50:07 +09:00
2017-09-16 22:26:52 +02:00
} else if (typeof this.props.control === 'string') { // else pick a predefined component
2017-08-19 16:17:52 +09:00
2016-04-07 15:24:38 +09:00
switch (this.props.control) {
2018-01-25 13:36:20 -06:00
2017-08-19 16:17:52 +09:00
case 'number':
return <Components.FormComponentNumber {...properties}/>;
2017-08-19 16:17:52 +09:00
case 'url':
return <Components.FormComponentUrl {...properties}/>;
2017-08-19 16:17:52 +09:00
case 'email':
return <Components.FormComponentEmail {...properties}/>;
2017-08-19 16:17:52 +09:00
case 'textarea':
return <Components.FormComponentTextarea {...properties}/>;
2017-08-19 16:17:52 +09:00
case 'checkbox':
2018-01-25 13:36:20 -06:00
// formsy-react-components expects a boolean value for checkbox
// https://github.com/twisty/formsy-react-components/blob/v0.11.1/src/checkbox.js#L20
properties.value = !!properties.value;
return <Components.FormComponentCheckbox {...properties} />;
2017-08-19 16:17:52 +09:00
case 'checkboxgroup':
2018-01-25 13:36:20 -06:00
// formsy-react-components expects an array value
// https://github.com/twisty/formsy-react-components/blob/v0.11.1/src/checkbox-group.js#L42
if (!Array.isArray(properties.value)) {
properties.value = [properties.value];
}
return <Components.FormComponentCheckboxGroup {...properties} />;
2017-08-19 16:17:52 +09:00
case 'radiogroup':
2018-01-25 13:36:20 -06:00
// formsy-react-compnents RadioGroup expects an onChange callback
// https://github.com/twisty/formsy-react-components/blob/v0.11.1/src/radio-group.js#L33
properties.onChange = (name, value) => {this.props.updateCurrentValues({[name]: value})};
return <Components.FormComponentRadioGroup {...properties} />;
2018-01-25 13:36:20 -06:00
2017-08-19 16:17:52 +09:00
case 'select':
const noneOption = {
label: this.context.intl.formatMessage({id: 'forms.select_option'}),
value: '',
disabled: true
};
properties.options = [noneOption, ...properties.options];
return <Components.FormComponentSelect {...properties} />;
case 'selectmultiple':
properties.multiple = true;
return <Components.FormComponentSelect {...properties} />;
2018-01-25 13:36:20 -06:00
2017-08-19 16:17:52 +09:00
case 'datetime':
return <Components.FormComponentDateTime {...properties} />;
2018-01-25 13:36:20 -06:00
case 'time':
return <Components.FormComponentTime {...properties} />;
2018-01-25 13:36:20 -06:00
2017-08-19 16:17:52 +09:00
case 'text':
return <Components.FormComponentDefault {...properties}/>;
2018-01-25 13:36:20 -06:00
2017-09-16 22:26:52 +02:00
default:
const CustomComponent = Components[this.props.control];
return <CustomComponent {...properties} document={document}/>;
2016-04-04 16:50:07 +09:00
}
2017-09-16 22:26:52 +02:00
} else {
return <Components.FormComponentDefault {...properties}/>;
}
}
renderErrors() {
return (
2017-08-19 16:17:52 +09:00
<ul className='form-input-errors'>
{this.props.errors.map((error, index) => <li key={index}>{error.message}</li>)}
</ul>
)
}
2017-10-18 20:07:43 +09:00
showClear = () => {
2018-03-03 11:09:58 +09:00
return ['datetime', 'time', 'select', 'radiogroup'].includes(this.props.control);
2017-10-18 20:07:43 +09:00
}
clearField = (e) => {
e.preventDefault();
const fieldName = this.props.name;
// clear value
this.props.updateCurrentValues({[fieldName]: null});
// add it to unset
this.context.addToDeletedValues(fieldName);
}
renderClear() {
return (
<a href="javascript:void(0)" className="form-component-clear" title={this.context.intl.formatMessage({id: 'forms.clear_field'})} onClick={this.clearField}><span></span></a>
2017-10-18 20:07:43 +09:00
)
}
render() {
const hasErrors = this.props.errors && this.props.errors.length;
const inputClass = classNames('form-input', `input-${this.props.name}`, `form-component-${this.props.control || 'default'}`,{'input-error': hasErrors});
return (
<div className={inputClass}>
{this.props.beforeComponent ? this.props.beforeComponent : null}
{this.renderComponent()}
{hasErrors ? this.renderErrors() : null}
2017-10-18 20:07:43 +09:00
{this.showClear() ? this.renderClear() : null}
{this.props.limit ? <div className={classNames('form-control-limit', {danger: this.state.limit < 10})}>{this.state.limit}</div> : null}
{this.props.afterComponent ? this.props.afterComponent : null}
</div>
)
}
}
FormComponent.propTypes = {
2017-05-19 14:42:43 -06:00
document: PropTypes.object,
name: PropTypes.string,
label: PropTypes.string,
value: PropTypes.any,
placeholder: PropTypes.string,
prefilledValue: PropTypes.any,
options: PropTypes.any,
control: PropTypes.any,
datatype: PropTypes.any,
disabled: PropTypes.bool,
updateCurrentValues: PropTypes.func
}
FormComponent.contextTypes = {
2017-10-18 20:07:43 +09:00
intl: intlShape,
addToDeletedValues: PropTypes.func,
};
registerComponent('FormComponent', FormComponent);