/* Generate the appropriate fragment for the current form, then wrap the main Form component with the necessary HoCs while passing them the fragment. This component is itself wrapped with: - withCurrentUser - withApollo (used to access the Apollo client for form pre-population) And wraps the Form component with: - withNew Or: - withDocument - withEdit - withRemove (When wrapping with withDocument, withEdit, and withRemove, a special Loader component is also added to wait for withDocument's loading prop to be false) */ import React, { PropTypes, Component } from 'react'; import { intlShape } from 'react-intl'; import { withApollo, compose } from 'react-apollo'; import { Components, registerComponent, withCurrentUser, Utils, withNew, withEdit, withRemove } from 'meteor/vulcan:core'; import Form from './Form.jsx'; import gql from 'graphql-tag'; import { withDocument } from 'meteor/vulcan:core'; class FormWrapper extends Component{ // return the current schema based on either the schema or collection prop getSchema() { return this.props.schema ? this.props.schema : Utils.stripTelescopeNamespace(this.props.collection.simpleSchema()._schema); } // if a document is being passed, this is an edit form getFormType() { return this.props.documentId || this.props.slug ? "edit" : "new"; } // get fragment used to decide what data to load from the server to populate the form, // as well as what data to ask for as return value for the mutation getFragments() { const prefix = `${this.props.collection._name}${Utils.capitalize(this.getFormType())}` const fragmentName = `${prefix}FormFragment`; const schema = this.getSchema(); const fields = this.props.fields; const insertableFields = _.filter(_.keys(schema), fieldName => !!schema[fieldName].insertableBy); const editableFields = _.filter(_.keys(schema), fieldName => !!schema[fieldName].editableBy); // get all editable/insertable fields (depending on current form type) let relevantFields = this.getFormType() === 'new' ? insertableFields : editableFields; // if "fields" prop is specified, restrict list of fields to it if (typeof fields !== "undefined" && fields.length > 0) { relevantFields = _.intersection(relevantFields, fields); } // handle fields with resolvers that contain "[" // note: you can override the generated fragment with your own fragment given as a prop! relevantFields = relevantFields.map(fieldName => { const resolveAs = this.getSchema()[fieldName].resolveAs; return resolveAs && resolveAs.includes('[') ? `${fieldName}{_id}` // if it's a custom resolver, add a basic query to its _id : fieldName; // else just ask for the field name }); // generate fragment based on the fields that can be edited. Note: always add _id. const generatedFragment = gql` fragment ${fragmentName} on ${this.props.collection.typeName} { _id ${relevantFields.join('\n')} } ` // get query & mutation fragments from props or else default to same as generatedFragment return { queryFragment: this.props.queryFragment || generatedFragment, mutationFragment: this.props.mutationFragment || generatedFragment, }; } shouldComponentUpdate(nextProps) { if (this.getFormType() === 'edit') { // re-render only if the document selector changes return nextProps.slug !== this.props.slug || nextProps.documentId !== this.props.documentId; } // prevent extra re-renderings for unknown reasons return false; } render() { // console.log(this) let WrappedComponent; const prefix = `${this.props.collection._name}${Utils.capitalize(this.getFormType())}` // props received from parent component (i.e. call) const parentProps = this.props; // props to pass on to child component (i.e.
) const childProps = { formType: this.getFormType(), schema: this.getSchema(), }; // options for withDocument HoC const queryOptions = { queryName: `${prefix}FormQuery`, collection: this.props.collection, fragment: this.getFragments().queryFragment, }; // options for withNew, withEdit, and withRemove HoCs const mutationOptions = { collection: this.props.collection, fragment: this.getFragments().mutationFragment, }; // if this is an edit from, load the necessary data using the withDocument HoC if (this.getFormType() === 'edit') { // create a stateless loader component that's wrapped with withDocument, // displays the loading state if needed, and passes on loading and document const Loader = props => { const { document, loading } = props; return loading ? : ; } Loader.displayName = `withLoader(Form)`; WrappedComponent = compose( withDocument(queryOptions), withEdit(mutationOptions), withRemove(mutationOptions) )(Loader); return } else { WrappedComponent = compose( withNew(mutationOptions) )(Form); return } } } FormWrapper.propTypes = { // main options collection: React.PropTypes.object.isRequired, documentId: React.PropTypes.string, // if a document is passed, this will be an edit form schema: React.PropTypes.object, // usually not needed queryFragment: React.PropTypes.object, mutationFragment: React.PropTypes.object, // 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), showRemove: 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, updateCurrentValues: React.PropTypes.func, throwError: React.PropTypes.func, getDocument: React.PropTypes.func } registerComponent('SmartForm', FormWrapper, withCurrentUser, withApollo); export default withCurrentUser(withApollo(FormWrapper));