mirror of
https://github.com/vale981/Vulcan
synced 2025-03-08 19:11:38 -05:00
522 lines
19 KiB
JavaScript
522 lines
19 KiB
JavaScript
// import React, { PropTypes, Component } from 'react';
|
|
// import { FormattedMessage, intlShape } from 'react-intl';
|
|
// import Formsy from 'formsy-react';
|
|
// import { Button } from 'react-bootstrap';
|
|
// import { withApollo, compose } from 'react-apollo';
|
|
// import { withCurrentUser, withNew, withEdit, withRemove } from 'meteor/nova:core';
|
|
// import Flash from "./Flash.jsx";
|
|
// import FormGroup from "./FormGroup.jsx";
|
|
// import { flatten, deepValue, getEditableFields, getInsertableFields } from './utils.js';
|
|
|
|
|
|
// /*
|
|
|
|
// 1. Constructor
|
|
// 2. Helpers
|
|
// 3. Errors
|
|
// 4. Context
|
|
// 4. Method & Callback
|
|
// 5. Render
|
|
|
|
// */
|
|
|
|
// class FormWrapper extends Component{
|
|
|
|
// // --------------------------------------------------------------------- //
|
|
// // ----------------------------- Constructor --------------------------- //
|
|
// // --------------------------------------------------------------------- //
|
|
|
|
// constructor(props) {
|
|
// super(props);
|
|
// this.submitForm = this.submitForm.bind(this);
|
|
// this.updateState = this.updateState.bind(this);
|
|
// // this.methodCallback = this.methodCallback.bind(this);
|
|
// this.mutationSuccessCallback = this.mutationSuccessCallback.bind(this);
|
|
// this.mutationErrorCallback = this.mutationErrorCallback.bind(this);
|
|
// this.addToAutofilledValues = this.addToAutofilledValues.bind(this);
|
|
// this.throwError = this.throwError.bind(this);
|
|
// this.clearForm = this.clearForm.bind(this);
|
|
// this.updateCurrentValue = this.updateCurrentValue.bind(this);
|
|
// this.formKeyDown = this.formKeyDown.bind(this);
|
|
// this.deleteDocument = this.deleteDocument.bind(this);
|
|
// // a debounced version of seState that only updates state every 500 ms (not used)
|
|
// this.debouncedSetState = _.debounce(this.setState, 500);
|
|
|
|
// this.state = {
|
|
// disabled: false,
|
|
// errors: [],
|
|
// autofilledValues: {},
|
|
// currentValues: {}
|
|
// };
|
|
// }
|
|
|
|
// // --------------------------------------------------------------------- //
|
|
// // ------------------------------- Helpers ----------------------------- //
|
|
// // --------------------------------------------------------------------- //
|
|
|
|
// // return the current schema based on either the schema or collection prop
|
|
// getSchema() {
|
|
// return this.props.schema ? this.props.schema : Telescope.utils.stripTelescopeNamespace(this.props.collection.simpleSchema()._schema);
|
|
// }
|
|
|
|
// getFieldGroups() {
|
|
|
|
// const schema = this.getSchema();
|
|
|
|
// // build fields array by iterating over the list of field names
|
|
// let fields = this.getFieldNames().map(fieldName => {
|
|
|
|
// // get schema for the current field
|
|
// const fieldSchema = schema[fieldName];
|
|
|
|
// fieldSchema.name = fieldName;
|
|
|
|
// // intialize properties
|
|
// let field = {
|
|
// name: fieldName,
|
|
// datatype: fieldSchema.type,
|
|
// control: fieldSchema.control,
|
|
// hidden: fieldSchema.hidden,
|
|
// layout: this.props.layout,
|
|
// order: fieldSchema.order
|
|
// }
|
|
|
|
// // add label or internationalized field name if necessary (field not hidden)
|
|
// if (!field.hidden) {
|
|
// field.label = fieldSchema.label ? fieldSchema.label : this.context.intl.formatMessage({id: this.props.collection._name+"."+fieldName});
|
|
// }
|
|
|
|
// // add value
|
|
// field.value = this.getDocument() && deepValue(this.getDocument(), fieldName) ? deepValue(this.getDocument(), fieldName) : "";
|
|
|
|
// // backward compatibility from 'autoform' to 'form'
|
|
// if (fieldSchema.autoform) {
|
|
// fieldSchema.form = fieldSchema.autoform;
|
|
// console.warn(`🔭 Telescope Nova Warning: The 'autoform' field is deprecated. You should rename it to 'form' instead. It was defined on your '${fieldName}' field on the '${this.props.collection._name}' collection`);
|
|
// }
|
|
|
|
// // replace value by prefilled value if value is empty
|
|
// if (fieldSchema.form && fieldSchema.form.prefill) {
|
|
// const prefilledValue = typeof fieldSchema.form.prefill === "function" ? fieldSchema.form.prefill.call(fieldSchema) : fieldSchema.form.prefill;
|
|
// if (!!prefilledValue && !field.value) {
|
|
// field.prefilledValue = prefilledValue;
|
|
// field.value = prefilledValue;
|
|
// }
|
|
// }
|
|
|
|
// // replace empty value, which has not been prefilled, by the default value from the schema
|
|
// if (fieldSchema.defaultValue && field.value === "") {
|
|
// field.value = fieldSchema.defaultValue;
|
|
// }
|
|
|
|
// // add options if they exist
|
|
// if (fieldSchema.form && fieldSchema.form.options) {
|
|
// field.options = typeof fieldSchema.form.options === "function" ? fieldSchema.form.options.call(fieldSchema, this.props) : fieldSchema.form.options;
|
|
// }
|
|
|
|
// if (fieldSchema.form && fieldSchema.form.disabled) {
|
|
// field.disabled = typeof fieldSchema.form.disabled === "function" ? fieldSchema.form.disabled.call(fieldSchema) : fieldSchema.form.disabled;
|
|
// }
|
|
|
|
// if (fieldSchema.form && fieldSchema.form.help) {
|
|
// field.help = typeof fieldSchema.form.help === "function" ? fieldSchema.form.help.call(fieldSchema) : fieldSchema.form.help;
|
|
// }
|
|
|
|
// // add placeholder
|
|
// if (fieldSchema.form && fieldSchema.form.placeholder) {
|
|
// field.placeholder = fieldSchema.form.placeholder;
|
|
// }
|
|
|
|
// if (fieldSchema.beforeComponent) field.beforeComponent = fieldSchema.beforeComponent;
|
|
// if (fieldSchema.afterComponent) field.afterComponent = fieldSchema.afterComponent;
|
|
|
|
// // add group
|
|
// if (fieldSchema.group) {
|
|
// field.group = fieldSchema.group;
|
|
// }
|
|
|
|
// // add document
|
|
// field.document = this.getDocument();
|
|
|
|
// return field;
|
|
|
|
// });
|
|
|
|
// // remove fields where hidden is set to true
|
|
// fields = _.reject(fields, field => field.hidden);
|
|
// fields = _.sortBy(fields, "order");
|
|
|
|
// // console.log(fields)
|
|
|
|
// // get list of all groups used in current fields
|
|
// let groups = _.compact(_.unique(_.pluck(fields, "group")));
|
|
|
|
// // for each group, add relevant fields
|
|
// groups = groups.map(group => {
|
|
// group.label = group.label || this.context.intl.formatMessage({id: group.name});
|
|
// group.fields = _.filter(fields, field => {return field.group && field.group.name === group.name});
|
|
// return group;
|
|
// });
|
|
|
|
// // add default group
|
|
// groups = [{
|
|
// name: "default",
|
|
// label: "default",
|
|
// order: 0,
|
|
// fields: _.filter(fields, field => {return !field.group;})
|
|
// }].concat(groups);
|
|
|
|
// // sort by order
|
|
// groups = _.sortBy(groups, "order");
|
|
|
|
// // console.log(groups);
|
|
|
|
// return groups;
|
|
// }
|
|
|
|
// // if a document is being passed, this is an edit form
|
|
// getFormType() {
|
|
// return this.props.document ? "edit" : "new";
|
|
// }
|
|
|
|
// // get relevant fields
|
|
// getFieldNames() {
|
|
// const fields = this.props.fields;
|
|
|
|
// // get all editable/insertable fields (depending on current form type)
|
|
// let relevantFields = this.getFormType() === "edit" ? getEditableFields(this.getSchema(), this.props.currentUser, this.getDocument()) : getInsertableFields(this.getSchema(), this.props.currentUser);
|
|
|
|
// // if "fields" prop is specified, restrict list of fields to it
|
|
// if (typeof fields !== "undefined" && fields.length > 0) {
|
|
// relevantFields = _.intersection(relevantFields, fields);
|
|
// }
|
|
|
|
// return relevantFields;
|
|
// }
|
|
|
|
// // for each field, we apply the following logic:
|
|
// // - if its value is currently being inputted, use that
|
|
// // - else if its value was provided by the db, use that (i.e. props.document)
|
|
// // - else if its value is provded by the autofilledValues object, use that
|
|
// getDocument() {
|
|
// const currentDocument = _.clone(this.props.document) || {};
|
|
// const document = Object.assign(_.clone(this.state.autofilledValues), currentDocument, _.clone(this.state.currentValues));
|
|
// return document;
|
|
// }
|
|
|
|
// // NOTE: this is not called anymore since we're updating on blur, not on change
|
|
// // whenever the form changes, update its state
|
|
// updateState(e) {
|
|
// // e can sometimes be event, sometims be currentValue
|
|
// // see https://github.com/christianalfoni/formsy-react/issues/203
|
|
// if (e.stopPropagation) {
|
|
// e.stopPropagation();
|
|
// } else {
|
|
// // get rid of empty fields
|
|
// _.forEach(e, (value, key) => {
|
|
// if (_.isEmpty(value)) {
|
|
// delete e[key];
|
|
// }
|
|
// });
|
|
// this.setState({
|
|
// currentValues: e
|
|
// });
|
|
// }
|
|
// }
|
|
|
|
// // manually update current value (i.e. on blur). See above for on change instead
|
|
// updateCurrentValue(fieldName, fieldValue) {
|
|
// const currentValues = this.state.currentValues;
|
|
// currentValues[fieldName] = fieldValue;
|
|
// this.setState({currentValues: currentValues});
|
|
// }
|
|
|
|
// // key down handler
|
|
// formKeyDown(event) {
|
|
|
|
// if( (event.ctrlKey || event.metaKey) && event.keyCode === 13) {
|
|
// this.submitForm(this.refs.form.getModel());
|
|
// }
|
|
// }
|
|
|
|
// // --------------------------------------------------------------------- //
|
|
// // ------------------------------- Errors ------------------------------ //
|
|
// // --------------------------------------------------------------------- //
|
|
|
|
// // clear and re-enable the form
|
|
// // by default, clear errors and keep current values
|
|
// clearForm({ clearErrors = true, clearCurrentValues = false}) {
|
|
// this.setState(prevState => ({
|
|
// errors: clearErrors ? [] : prevState.errors,
|
|
// currentValues: clearCurrentValues ? {} : prevState.currentValues,
|
|
// disabled: false,
|
|
// }));
|
|
// }
|
|
|
|
// // render errors
|
|
// renderErrors() {
|
|
// return <div className="form-errors">{this.state.errors.map(message => <Flash key={message} message={message}/>)}</div>
|
|
// }
|
|
|
|
// // --------------------------------------------------------------------- //
|
|
// // ------------------------------- Context ----------------------------- //
|
|
// // --------------------------------------------------------------------- //
|
|
|
|
// // add error to state
|
|
// throwError(error) {
|
|
// this.setState({
|
|
// errors: [error]
|
|
// });
|
|
// }
|
|
|
|
// // add something to prefilled values
|
|
// addToAutofilledValues(property) {
|
|
// this.setState(function(state){
|
|
// return {
|
|
// autofilledValues: {
|
|
// ...state.autofilledValues,
|
|
// ...property
|
|
// }
|
|
// };
|
|
// });
|
|
// }
|
|
|
|
// // clear value
|
|
// clearValue(property) {
|
|
|
|
// }
|
|
|
|
// // pass on context to all child components
|
|
// getChildContext() {
|
|
// return {
|
|
// throwError: this.throwError,
|
|
// autofilledValues: this.state.autofilledValues,
|
|
// addToAutofilledValues: this.addToAutofilledValues,
|
|
// updateCurrentValue: this.updateCurrentValue,
|
|
// getDocument: this.getDocument,
|
|
// };
|
|
// }
|
|
|
|
// // --------------------------------------------------------------------- //
|
|
// // ------------------------------- Method ------------------------------ //
|
|
// // --------------------------------------------------------------------- //
|
|
|
|
// mutationSuccessCallback(result) {
|
|
|
|
// const document = result.data[Object.keys(result.data)[0]]; // document is always on first property
|
|
|
|
// // run success callback if it exists
|
|
// if (this.props.successCallback) this.props.successCallback(document);
|
|
|
|
// // run close callback if it exists in context (i.e. we're inside a modal)
|
|
// if (this.context.closeCallback) {
|
|
// this.context.closeCallback();
|
|
|
|
// // else there is no close callback (i.e. we're not inside a modal), call the clear form method
|
|
// // note: we don't want to update the state of an unmounted component
|
|
// } else {
|
|
// let clearCurrentValues = false;
|
|
|
|
// // reset form if this is a new document form
|
|
// if (this.getFormType() === "new") {
|
|
// this.refs.form.reset();
|
|
// clearCurrentValues = true;
|
|
// }
|
|
|
|
// this.clearForm({clearErrors: true, clearCurrentValues});
|
|
// }
|
|
// }
|
|
|
|
// // catch graphql errors
|
|
// mutationErrorCallback(error) {
|
|
|
|
// this.setState({disabled: false});
|
|
|
|
// console.log("// graphQL Error");
|
|
// console.log(error);
|
|
|
|
// if (!_.isEmpty(error)) {
|
|
// // add error to state
|
|
// this.throwError({
|
|
// content: error.message,
|
|
// type: "error"
|
|
// });
|
|
// }
|
|
|
|
// // note: we don't have access to the document here :( maybe use redux-forms and get it from the store?
|
|
// // run error callback if it exists
|
|
// // if (this.props.errorCallback) this.props.errorCallback(document, error);
|
|
// }
|
|
|
|
// // submit form handler
|
|
// submitForm(data) {
|
|
// this.setState({disabled: true});
|
|
|
|
// // complete the data with values from custom components which are not being catched by Formsy mixin
|
|
// // note: it follows the same logic as NovaForm's getDocument method
|
|
// data = {
|
|
// ...this.state.autofilledValues, // ex: can be values from EmbedlyURL or NewsletterSubscribe component
|
|
// ...data, // original data generated thanks to Formsy
|
|
// ...this.state.currentValues, // ex: can be values from DateTime component
|
|
// };
|
|
|
|
// const fields = this.getFieldNames();
|
|
|
|
// // if there's a submit callback, run it
|
|
// if (this.props.submitCallback) {
|
|
// data = this.props.submitCallback(data);
|
|
// }
|
|
|
|
// if (this.getFormType() === "new") { // new document form
|
|
|
|
// // remove any empty properties
|
|
// let document = _.compactObject(flatten(data));
|
|
|
|
// // add prefilled properties
|
|
// if (this.props.prefilledProps) {
|
|
// document = Object.assign(document, this.props.prefilledProps);
|
|
// }
|
|
|
|
// // call method with new document
|
|
// this.props.newMutation({document}).then(this.mutationSuccessCallback).catch(this.mutationErrorCallback);
|
|
|
|
// } else { // edit document form
|
|
|
|
// const document = this.getDocument();
|
|
|
|
// // put all keys with data on $set
|
|
// const set = _.compactObject(flatten(data));
|
|
|
|
// // put all keys without data on $unset
|
|
// const unsetKeys = _.difference(fields, _.keys(set));
|
|
// const unset = _.object(unsetKeys, unsetKeys.map(()=>true));
|
|
|
|
// // build modifier
|
|
// const modifier = {$set: set};
|
|
// if (!_.isEmpty(unset)) modifier.$unset = unset;
|
|
// // call method with _id of document being edited and modifier
|
|
// // Meteor.call(this.props.methodName, document._id, modifier, this.methodCallback);
|
|
// this.props.editMutation({documentId: document._id, set: set, unset: unset}).then(this.mutationSuccessCallback).catch(this.mutationErrorCallback);
|
|
// }
|
|
|
|
// }
|
|
|
|
// deleteDocument() {
|
|
// const document = this.getDocument();
|
|
// const documentId = document._id;
|
|
// const documentTitle = document.title || document.name || "this document";
|
|
|
|
// const deleteDocumentConfirm = this.context.intl.formatMessage({id: `${this.props.collection._name}.delete_confirm`}, {title: documentTitle});
|
|
|
|
// if (window.confirm(deleteDocumentConfirm)) {
|
|
// this.props.removeMutation({documentId})
|
|
// .then((mutationResult) => { // the mutation result looks like {data:{collectionRemove: null}} if succeeded
|
|
// this.props.removeSuccessCallback({documentId, documentTitle});
|
|
// })
|
|
// .catch(() => {
|
|
// console.log('Something went bad when removing the document', documentId);
|
|
// });
|
|
// }
|
|
// }
|
|
|
|
// // --------------------------------------------------------------------- //
|
|
// // ------------------------- Lifecycle Hooks --------------------------- //
|
|
// // --------------------------------------------------------------------- //
|
|
|
|
// render() {
|
|
|
|
// const fieldGroups = this.getFieldGroups();
|
|
// const collectionName = this.props.collection._name;
|
|
|
|
// return (
|
|
// <div className={"document-"+this.getFormType()}>
|
|
// <Formsy.Form
|
|
// onSubmit={this.submitForm}
|
|
// onKeyDown={this.formKeyDown}
|
|
// disabled={this.state.disabled}
|
|
// ref="form"
|
|
// >
|
|
// {this.renderErrors()}
|
|
// {fieldGroups.map(group => <FormGroup key={group.name} {...group} updateCurrentValue={this.updateCurrentValue} />)}
|
|
// <Button type="submit" bsStyle="primary"><FormattedMessage id="forms.submit"/></Button>
|
|
// {this.props.cancelCallback ? <a className="form-cancel" onClick={this.props.cancelCallback}><FormattedMessage id="forms.cancel"/></a> : null}
|
|
// </Formsy.Form>
|
|
|
|
// {
|
|
// this.props.noRemoveMutation
|
|
// ? null
|
|
// : <div>
|
|
// <hr/>
|
|
// <a onClick={this.deleteDocument} className={`${collectionName}-delete-link`}>
|
|
// <Telescope.components.Icon name="close"/> <FormattedMessage id={`${collectionName}.delete`}/>
|
|
// </a>
|
|
// </div>
|
|
// }
|
|
// </div>
|
|
// )
|
|
// }
|
|
|
|
// componentWillUnmount() {
|
|
// // note: patch to cancel closeCallback given by parent
|
|
// // we clean the event by hand
|
|
// // example : the closeCallback is a function that closes a modal by calling setState, this modal being the parent of this NovaForm component
|
|
// // if this componentWillUnmount hook is triggered, that means that the modal doesn't exist anymore!
|
|
// // let's not call setState on an unmounted component (avoid no-op / memory leak)
|
|
// this.context.closeCallback = f => f;
|
|
// }
|
|
|
|
// }
|
|
|
|
// FormWrapper.propTypes = {
|
|
|
|
// // main options
|
|
// collection: React.PropTypes.object,
|
|
// document: React.PropTypes.object, // if a document is passed, this will be an edit form
|
|
// schema: React.PropTypes.object, // usually not needed
|
|
|
|
// // graphQL
|
|
// newMutation: React.PropTypes.func, // the new mutation
|
|
// editMutation: React.PropTypes.func, // the edit mutation
|
|
// removeMutation: React.PropTypes.func, // the remove mutation
|
|
|
|
// // form
|
|
// prefilledProps: React.PropTypes.object,
|
|
// layout: React.PropTypes.string,
|
|
// fields: React.PropTypes.arrayOf(React.PropTypes.string),
|
|
// noRemoveMutation: React.PropTypes.bool,
|
|
|
|
// // callbacks
|
|
// submitCallback: React.PropTypes.func,
|
|
// successCallback: React.PropTypes.func,
|
|
// removeSuccessCallback: React.PropTypes.func,
|
|
// errorCallback: React.PropTypes.func,
|
|
// cancelCallback: React.PropTypes.func,
|
|
|
|
// currentUser: React.PropTypes.object,
|
|
// client: React.PropTypes.object,
|
|
// }
|
|
|
|
// FormWrapper.defaultProps = {
|
|
// layout: "horizontal",
|
|
// }
|
|
|
|
// FormWrapper.contextTypes = {
|
|
// closeCallback: React.PropTypes.func,
|
|
// intl: intlShape
|
|
// }
|
|
|
|
// FormWrapper.childContextTypes = {
|
|
// autofilledValues: React.PropTypes.object,
|
|
// addToAutofilledValues: React.PropTypes.func,
|
|
// updateCurrentValue: React.PropTypes.func,
|
|
// throwError: React.PropTypes.func,
|
|
// getDocument: React.PropTypes.func
|
|
// }
|
|
|
|
// module.exports = compose(
|
|
// withCurrentUser,
|
|
// withNew,
|
|
// withEdit,
|
|
// withRemove,
|
|
// withApollo
|
|
// )(FormWrapper);
|