Reorganize forms package; individual forms component can now be overridden with replaceComponent

This commit is contained in:
SachaG 2017-08-19 10:41:08 +09:00
parent 03c7a8e4c8
commit a5d88880fa
29 changed files with 509 additions and 308 deletions

View file

@ -0,0 +1 @@
export * from '../modules/index.js';

View file

@ -1,162 +0,0 @@
/* eslint-disable react/display-name */
'use strict';
import React from 'react';
import PropTypes from 'prop-types';
module.exports = {
propTypes: {
layout: PropTypes.string,
validatePristine: PropTypes.bool,
rowClassName: PropTypes.oneOfType([
PropTypes.string,
PropTypes.array,
PropTypes.object
]),
labelClassName: PropTypes.oneOfType([
PropTypes.string,
PropTypes.array,
PropTypes.object
]),
elementWrapperClassName: PropTypes.oneOfType([
PropTypes.string,
PropTypes.array,
PropTypes.object
])
},
contextTypes: {
layout: PropTypes.string,
validatePristine: PropTypes.bool,
rowClassName: PropTypes.oneOfType([
PropTypes.string,
PropTypes.array,
PropTypes.object
]),
labelClassName: PropTypes.oneOfType([
PropTypes.string,
PropTypes.array,
PropTypes.object
]),
elementWrapperClassName: PropTypes.oneOfType([
PropTypes.string,
PropTypes.array,
PropTypes.object
])
},
getDefaultProps: function() {
return {
disabled: false,
validatePristine: false,
onChange: function() {},
onFocus: function() {},
onBlur: function() {}
};
},
/**
* Accessors for "special" properties.
*
* The following methods are used to merge master default properties that
* are optionally set on the parent form. This to to allow customising these
* properties 'as a whole' for the form, while retaining the ability to
* override the properties on a component basis.
*
* Also see the parent-context mixin.
*/
getLayout: function() {
var defaultProperty = this.context.layout || 'horizontal';
return this.props.layout ? this.props.layout : defaultProperty;
},
getValidatePristine: function() {
var defaultProperty = this.context.validatePristine || false;
return this.props.validatePristine ? this.props.validatePristine : defaultProperty;
},
getRowClassName: function() {
return [this.context.rowClassName, this.props.rowClassName];
},
getLabelClassName: function() {
return [this.context.labelClassName, this.props.labelClassName];
},
getElementWrapperClassName: function() {
return [this.context.elementWrapperClassName, this.props.elementWrapperClassName];
},
getRowProperties: function() {
return {
label: this.props.label,
rowClassName: this.getRowClassName(),
labelClassName: this.getLabelClassName(),
elementWrapperClassName: this.getElementWrapperClassName(),
layout: this.getLayout(),
required: this.isRequired(),
hasErrors: this.showErrors()
};
},
hashString: function(string) {
var hash = 0;
for (var i = 0; i < string.length; i++) {
hash = (((hash << 5) - hash) + string.charCodeAt(i)) & 0xFFFFFFFF;
}
return hash;
},
/**
* getId
*
* The ID is used as an attribute on the form control, and is used to allow
* associating the label element with the form control.
*
* If we don't explicitly pass an `id` prop, we generate one based on the
* `name` and `label` properties.
*/
getId: function() {
if (this.props.id) {
return this.props.id;
}
var label = (typeof this.props.label === 'undefined' ? '' : this.props.label);
return [
'frc',
this.props.name.split('[').join('_').replace(']', ''),
this.hashString(JSON.stringify(label))
].join('-');
},
renderHelp: function() {
if (!this.props.help) {
return '';
}
return (
<span className="help-block">{this.props.help}</span>
);
},
renderErrorMessage: function() {
if (!this.showErrors()) {
return '';
}
var errorMessages = this.getErrorMessages() || [];
return errorMessages.map((message, key) => {
return (
<span key={key} className="help-block validation-message">{message}</span>
);
});
},
showErrors: function() {
if (this.isPristine() === true) {
if (this.getValidatePristine() === false) {
return false;
}
}
return (this.isValid() === false);
}
};

