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';
|
2017-06-07 17:59:02 -07:00
|
|
|
import classNames from 'classnames';
|
2017-08-19 10:41:08 +09:00
|
|
|
import { Components } from 'meteor/vulcan:core';
|
2017-10-17 09:35:41 -04:00
|
|
|
import { registerComponent } from 'meteor/vulcan:core';
|
2018-03-19 11:59:30 +09:00
|
|
|
import debounce from 'lodash.debounce';
|
2018-03-25 12:13:30 +09:00
|
|
|
import get from 'lodash/get';
|
2016-03-30 19:08:06 +09:00
|
|
|
|
2017-05-19 14:42:43 -06:00
|
|
|
class FormComponent extends PureComponent {
|
2018-03-25 12:13:30 +09:00
|
|
|
constructor(props, context) {
|
2016-04-17 16:47:04 +09:00
|
|
|
super(props);
|
2017-06-07 17:59:02 -07:00
|
|
|
|
2018-03-25 12:13:30 +09:00
|
|
|
const value = this.getValue(props, context);
|
|
|
|
|
2017-06-07 17:59:02 -07:00
|
|
|
if (props.limit) {
|
|
|
|
this.state = {
|
2018-03-25 12:13:30 +09:00
|
|
|
limit: value ? props.limit - value.length : props.limit,
|
|
|
|
};
|
2017-06-07 17:59:02 -07:00
|
|
|
}
|
2016-04-17 16:47:04 +09:00
|
|
|
}
|
|
|
|
|
2018-03-19 11:59:30 +09:00
|
|
|
handleChange = (name, value) => {
|
2018-03-25 12:13:30 +09:00
|
|
|
// if this is a number field, convert value before sending it up to Form
|
|
|
|
if (this.getType() === 'number') {
|
|
|
|
value = Number(value);
|
|
|
|
}
|
|
|
|
|
|
|
|
this.context.updateCurrentValues({ [this.props.path]: value });
|
2018-03-23 08:51:24 +09:00
|
|
|
|
2018-03-19 11:59:30 +09:00
|
|
|
// for text fields, update character count on change
|
2018-03-25 12:13:30 +09:00
|
|
|
if (['number', 'url', 'email', 'textarea', 'text'].includes(this.getType())) {
|
|
|
|
this.updateCharacterCount(value);
|
2016-10-05 11:19:20 +02:00
|
|
|
}
|
2018-03-25 12:13:30 +09:00
|
|
|
};
|
2016-04-17 16:47:04 +09:00
|
|
|
|
2018-03-24 11:16:11 +09:00
|
|
|
/*
|
|
|
|
|
|
|
|
Note: not currently used because when function is debounced
|
|
|
|
some changes might not register if the user submits form too soon
|
|
|
|
|
|
|
|
*/
|
2018-03-25 12:13:30 +09:00
|
|
|
handleChangeDebounced = debounce(this.handleChange, 500, { leading: true });
|
2018-03-19 11:59:30 +09:00
|
|
|
|
2018-03-25 12:13:30 +09:00
|
|
|
updateCharacterCount = (value) => {
|
2017-06-07 17:59:02 -07:00
|
|
|
if (this.props.limit) {
|
2017-08-16 16:24:50 +09:00
|
|
|
const characterCount = value ? value.length : 0;
|
2017-06-07 17:59:02 -07:00
|
|
|
this.setState({
|
2018-03-25 12:13:30 +09:00
|
|
|
limit: this.props.limit - characterCount,
|
2017-06-07 17:59:02 -07:00
|
|
|
});
|
|
|
|
}
|
2018-03-25 12:13:30 +09:00
|
|
|
};
|
2017-06-07 17:59:02 -07:00
|
|
|
|
2018-03-25 12:13:30 +09:00
|
|
|
/*
|
|
|
|
|
|
|
|
Get value from Form state through context
|
|
|
|
|
|
|
|
*/
|
|
|
|
getValue = (props, context) => {
|
|
|
|
const p = props || this.props;
|
|
|
|
const c = context || this.context;
|
|
|
|
return get(c.getDocument(), p.path);
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
Get errors from Form state through context
|
|
|
|
|
|
|
|
*/
|
|
|
|
getErrors = () => {
|
|
|
|
const fieldErrors = this.context.errors.filter(error => error.data.name === this.props.path);
|
|
|
|
return fieldErrors;
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
Get form control type, either based on control props, or by guessing
|
|
|
|
based on form field type
|
2016-03-30 19:08:06 +09:00
|
|
|
|
2018-03-25 12:13:30 +09:00
|
|
|
*/
|
|
|
|
getType = () => {
|
|
|
|
return this.props.control || 'text';
|
|
|
|
};
|
|
|
|
|
|
|
|
renderComponent() {
|
2016-09-07 14:35:49 +02:00
|
|
|
// see https://facebook.github.io/react/warnings/unknown-prop.html
|
2018-03-25 12:13:30 +09:00
|
|
|
const {
|
|
|
|
control,
|
|
|
|
group,
|
|
|
|
updateCurrentValues,
|
|
|
|
document,
|
|
|
|
beforeComponent,
|
|
|
|
afterComponent,
|
|
|
|
limit,
|
|
|
|
errors,
|
|
|
|
nestedSchema,
|
|
|
|
nestedFields,
|
|
|
|
datatype,
|
|
|
|
parentFieldName,
|
|
|
|
itemIndex,
|
|
|
|
path,
|
|
|
|
...rest
|
|
|
|
} = this.props; // eslint-disable-line
|
2016-09-07 14:35:49 +02:00
|
|
|
|
2016-04-17 16:47:04 +09:00
|
|
|
const properties = {
|
2017-05-04 11:16:03 +09:00
|
|
|
...rest,
|
2018-03-25 12:13:30 +09:00
|
|
|
// onBlur: this.handleChange,
|
2018-03-24 11:16:11 +09:00
|
|
|
onChange: this.handleChange,
|
2018-03-19 11:59:30 +09:00
|
|
|
document,
|
2018-03-25 12:13:30 +09:00
|
|
|
value: this.getValue(),
|
2016-04-17 16:47:04 +09:00
|
|
|
};
|
|
|
|
|
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') {
|
2018-03-25 12:13:30 +09:00
|
|
|
return <this.props.control {...properties} />;
|
|
|
|
} else {
|
|
|
|
// else pick a predefined component
|
|
|
|
|
|
|
|
switch (this.getType()) {
|
|
|
|
case 'nested':
|
|
|
|
return (
|
|
|
|
<Components.FormNested
|
|
|
|
path={path}
|
|
|
|
updateCurrentValues={updateCurrentValues}
|
|
|
|
nestedSchema={nestedSchema}
|
|
|
|
nestedFields={nestedFields}
|
|
|
|
datatype={datatype}
|
|
|
|
{...properties}
|
|
|
|
/>
|
|
|
|
);
|
2018-03-22 19:22:54 +09:00
|
|
|
|
2017-08-19 16:17:52 +09:00
|
|
|
case 'number':
|
2018-03-25 12:13:30 +09:00
|
|
|
return <Components.FormComponentNumber {...properties} />;
|
2017-08-19 10:41:08 +09:00
|
|
|
|
2017-08-19 16:17:52 +09:00
|
|
|
case 'url':
|
2018-03-25 12:13:30 +09:00
|
|
|
return <Components.FormComponentUrl {...properties} />;
|
2017-08-19 10:41:08 +09:00
|
|
|
|
2017-08-19 16:17:52 +09:00
|
|
|
case 'email':
|
2018-03-25 12:13:30 +09:00
|
|
|
return <Components.FormComponentEmail {...properties} />;
|
2017-08-19 10:41:08 +09:00
|
|
|
|
2017-08-19 16:17:52 +09:00
|
|
|
case 'textarea':
|
2018-03-25 12:13:30 +09:00
|
|
|
return <Components.FormComponentTextarea {...properties} />;
|
2017-08-19 10:41:08 +09:00
|
|
|
|
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;
|
2017-08-19 10:41:08 +09:00
|
|
|
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];
|
|
|
|
}
|
2017-08-19 10:41:08 +09:00
|
|
|
return <Components.FormComponentCheckboxGroup {...properties} />;
|
|
|
|
|
2017-08-19 16:17:52 +09:00
|
|
|
case 'radiogroup':
|
2018-03-25 12:13:30 +09:00
|
|
|
// TODO: remove this?
|
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
|
2018-03-25 12:13:30 +09:00
|
|
|
// properties.onChange = (name, value) => {
|
|
|
|
// this.context.updateCurrentValues({ [name]: value });
|
|
|
|
// };
|
2017-08-19 10:41:08 +09:00
|
|
|
return <Components.FormComponentRadioGroup {...properties} />;
|
2018-01-25 13:36:20 -06:00
|
|
|
|
2017-08-19 16:17:52 +09:00
|
|
|
case 'select':
|
2018-03-08 11:28:29 +09:00
|
|
|
const noneOption = {
|
2018-03-25 12:13:30 +09:00
|
|
|
label: this.context.intl.formatMessage({ id: 'forms.select_option' }),
|
|
|
|
value: '',
|
|
|
|
disabled: true,
|
2018-03-08 11:28:29 +09:00
|
|
|
};
|
|
|
|
|
|
|
|
properties.options = [noneOption, ...properties.options];
|
|
|
|
return <Components.FormComponentSelect {...properties} />;
|
|
|
|
|
|
|
|
case 'selectmultiple':
|
|
|
|
properties.multiple = true;
|
2017-08-19 10:41:08 +09:00
|
|
|
return <Components.FormComponentSelect {...properties} />;
|
2018-01-25 13:36:20 -06:00
|
|
|
|
2017-08-19 16:17:52 +09:00
|
|
|
case 'datetime':
|
2017-08-19 10:41:08 +09:00
|
|
|
return <Components.FormComponentDateTime {...properties} />;
|
2018-01-25 13:36:20 -06:00
|
|
|
|
2018-03-19 11:59:30 +09:00
|
|
|
case 'date':
|
|
|
|
return <Components.FormComponentDate {...properties} />;
|
|
|
|
|
2017-11-03 11:40:53 +09: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':
|
2018-03-25 12:13:30 +09:00
|
|
|
return <Components.FormComponentDefault {...properties} />;
|
2018-01-25 13:36:20 -06:00
|
|
|
|
2018-03-25 12:13:30 +09:00
|
|
|
default:
|
2017-09-16 22:26:52 +02:00
|
|
|
const CustomComponent = Components[this.props.control];
|
2018-03-25 12:13:30 +09:00
|
|
|
return CustomComponent ? (
|
|
|
|
<CustomComponent {...properties} />
|
|
|
|
) : (
|
|
|
|
<Components.FormComponentDefault {...properties} />
|
|
|
|
);
|
2016-04-04 16:50:07 +09:00
|
|
|
}
|
2016-04-01 20:57:37 +09:00
|
|
|
}
|
2016-03-30 19:08:06 +09:00
|
|
|
}
|
|
|
|
|
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);
|
2018-03-25 12:13:30 +09:00
|
|
|
};
|
2017-10-18 20:07:43 +09:00
|
|
|
|
2018-03-25 12:13:30 +09:00
|
|
|
clearField = e => {
|
2017-10-18 20:07:43 +09:00
|
|
|
e.preventDefault();
|
|
|
|
const fieldName = this.props.name;
|
|
|
|
// clear value
|
2018-03-25 12:13:30 +09:00
|
|
|
this.context.updateCurrentValues({ [fieldName]: null });
|
2017-10-18 20:07:43 +09:00
|
|
|
// add it to unset
|
2018-03-23 15:46:31 +09:00
|
|
|
// TODO: not needed anymore?
|
|
|
|
// this.context.addToDeletedValues(fieldName);
|
2018-03-25 12:13:30 +09:00
|
|
|
};
|
2017-10-18 20:07:43 +09:00
|
|
|
|
|
|
|
renderClear() {
|
|
|
|
return (
|
2018-03-25 12:13:30 +09:00
|
|
|
<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
|
|
|
}
|
|
|
|
|
2016-04-01 20:57:37 +09:00
|
|
|
render() {
|
2018-03-25 10:54:45 +09:00
|
|
|
const hasErrors = this.getErrors() && this.getErrors().length;
|
2018-03-25 12:13:30 +09:00
|
|
|
const inputClass = classNames(
|
|
|
|
'form-input',
|
|
|
|
`input-${this.props.name}`,
|
|
|
|
`form-component-${this.props.control || 'default'}`,
|
|
|
|
{ 'input-error': hasErrors }
|
|
|
|
);
|
2017-08-16 16:18:40 +09:00
|
|
|
|
2016-06-28 17:33:30 +09:00
|
|
|
return (
|
2017-08-16 16:18:40 +09:00
|
|
|
<div className={inputClass}>
|
2016-06-28 17:33:30 +09:00
|
|
|
{this.props.beforeComponent ? this.props.beforeComponent : null}
|
|
|
|
{this.renderComponent()}
|
2018-03-25 12:13:30 +09:00
|
|
|
{hasErrors ? <Components.FieldErrors errors={this.getErrors()} /> : null}
|
2017-10-18 20:07:43 +09:00
|
|
|
{this.showClear() ? this.renderClear() : null}
|
2018-03-25 12:13:30 +09:00
|
|
|
{this.props.limit ? (
|
|
|
|
<div className={classNames('form-control-limit', { danger: this.state.limit < 10 })}>{this.state.limit}</div>
|
|
|
|
) : null}
|
2016-06-28 17:33:30 +09:00
|
|
|
{this.props.afterComponent ? this.props.afterComponent : null}
|
|
|
|
</div>
|
2018-03-25 12:13:30 +09:00
|
|
|
);
|
2016-03-30 19:08:06 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-01 20:57:37 +09:00
|
|
|
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,
|
2018-03-25 12:13:30 +09:00
|
|
|
updateCurrentValues: PropTypes.func,
|
|
|
|
};
|
2016-04-01 20:57:37 +09:00
|
|
|
|
2017-01-31 17:55:26 +09:00
|
|
|
FormComponent.contextTypes = {
|
2017-10-18 20:07:43 +09:00
|
|
|
intl: intlShape,
|
|
|
|
addToDeletedValues: PropTypes.func,
|
2018-03-25 10:54:45 +09:00
|
|
|
errors: PropTypes.array,
|
2018-03-25 12:13:30 +09:00
|
|
|
currentValues: PropTypes.object,
|
|
|
|
autofilledValues: PropTypes.object,
|
|
|
|
deletedValues: PropTypes.array,
|
|
|
|
getDocument: PropTypes.func,
|
|
|
|
updateCurrentValues: PropTypes.func,
|
2017-01-31 17:55:26 +09:00
|
|
|
};
|
|
|
|
|
2017-10-17 09:35:41 -04:00
|
|
|
registerComponent('FormComponent', FormComponent);
|