Cleaning up FormComponent props

This commit is contained in:
SachaG 2018-03-26 17:50:03 +09:00
parent 9d5963da12
commit 5675b50fb5
18 changed files with 119 additions and 137 deletions

View file

@ -110,7 +110,7 @@ class Upload extends PureComponent {
*/
enableMultiple() {
return this.props.datatype.definitions[0].type === Array;
return this.props.datatype && this.props.datatype[0].type === Array;
}
/*

View file

@ -1,28 +1,28 @@
import React from 'react';
import PropTypes from 'prop-types';
import Alert from 'react-bootstrap/lib/Alert'
import { registerComponent } from 'meteor/vulcan:core';
// import React from 'react';
// import PropTypes from 'prop-types';
// import Alert from 'react-bootstrap/lib/Alert'
// import { registerComponent } from 'meteor/vulcan:core';
const Flash = ({message, type}) => {
// const Flash = ({message, type}) => {
type = type === "error" ? "danger" : type; // if type is "error", use "danger" instead
// type = type === "error" ? "danger" : type; // if type is "error", use "danger" instead
return (
<Alert className="flash-message" bsStyle={type}>
{Array.isArray(message) ?
<ul>
{message.map((message, index) =>
<li key={index}>{message.content}</li>
)}
</ul>
: <span>{message.content}</span>
}
</Alert>
)
}
// return (
// <Alert className="flash-message" bsStyle={type}>
// {Array.isArray(message) ?
// <ul>
// {message.map((message, index) =>
// <li key={index}>{message.content}</li>
// )}
// </ul>
// : <span>{message.content}</span>
// }
// </Alert>
// )
// }
Flash.propTypes = {
message: PropTypes.oneOfType([PropTypes.object.isRequired, PropTypes.array.isRequired])
}
// Flash.propTypes = {
// message: PropTypes.oneOfType([PropTypes.object.isRequired, PropTypes.array.isRequired])
// }
registerComponent('FormFlash', Flash);
// registerComponent('FormFlash', Flash);

View file

@ -22,7 +22,7 @@ This component expects:
*/
import { Components, runCallbacks, getCollection } from 'meteor/vulcan:core';
import { registerComponent, Components, runCallbacks, getCollection } from 'meteor/vulcan:core';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { intlShape } from 'meteor/vulcan:i18n';
@ -73,14 +73,12 @@ class Form extends Component {
// the initial document passed as props
this.initialDocument = merge({}, this.props.prefilledProps, this.props.document);
}
submitFormCallbacks = [];
successFormCallbacks = [];
failureFormCallbacks = [];
// --------------------------------------------------------------------- //
// ------------------------------- Helpers ----------------------------- //
// --------------------------------------------------------------------- //
@ -109,7 +107,6 @@ class Form extends Component {
*/
/*
Get the current document (for edit forms)
@ -122,13 +119,8 @@ class Form extends Component {
*/
getDocument = () => {
const document = merge(
{},
this.initialDocument,
this.state.autofilledValues,
this.state.currentValues,
);
const document = merge({}, this.initialDocument, this.state.autofilledValues, this.state.currentValues);
return document;
};
@ -240,7 +232,6 @@ class Form extends Component {
return relevantFields;
};
/*
Given a field's name, the containing schema, and parent, create the
@ -248,7 +239,6 @@ class Form extends Component {
*/
createField = (fieldName, schema, parentFieldName, parentPath) => {
const fieldPath = parentPath ? `${parentPath}.${fieldName}` : fieldName;
const fieldSchema = schema[fieldName];
@ -284,17 +274,14 @@ class Form extends Component {
field.options = field.options.call(fieldSchema, this.props);
}
// add any properties specified in fieldProperties or form as extra props passed on
// to the form component
const fieldProperties = fieldSchema.fieldProperties || fieldSchema.form;
if (fieldProperties) {
for (const prop in fieldProperties) {
if (prop !== 'prefill' && prop !== 'options' && fieldProperties.hasOwnProperty(prop)) {
field[prop] =
typeof fieldProperties[prop] === 'function'
? fieldProperties[prop].call(fieldSchema)
: fieldProperties[prop];
}
// add any properties specified in fieldSchema.form as extra props passed on
// to the form component, calling them if they are functions
if (fieldSchema.form) {
for (const prop in fieldSchema.form) {
field[prop] =
typeof fieldSchema.form[prop] === 'function'
? fieldSchema.form[prop].call(fieldSchema, this.props)
: fieldSchema.form[prop];
}
}
@ -661,7 +648,12 @@ class Form extends Component {
<Components.FormErrors errors={this.state.errors} />
{fieldGroups.map(group => (
<Components.FormGroup key={group.name} {...group} updateCurrentValues={this.updateCurrentValues} formType={this.getFormType()}/>
<Components.FormGroup
key={group.name}
{...group}
updateCurrentValues={this.updateCurrentValues}
formType={this.getFormType()}
/>
))}
{this.props.repeatErrors && this.renderErrors()}
@ -752,3 +744,5 @@ Form.childContextTypes = {
};
module.exports = Form;
registerComponent('Form', Form);

View file

@ -7,7 +7,6 @@ import { registerComponent } from 'meteor/vulcan:core';
import debounce from 'lodash.debounce';
import get from 'lodash/get';
import { isEmptyValue } from '../modules/utils.js';
import { formProperties } from '../modules/schema_utils';
class FormComponent extends PureComponent {
constructor(props, context) {
@ -15,15 +14,15 @@ class FormComponent extends PureComponent {
const value = this.getValue(props, context);
if (props.limit) {
if (this.showCharsRemaining(props)) {
const characterCount = value ? value.length : 0;
this.state = {
limit: value ? props.limit - value.length : props.limit,
charsRemaining: props.max - characterCount,
};
}
}
handleChange = (name, value) => {
if (!!value) {
// if this is a number field, convert value before sending it up to Form
if (this.getType() === 'number') {
@ -35,7 +34,7 @@ class FormComponent extends PureComponent {
}
// for text fields, update character count on change
if (['number', 'url', 'email', 'textarea', 'text'].includes(this.getType())) {
if (this.showCharsRemaining()) {
this.updateCharacterCount(value);
}
};
@ -49,12 +48,10 @@ class FormComponent extends PureComponent {
handleChangeDebounced = debounce(this.handleChange, 500, { leading: true });
updateCharacterCount = (value) => {
if (this.props.limit) {
const characterCount = value ? value.length : 0;
this.setState({
limit: this.props.limit - characterCount,
});
}
const characterCount = value ? value.length : 0;
this.setState({
charsRemaining: this.props.max - characterCount,
});
};
/*
@ -72,14 +69,24 @@ class FormComponent extends PureComponent {
// replace empty value, which has not been prefilled, by the default value from the schema
// keep defaultValue for backwards compatibility even though it doesn't actually work
if (isEmptyValue(value)) {
if (this.props.defaultValue) value = this.props.defaultValue;
if (this.props.default) value = this.props.default;
if (p.defaultValue) value = p.defaultValue;
if (p.default) value = p.default;
}
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
*/
@ -94,43 +101,48 @@ class FormComponent extends PureComponent {
based on form field type
*/
getType = () => {
return this.props.control || 'text';
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() {
// see https://facebook.github.io/react/warnings/unknown-prop.html
const {
control,
group,
updateCurrentValues,
document,
beforeComponent,
afterComponent,
limit,
errors,
nestedSchema,
nestedFields,
datatype,
parentFieldName,
itemIndex,
options,
path,
name,
label,
form,
formType,
optional,
...rest
} = this.props; // eslint-disable-line
} = this.props;
const properties = {
...rest,
// onBlur: this.handleChange,
// 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,
document,
value: this.getValue(),
...form,
};
const properties = { ...this.props, inputProperties };
// if control is a React component, use it
if (typeof this.props.control === 'function') {
return <this.props.control {...properties} />;
if (typeof control === 'function') {
const ControlComponent = control;
return <ControlComponent {...properties} />;
} else {
// else pick a predefined component
@ -174,7 +186,7 @@ class FormComponent extends PureComponent {
// in case of checkbox groups, check "checked" option to populate value if this is a "new document" form
const checkedValues = _.where(properties.options, { checked: true }).map(option => option.value);
if (checkedValues.length && !properties.value && this.props.formType === 'new') {
if (checkedValues.length && !properties.value && formType === 'new') {
properties.value = checkedValues;
}
return <Components.FormComponentCheckboxGroup {...properties} />;
@ -215,7 +227,7 @@ class FormComponent extends PureComponent {
return <Components.FormComponentDefault {...properties} />;
default:
const CustomComponent = Components[this.props.control];
const CustomComponent = Components[control];
return CustomComponent ? (
<CustomComponent {...properties} />
) : (
@ -253,24 +265,24 @@ class FormComponent extends PureComponent {
}
render() {
const { beforeComponent, afterComponent, max, name, control } = this.props;
const hasErrors = this.getErrors() && this.getErrors().length;
const inputClass = classNames(
'form-input',
`input-${this.props.name}`,
`form-component-${this.props.control || 'default'}`,
`input-${name}`,
`form-component-${control || 'default'}`,
{ 'input-error': hasErrors }
);
return (
<div className={inputClass}>
{this.props.beforeComponent ? this.props.beforeComponent : null}
{beforeComponent ? beforeComponent : null}
{this.renderComponent()}
{hasErrors ? <Components.FieldErrors errors={this.getErrors()} /> : null}
{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}
{this.showCharsRemaining() && <div className={classNames('form-control-limit', { danger: this.state.charsRemaining < 10 })}>{this.state.charsRemaining}</div>}
{afterComponent ? afterComponent : null}
</div>
);
}

View file

@ -39,7 +39,6 @@ import {
getFragment,
getCollection,
} from 'meteor/vulcan:core';
import Form from './Form.jsx';
import gql from 'graphql-tag';
import { withDocument } from 'meteor/vulcan:core';
import { graphql } from 'react-apollo';
@ -177,7 +176,7 @@ class FormWrapper extends PureComponent {
const { document, loading } = props;
return loading ?
<Components.Loading /> :
<Form
<Components.Form
document={document}
loading={loading}
{...childProps}
@ -224,7 +223,7 @@ class FormWrapper extends PureComponent {
} else {
WrappedComponent = compose(
withNew(mutationOptions)
)(Form);
)(Components.Form);
}
return <WrappedComponent {...childProps} />;

View file

@ -2,6 +2,6 @@ import React from 'react';
import { Checkbox } from 'formsy-react-components';
import { registerComponent } from 'meteor/vulcan:core';
const CheckboxComponent = ({refFunction, ...properties}) => <Checkbox {...properties} ref={refFunction} />
const CheckboxComponent = ({refFunction, inputProperties, ...properties}) => <Checkbox {...inputProperties} ref={refFunction} />
registerComponent('FormComponentCheckbox', CheckboxComponent);

View file

@ -2,6 +2,6 @@ import React from 'react';
import { CheckboxGroup } from 'formsy-react-components';
import { registerComponent } from 'meteor/vulcan:core';
const CheckboxGroupComponent = ({refFunction, ...properties}) => <CheckboxGroup {...properties} ref={refFunction} />
const CheckboxGroupComponent = ({refFunction, inputProperties, ...properties}) => <CheckboxGroup {...inputProperties} ref={refFunction} />
registerComponent('FormComponentCheckboxGroup', CheckboxGroupComponent);

View file

@ -2,6 +2,6 @@ import React from 'react';
import { Input } from 'formsy-react-components';
import { registerComponent } from 'meteor/vulcan:core';
const Default = ({refFunction, ...properties}) => <Input {...properties} ref={refFunction} type="text" />
const Default = ({refFunction, inputProperties, ...properties}) => <Input {...inputProperties} ref={refFunction} type="text" />
registerComponent('FormComponentDefault', Default);

View file

@ -2,6 +2,6 @@ import React from 'react';
import { Input } from 'formsy-react-components';
import { registerComponent } from 'meteor/vulcan:core';
const EmailComponent = ({refFunction, ...properties}) => <Input {...properties} ref={refFunction} type="email" />
const EmailComponent = ({refFunction, inputProperties, ...properties}) => <Input {...inputProperties} ref={refFunction} type="email" />
registerComponent('FormComponentEmail', EmailComponent);

View file

@ -2,6 +2,6 @@ import React from 'react';
import { Input } from 'formsy-react-components';
import { registerComponent } from 'meteor/vulcan:core';
const NumberComponent = ({refFunction, ...properties}) => <Input {...properties} ref={refFunction} type="number" />
const NumberComponent = ({refFunction, inputProperties, ...properties}) => <Input {...inputProperties} ref={refFunction} type="number" />
registerComponent('FormComponentNumber', NumberComponent);

View file

@ -2,6 +2,6 @@ import React from 'react';
import { RadioGroup } from 'formsy-react-components';
import { registerComponent } from 'meteor/vulcan:core';
const RadioGroupComponent = ({refFunction, ...properties}) => <RadioGroup {...properties} ref={refFunction}/>
const RadioGroupComponent = ({refFunction, inputProperties, ...properties}) => <RadioGroup {...inputProperties} ref={refFunction}/>
registerComponent('FormComponentRadioGroup', RadioGroupComponent);

View file

@ -2,6 +2,6 @@ import React from 'react';
import { Select } from 'formsy-react-components';
import { registerComponent } from 'meteor/vulcan:core';
const SelectComponent = ({refFunction, ...properties}) => <Select {...properties} ref={refFunction}/>
const SelectComponent = ({refFunction, inputProperties, ...properties}) => <Select {...inputProperties} ref={refFunction}/>
registerComponent('FormComponentSelect', SelectComponent);

View file

@ -2,6 +2,6 @@ import React from 'react';
import { Textarea } from 'formsy-react-components';
import { registerComponent } from 'meteor/vulcan:core';
const TextareaComponent = ({refFunction, ...properties}) => <Textarea ref={refFunction} {...properties}/>
const TextareaComponent = ({refFunction, inputProperties, ...properties}) => <Textarea ref={refFunction} {...inputProperties}/>
registerComponent('FormComponentTextarea', TextareaComponent);

View file

@ -2,6 +2,6 @@ import React from 'react';
import { Input } from 'formsy-react-components';
import { registerComponent } from 'meteor/vulcan:core';
const UrlComponent = ({refFunction, ...properties}) => <Input ref={refFunction} {...properties} type="url" />
const UrlComponent = ({refFunction, inputProperties, ...properties}) => <Input ref={refFunction} {...inputProperties} type="url" />
registerComponent('FormComponentUrl', UrlComponent);

View file

@ -19,3 +19,4 @@ import '../components/FormNested.jsx';
import '../components/FormGroup.jsx';
import '../components/FormSubmit.jsx';
import '../components/FormWrapper.jsx';
import '../components/Form.jsx';

View file

@ -1,21 +1,5 @@
import SimpleSchema from 'simpl-schema';
import { registerComponent } from 'meteor/vulcan:core';
import './components.js';
if (typeof SimpleSchema !== "undefined") {
SimpleSchema.extendOptions([
'control', // SmartForm control (String or React component)
'order', // order in the form
'group', // form fieldset group
'hidden',
'beforeComponent',
'afterComponent',
'placeholder',
'options',
'query',
'fieldProperties',
]);
}
export {default as FormWrapper} from '../components/FormWrapper.jsx';

View file

@ -80,8 +80,6 @@ export const schemaProperties = [
'custom',
'defaultValue',
'autoValue',
'private',
'editable', // editable: true means the field can be edited by the document's owner
'hidden', // hidden: true means the field is never shown in a form no matter what
'mustComplete', // mustComplete: true means the field is required to have a complete profile
'profile', // profile: true means the field is shown on user profiles
@ -98,9 +96,7 @@ export const schemaProperties = [
'insertableBy',
'editableBy',
'resolveAs',
'limit',
'searchable',
'default',
'description',
'beforeComponent',
'afterComponent',
@ -131,8 +127,6 @@ export const formProperties = [
'control', // SmartForm control (String or React component)
'order', // position in the form
'group', // form fieldset group
'limit',
'default',
'description',
'beforeComponent',
'afterComponent',

View file

@ -14,28 +14,26 @@ Vulcan.VERSION = '1.8.11';
// ------------------------------------- Schemas -------------------------------- //
SimpleSchema.extendOptions([
'private',
'editable', // editable: true means the field can be edited by the document's owner
'hidden', // hidden: true means the field is never shown in a form no matter what
'mustComplete', // mustComplete: true means the field is required to have a complete profile
'profile', // profile: true means the field is shown on user profiles
'template', // legacy template used to display the field; backward compatibility (not used anymore)
'form', // form placeholder
'autoform', // legacy form placeholder; backward compatibility (not used anymore)
'form', // extra form properties
'control', // SmartForm control (String or React component)
'order', // position in the form
'group', // form fieldset group
'onInsert', // field insert callback
'onEdit', // field edit callback
'onRemove', // field remove callback
'viewableBy',
'insertableBy',
'editableBy',
'resolveAs',
'limit',
'searchable',
'default',
'description',
'viewableBy', // who can view the field
'insertableBy', // who can insert the field
'editableBy', // who can edit the field
'resolveAs', // field-level resolver
'searchable', // whether a field is searchable
'description', // description/help
'beforeComponent', // before form component
'afterComponent', // after form component
'placeholder', // form field placeholder value
'options', // form options
'query', // field-specific data loading query
]);
// eslint-disable-next-line no-undef