View file

@ -0,0 +1,57 @@
// import React, { PureComponent } from 'react';
// import PropTypes from 'prop-types';
// import DateTimePicker from 'react-datetime';
// class DateTime extends PureComponent {
// constructor(props) {
// super(props);
// this.updateDate = this.updateDate.bind(this);
// }
// // when the datetime picker has mounted, SmartForm will catch the date value (no formsy mixin in this component)
// componentDidMount() {
// if (this.props.value) {
// this.updateDate(this.props.value);
// }
// }
// updateDate(date) {
// this.context.updateCurrentValues({[this.props.name]: date});
// }
// render() {
// const date = this.props.value ? (typeof this.props.value === 'string' ? new Date(this.props.value) : this.props.value) : null;
// return (
// <div className="form-group row">
// <label className="control-label col-sm-3">{this.props.label}</label>
// <div className="col-sm-9">
// <DateTimePicker
// value={date}
// // newDate argument is a Moment object given by react-datetime
// onChange={newDate => this.updateDate(newDate._d)}
// format={"x"}
// inputProps={{name: this.props.name}}
// />
// </div>
// </div>
// );
// }
// }
// DateTime.propTypes = {
// control: PropTypes.any,
// datatype: PropTypes.any,
// group: PropTypes.any,
// label: PropTypes.string,
// name: PropTypes.string,
// value: PropTypes.any,
// };
// DateTime.contextTypes = {
// updateCurrentValues: PropTypes.func,
// };
// export default DateTime;

View file

