import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { intlShape } from 'meteor/vulcan:i18n';
import classNames from 'classnames';
import { Components } from 'meteor/vulcan:core';
import { registerComponent } from 'meteor/vulcan:core';
import debounce from 'lodash.debounce';
import get from 'lodash/get';
import merge from 'lodash/merge';
import { isEmptyValue } from '../modules/utils.js';
class FormComponent extends PureComponent {
constructor(props) {
super(props);
const value = this.getValue(props);
if (this.showCharsRemaining(props)) {
const characterCount = value ? value.length : 0;
this.state = {
charsRemaining: props.max - characterCount,
};
}
}
handleChange = (name, value) => {
// if value is an empty string, delete the field
if (value === '') {
value = null;
}
// if this is a number field, convert value before sending it up to Form
if (this.getType() === 'number') {
value = Number(value);
}
this.props.updateCurrentValues({ [this.props.path]: value });
// for text fields, update character count on change
if (this.showCharsRemaining()) {
this.updateCharacterCount(value);
}
};
/*
Note: not currently used because when function is debounced
some changes might not register if the user submits form too soon
*/
handleChangeDebounced = debounce(this.handleChange, 500, { leading: true });
updateCharacterCount = value => {
const characterCount = value ? value.length : 0;
this.setState({
charsRemaining: this.props.max - characterCount,
});
};
/*
Get value from Form state through document and currentValues props
*/
getValue = props => {
let value;
const p = props || this.props;
const { document, currentValues, defaultValue, path, datatype } = p;
const documentValue = get(document, path);
const currentValue = currentValues[path];
const isDeleted = p.deletedValues.includes(path);
if (isDeleted) {
value = '';
} else {
if (datatype[0].type === Array) {
// for object and arrays, use lodash's merge
// if field type is array, use [] as merge seed to force result to be an array as well
value = merge([], documentValue, currentValue);
} else if (datatype[0].type === Object) {
value = merge({}, documentValue, currentValue);
} else {
// note: value has to default to '' to make component controlled
value = currentValue || documentValue || '';
}
// replace empty value, which has not been prefilled, by the default value from the schema
if (isEmptyValue(value)) {
if (defaultValue) value = defaultValue;
}
}
return value;
};
/*
Whether to keep track of and show remaining chars
*/
showCharsRemaining = props => {
const p = props || this.props;
return p.max && ['url', 'email', 'textarea', 'text'].includes(this.getType(p));
};
/*
Get errors from Form state through context
*/
getErrors = () => {
const fieldErrors = this.props.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
*/
getType = props => {
const p = props || this.props;
const fieldType = p.datatype && p.datatype[0].type;
const autoType =
fieldType === Number ? 'number' : fieldType === Boolean ? 'checkbox' : fieldType === Date ? 'date' : 'text';
return p.control || autoType;
};
renderComponent() {
const {
control,
beforeComponent,
afterComponent,
options,
name,
label,
form,
formType,
throwError,
updateCurrentValues,
currentValues,
addToDeletedValues,
deletedValues,
} = this.props;
const value = this.getValue();
// these properties are whitelisted so that they can be safely passed to the actual form input
// and avoid https://facebook.github.io/react/warnings/unknown-prop.html warnings
const inputProperties = {
name,
options,
label,
onChange: this.handleChange,
value,
...form,
};
// note: we also pass value on props directly
const properties = {
...this.props,
value,
errors: this.getErrors(), // only get errors for the current field
throwError,
inputProperties,
currentValues,
updateCurrentValues,
deletedValues,
addToDeletedValues,
};
// if control is a React component, use it
if (typeof control === 'function') {
const ControlComponent = control;
return