2018-04-30 09:36:42 +09:00
|
|
|
|
import React, { Component } from 'react';
|
2017-05-19 14:42:43 -06:00
|
|
|
|
import PropTypes from 'prop-types';
|
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-25 12:13:30 +09:00
|
|
|
|
import get from 'lodash/get';
|
2018-05-08 20:09:42 -04:00
|
|
|
|
import isEqual from 'lodash/isEqual';
|
2018-06-29 20:01:55 +02:00
|
|
|
|
import { isEmptyValue, mergeValue } from '../modules/utils.js';
|
2016-03-30 19:08:06 +09:00
|
|
|
|
|
2018-04-30 09:36:42 +09:00
|
|
|
|
class FormComponent extends Component {
|
2018-05-11 09:52:04 +09:00
|
|
|
|
constructor(props) {
|
2016-04-17 16:47:04 +09:00
|
|
|
|
super(props);
|
2017-06-07 17:59:02 -07:00
|
|
|
|
|
2018-05-08 20:09:42 -04:00
|
|
|
|
this.state = {};
|
|
|
|
|
}
|
2018-03-25 12:13:30 +09:00
|
|
|
|
|
2018-05-11 09:52:04 +09:00
|
|
|
|
componentWillMount() {
|
2018-05-08 20:09:42 -04:00
|
|
|
|
if (this.showCharsRemaining()) {
|
|
|
|
|
const value = this.getValue();
|
|
|
|
|
this.updateCharacterCount(value);
|
2017-06-07 17:59:02 -07:00
|
|
|
|
}
|
2016-04-17 16:47:04 +09:00
|
|
|
|
}
|
|
|
|
|
|
2018-05-11 09:52:04 +09:00
|
|
|
|
shouldComponentUpdate(nextProps, nextState) {
|
2018-05-08 20:09:42 -04:00
|
|
|
|
// allow custom controls to determine if they should update
|
2018-05-10 10:03:59 -04:00
|
|
|
|
if (this.isCustomInput(this.getType(nextProps))) {
|
2018-05-08 20:09:42 -04:00
|
|
|
|
return true;
|
|
|
|
|
}
|
2018-05-11 09:52:04 +09:00
|
|
|
|
|
2018-05-08 20:09:42 -04:00
|
|
|
|
const { currentValues, deletedValues, errors } = nextProps;
|
|
|
|
|
const { path } = this.props;
|
2018-05-11 09:52:04 +09:00
|
|
|
|
|
2018-05-23 16:02:36 -04:00
|
|
|
|
const valueChanged = get(currentValues, path) !== get(this.props.currentValues, path);
|
2018-05-08 20:09:42 -04:00
|
|
|
|
const errorChanged = !isEqual(this.getErrors(errors), this.getErrors());
|
|
|
|
|
const deleteChanged = deletedValues.includes(path) !== this.props.deletedValues.includes(path);
|
|
|
|
|
const charsChanged = nextState.charsRemaining !== this.state.charsRemaining;
|
2018-06-30 09:23:49 +02:00
|
|
|
|
const disabledChanged = nextProps.disabled !== this.props.disabled;
|
2018-05-11 09:52:04 +09:00
|
|
|
|
|
2018-06-30 09:23:49 +02:00
|
|
|
|
return valueChanged || errorChanged || deleteChanged || charsChanged || disabledChanged;
|
2018-05-08 20:09:42 -04:00
|
|
|
|
}
|
2018-05-11 09:52:04 +09:00
|
|
|
|
|
2018-05-08 11:05:24 +09:00
|
|
|
|
/*
|
|
|
|
|
|
2018-05-21 09:42:08 +09:00
|
|
|
|
If this is an intl input, get _intl field instead
|
2018-05-08 11:05:24 +09:00
|
|
|
|
|
|
|
|
|
*/
|
2018-05-11 09:52:04 +09:00
|
|
|
|
getPath = props => {
|
2018-05-08 11:05:24 +09:00
|
|
|
|
const p = props || this.props;
|
2018-05-21 09:42:08 +09:00
|
|
|
|
return p.intlInput ? `${p.path}_intl` : p.path;
|
2018-05-11 09:52:04 +09:00
|
|
|
|
};
|
2018-05-08 11:05:24 +09:00
|
|
|
|
|
2018-05-08 20:09:42 -04:00
|
|
|
|
/*
|
|
|
|
|
|
2018-05-10 10:03:59 -04:00
|
|
|
|
Returns true if the passed input type is a custom
|
|
|
|
|
|
|
|
|
|
*/
|
2018-05-11 09:52:04 +09:00
|
|
|
|
isCustomInput = inputType => {
|
|
|
|
|
const isStandardInput = [
|
|
|
|
|
'nested',
|
|
|
|
|
'number',
|
|
|
|
|
'url',
|
|
|
|
|
'email',
|
|
|
|
|
'textarea',
|
|
|
|
|
'checkbox',
|
|
|
|
|
'checkboxgroup',
|
|
|
|
|
'radiogroup',
|
|
|
|
|
'select',
|
|
|
|
|
'selectmultiple',
|
|
|
|
|
'datetime',
|
|
|
|
|
'date',
|
|
|
|
|
'time',
|
|
|
|
|
'text',
|
|
|
|
|
].includes(inputType);
|
2018-05-10 10:03:59 -04:00
|
|
|
|
return !isStandardInput;
|
|
|
|
|
};
|
2018-05-11 09:52:04 +09:00
|
|
|
|
|
2018-05-10 10:03:59 -04:00
|
|
|
|
/*
|
|
|
|
|
|
2018-05-08 20:09:42 -04:00
|
|
|
|
Function passed to form controls (always controlled) to update their value
|
|
|
|
|
|
|
|
|
|
*/
|
2018-03-19 11:59:30 +09:00
|
|
|
|
handleChange = (name, value) => {
|
2018-04-06 17:56:25 +09:00
|
|
|
|
// if value is an empty string, delete the field
|
|
|
|
|
if (value === '') {
|
|
|
|
|
value = null;
|
|
|
|
|
}
|
2018-03-29 11:58:24 +09:00
|
|
|
|
// if this is a number field, convert value before sending it up to Form
|
2018-05-08 20:09:42 -04:00
|
|
|
|
if (this.getType() === 'number' && value != null) {
|
2018-03-29 11:58:24 +09:00
|
|
|
|
value = Number(value);
|
2018-03-25 12:13:30 +09:00
|
|
|
|
}
|
2018-05-08 11:05:24 +09:00
|
|
|
|
|
2018-05-21 09:42:08 +09:00
|
|
|
|
const updateValue = this.props.locale ? { locale: this.props.locale, value } : value;
|
|
|
|
|
this.props.updateCurrentValues({ [this.getPath()]: updateValue });
|
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-26 17:50:03 +09:00
|
|
|
|
if (this.showCharsRemaining()) {
|
2018-03-25 12:13:30 +09:00
|
|
|
|
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
|
|
|
|
/*
|
2018-05-08 20:09:42 -04:00
|
|
|
|
|
|
|
|
|
Updates the state of charsCount and charsRemaining as the users types
|
|
|
|
|
|
2018-03-24 11:16:11 +09:00
|
|
|
|
*/
|
2018-03-26 18:00:26 +09:00
|
|
|
|
updateCharacterCount = value => {
|
2018-03-26 17:50:03 +09:00
|
|
|
|
const characterCount = value ? value.length : 0;
|
|
|
|
|
this.setState({
|
|
|
|
|
charsRemaining: this.props.max - characterCount,
|
2018-05-08 20:09:42 -04:00
|
|
|
|
charsCount: characterCount,
|
2018-03-26 17:50:03 +09: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
|
|
|
|
/*
|
|
|
|
|
|
2018-03-27 10:45:17 +09:00
|
|
|
|
Get value from Form state through document and currentValues props
|
2018-03-25 12:13:30 +09:00
|
|
|
|
|
|
|
|
|
*/
|
2018-03-28 11:20:41 +09:00
|
|
|
|
getValue = props => {
|
2018-03-28 11:51:18 +09:00
|
|
|
|
let value;
|
2018-03-25 12:13:30 +09:00
|
|
|
|
const p = props || this.props;
|
2018-06-29 20:01:55 +02:00
|
|
|
|
const { document, currentValues, defaultValue } = p;
|
2018-05-21 09:42:08 +09:00
|
|
|
|
// for intl field fetch the actual field value by adding .value to the path
|
|
|
|
|
const path = p.locale ? `${this.getPath(p)}.value` : this.getPath(p);
|
2018-03-28 11:51:18 +09:00
|
|
|
|
const documentValue = get(document, path);
|
2018-05-23 16:02:36 -04:00
|
|
|
|
const currentValue = get(currentValues, path);
|
2018-03-28 11:51:18 +09:00
|
|
|
|
const isDeleted = p.deletedValues.includes(path);
|
2018-04-06 17:56:25 +09:00
|
|
|
|
|
2018-03-28 11:51:18 +09:00
|
|
|
|
if (isDeleted) {
|
|
|
|
|
value = '';
|
|
|
|
|
} else {
|
2018-06-29 12:55:03 +02:00
|
|
|
|
value = mergeValue({ currentValue, documentValue, ...p });
|
|
|
|
|
if (typeof value === 'undefined') {
|
2018-03-28 11:51:18 +09:00
|
|
|
|
// note: value has to default to '' to make component controlled
|
2018-05-10 10:03:59 -04:00
|
|
|
|
value = '';
|
|
|
|
|
if (typeof currentValue !== 'undefined' && currentValue !== null) {
|
|
|
|
|
value = currentValue;
|
|
|
|
|
} else if (typeof documentValue !== 'undefined' && documentValue !== null) {
|
2018-05-08 20:09:42 -04:00
|
|
|
|
value = documentValue;
|
|
|
|
|
}
|
2018-03-28 11:51:18 +09:00
|
|
|
|
}
|
2018-06-02 08:33:50 +09:00
|
|
|
|
// replace empty value, which has not been prefilled, by the default value from the schema – for new forms only
|
|
|
|
|
if (isEmptyValue(value) && p.formType === 'new') {
|
2018-03-28 11:51:18 +09:00
|
|
|
|
if (defaultValue) value = defaultValue;
|
|
|
|
|
}
|
2018-03-26 14:27:45 +09:00
|
|
|
|
}
|
2018-03-28 11:20:41 +09:00
|
|
|
|
|
2018-05-10 10:03:59 -04:00
|
|
|
|
return this.cleanValue(p, value);
|
|
|
|
|
};
|
2018-05-11 09:52:04 +09:00
|
|
|
|
|
2018-05-10 10:03:59 -04:00
|
|
|
|
/*
|
|
|
|
|
|
|
|
|
|
For some input types apply additional normalization
|
|
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
cleanValue = (props, value) => {
|
|
|
|
|
const p = props || this.props;
|
2018-05-11 09:52:04 +09:00
|
|
|
|
|
2018-05-10 10:03:59 -04:00
|
|
|
|
if (p.input === 'checkbox') {
|
|
|
|
|
value = !!value;
|
|
|
|
|
} else if (p.input === 'checkboxgroup') {
|
|
|
|
|
if (!Array.isArray(value)) {
|
|
|
|
|
value = [value];
|
|
|
|
|
}
|
2018-05-11 09:52:04 +09:00
|
|
|
|
// in case of checkbox groups, check "checked" option to populate value
|
2018-05-10 10:03:59 -04:00
|
|
|
|
// if this is a "new document" form
|
2018-05-11 09:52:04 +09:00
|
|
|
|
const checkedValues = _.where(p.options, { checked: true }).map(option => option.value);
|
2018-05-10 10:03:59 -04:00
|
|
|
|
if (checkedValues.length && !value && p.formType === 'new') {
|
|
|
|
|
value = checkedValues;
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-05-11 09:52:04 +09:00
|
|
|
|
|
2018-03-26 14:27:45 +09:00
|
|
|
|
return value;
|
2018-03-25 12:13:30 +09:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
2018-03-26 17:50:03 +09:00
|
|
|
|
Whether to keep track of and show remaining chars
|
|
|
|
|
|
|
|
|
|
*/
|
2018-03-26 18:00:26 +09:00
|
|
|
|
showCharsRemaining = props => {
|
2018-03-26 17:50:03 +09:00
|
|
|
|
const p = props || this.props;
|
|
|
|
|
return p.max && ['url', 'email', 'textarea', 'text'].includes(this.getType(p));
|
2018-03-26 18:00:26 +09:00
|
|
|
|
};
|
2018-03-26 17:50:03 +09:00
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
2018-03-25 12:13:30 +09:00
|
|
|
|
Get errors from Form state through context
|
|
|
|
|
|
2018-06-28 21:35:44 +02:00
|
|
|
|
Note: we use `includes` to get all errors from nested components, which have longer paths
|
|
|
|
|
|
2018-03-25 12:13:30 +09:00
|
|
|
|
*/
|
2018-05-11 09:52:04 +09:00
|
|
|
|
getErrors = errors => {
|
2018-05-08 20:09:42 -04:00
|
|
|
|
errors = errors || this.props.errors;
|
2018-06-28 21:35:44 +02:00
|
|
|
|
const fieldErrors = errors.filter(error => error.path.includes(this.props.path));
|
2018-03-25 12:13:30 +09:00
|
|
|
|
return fieldErrors;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
2018-05-10 10:03:59 -04:00
|
|
|
|
Get form input type, either based on input 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
|
|
|
|
*/
|
2018-03-26 18:00:26 +09:00
|
|
|
|
getType = props => {
|
2018-03-26 17:50:03 +09:00
|
|
|
|
const p = props || this.props;
|
|
|
|
|
const fieldType = p.datatype && p.datatype[0].type;
|
|
|
|
|
const autoType =
|
2018-05-11 09:52:04 +09:00
|
|
|
|
fieldType === Number ? 'number' : fieldType === Boolean ? 'checkbox' : fieldType === Date ? 'date' : 'text';
|
2018-04-14 18:09:35 +09:00
|
|
|
|
return p.input || autoType;
|
2018-03-25 12:13:30 +09:00
|
|
|
|
};
|
|
|
|
|
|
2018-05-07 17:41:22 +09:00
|
|
|
|
/*
|
2018-05-08 20:09:42 -04:00
|
|
|
|
|
|
|
|
|
Function passed to form controls to clear their contents (set their value to null)
|
|
|
|
|
|
2018-05-07 17:41:22 +09:00
|
|
|
|
*/
|
2018-05-08 20:09:42 -04:00
|
|
|
|
clearField = event => {
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
event.stopPropagation();
|
2018-04-06 17:56:25 +09:00
|
|
|
|
this.props.updateCurrentValues({ [this.props.path]: null });
|
2018-05-08 20:09:42 -04:00
|
|
|
|
if (this.showCharsRemaining()) {
|
|
|
|
|
this.updateCharacterCount(null);
|
|
|
|
|
}
|
2018-03-25 12:13:30 +09:00
|
|
|
|
};
|
2018-05-11 09:52:04 +09:00
|
|
|
|
|
2018-05-10 10:03:59 -04:00
|
|
|
|
/*
|
|
|
|
|
|
|
|
|
|
Function passed to FormComponentInner to help with rendering the component
|
|
|
|
|
|
|
|
|
|
*/
|
2018-05-23 17:12:04 +09:00
|
|
|
|
getFormInput = () => {
|
|
|
|
|
const inputType = this.getType();
|
2018-05-11 09:52:04 +09:00
|
|
|
|
|
2018-04-14 18:09:35 +09:00
|
|
|
|
// if input is a React component, use it
|
2018-05-23 17:12:04 +09:00
|
|
|
|
if (typeof this.props.input === 'function') {
|
|
|
|
|
const InputComponent = this.props.input;
|
|
|
|
|
return InputComponent;
|
2018-03-25 12:13:30 +09:00
|
|
|
|
} else {
|
|
|
|
|
// else pick a predefined component
|
2018-05-11 09:52:04 +09:00
|
|
|
|
|
2018-05-10 10:03:59 -04:00
|
|
|
|
switch (inputType) {
|
|
|
|
|
case 'text':
|
2018-05-23 17:12:04 +09:00
|
|
|
|
return Components.FormComponentDefault;
|
2018-05-11 09:52:04 +09:00
|
|
|
|
|
2017-08-19 16:17:52 +09:00
|
|
|
|
case 'number':
|
2018-05-23 17:12:04 +09:00
|
|
|
|
return Components.FormComponentNumber;
|
2018-05-11 09:52:04 +09:00
|
|
|
|
|
2017-08-19 16:17:52 +09:00
|
|
|
|
case 'url':
|
2018-05-23 17:12:04 +09:00
|
|
|
|
return Components.FormComponentUrl;
|
2018-05-11 09:52:04 +09:00
|
|
|
|
|
2017-08-19 16:17:52 +09:00
|
|
|
|
case 'email':
|
2018-05-23 17:12:04 +09:00
|
|
|
|
return Components.FormComponentEmail;
|
2018-05-11 09:52:04 +09:00
|
|
|
|
|
2017-08-19 16:17:52 +09:00
|
|
|
|
case 'textarea':
|
2018-05-23 17:12:04 +09:00
|
|
|
|
return Components.FormComponentTextarea;
|
2018-05-11 09:52:04 +09:00
|
|
|
|
|
2017-08-19 16:17:52 +09:00
|
|
|
|
case 'checkbox':
|
2018-05-23 17:12:04 +09:00
|
|
|
|
return Components.FormComponentCheckbox;
|
2018-05-11 09:52:04 +09:00
|
|
|
|
|
2017-08-19 16:17:52 +09:00
|
|
|
|
case 'checkboxgroup':
|
2018-05-23 17:12:04 +09:00
|
|
|
|
return Components.FormComponentCheckboxGroup;
|
2018-05-11 09:52:04 +09:00
|
|
|
|
|
2017-08-19 16:17:52 +09:00
|
|
|
|
case 'radiogroup':
|
2018-05-23 17:12:04 +09:00
|
|
|
|
return Components.FormComponentRadioGroup;
|
2018-05-11 09:52:04 +09:00
|
|
|
|
|
2017-08-19 16:17:52 +09:00
|
|
|
|
case 'select':
|
2018-05-23 17:12:04 +09:00
|
|
|
|
return Components.FormComponentSelect;
|
2018-05-11 09:52:04 +09:00
|
|
|
|
|
2018-03-08 11:28:29 +09:00
|
|
|
|
case 'selectmultiple':
|
2018-05-23 17:12:04 +09:00
|
|
|
|
return Components.FormComponentSelectMultiple;
|
2018-05-11 09:52:04 +09:00
|
|
|
|
|
2017-08-19 16:17:52 +09:00
|
|
|
|
case 'datetime':
|
2018-05-23 17:12:04 +09:00
|
|
|
|
return Components.FormComponentDateTime;
|
2018-05-11 09:52:04 +09:00
|
|
|
|
|
2018-03-19 11:59:30 +09:00
|
|
|
|
case 'date':
|
2018-05-23 17:12:04 +09:00
|
|
|
|
return Components.FormComponentDate;
|
2018-05-11 09:52:04 +09:00
|
|
|
|
|
2017-11-03 11:40:53 +09:00
|
|
|
|
case 'time':
|
2018-05-23 17:12:04 +09:00
|
|
|
|
return Components.FormComponentTime;
|
2018-05-11 09:52:04 +09:00
|
|
|
|
|
2018-03-25 12:13:30 +09:00
|
|
|
|
default:
|
2018-05-23 17:12:04 +09:00
|
|
|
|
const CustomComponent = Components[this.props.input];
|
|
|
|
|
return CustomComponent ? CustomComponent : Components.FormComponentDefault;
|
2016-04-04 16:50:07 +09:00
|
|
|
|
}
|
2016-04-01 20:57:37 +09:00
|
|
|
|
}
|
2018-03-25 12:13:30 +09:00
|
|
|
|
};
|
2018-05-11 09:52:04 +09:00
|
|
|
|
|
|
|
|
|
render() {
|
2018-06-28 18:36:57 +02:00
|
|
|
|
if (this.props.intlInput) {
|
|
|
|
|
return <Components.FormIntl {...this.props} />;
|
|
|
|
|
} else if (this.props.nestedInput){
|
|
|
|
|
return <Components.FormNested {...this.props} />;
|
|
|
|
|
}
|
2017-10-18 20:07:43 +09:00
|
|
|
|
return (
|
2018-05-08 20:09:42 -04:00
|
|
|
|
<Components.FormComponentInner
|
|
|
|
|
{...this.props}
|
|
|
|
|
{...this.state}
|
|
|
|
|
inputType={this.getType()}
|
|
|
|
|
value={this.getValue()}
|
|
|
|
|
errors={this.getErrors()}
|
|
|
|
|
document={this.context.getDocument()}
|
|
|
|
|
showCharsRemaining={!!this.showCharsRemaining()}
|
|
|
|
|
onChange={this.handleChange}
|
|
|
|
|
clearField={this.clearField}
|
2018-05-23 17:12:04 +09:00
|
|
|
|
formInput={this.getFormInput()}
|
2018-05-08 20:09:42 -04:00
|
|
|
|
/>
|
2018-03-25 12:13:30 +09:00
|
|
|
|
);
|
2017-10-18 20:07:43 +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,
|
2018-05-08 20:09:42 -04:00
|
|
|
|
name: PropTypes.string.isRequired,
|
2017-05-19 14:42:43 -06:00
|
|
|
|
label: PropTypes.string,
|
|
|
|
|
value: PropTypes.any,
|
|
|
|
|
placeholder: PropTypes.string,
|
|
|
|
|
prefilledValue: PropTypes.any,
|
|
|
|
|
options: PropTypes.any,
|
2018-04-14 18:09:35 +09:00
|
|
|
|
input: PropTypes.any,
|
2017-05-19 14:42:43 -06:00
|
|
|
|
datatype: PropTypes.any,
|
2018-05-08 20:09:42 -04:00
|
|
|
|
path: PropTypes.string.isRequired,
|
2017-05-19 14:42:43 -06:00
|
|
|
|
disabled: PropTypes.bool,
|
2018-03-26 18:00:26 +09:00
|
|
|
|
nestedSchema: PropTypes.object,
|
2018-05-08 20:09:42 -04:00
|
|
|
|
currentValues: PropTypes.object.isRequired,
|
|
|
|
|
deletedValues: PropTypes.array.isRequired,
|
|
|
|
|
throwError: PropTypes.func.isRequired,
|
|
|
|
|
updateCurrentValues: PropTypes.func.isRequired,
|
|
|
|
|
errors: PropTypes.array.isRequired,
|
2018-04-06 17:56:25 +09:00
|
|
|
|
addToDeletedValues: PropTypes.func,
|
2018-05-08 20:09:42 -04:00
|
|
|
|
clearFieldErrors: PropTypes.func.isRequired,
|
|
|
|
|
currentUser: PropTypes.object,
|
2018-03-25 12:13:30 +09:00
|
|
|
|
};
|
2016-04-01 20:57:37 +09:00
|
|
|
|
|
2017-01-31 17:55:26 +09:00
|
|
|
|
FormComponent.contextTypes = {
|
2018-05-08 20:09:42 -04:00
|
|
|
|
getDocument: PropTypes.func.isRequired,
|
2017-01-31 17:55:26 +09:00
|
|
|
|
};
|
|
|
|
|
|
2017-10-17 09:35:41 -04:00
|
|
|
|
registerComponent('FormComponent', FormComponent);
|