@ -30,7 +30,7 @@ import Formsy from 'formsy-react';
import Button from 'react-bootstrap/lib/Button';
import Flash from "./Flash.jsx";
import FormGroup from "./FormGroup.jsx";
import { flatten, deepValue, getEditableFields, getInsertableFields } from './utils.js';
import { flatten, deepValue, getEditableFields, getInsertableFields } from '../modules/utils.js';
/*

View file

@ -5,7 +5,8 @@ import { intlShape } from 'meteor/vulcan:i18n';
import DateTime from './DateTime.jsx';
import classNames from 'classnames';
import Flash from './Flash.jsx';
import { Components } from 'meteor/vulcan:core';
 
// import Utils from './utils.js';
const Checkbox = FRC.Checkbox;
@ -72,33 +73,76 @@ class FormComponent extends PureComponent {
} else { // else pick a predefined component
switch (this.props.control) {
case "number":
return <Input {...properties} type="number" onChange={this.updateCharacterCount} />;
properties.onChange = this.updateCharacterCount;
return <Components.FormComponentNumber {...properties}/>;
case "url":
return <Input {...properties} type="url" onChange={this.updateCharacterCount} />;
properties.onChange = this.updateCharacterCount;
return <Components.FormComponentUrl {...properties}/>;
case "email":
return <Input {...properties} type="email" onChange={this.updateCharacterCount} />;
properties.onChange = this.updateCharacterCount;
return <Components.FormComponentEmail {...properties}/>;
case "textarea":
return <Textarea {...properties} onChange={this.updateCharacterCount} />;
properties.onChange = this.updateCharacterCount;
return <Components.FormComponentTextarea {...properties}/>;
case "checkbox":
return <Checkbox {...properties} />;
return <Components.FormComponentCheckbox {...properties} />;
case "checkboxgroup":
// if (!properties.value) properties.value = [];
return <CheckboxGroup {...properties} />;
return <Components.FormComponentCheckboxGroup {...properties} />;
case "radiogroup":
// not sure why, but onChange needs to be specified here
return <RadioGroup {...properties} onChange={(name, value) => {this.props.updateCurrentValues({[name]: value})}}/>;
properties.onChange = (name, value) => {this.props.updateCurrentValues({[name]: value})};
return <Components.FormComponentRadioGroup {...properties} />;
case "select":
properties.options = [{label: this.context.intl.formatMessage({id: "forms.select_option"}), disabled: true}, ...properties.options];
return <Select {...properties} />;
return <Components.FormComponentSelect {...properties} />;
case "datetime":
return <DateTime {...properties} />;
return <Components.FormComponentDateTime {...properties} />;
case "text":
default:
return <Input {...properties} type="text" onChange={this.updateCharacterCount} />;
default:
properties.onChange = this.updateCharacterCount;
return <Components.FormComponentDefault {...properties}/>;
}
// switch (this.props.control) {
// case "number":
// return <Input {...properties} type="number" onChange={this.updateCharacterCount} />;
// case "url":
// return <Input {...properties} type="url" onChange={this.updateCharacterCount} />;
// case "email":
// return <Input {...properties} type="email" onChange={this.updateCharacterCount} />;
// case "textarea":
// return <Textarea {...properties} onChange={this.updateCharacterCount} />;
// case "checkbox":
// return <Checkbox {...properties} />;
// case "checkboxgroup":
// // if (!properties.value) properties.value = [];
// return <CheckboxGroup {...properties} />;
// case "radiogroup":
// // not sure why, but onChange needs to be specified here
// return <RadioGroup {...properties} onChange={(name, value) => {this.props.updateCurrentValues({[name]: value})}}/>;
// case "select":
// properties.options = [{label: this.context.intl.formatMessage({id: "forms.select_option"}), disabled: true}, ...properties.options];
// return <Select {...properties} />;
// case "datetime":
// return <DateTime {...properties} />;
// case "text":
// default:
// return <Input {...properties} type="text" onChange={this.updateCharacterCount} />;
// }
}
}

View file

@ -0,0 +1,8 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Checkbox } from 'formsy-react-components';
import { registerComponent } from 'meteor/vulcan:core';
const CheckboxComponent = properties => <Checkbox {...properties} />
registerComponent('FormComponentCheckbox', CheckboxComponent);

View file

@ -0,0 +1,8 @@
import React from 'react';
import PropTypes from 'prop-types';
import { CheckboxGroup } from 'formsy-react-components';
import { registerComponent } from 'meteor/vulcan:core';
const CheckboxGroupComponent = properties => <CheckboxGroup {...properties} />
registerComponent('FormComponentCheckboxGroup', CheckboxGroupComponent);

View file

@ -1,6 +1,7 @@
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import DateTimePicker from 'react-datetime';
import { registerComponent } from 'meteor/vulcan:core';
class DateTime extends PureComponent {
@ -55,3 +56,5 @@ DateTime.contextTypes = {
};
export default DateTime;
registerComponent('FormComponentDateTime', DateTime);

View file

@ -0,0 +1,8 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Input } from 'formsy-react-components';
import { registerComponent } from 'meteor/vulcan:core';
const Default = properties => <Input {...properties} type="text" />
registerComponent('FormComponentDefault', Default);

View file

@ -0,0 +1,8 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Input } from 'formsy-react-components';
import { registerComponent } from 'meteor/vulcan:core';
const EmailComponent = properties => <Input {...properties} type="email" />
registerComponent('FormComponentEmail', EmailComponent);

View file

@ -0,0 +1,9 @@
// import React from 'react';
// import PropTypes from 'prop-types';
// import { Form } from 'formsy-react-components';
// import { registerComponent } from 'meteor/vulcan:core';
// const FormComponentForm = properties =>
// <Form {...properties}>{this.children}</Form>
// registerComponent('FormComponentForm', FormComponentForm);

View file

@ -0,0 +1,8 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Input } from 'formsy-react-components';
import { registerComponent } from 'meteor/vulcan:core';
const NumberComponent = properties => <Input {...properties} type="number" />
registerComponent('FormComponentNumber', NumberComponent);

View file

@ -0,0 +1,8 @@
import React from 'react';
import PropTypes from 'prop-types';
import { RadioGroup } from 'formsy-react-components';
import { registerComponent } from 'meteor/vulcan:core';
const RadioGroupComponent = properties => <RadioGroup {...properties}/>
registerComponent('FormComponentRadioGroup', RadioGroupComponent);

View file

@ -0,0 +1,8 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Select } from 'formsy-react-components';
import { registerComponent } from 'meteor/vulcan:core';
const SelectComponent = properties => <Select {...properties}/>
registerComponent('FormComponentSelect', SelectComponent);

View file

@ -0,0 +1,8 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Textarea } from 'formsy-react-components';
import { registerComponent } from 'meteor/vulcan:core';
const TextareaComponent = properties => <Textarea {...properties}/>
registerComponent('FormComponentTextarea', TextareaComponent);

View file

@ -0,0 +1,8 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Input } from 'formsy-react-components';
import { registerComponent } from 'meteor/vulcan:core';
const UrlComponent = properties => <Input {...properties} type="url" />
registerComponent('FormComponentUrl', UrlComponent);

View file

@ -0,0 +1,162 @@
// /* eslint-disable react/display-name */
// 'use strict';
// import React from 'react';
// import PropTypes from 'prop-types';
// module.exports = {
// propTypes: {
// layout: PropTypes.string,
// validatePristine: PropTypes.bool,
// rowClassName: PropTypes.oneOfType([
// PropTypes.string,
// PropTypes.array,
// PropTypes.object
// ]),
// labelClassName: PropTypes.oneOfType([
// PropTypes.string,
// PropTypes.array,
// PropTypes.object
// ]),
// elementWrapperClassName: PropTypes.oneOfType([
// PropTypes.string,
// PropTypes.array,
// PropTypes.object
// ])
// },
// contextTypes: {
// layout: PropTypes.string,
// validatePristine: PropTypes.bool,
// rowClassName: PropTypes.oneOfType([
// PropTypes.string,
// PropTypes.array,
// PropTypes.object
// ]),
// labelClassName: PropTypes.oneOfType([
// PropTypes.string,
// PropTypes.array,
// PropTypes.object
// ]),
// elementWrapperClassName: PropTypes.oneOfType([
// PropTypes.string,
// PropTypes.array,
// PropTypes.object
// ])
// },
// getDefaultProps: function() {
// return {
// disabled: false,
// validatePristine: false,
// onChange: function() {},
// onFocus: function() {},
// onBlur: function() {}
// };
// },
// /**
// * Accessors for "special" properties.
// *
// * The following methods are used to merge master default properties that
// * are optionally set on the parent form. This to to allow customising these
// * properties 'as a whole' for the form, while retaining the ability to
// * override the properties on a component basis.
// *
// * Also see the parent-context mixin.
// */
// getLayout: function() {
// var defaultProperty = this.context.layout || 'horizontal';
// return this.props.layout ? this.props.layout : defaultProperty;
// },
// getValidatePristine: function() {
// var defaultProperty = this.context.validatePristine || false;
// return this.props.validatePristine ? this.props.validatePristine : defaultProperty;
// },
// getRowClassName: function() {
// return [this.context.rowClassName, this.props.rowClassName];
// },
// getLabelClassName: function() {
// return [this.context.labelClassName, this.props.labelClassName];
// },
// getElementWrapperClassName: function() {
// return [this.context.elementWrapperClassName, this.props.elementWrapperClassName];
// },
// getRowProperties: function() {
// return {
// label: this.props.label,
// rowClassName: this.getRowClassName(),
// labelClassName: this.getLabelClassName(),
// elementWrapperClassName: this.getElementWrapperClassName(),
// layout: this.getLayout(),
// required: this.isRequired(),
// hasErrors: this.showErrors()
// };
// },
// hashString: function(string) {
// var hash = 0;
// for (var i = 0; i < string.length; i++) {
// hash = (((hash << 5) - hash) + string.charCodeAt(i)) & 0xFFFFFFFF;
// }
// return hash;
// },
// /**
// * getId
// *
// * The ID is used as an attribute on the form control, and is used to allow
// * associating the label element with the form control.
// *
// * If we don't explicitly pass an `id` prop, we generate one based on the
// * `name` and `label` properties.
// */
// getId: function() {
// if (this.props.id) {
// return this.props.id;
// }
// var label = (typeof this.props.label === 'undefined' ? '' : this.props.label);
// return [
// 'frc',
// this.props.name.split('[').join('_').replace(']', ''),
// this.hashString(JSON.stringify(label))
// ].join('-');
// },
// renderHelp: function() {
// if (!this.props.help) {
// return '';
// }
// return (
// <span className="help-block">{this.props.help}</span>
// );
// },
// renderErrorMessage: function() {
// if (!this.showErrors()) {
// return '';
// }
// var errorMessages = this.getErrorMessages() || [];
// return errorMessages.map((message, key) => {
// return (
// <span key={key} className="help-block validation-message">{message}</span>
// );
// });
// },
// showErrors: function() {
// if (this.isPristine() === true) {
// if (this.getValidatePristine() === false) {
// return false;
// }
// }
// return (this.isValid() === false);
// }
// };

