2016-11-26 11:33:27 +09:00
/ *
Main form component .
This component expects :
# # # All Forms :
- collection
- currentUser
- client ( Apollo client )
# # # New Form :
- newMutation
# # # Edit Form :
- editMutation
- removeMutation
- document
* /
2018-01-26 17:41:15 -06:00
import { Components , Utils , runCallbacks , getCollection } from 'meteor/vulcan:core' ;
2017-06-01 10:00:16 +09:00
import React , { Component } from 'react' ;
import PropTypes from 'prop-types' ;
2017-10-17 09:35:41 -04:00
import { intlShape } from 'meteor/vulcan:i18n' ;
2016-11-23 17:22:29 +09:00
import Formsy from 'formsy-react' ;
2018-03-08 11:28:29 +09:00
import { getEditableFields , getInsertableFields , isEmptyValue } from '../modules/utils.js' ;
2016-11-23 17:22:29 +09:00
/ *
1. Constructor
2. Helpers
3. Errors
4. Context
4. Method & Callback
5. Render
* /
2016-12-08 23:48:16 +01:00
class Form extends Component {
2016-11-23 17:22:29 +09:00
// --------------------------------------------------------------------- //
// ----------------------------- Constructor --------------------------- //
// --------------------------------------------------------------------- //
constructor ( props ) {
super ( props ) ;
this . submitForm = this . submitForm . bind ( this ) ;
this . updateState = this . updateState . bind ( this ) ;
// this.methodCallback = this.methodCallback.bind(this);
2016-11-25 12:22:13 +09:00
this . newMutationSuccessCallback = this . newMutationSuccessCallback . bind ( this ) ;
this . editMutationSuccessCallback = this . editMutationSuccessCallback . bind ( this ) ;
2016-11-23 17:22:29 +09:00
this . mutationSuccessCallback = this . mutationSuccessCallback . bind ( this ) ;
this . mutationErrorCallback = this . mutationErrorCallback . bind ( this ) ;
this . addToAutofilledValues = this . addToAutofilledValues . bind ( this ) ;
2017-08-05 15:01:13 +09:00
this . getAutofilledValues = this . getAutofilledValues . bind ( this ) ;
2017-05-06 16:08:01 +09:00
this . addToDeletedValues = this . addToDeletedValues . bind ( this ) ;
2017-06-01 11:50:47 +09:00
this . addToSubmitForm = this . addToSubmitForm . bind ( this ) ;
2017-07-06 12:49:28 -07:00
this . addToSuccessForm = this . addToSuccessForm . bind ( this ) ;
this . addToFailureForm = this . addToFailureForm . bind ( this ) ;
2016-11-23 17:22:29 +09:00
this . throwError = this . throwError . bind ( this ) ;
this . clearForm = this . clearForm . bind ( this ) ;
2017-01-23 15:50:55 +01:00
this . updateCurrentValues = this . updateCurrentValues . bind ( this ) ;
2016-11-23 17:22:29 +09:00
this . formKeyDown = this . formKeyDown . bind ( this ) ;
this . deleteDocument = this . deleteDocument . bind ( this ) ;
2017-06-28 08:50:51 +09:00
this . getDocument = this . getDocument . bind ( this ) ;
2016-11-23 17:22:29 +09:00
// a debounced version of seState that only updates state every 500 ms (not used)
this . debouncedSetState = _ . debounce ( this . setState , 500 ) ;
2017-04-20 16:04:24 +09:00
this . setFormState = this . setFormState . bind ( this ) ;
2017-08-16 16:18:40 +09:00
this . getLabel = this . getLabel . bind ( this ) ;
this . getErrorMessage = this . getErrorMessage . bind ( this ) ;
2017-09-08 22:52:54 -07:00
// version of submitForm that is made available to context, behaves like updateCurrentValues but submits afterwards
this . submitFormContext = this . submitFormContext . bind ( this ) ;
2018-01-26 17:41:15 -06:00
this . getCollection = this . getCollection . bind ( this ) ;
2016-11-23 17:22:29 +09:00
this . state = {
disabled : false ,
errors : [ ] ,
2018-01-20 13:31:34 +09:00
autofilledValues : { } ,
2017-05-06 16:08:01 +09:00
deletedValues : [ ] ,
2016-11-23 17:22:29 +09:00
currentValues : { }
} ;
2017-05-30 09:49:38 +09:00
this . submitFormCallbacks = [ ] ;
2017-07-06 12:49:28 -07:00
this . successFormCallbacks = [ ] ;
this . failureFormCallbacks = [ ] ;
2016-11-23 17:22:29 +09:00
}
// --------------------------------------------------------------------- //
// ------------------------------- Helpers ----------------------------- //
// --------------------------------------------------------------------- //
2018-01-26 17:41:15 -06:00
getCollection ( ) {
return this . props . collection || getCollection ( this . props . collectionName ) ;
}
2016-11-23 17:22:29 +09:00
// return the current schema based on either the schema or collection prop
getSchema ( ) {
2018-01-26 17:41:15 -06:00
return this . props . schema ? this . props . schema : Utils . stripTelescopeNamespace ( this . getCollection ( ) . simpleSchema ( ) . _schema ) ;
2016-11-23 17:22:29 +09:00
}
getFieldGroups ( ) {
const schema = this . getSchema ( ) ;
2017-08-16 16:18:40 +09:00
const document = this . getDocument ( ) ;
2016-11-23 17:22:29 +09:00
// 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 ;
2017-07-06 12:49:28 -07:00
2016-11-23 17:22:29 +09:00
// intialize properties
let field = {
name : fieldName ,
datatype : fieldSchema . type ,
control : fieldSchema . control ,
layout : this . props . layout ,
order : fieldSchema . order
}
2017-07-06 12:49:28 -07:00
2017-10-01 11:49:19 +09:00
field . label = this . getLabel ( fieldName ) ;
2016-11-23 17:22:29 +09:00
// add value
2018-02-15 12:05:16 +09:00
if ( typeof document [ fieldName ] !== 'undefined' && document [ fieldName ] !== null ) {
2016-11-23 17:22:29 +09:00
2017-08-16 16:18:40 +09:00
field . value = document [ fieldName ] ;
// convert value type if needed
if ( fieldSchema . type . definitions [ 0 ] . type === Number ) field . value = Number ( field . value ) ;
2017-09-08 22:52:54 -07:00
2017-08-16 16:18:40 +09:00
// if value is an array of objects ({_id: '123'}, {_id: 'abc'}), flatten it into an array of strings (['123', 'abc'])
// fallback to item itself if item._id is not defined (ex: item is not an object or item is just {slug: 'xxx'})
if ( Array . isArray ( field . value ) ) {
field . value = field . value . map ( item => item . _id || item ) ;
}
2017-05-04 11:16:03 +09:00
2016-12-12 09:55:24 +09:00
}
2016-11-23 17:22:29 +09:00
// backward compatibility from 'autoform' to 'form'
if ( fieldSchema . autoform ) {
fieldSchema . form = fieldSchema . autoform ;
2018-01-26 17:41:15 -06:00
console . warn ( ` Vulcan Warning: The 'autoform' field is deprecated. You should rename it to 'form' instead. It was defined on your ' ${ fieldName } ' field on the ' ${ this . getCollection ( ) . _name } ' collection ` ) ; // eslint-disable-line
2016-11-23 17:22:29 +09:00
}
// replace value by prefilled value if value is empty
2017-12-28 11:31:55 +09:00
const prefill = fieldSchema . prefill || fieldSchema . form && fieldSchema . form . prefill ;
if ( prefill ) {
const prefilledValue = typeof prefill === "function" ? prefill . call ( fieldSchema ) : prefill ;
2016-11-23 17:22:29 +09:00
if ( ! ! prefilledValue && ! field . value ) {
field . prefilledValue = prefilledValue ;
field . value = prefilledValue ;
}
}
2016-11-26 02:46:55 +08:00
2016-11-23 17:22:29 +09:00
// add options if they exist
2017-12-28 11:31:55 +09:00
const fieldOptions = fieldSchema . options || fieldSchema . form && fieldSchema . form . options ;
if ( fieldOptions ) {
field . options = typeof fieldOptions === "function" ? fieldOptions . call ( fieldSchema , this . props ) : fieldOptions ;
2018-01-26 17:41:15 -06:00
2018-01-03 22:03:20 +09:00
// in case of checkbox groups, check "checked" option to populate value if this is a "new document" form
2018-03-08 11:28:29 +09:00
const checkedValues = _ . where ( field . options , { checked : true } ) . map ( option => option . value ) ;
if ( checkedValues . length && ! field . value && this . getFormType ( ) === 'new' ) {
field . value = checkedValues
2017-10-19 12:36:18 +09:00
}
2016-11-23 17:22:29 +09:00
}
2018-01-26 17:41:15 -06:00
2018-03-08 11:28:29 +09:00
// 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 ( field . value ) ) {
if ( fieldSchema . defaultValue ) field . value = fieldSchema . defaultValue ;
if ( fieldSchema . default ) field . value = fieldSchema . default ;
}
2017-12-28 11:31:55 +09:00
// 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 ] ;
2017-11-10 15:35:01 -05:00
}
}
2016-11-23 17:22:29 +09:00
}
2017-06-07 17:59:02 -07:00
// add limit
if ( fieldSchema . limit ) {
field . limit = fieldSchema . limit ;
}
2017-07-06 12:49:28 -07:00
2018-02-12 18:48:12 +09:00
// add description as help prop
if ( fieldSchema . description ) {
field . help = fieldSchema . description ;
}
2016-11-23 17:22:29 +09:00
// add placeholder
2017-03-27 10:53:44 +09:00
if ( fieldSchema . placeholder ) {
field . placeholder = fieldSchema . placeholder ;
2016-11-23 17:22:29 +09:00
}
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 ( ) ;
2017-08-16 16:18:40 +09:00
// add error state
const validationError = _ . findWhere ( this . state . errors , { name : 'app.validation_error' } ) ;
if ( validationError ) {
2017-08-16 16:24:50 +09:00
const fieldErrors = _ . filter ( validationError . data . errors , error => error . data . fieldName === fieldName ) ;
2017-08-16 16:18:40 +09:00
if ( fieldErrors ) {
field . errors = fieldErrors . map ( error => ( { ... error , message : this . getErrorMessage ( error ) } ) ) ;
}
}
2016-11-23 17:22:29 +09:00
return field ;
} ) ;
fields = _ . sortBy ( fields , "order" ) ;
2016-12-12 09:55:24 +09:00
// get list of all unique groups (based on their name) used in current fields
let groups = _ . compact ( _ . unique ( _ . pluck ( fields , "group" ) , false , g => g && g . name ) ) ;
2016-11-23 17:22:29 +09:00
// 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 ( ) {
2017-10-01 11:49:19 +09:00
const { fields , hideFields } = this . props ;
const schema = this . getSchema ( ) ;
2016-11-23 17:22:29 +09:00
// get all editable/insertable fields (depending on current form type)
2017-10-01 11:49:19 +09:00
let relevantFields = this . getFormType ( ) === "edit" ? getEditableFields ( schema , this . props . currentUser , this . getDocument ( ) ) : getInsertableFields ( schema , this . props . currentUser ) ;
2016-11-23 17:22:29 +09:00
// if "fields" prop is specified, restrict list of fields to it
if ( typeof fields !== "undefined" && fields . length > 0 ) {
relevantFields = _ . intersection ( relevantFields , fields ) ;
2017-10-01 11:49:19 +09:00
} else {
// else if fields is not specified, remove all hidden fields
relevantFields = _ . reject ( relevantFields , fieldName => schema [ fieldName ] . hidden ) ;
}
// if "hideFields" prop is specified, remove its fields
if ( typeof hideFields !== "undefined" && hideFields . length > 0 ) {
relevantFields = _ . difference ( relevantFields , hideFields ) ;
2016-11-23 17:22:29 +09:00
}
return relevantFields ;
}
// for each field, we apply the following logic:
// - if its value is currently being inputted, use that
2017-04-15 21:40:15 +09:00
// - else if its value is provided by the autofilledValues object, use that
2016-11-23 17:22:29 +09:00
// - else if its value was provided by the db, use that (i.e. props.document)
2018-01-20 13:31:34 +09:00
// - else if its value was provided by prefilledProps, use that
2016-11-23 17:22:29 +09:00
getDocument ( ) {
const currentDocument = _ . clone ( this . props . document ) || { } ;
2018-01-20 13:31:34 +09:00
const document = Object . assign ( _ . clone ( this . props . prefilledProps || { } ) , currentDocument , _ . clone ( this . state . autofilledValues ) , _ . clone ( this . state . currentValues ) ) ;
2016-11-23 17:22:29 +09:00
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 ] ;
}
} ) ;
2017-02-02 15:15:51 +01:00
this . setState ( prevState => ( {
2016-11-23 17:22:29 +09:00
currentValues : e
2017-02-02 15:15:51 +01:00
} ) ) ;
2016-11-23 17:22:29 +09:00
}
}
2017-01-23 15:50:55 +01:00
// manually update the current values of one or more fields(i.e. on blur). See above for on change instead
updateCurrentValues ( newValues ) {
2017-02-02 15:15:51 +01:00
// keep the previous ones and extend (with possible replacement) with new ones
2017-01-23 15:50:55 +01:00
this . setState ( prevState => ( {
2017-02-02 15:15:51 +01:00
currentValues : {
... prevState . currentValues ,
... newValues ,
}
2017-01-23 15:50:55 +01:00
} ) ) ;
2016-11-23 17:22:29 +09:00
}
// key down handler
formKeyDown ( event ) {
if ( ( event . ctrlKey || event . metaKey ) && event . keyCode === 13 ) {
this . submitForm ( this . refs . form . getModel ( ) ) ;
}
}
2017-08-16 16:18:40 +09:00
getLabel ( fieldName ) {
2018-01-26 17:41:15 -06:00
return this . context . intl . formatMessage ( { id : this . getCollection ( ) . _name + "." + fieldName , defaultMessage : this . getSchema ( ) [ fieldName ] . label } ) ;
2017-08-16 16:18:40 +09:00
}
getErrorMessage ( error ) {
2017-08-16 16:24:50 +09:00
if ( error . data . fieldName ) {
// if error has a corresponding field name, "labelify" that field name
const fieldName = this . getLabel ( error . data . fieldName ) ;
return this . context . intl . formatMessage ( { id : error . id , defaultMessage : error . id } , { ... error . data , fieldName } ) ;
} else {
return this . context . intl . formatMessage ( { id : error . id , defaultMessage : error . id } , error . data ) ;
}
2017-08-16 16:18:40 +09:00
}
2016-11-23 17:22:29 +09:00
// --------------------------------------------------------------------- //
// ------------------------------- 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 ( ) {
2017-08-16 16:18:40 +09:00
2017-07-07 10:21:15 +09:00
return (
< div className = "form-errors" >
2017-08-16 16:18:40 +09:00
{ this . state . errors . map ( ( error , index ) => {
let message ;
2017-09-06 18:10:26 +02:00
if ( error . data && error . data . errors ) { // this error is a "multi-error" with multiple sub-errors
2017-08-16 16:18:40 +09:00
message = error . data . errors . map ( error => {
return {
2017-12-20 09:43:42 +09:00
content : this . getErrorMessage ( error ) ,
data : error . data ,
2017-08-16 16:18:40 +09:00
}
} ) ;
2017-09-08 22:52:54 -07:00
2017-08-16 16:18:40 +09:00
} else { // this is a regular error
2017-09-08 22:52:54 -07:00
2017-08-16 16:24:50 +09:00
message = { content : error . message || this . context . intl . formatMessage ( { id : error . id , defaultMessage : error . id } , error . data ) }
2017-08-16 16:18:40 +09:00
}
2018-01-26 17:41:15 -06:00
2017-10-17 09:35:41 -04:00
return < Components.FormFlash key = { index } message = { message } type = "error" / > ;
2017-08-16 16:18:40 +09:00
} ) }
2017-07-07 10:21:15 +09:00
< / div >
)
2016-11-23 17:22:29 +09:00
}
// --------------------------------------------------------------------- //
// ------------------------------- Context ----------------------------- //
// --------------------------------------------------------------------- //
2017-07-06 12:49:28 -07:00
// add error to form state
2017-02-02 15:15:51 +01:00
// from "GraphQL Error: You have an error [error_code]"
// to { content: "You have an error", type: "error" }
2017-07-07 10:21:15 +09:00
throwError ( error ) {
2017-02-02 15:15:51 +01:00
2017-07-07 10:21:15 +09:00
// get graphQL error (see https://github.com/thebigredgeek/apollo-errors/issues/12)
const graphQLError = error . graphQLErrors [ 0 ] ;
2018-01-25 15:03:03 -06:00
// eslint-disable-next-line no-console
console . log ( graphQLError ) ;
2017-07-06 12:49:28 -07:00
2017-07-07 10:21:15 +09:00
// add error to state
2017-02-02 15:15:51 +01:00
this . setState ( prevState => ( {
2017-07-07 10:21:15 +09:00
errors : [ ... prevState . errors , graphQLError ]
2017-02-02 15:15:51 +01:00
} ) ) ;
2016-11-23 17:22:29 +09:00
}
2017-05-06 16:08:01 +09:00
// add something to autofilled values
2016-11-23 17:22:29 +09:00
addToAutofilledValues ( property ) {
2017-02-02 15:15:51 +01:00
this . setState ( prevState => ( {
autofilledValues : {
... prevState . autofilledValues ,
... property
}
} ) ) ;
2016-11-23 17:22:29 +09:00
}
2017-08-05 15:01:13 +09:00
// get autofilled values
getAutofilledValues ( ) {
return this . state . autofilledValues ;
}
2017-05-06 16:08:01 +09:00
// add something to deleted values
addToDeletedValues ( name ) {
this . setState ( prevState => ( {
deletedValues : [ ... prevState . deletedValues , name ]
} ) ) ;
}
2017-05-30 09:49:38 +09:00
// add a callback to the form submission
addToSubmitForm ( callback ) {
this . submitFormCallbacks . push ( callback ) ;
}
2017-07-06 12:49:28 -07:00
// add a callback to form submission success
addToSuccessForm ( callback ) {
this . successFormCallbacks . push ( callback ) ;
}
// add a callback to form submission failure
addToFailureForm ( callback ) {
this . failureFormCallbacks . push ( callback ) ;
}
2017-04-20 16:04:24 +09:00
setFormState ( fn ) {
this . setState ( fn ) ;
}
2017-09-08 22:52:54 -07:00
submitFormContext ( newValues ) {
// keep the previous ones and extend (with possible replacement) with new ones
this . setState ( prevState => ( {
currentValues : {
... prevState . currentValues ,
... newValues ,
} // Submit form after setState update completed
} ) , ( ) => this . submitForm ( this . refs . form . getModel ( ) ) ) ;
}
2016-11-23 17:22:29 +09:00
// pass on context to all child components
getChildContext ( ) {
return {
throwError : this . throwError ,
2017-02-02 15:15:51 +01:00
clearForm : this . clearForm ,
2017-09-08 22:52:54 -07:00
submitForm : this . submitFormContext , //Change in name because we already have a function called submitForm, but no reason for the user to know about that
2017-08-05 15:01:13 +09:00
getAutofilledValues : this . getAutofilledValues ,
2016-11-23 17:22:29 +09:00
addToAutofilledValues : this . addToAutofilledValues ,
2017-05-06 16:08:01 +09:00
addToDeletedValues : this . addToDeletedValues ,
2017-01-23 15:50:55 +01:00
updateCurrentValues : this . updateCurrentValues ,
2016-11-23 17:22:29 +09:00
getDocument : this . getDocument ,
2017-04-20 16:04:24 +09:00
setFormState : this . setFormState ,
2017-06-01 11:50:47 +09:00
addToSubmitForm : this . addToSubmitForm ,
2017-07-06 12:49:28 -07:00
addToSuccessForm : this . addToSuccessForm ,
addToFailureForm : this . addToFailureForm ,
2016-11-23 17:22:29 +09:00
} ;
}
// --------------------------------------------------------------------- //
// ------------------------------- Method ------------------------------ //
// --------------------------------------------------------------------- //
2016-11-25 12:22:13 +09:00
newMutationSuccessCallback ( result ) {
this . mutationSuccessCallback ( result , 'new' ) ;
}
editMutationSuccessCallback ( result ) {
this . mutationSuccessCallback ( result , 'edit' ) ;
}
mutationSuccessCallback ( result , mutationType ) {
2016-11-23 17:22:29 +09:00
const document = result . data [ Object . keys ( result . data ) [ 0 ] ] ; // document is always on first property
2016-11-25 12:22:13 +09:00
// for new mutation, run refetch function if it exists
if ( mutationType === 'new' && this . props . refetch ) this . props . refetch ( ) ;
2017-01-13 18:17:08 +01:00
// call the clear form method (i.e. trigger setState) only if the form has not been unmounted (we are in an async callback, everything can happen!)
if ( typeof this . refs . form !== 'undefined' ) {
let clearCurrentValues = false ;
// reset form if this is a new document form
if ( this . props . formType === "new" ) {
this . refs . form . reset ( ) ;
clearCurrentValues = true ;
}
this . clearForm ( { clearErrors : true , clearCurrentValues } ) ;
}
2017-01-10 17:49:03 +09:00
2017-07-06 12:49:28 -07:00
// run document through mutation success callbacks
result = runCallbacks ( this . successFormCallbacks , result ) ;
2016-11-23 17:22:29 +09:00
// run success callback if it exists
if ( this . props . successCallback ) this . props . successCallback ( document ) ;
}
// catch graphql errors
mutationErrorCallback ( error ) {
2017-02-02 15:15:51 +01:00
this . setState ( prevState => ( { disabled : false } ) ) ;
2016-11-23 17:22:29 +09:00
2018-01-25 15:03:03 -06:00
// eslint-disable-next-line no-console
console . log ( "// graphQL Error" ) ;
// eslint-disable-next-line no-console
console . log ( error ) ;
2017-07-06 12:49:28 -07:00
// run mutation failure callbacks on error, we do not allow the callbacks to change the error
runCallbacks ( this . failureFormCallbacks , error ) ;
2016-11-23 17:22:29 +09:00
if ( ! _ . isEmpty ( error ) ) {
// add error to state
2017-07-07 10:21:15 +09:00
this . throwError ( error ) ;
2016-11-23 17:22:29 +09:00
}
// 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 ) {
2017-06-22 16:41:56 +09:00
// if form is disabled (there is already a submit handler running) don't do anything
if ( this . state . disabled ) {
return ;
}
2017-07-06 12:49:28 -07:00
2017-08-16 16:24:50 +09:00
// clear errors and disable form while it's submitting
this . setState ( prevState => ( { errors : [ ] , disabled : true } ) ) ;
2016-11-23 17:22:29 +09:00
// complete the data with values from custom components which are not being catched by Formsy mixin
2016-12-20 09:27:16 +09:00
// note: it follows the same logic as SmartForm's getDocument method
2016-11-26 02:46:55 +08:00
data = {
2018-01-20 13:31:34 +09:00
... this . props . prefilledProps , // ex: can be values passed from the form's parent component
2017-02-02 15:15:51 +01:00
... this . state . autofilledValues , // ex: can be values from NewsletterSubscribe component
2016-11-23 17:22:29 +09:00
... data , // original data generated thanks to Formsy
... this . state . currentValues , // ex: can be values from DateTime component
} ;
2017-05-30 09:49:38 +09:00
// run data object through submitForm callbacks
data = runCallbacks ( this . submitFormCallbacks , data ) ;
2017-07-06 12:49:28 -07:00
2016-11-23 17:22:29 +09:00
const fields = this . getFieldNames ( ) ;
// if there's a submit callback, run it
if ( this . props . submitCallback ) {
data = this . props . submitCallback ( data ) ;
}
if ( this . props . formType === "new" ) { // new document form
// remove any empty properties
2017-06-20 10:25:34 +09:00
let document = _ . compactObject ( data ) ;
2016-11-23 17:22:29 +09:00
// call method with new document
2016-11-25 12:22:13 +09:00
this . props . newMutation ( { document } ) . then ( this . newMutationSuccessCallback ) . catch ( this . mutationErrorCallback ) ;
2016-11-23 17:22:29 +09:00
} else { // edit document form
const document = this . getDocument ( ) ;
// put all keys with data on $set
2017-06-20 10:25:34 +09:00
const set = _ . compactObject ( data ) ;
2016-11-23 17:22:29 +09:00
// put all keys without data on $unset
2017-05-06 16:08:01 +09:00
const setKeys = _ . keys ( set ) ;
let unsetKeys = _ . difference ( fields , setKeys ) ;
// add all keys to delete (minus those that have data associated)
unsetKeys = _ . unique ( unsetKeys . concat ( _ . difference ( this . state . deletedValues , setKeys ) ) ) ;
2016-11-23 17:22:29 +09:00
2017-05-06 16:08:01 +09:00
// build mutation arguments object
2017-05-07 22:01:52 +09:00
const args = { documentId : document . _id , set : set , unset : { } } ;
2017-05-06 16:08:01 +09:00
if ( unsetKeys . length > 0 ) {
2017-05-07 22:01:52 +09:00
args . unset = _ . object ( unsetKeys , unsetKeys . map ( ( ) => true ) ) ;
2017-05-06 16:08:01 +09:00
}
2016-11-23 17:22:29 +09:00
// call method with _id of document being edited and modifier
2017-05-06 16:08:01 +09:00
this . props . editMutation ( args ) . then ( this . editMutationSuccessCallback ) . catch ( this . mutationErrorCallback ) ;
2016-11-23 17:22:29 +09:00
}
}
deleteDocument ( ) {
const document = this . getDocument ( ) ;
2016-11-25 12:22:13 +09:00
const documentId = this . props . document . _id ;
2016-11-27 08:39:25 +09:00
const documentTitle = document . title || document . name || '' ;
2016-11-23 17:22:29 +09:00
2017-03-18 15:59:31 +09:00
const deleteDocumentConfirm = this . context . intl . formatMessage ( { id : 'forms.delete_confirm' } , { title : documentTitle } ) ;
2016-11-23 17:22:29 +09:00
2016-12-08 23:48:16 +01:00
if ( window . confirm ( deleteDocumentConfirm ) ) {
2016-11-23 17:22:29 +09:00
this . props . removeMutation ( { documentId } )
. then ( ( mutationResult ) => { // the mutation result looks like {data:{collectionRemove: null}} if succeeded
2016-11-24 15:47:51 +09:00
if ( this . props . removeSuccessCallback ) this . props . removeSuccessCallback ( { documentId , documentTitle } ) ;
2016-11-25 12:22:13 +09:00
if ( this . props . refetch ) this . props . refetch ( ) ;
2016-11-23 17:22:29 +09:00
} )
2016-11-24 15:47:51 +09:00
. catch ( ( error ) => {
2018-01-25 15:03:03 -06:00
// eslint-disable-next-line no-console
2016-11-24 15:47:51 +09:00
console . log ( error ) ;
2016-11-23 17:22:29 +09:00
} ) ;
}
}
// --------------------------------------------------------------------- //
// ------------------------- Lifecycle Hooks --------------------------- //
// --------------------------------------------------------------------- //
render ( ) {
const fieldGroups = this . getFieldGroups ( ) ;
2018-01-26 17:41:15 -06:00
const collectionName = this . getCollection ( ) . _name ;
2016-11-23 17:22:29 +09:00
return (
< div className = { "document-" + this . props . formType } >
< Formsy.Form
onSubmit = { this . submitForm }
onKeyDown = { this . formKeyDown }
disabled = { this . state . disabled }
ref = "form"
>
2017-11-09 10:01:22 +09:00
2016-11-23 17:22:29 +09:00
{ this . renderErrors ( ) }
2018-01-26 17:41:15 -06:00
2017-10-17 09:35:41 -04:00
{ fieldGroups . map ( group => < Components.FormGroup key = { group . name } { ...group } updateCurrentValues = { this . updateCurrentValues } / > ) }
2018-01-26 17:41:15 -06:00
2017-11-09 10:01:22 +09:00
{ this . props . repeatErrors && this . renderErrors ( ) }
2017-10-17 09:35:41 -04:00
< Components.FormSubmit submitLabel = { this . props . submitLabel }
cancelLabel = { this . props . cancelLabel }
cancelCallback = { this . props . cancelCallback }
document = { this . getDocument ( ) }
deleteDocument = { ( this . props . formType === 'edit'
&& this . props . showRemove
&& this . deleteDocument )
|| null }
collectionName = { collectionName }
/ >
2017-06-01 11:50:47 +09:00
2016-11-23 17:22:29 +09:00
< / Formsy.Form >
< / div >
)
}
}
2016-12-08 23:48:16 +01:00
Form . propTypes = {
2016-11-23 17:22:29 +09:00
// main options
2017-02-02 15:15:51 +01:00
collection : PropTypes . object ,
2018-01-26 17:41:15 -06:00
collectionName : ( props , propName , componentName ) => {
if ( ! props . collection && ! props . collectionName ) {
return new Error ( ` One of props 'collection' or 'collectionName' was not specified in ' ${ componentName } '. ` ) ;
}
if ( ! props . collection && typeof props [ 'collectionName' ] !== 'string' ) {
return new Error ( ` Prop collectionName was not of type string in ' ${ componentName } ` ) ;
}
} ,
2017-02-02 15:15:51 +01:00
document : PropTypes . object , // if a document is passed, this will be an edit form
schema : PropTypes . object , // usually not needed
2016-11-23 17:22:29 +09:00
// graphQL
2017-02-02 15:15:51 +01:00
newMutation : PropTypes . func , // the new mutation
editMutation : PropTypes . func , // the edit mutation
removeMutation : PropTypes . func , // the remove mutation
2016-11-23 17:22:29 +09:00
// form
2017-02-02 15:15:51 +01:00
prefilledProps : PropTypes . object ,
layout : PropTypes . string ,
fields : PropTypes . arrayOf ( PropTypes . string ) ,
2017-10-01 11:49:19 +09:00
hideFields : PropTypes . arrayOf ( PropTypes . string ) ,
2017-02-02 15:15:51 +01:00
showRemove : PropTypes . bool ,
2017-06-01 11:50:47 +09:00
submitLabel : PropTypes . string ,
cancelLabel : PropTypes . string ,
2017-11-09 10:01:22 +09:00
repeatErrors : PropTypes . bool ,
2016-11-23 17:22:29 +09:00
// callbacks
2017-02-02 15:15:51 +01:00
submitCallback : PropTypes . func ,
successCallback : PropTypes . func ,
removeSuccessCallback : PropTypes . func ,
errorCallback : PropTypes . func ,
cancelCallback : PropTypes . func ,
currentUser : PropTypes . object ,
client : PropTypes . object ,
2016-11-23 17:22:29 +09:00
}
2016-12-08 23:48:16 +01:00
Form . defaultProps = {
2017-11-09 10:01:22 +09:00
layout : 'horizontal' ,
repeatErrors : false ,
2016-11-23 17:22:29 +09:00
}
2016-12-08 23:48:16 +01:00
Form . contextTypes = {
2016-11-23 17:22:29 +09:00
intl : intlShape
}
2016-12-08 23:48:16 +01:00
Form . childContextTypes = {
2017-08-05 15:01:13 +09:00
getAutofilledValues : PropTypes . func ,
2017-02-02 15:15:51 +01:00
addToAutofilledValues : PropTypes . func ,
2017-05-06 16:08:01 +09:00
addToDeletedValues : PropTypes . func ,
2017-05-30 09:49:38 +09:00
addToSubmitForm : PropTypes . func ,
2017-07-06 12:49:28 -07:00
addToFailureForm : PropTypes . func ,
addToSuccessForm : PropTypes . func ,
2017-02-02 15:15:51 +01:00
updateCurrentValues : PropTypes . func ,
2017-04-20 16:04:24 +09:00
setFormState : PropTypes . func ,
2017-02-02 15:15:51 +01:00
throwError : PropTypes . func ,
clearForm : PropTypes . func ,
2017-09-08 22:52:54 -07:00
getDocument : PropTypes . func ,
submitForm : PropTypes . func ,
2016-11-23 17:22:29 +09:00
}
2016-12-08 23:48:16 +01:00
module . exports = Form