View file

@ -0,0 +1,12 @@
import '../components/controls/Checkbox.jsx';
import '../components/controls/Checkboxgroup.jsx';
import '../components/controls/Datetime.jsx';
import '../components/controls/Default.jsx';
import '../components/controls/Email.jsx';
import '../components/controls/Number.jsx';
import '../components/controls/Radiogroup.jsx';
import '../components/controls/Select.jsx';
import '../components/controls/Textarea.jsx';
import '../components/controls/Url.jsx';
import '../components/FormWrapper.jsx';

View file

@ -1,6 +1,8 @@
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)
@ -12,5 +14,5 @@ if (typeof SimpleSchema !== "undefined") {
]);
}
import FormWrapper from './FormWrapper.jsx';
import FormWrapper from '../components/FormWrapper.jsx';
export default FormWrapper;

View file

@ -0,0 +1,127 @@
// /*jshint node:true */
// 'use strict';
// var React = require('react');
// import PropTypes from 'prop-types';
// var classNames = require('classnames/dedupe');
// var Row = React.createClass({
// propTypes: {
// label: PropTypes.node,
// rowClassName: PropTypes.oneOfType([
// PropTypes.string,
// PropTypes.array,
// PropTypes.object
// ]),
// labelClassName: PropTypes.oneOfType([
// PropTypes.string,
// PropTypes.array,
// PropTypes.object
// ]),
// elementWrapperClassName: PropTypes.oneOfType([
// PropTypes.string,
// PropTypes.array,
// PropTypes.object
// ]),
// required: PropTypes.bool,
// hasErrors: PropTypes.bool,
// fakeLabel: PropTypes.bool,
// layout: PropTypes.oneOf(['horizontal', 'vertical', 'elementOnly']),
// htmlFor: PropTypes.string
// },
// getDefaultProps: function() {
// return {
// label: '',
// rowClassName: '',
// labelClassName: '',
// elementWrapperClassName: '',
// required: false,
// hasErrors: false,
// fakeLabel: false
// };
// },
// renderLabel: function() {
// if (this.props.layout === 'elementOnly') {
// return '';
// }
// var labelClassNames = [];
// labelClassNames.push('control-label');
// if (this.props.layout === 'horizontal') {
// labelClassNames.push('col-sm-3');
// }
// labelClassNames.push(this.props.labelClassName);
// if (this.props.fakeLabel) {
// return (
// <div className={classNames(labelClassNames)}>
// <strong>
// {this.props.label}
// {this.props.required ? ' *' : null}
// </strong>
// </div>
// );
// }
// return (
// <label className={classNames(labelClassNames)} htmlFor={this.props.htmlFor}>
// {this.props.label}
// {this.props.required ? ' *' : null}
// </label>
// );
// },
// render: function() {
// if (this.props.layout === 'elementOnly') {
// return (
// <span>
// {this.props.children}
// </span>
// );
// }
// var cssClasses = {
// row: ['form-group'],
// elementWrapper: []
// };
// if (this.props.hasErrors) {
// cssClasses.row.push('has-error');
// cssClasses.row.push('has-feedback');
// }
// var element = this.props.children;
// if (this.props.layout === 'horizontal') {
// // Horizontal layout needs a 'row' class for Bootstrap 4
// cssClasses.row.push('row');
// cssClasses.elementWrapper.push('col-sm-9');
// cssClasses.elementWrapper.push(this.props.elementWrapperClassName);
// element = (
// <div className={classNames(cssClasses.elementWrapper)}>
// {this.props.children}
// </div>
// );
// }
// cssClasses.row.push(this.props.rowClassName);
// return (
// <div className={classNames(cssClasses.row)}>
// {this.renderLabel()}
// {element}
// </div>
// );
// }
// });
// module.exports = Row;

View file

@ -1,127 +0,0 @@
/*jshint node:true */
'use strict';
var React = require('react');
import PropTypes from 'prop-types';
var classNames = require('classnames/dedupe');
var Row = React.createClass({
propTypes: {
label: PropTypes.node,
rowClassName: PropTypes.oneOfType([
PropTypes.string,
PropTypes.array,
PropTypes.object
]),
labelClassName: PropTypes.oneOfType([
PropTypes.string,
PropTypes.array,
PropTypes.object
]),
elementWrapperClassName: PropTypes.oneOfType([
PropTypes.string,
PropTypes.array,
PropTypes.object
]),
required: PropTypes.bool,
hasErrors: PropTypes.bool,
fakeLabel: PropTypes.bool,
layout: PropTypes.oneOf(['horizontal', 'vertical', 'elementOnly']),
htmlFor: PropTypes.string
},
getDefaultProps: function() {
return {
label: '',
rowClassName: '',
labelClassName: '',
elementWrapperClassName: '',
required: false,
hasErrors: false,
fakeLabel: false
};
},
renderLabel: function() {
if (this.props.layout === 'elementOnly') {
return '';
}
var labelClassNames = [];
labelClassNames.push('control-label');
if (this.props.layout === 'horizontal') {
labelClassNames.push('col-sm-3');
}
labelClassNames.push(this.props.labelClassName);
if (this.props.fakeLabel) {
return (
<div className={classNames(labelClassNames)}>
<strong>
{this.props.label}
{this.props.required ? ' *' : null}
</strong>
</div>
);
}
return (
<label className={classNames(labelClassNames)} htmlFor={this.props.htmlFor}>
{this.props.label}
{this.props.required ? ' *' : null}
</label>
);
},
render: function() {
if (this.props.layout === 'elementOnly') {
return (
<span>
{this.props.children}
</span>
);
}
var cssClasses = {
row: ['form-group'],
elementWrapper: []
};
if (this.props.hasErrors) {
cssClasses.row.push('has-error');
cssClasses.row.push('has-feedback');
}
var element = this.props.children;
if (this.props.layout === 'horizontal') {
// Horizontal layout needs a 'row' class for Bootstrap 4
cssClasses.row.push('row');
cssClasses.elementWrapper.push('col-sm-9');
cssClasses.elementWrapper.push(this.props.elementWrapperClassName);
element = (
<div className={classNames(cssClasses.elementWrapper)}>
{this.props.children}
</div>
);
}
cssClasses.row.push(this.props.rowClassName);
return (
<div className={classNames(cssClasses.row)}>
{this.renderLabel()}
{element}
</div>
);
}
});
module.exports = Row;

View file

@ -0,0 +1 @@
export * from '../modules/index.js';

View file

@ -16,11 +16,12 @@ Package.onUse(function(api) {
]);
api.addFiles([
"lib/style.scss",
"lib/datetime.scss"
"lib/stylesheets/style.scss",
"lib/stylesheets/datetime.scss"
], "client");
api.mainModule("lib/export.js", ["client", "server"]);
api.mainModule("lib/client/main.js", ["client"]);
api.mainModule("lib/server/main.js", ["server"]);
});
@ -31,5 +32,4 @@ Package.onTest(function(api) {
'vulcan:forms'
]);
api.mainModule('test.js');
});