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-05-08 20:09:42 -04:00
import {
2018-05-11 09:52:04 +09:00
registerComponent ,
Components ,
runCallbacks ,
getErrors ,
getSetting ,
Utils ,
2018-11-14 11:38:41 +01:00
isIntlField ,
mergeWithComponents
2018-05-08 20:09:42 -04:00
} from 'meteor/vulcan:core' ;
2017-06-01 10:00:16 +09:00
import React , { Component } from 'react' ;
2018-10-01 10:47:06 +03:00
import SimpleSchema from 'simpl-schema' ;
2017-06-01 10:00:16 +09:00
import PropTypes from 'prop-types' ;
2017-10-17 09:35:41 -04:00
import { intlShape } from 'meteor/vulcan:i18n' ;
2018-03-24 11:16:11 +09:00
import cloneDeep from 'lodash/cloneDeep' ;
2018-08-07 10:13:06 +09:00
import get from 'lodash/get' ;
2018-03-24 11:16:11 +09:00
import set from 'lodash/set' ;
import unset from 'lodash/unset' ;
import compact from 'lodash/compact' ;
import update from 'lodash/update' ;
2018-03-26 14:27:45 +09:00
import merge from 'lodash/merge' ;
2018-05-08 20:09:42 -04:00
import find from 'lodash/find' ;
2018-05-23 16:02:36 -04:00
import pick from 'lodash/pick' ;
2018-08-09 11:41:06 +09:00
import isEqual from 'lodash/isEqual' ;
2018-05-08 20:09:42 -04:00
import isEqualWith from 'lodash/isEqualWith' ;
2018-07-10 10:02:08 +02:00
import uniq from 'lodash/uniq' ;
2018-08-05 10:30:56 +09:00
import uniqBy from 'lodash/uniqBy' ;
2018-08-06 10:29:06 +09:00
import isObject from 'lodash/isObject' ;
2018-09-05 10:44:56 +09:00
import mapValues from 'lodash/mapValues' ;
import pickBy from 'lodash/pickBy' ;
2018-05-08 20:09:42 -04:00
2018-03-26 14:27:45 +09:00
import { convertSchema , formProperties } from '../modules/schema_utils' ;
2018-08-07 16:42:45 +09:00
import { isEmptyValue } from '../modules/utils' ;
2018-06-29 13:01:56 +02:00
import { getParentPath } from '../modules/path_utils' ;
2018-10-29 22:08:41 +01:00
import {
getEditableFields ,
getInsertableFields
2018-10-29 22:34:29 +01:00
} from '../modules/schema_utils.js' ;
2018-10-29 22:08:41 +01:00
import withCollectionProps from './withCollectionProps' ;
2018-10-29 22:34:29 +01:00
import { callbackProps } from './propTypes' ;
2018-03-24 11:16:11 +09:00
2018-11-26 14:59:57 +01:00
// props that should trigger a form reset
const RESET _PROPS = [
'collection' , 'collectionName' , 'typeName' , 'document' , 'schema' , 'currentUser' ,
2018-11-26 15:06:57 +01:00
'fields' , 'removeFields' ,
'prefilledProps' // TODO: prefilledProps should be merged instead?
2018-12-31 15:22:17 +09:00
] ;
2018-11-26 14:59:57 +01:00
2018-08-07 10:13:06 +09:00
const compactParent = ( object , path ) => {
const parentPath = getParentPath ( path ) ;
2018-05-23 17:09:32 +09:00
// note: we only want to compact arrays, not objects
2018-10-24 15:25:21 +02:00
const compactIfArray = x => ( Array . isArray ( x ) ? compact ( x ) : x ) ;
2018-05-23 17:09:32 +09:00
update ( object , parentPath , compactIfArray ) ;
2018-03-24 11:33:28 +09:00
} ;
2016-11-23 17:22:29 +09:00
2018-09-05 10:44:56 +09:00
const getDefaultValues = convertedSchema => {
// TODO: make this work with nested schemas, too
2018-10-24 15:25:21 +02:00
return pickBy (
mapValues ( convertedSchema , field => field . defaultValue ) ,
value => value
) ;
} ;
2018-09-05 10:44:56 +09:00
2018-10-24 15:25:21 +02:00
const getInitialStateFromProps = nextProps => {
2018-10-29 22:34:29 +01:00
const collection = nextProps . collection ;
2018-10-24 15:25:21 +02:00
const schema = nextProps . schema
? new SimpleSchema ( nextProps . schema )
: collection . simpleSchema ( ) ;
2018-09-05 10:44:56 +09:00
const convertedSchema = convertSchema ( schema ) ;
const formType = nextProps . document ? 'edit' : 'new' ;
// for new document forms, add default values to initial document
2018-10-24 15:25:21 +02:00
const defaultValues =
formType === 'new' ? getDefaultValues ( convertedSchema ) : { } ;
const initialDocument = merge (
{ } ,
defaultValues ,
nextProps . prefilledProps ,
nextProps . document
) ;
2018-12-07 04:45:17 -06:00
//if minCount is specified, go ahead and create empty nested documents
Object . keys ( convertedSchema ) . forEach ( key => {
let minCount = convertedSchema [ key ] . minCount ;
if ( minCount ) {
2018-12-07 05:45:47 -06:00
initialDocument [ key ] = initialDocument [ key ] || [ ] ;
while ( initialDocument [ key ] . length < minCount )
2018-12-07 04:45:17 -06:00
initialDocument [ key ] . push ( { } ) ;
}
2018-12-31 15:22:17 +09:00
} ) ;
2018-12-07 04:45:17 -06:00
2018-08-17 19:02:44 +09:00
// remove all instances of the `__typename` property from document
Utils . removeProperty ( initialDocument , '__typename' ) ;
2018-04-20 14:35:53 -05:00
return {
2018-08-09 11:41:06 +09:00
disabled : false ,
errors : [ ] ,
deletedValues : [ ] ,
currentValues : { } ,
2018-04-20 14:35:53 -05:00
// convert SimpleSchema schema into JSON object
2018-09-05 10:44:56 +09:00
schema : convertedSchema ,
2018-04-20 14:35:53 -05:00
// Also store all field schemas (including nested schemas) in a flat structure
flatSchema : convertSchema ( schema , true ) ,
// the initial document passed as props
2018-08-07 16:42:45 +09:00
initialDocument ,
// initialize the current document to be the same as the initial document
2018-10-24 15:25:21 +02:00
currentDocument : initialDocument
2018-04-20 14:35:53 -05:00
} ;
} ;
2016-11-23 17:22:29 +09:00
/ *
1. Constructor
2. Helpers
3. Errors
4. Context
4. Method & Callback
5. Render
* /
2018-06-20 10:23:54 +09:00
class SmartForm extends Component {
2018-05-11 09:52:04 +09:00
constructor ( props ) {
2018-03-25 12:13:30 +09:00
super ( props ) ;
this . state = {
2018-10-24 15:25:21 +02:00
... getInitialStateFromProps ( props )
2018-03-25 12:13:30 +09:00
} ;
}
2018-12-07 05:45:47 -06:00
defaultValues = { } ;
2018-04-10 17:23:23 +09:00
2018-03-24 11:21:39 +09:00
submitFormCallbacks = [ ] ;
successFormCallbacks = [ ] ;
failureFormCallbacks = [ ] ;
2016-11-23 17:22:29 +09:00
// --------------------------------------------------------------------- //
// ------------------------------- Helpers ----------------------------- //
// --------------------------------------------------------------------- //
2018-03-24 11:33:28 +09:00
/ *
If a document is being passed , this is an edit form
* /
getFormType = ( ) => {
return this . props . document ? 'edit' : 'new' ;
} ;
/ *
2018-07-10 10:02:08 +02:00
Get a list of all insertable fields
* /
2018-10-24 15:25:21 +02:00
getInsertableFields = schema => {
return getInsertableFields (
schema || this . state . schema ,
this . props . currentUser
) ;
} ;
2018-07-10 10:02:08 +02:00
/ *
Get a list of all editable fields
* /
2018-10-24 15:25:21 +02:00
getEditableFields = schema => {
return getEditableFields (
schema || this . state . schema ,
this . props . currentUser ,
this . state . initialDocument
) ;
} ;
2018-07-10 10:02:08 +02:00
/ *
Get a list of all mutable ( insertable / editable depending on current form type ) fields
* /
2018-10-24 15:25:21 +02:00
getMutableFields = schema => {
return this . getFormType ( ) === 'edit'
? this . getEditableFields ( schema )
: this . getInsertableFields ( schema ) ;
} ;
2018-07-10 10:02:08 +02:00
/ *
2018-04-10 17:23:23 +09:00
Get the current document
2018-03-24 11:33:28 +09:00
* /
getDocument = ( ) => {
2018-08-07 16:42:45 +09:00
return this . state . currentDocument ;
2018-03-24 11:33:28 +09:00
} ;
/ *
2018-04-20 14:35:53 -05:00
Like getDocument , but cross - reference with getFieldNames ( )
2018-03-24 11:33:28 +09:00
to only return fields that actually need to be submitted
2018-04-20 14:35:53 -05:00
Also remove any deleted values .
2018-07-10 10:02:08 +02:00
2018-03-24 11:33:28 +09:00
* /
2018-10-24 15:25:21 +02:00
getData = customArgs => {
const args = {
excludeHiddenFields : false ,
replaceIntlFields : true ,
addExtraFields : false ,
... customArgs
} ;
2018-07-24 18:29:27 +02:00
2018-03-24 11:33:28 +09:00
// only keep relevant fields
2018-05-21 09:42:08 +09:00
// for intl fields, make sure we look in foo_intl and not foo
2018-07-10 10:02:08 +02:00
const fields = this . getFieldNames ( args ) ;
2018-05-23 16:02:36 -04:00
let data = pick ( this . getDocument ( ) , ... fields ) ;
2018-03-24 11:33:28 +09:00
2018-08-07 10:13:06 +09:00
// compact deleted values
2018-03-24 11:33:28 +09:00
this . state . deletedValues . forEach ( path => {
2018-08-05 11:17:46 +09:00
if ( path . includes ( '.' ) ) {
/ *
2018-08-07 10:13:06 +09:00
If deleted field is a nested field , nested array , or nested array item , try to compact its parent array
2018-08-05 11:17:46 +09:00
- Nested field : 'address.city'
- Nested array : 'addresses.1'
- Nested array item : 'addresses.1.city'
* /
2018-09-05 17:08:29 +02:00
compactParent ( data , path ) ;
2018-08-05 11:17:46 +09:00
}
2018-08-07 10:13:06 +09:00
} ) ;
2018-03-24 11:33:28 +09:00
// run data object through submitForm callbacks
2018-12-15 18:25:15 +09:00
data = runCallbacks ( { callbacks : this . submitFormCallbacks , iterator : data , properties : { form : this } } ) ;
2018-03-24 11:33:28 +09:00
return data ;
} ;
2018-09-23 08:13:09 +09:00
/ *
Get form components , in case any has been overwritten for this specific form
* /
2018-03-24 11:33:28 +09:00
// --------------------------------------------------------------------- //
// -------------------------------- Fields ----------------------------- //
// --------------------------------------------------------------------- //
/ *
Get all field groups
2018-03-22 19:22:54 +09:00
2018-03-24 11:33:28 +09:00
* /
getFieldGroups = ( ) => {
2018-03-22 19:22:54 +09:00
// build fields array by iterating over the list of field names
2018-03-26 11:51:08 +09:00
let fields = this . getFieldNames ( ) . map ( fieldName => {
2018-03-22 19:22:54 +09:00
// get schema for the current field
2018-04-20 14:35:53 -05:00
return this . createField ( fieldName , this . state . schema ) ;
2016-11-23 17:22:29 +09:00
} ) ;
2018-03-24 11:33:28 +09:00
fields = _ . sortBy ( fields , 'order' ) ;
2016-11-23 17:22:29 +09:00
2016-12-12 09:55:24 +09:00
// get list of all unique groups (based on their name) used in current fields
2018-08-05 10:30:56 +09:00
let groups = _ . compact ( uniqBy ( _ . pluck ( fields , 'group' ) , g => g && g . name ) ) ;
2016-11-23 17:22:29 +09:00
// for each group, add relevant fields
groups = groups . map ( group => {
2018-10-24 15:25:21 +02:00
group . label =
group . label || this . context . intl . formatMessage ( { id : group . name } ) ;
2018-03-24 11:33:28 +09:00
group . fields = _ . filter ( fields , field => {
return field . group && field . group . name === group . name ;
} ) ;
2016-11-23 17:22:29 +09:00
return group ;
} ) ;
// add default group
2018-03-24 11:33:28 +09:00
groups = [
{
name : 'default' ,
label : 'default' ,
order : 0 ,
fields : _ . filter ( fields , field => {
return ! field . group ;
2018-10-24 15:25:21 +02:00
} )
}
2018-03-24 11:33:28 +09:00
] . concat ( groups ) ;
2016-11-23 17:22:29 +09:00
// sort by order
2018-03-24 11:33:28 +09:00
groups = _ . sortBy ( groups , 'order' ) ;
2016-11-23 17:22:29 +09:00
// console.log(groups);
return groups ;
2018-03-24 11:33:28 +09:00
} ;
2016-11-23 17:22:29 +09:00
2018-03-24 11:33:28 +09:00
/ *
2018-07-10 10:02:08 +02:00
2018-03-24 11:33:28 +09:00
Get a list of the fields to be included in the current form
2016-11-23 17:22:29 +09:00
2018-07-10 10:02:08 +02:00
Note : when submitting the form ( getData ( ) ) , do not include any extra fields .
2018-03-24 11:33:28 +09:00
* /
2019-01-18 11:43:01 +01:00
getFieldNames = ( args ) => {
// we do this to avoid having default values in arrow functions, which breaks MS Edge support. See https://github.com/meteor/meteor/issues/10171
let args0 = args || { } ;
2018-10-24 15:25:21 +02:00
const {
schema = this . state . schema ,
excludeHiddenFields = true ,
replaceIntlFields = false ,
addExtraFields = true
2019-01-18 11:43:01 +01:00
} = args0 ;
2018-07-24 18:29:27 +02:00
2018-10-24 15:25:21 +02:00
const { fields , addFields } = this . props ;
2016-11-23 17:22:29 +09:00
// get all editable/insertable fields (depending on current form type)
2018-07-24 18:29:27 +02:00
let relevantFields = this . getMutableFields ( schema ) ;
2016-11-23 17:22:29 +09:00
// if "fields" prop is specified, restrict list of fields to it
2018-03-24 11:33:28 +09:00
if ( typeof fields !== 'undefined' && fields . length > 0 ) {
2016-11-23 17:22:29 +09:00
relevantFields = _ . intersection ( relevantFields , fields ) ;
2017-10-01 11:49:19 +09:00
}
2018-07-10 10:02:08 +02:00
2018-11-22 16:09:31 +09:00
// if "hideFields" prop is specified, remove its fields
2018-07-10 10:02:08 +02:00
const removeFields = this . props . hideFields || this . props . removeFields ;
if ( typeof removeFields !== 'undefined' && removeFields . length > 0 ) {
relevantFields = _ . difference ( relevantFields , removeFields ) ;
}
// if "addFields" prop is specified, add its fields
2018-10-24 15:25:21 +02:00
if (
addExtraFields &&
typeof addFields !== 'undefined' &&
addFields . length > 0
) {
2018-07-10 10:02:08 +02:00
relevantFields = relevantFields . concat ( addFields ) ;
2016-11-23 17:22:29 +09:00
}
2018-04-07 10:09:38 +09:00
// remove all hidden fields
if ( excludeHiddenFields ) {
2018-05-08 20:09:42 -04:00
const document = this . getDocument ( ) ;
2018-04-07 10:09:38 +09:00
relevantFields = _ . reject ( relevantFields , fieldName => {
const hidden = schema [ fieldName ] . hidden ;
2018-10-24 15:25:21 +02:00
return typeof hidden === 'function'
? hidden ( { ... this . props , document } )
: hidden ;
2018-04-07 10:09:38 +09:00
} ) ;
}
2018-05-21 09:42:08 +09:00
// replace intl fields
if ( replaceIntlFields ) {
2018-10-24 15:25:21 +02:00
relevantFields = relevantFields . map (
fieldName =>
isIntlField ( schema [ fieldName ] ) ? ` ${ fieldName } _intl ` : fieldName
) ;
2018-05-21 09:42:08 +09:00
}
2018-07-10 10:02:08 +02:00
// remove any duplicates
relevantFields = uniq ( relevantFields ) ;
2016-11-23 17:22:29 +09:00
return relevantFields ;
2018-03-24 11:33:28 +09:00
} ;
2016-11-23 17:22:29 +09:00
2018-09-07 17:20:41 +02:00
initField = ( fieldName , fieldSchema ) => {
2018-03-26 14:27:45 +09:00
// intialize properties
let field = {
... _ . pick ( fieldSchema , formProperties ) ,
2018-04-20 14:35:53 -05:00
document : this . state . initialDocument ,
2018-03-26 14:27:45 +09:00
name : fieldName ,
datatype : fieldSchema . type ,
layout : this . props . layout ,
2018-10-24 15:25:21 +02:00
input : fieldSchema . input || fieldSchema . control
2018-03-26 14:27:45 +09:00
} ;
field . label = this . getLabel ( fieldName ) ;
// // replace value by prefilled value if value is empty
// const prefill = fieldSchema.prefill || (fieldSchema.form && fieldSchema.form.prefill);
// if (prefill) {
// const prefilledValue = typeof prefill === 'function' ? prefill.call(fieldSchema) : prefill;
// if (!!prefilledValue && !field.value) {
// field.prefilledValue = prefilledValue;
// field.value = prefilledValue;
// }
// }
// if options are a function, call it
if ( typeof field . options === 'function' ) {
field . options = field . options . call ( fieldSchema , this . props ) ;
}
2018-09-07 17:20:41 +02:00
// if this an intl'd field, use a special intlInput
if ( isIntlField ( fieldSchema ) ) {
field . intlInput = true ;
}
2018-03-26 17:50:03 +09:00
// add any properties specified in fieldSchema.form as extra props passed on
// to the form component, calling them if they are functions
2018-10-24 15:25:21 +02:00
const inputProperties =
fieldSchema . form || fieldSchema . inputProperties || { } ;
2018-07-10 10:02:08 +02:00
for ( const prop in inputProperties ) {
const property = inputProperties [ prop ] ;
2018-10-24 15:25:21 +02:00
field [ prop ] =
typeof property === 'function'
? property . call ( fieldSchema , this . props )
: property ;
2018-07-10 10:02:08 +02:00
}
2018-03-26 14:27:45 +09:00
// add description as help prop
if ( fieldSchema . description ) {
field . help = fieldSchema . description ;
}
2018-10-24 15:25:21 +02:00
return field ;
} ;
2018-09-07 17:20:41 +02:00
handleFieldPath = ( field , fieldName , parentPath ) => {
const fieldPath = parentPath ? ` ${ parentPath } . ${ fieldName } ` : fieldName ;
2018-10-24 15:25:21 +02:00
field . path = fieldPath ;
if ( field . defaultValue ) {
set ( this . defaultValues , fieldPath , field . defaultValue ) ;
}
return field ;
} ;
2018-09-07 17:20:41 +02:00
handleFieldParent = ( field , parentFieldName ) => {
// if field has a parent field, pass it on
if ( parentFieldName ) {
field . parentFieldName = parentFieldName ;
}
2018-10-24 15:25:21 +02:00
return field ;
} ;
2018-09-07 17:20:41 +02:00
handlePermissions = ( field , fieldName , schema ) => {
// if field is not creatable/updatable, disable it
if ( ! this . getMutableFields ( schema ) . includes ( fieldName ) ) {
field . disabled = true ;
}
2018-10-24 15:25:21 +02:00
return field ;
} ;
2018-09-07 17:20:41 +02:00
handleFieldChildren = ( field , fieldName , fieldSchema , schema ) => {
2018-10-24 15:25:21 +02:00
// array field
2018-09-07 16:31:04 +02:00
if ( fieldSchema . field ) {
2018-10-24 15:25:21 +02:00
field . arrayFieldSchema = fieldSchema . field ;
2018-09-07 17:20:41 +02:00
// create a field that can be exploited by the form
2018-10-24 15:25:21 +02:00
field . arrayField = this . createArraySubField (
fieldName ,
field . arrayFieldSchema ,
schema
) ;
2018-09-07 17:20:41 +02:00
//field.nestedInput = true
2018-09-07 16:31:04 +02:00
}
2018-04-20 16:25:11 +02:00
// nested fields: set input to "nested"
2018-03-26 14:27:45 +09:00
if ( fieldSchema . schema ) {
field . nestedSchema = fieldSchema . schema ;
2018-05-23 22:04:32 +09:00
field . nestedInput = true ;
2018-07-26 17:07:48 +02:00
2018-03-26 14:27:45 +09:00
// get nested schema
// for each nested field, get field object by calling createField recursively
2018-10-24 15:25:21 +02:00
field . nestedFields = this . getFieldNames ( {
schema : field . nestedSchema
} ) . map ( subFieldName => {
return this . createField (
subFieldName ,
field . nestedSchema ,
fieldName ,
field . path
) ;
2018-03-26 14:27:45 +09:00
} ) ;
}
return field ;
2018-10-24 15:25:21 +02:00
} ;
2018-03-26 14:27:45 +09:00
2018-03-24 11:33:28 +09:00
/ *
2018-09-07 17:20:41 +02:00
Given a field ' s name , the containing schema , and parent , create the
complete field object to be passed to the component
* /
createField = ( fieldName , schema , parentFieldName , parentPath ) => {
2018-10-24 15:25:21 +02:00
const fieldSchema = schema [ fieldName ] ;
let field = this . initField ( fieldName , fieldSchema ) ;
field = this . handleFieldPath ( field , fieldName , parentPath ) ;
field = this . handleFieldParent ( field , parentFieldName ) ;
field = this . handlePermissions ( field , fieldName , schema ) ;
field = this . handleFieldChildren ( field , fieldName , fieldSchema , schema ) ;
return field ;
2018-09-07 17:20:41 +02:00
} ;
createArraySubField = ( fieldName , subFieldSchema , schema ) => {
2018-10-24 15:25:21 +02:00
const subFieldName = ` ${ fieldName } . $ ` ;
let subField = this . initField ( subFieldName , subFieldSchema ) ;
2018-09-07 17:20:41 +02:00
// array subfield has the same path and permissions as its parent
// so we use parent name (fieldName) and not subfieldName
2018-10-24 15:25:21 +02:00
subField = this . handleFieldPath ( subField , fieldName ) ;
subField = this . handlePermissions ( subField , fieldName , schema ) ;
2018-09-07 17:20:41 +02:00
// we do not allow nesting yet
//subField = this.handleFieldChildren(field, fieldSchema)
2018-10-24 15:25:21 +02:00
return subField ;
} ;
2018-03-24 11:16:11 +09:00
2018-09-07 17:20:41 +02:00
/ *
2018-04-20 14:35:53 -05:00
Get a field ' s label
2018-09-07 17:20:41 +02:00
2018-04-20 14:35:53 -05:00
* /
2018-07-02 16:59:29 +02:00
getLabel = ( fieldName , fieldLocale ) => {
2018-10-29 22:34:29 +01:00
const collectionName = this . props . collectionName . toLowerCase ( ) ;
2018-09-19 21:12:27 -04:00
const defaultMessage = '|*|*|' ;
let id = ` ${ collectionName } . ${ fieldName } ` ;
let intlLabel ;
intlLabel = this . context . intl . formatMessage ( { id , defaultMessage } ) ;
if ( intlLabel === defaultMessage ) {
id = ` global. ${ fieldName } ` ;
intlLabel = this . context . intl . formatMessage ( { id } ) ;
if ( intlLabel === defaultMessage ) {
id = fieldName ;
intlLabel = this . context . intl . formatMessage ( { id } ) ;
}
}
2018-10-24 15:25:21 +02:00
const schemaLabel =
this . state . flatSchema [ fieldName ] &&
this . state . flatSchema [ fieldName ] . label ;
2018-07-02 16:59:29 +02:00
const label = intlLabel || schemaLabel || fieldName ;
if ( fieldLocale ) {
2018-10-24 15:25:21 +02:00
const intlFieldLocale = this . context . intl . formatMessage ( {
id : ` locales. ${ fieldLocale } ` ,
defaultMessage : fieldLocale
} ) ;
2018-07-02 16:59:29 +02:00
return ` ${ label } ( ${ intlFieldLocale } ) ` ;
} else {
return label ;
}
2018-03-24 11:33:28 +09:00
} ;
2018-03-24 11:16:11 +09:00
2018-03-24 11:33:28 +09:00
// --------------------------------------------------------------------- //
// ------------------------------- Errors ------------------------------ //
// --------------------------------------------------------------------- //
2017-08-16 16:18:40 +09:00
2018-04-09 13:10:42 +09:00
/ *
2018-09-07 17:20:41 +02:00
2018-04-09 13:10:42 +09:00
Add error to form state
2018-09-07 17:20:41 +02:00
2018-04-09 13:10:42 +09:00
Errors can have the following properties :
- id : used as an internationalization key , for example ` errors.required `
- path : for field - specific errors , the path of the field with the issue
2018-04-21 17:57:53 +09:00
- properties : additional data . Will be passed to vulcan - i18n as values
2018-04-14 18:09:35 +09:00
- message : if id cannot be used as i81n key , message will be used
2018-04-21 17:57:53 +09:00
2018-04-09 13:10:42 +09:00
* /
2018-03-24 11:33:28 +09:00
throwError = error => {
2018-04-28 10:54:03 +09:00
let formErrors = getErrors ( error ) ;
2018-04-09 13:10:42 +09:00
2018-01-25 15:03:03 -06:00
// eslint-disable-next-line no-console
2018-03-29 11:58:24 +09:00
console . log ( formErrors ) ;
2018-04-09 13:10:42 +09:00
2018-03-29 11:58:24 +09:00
// add error(s) to state
2017-02-02 15:15:51 +01:00
this . setState ( prevState => ( {
2018-10-24 15:25:21 +02:00
errors : [ ... prevState . errors , ... formErrors ]
2017-02-02 15:15:51 +01:00
} ) ) ;
2018-03-24 11:33:28 +09:00
} ;
2016-11-23 17:22:29 +09:00
2018-04-14 17:21:10 +09:00
/ *
2018-09-07 17:20:41 +02:00
2018-04-14 17:21:10 +09:00
Clear errors for a field
2018-09-07 17:20:41 +02:00
2018-04-14 17:21:10 +09:00
* /
clearFieldErrors = path => {
const errors = this . state . errors . filter ( error => error . path !== path ) ;
this . setState ( { errors } ) ;
2018-04-21 17:57:53 +09:00
} ;
2018-04-14 17:21:10 +09:00
2018-03-26 14:27:45 +09:00
// --------------------------------------------------------------------- //
// ------------------------------- Context ----------------------------- //
// --------------------------------------------------------------------- //
2017-05-06 16:08:01 +09:00
// add something to deleted values
2018-03-24 11:33:28 +09:00
addToDeletedValues = name => {
2017-05-06 16:08:01 +09:00
this . setState ( prevState => ( {
2018-10-24 15:25:21 +02:00
deletedValues : [ ... prevState . deletedValues , name ]
2017-05-06 16:08:01 +09:00
} ) ) ;
2018-03-24 11:33:28 +09:00
} ;
2017-05-06 16:08:01 +09:00
2017-05-30 09:49:38 +09:00
// add a callback to the form submission
2018-03-24 11:33:28 +09:00
addToSubmitForm = callback => {
2017-05-30 09:49:38 +09:00
this . submitFormCallbacks . push ( callback ) ;
2018-03-24 11:33:28 +09:00
} ;
2017-05-30 09:49:38 +09:00
2017-07-06 12:49:28 -07:00
// add a callback to form submission success
2018-03-24 11:33:28 +09:00
addToSuccessForm = callback => {
2017-07-06 12:49:28 -07:00
this . successFormCallbacks . push ( callback ) ;
2018-03-24 11:33:28 +09:00
} ;
2017-07-06 12:49:28 -07:00
// add a callback to form submission failure
2018-03-24 11:33:28 +09:00
addToFailureForm = callback => {
2017-07-06 12:49:28 -07:00
this . failureFormCallbacks . push ( callback ) ;
2018-03-24 11:33:28 +09:00
} ;
2017-07-06 12:49:28 -07:00
2018-03-24 11:33:28 +09:00
setFormState = fn => {
2017-04-20 16:04:24 +09:00
this . setState ( fn ) ;
2018-03-24 11:33:28 +09:00
} ;
2017-04-20 16:04:24 +09:00
2018-03-24 11:33:28 +09:00
submitFormContext = newValues => {
2017-09-08 22:52:54 -07:00
// keep the previous ones and extend (with possible replacement) with new ones
2018-03-24 11:33:28 +09:00
this . setState (
prevState => ( {
currentValues : {
... prevState . currentValues ,
2018-10-24 15:25:21 +02:00
... newValues
} // Submit form after setState update completed
2018-03-24 11:33:28 +09:00
} ) ,
2018-06-30 11:34:17 +02:00
( ) => this . submitForm ( this . form . getModel ( ) )
2018-03-24 11:33:28 +09:00
) ;
} ;
2017-09-08 22:52:54 -07:00
2016-11-23 17:22:29 +09:00
// pass on context to all child components
2018-03-24 11:21:39 +09:00
getChildContext = ( ) => {
2016-11-23 17:22:29 +09:00
return {
throwError : this . throwError ,
2017-02-02 15:15:51 +01:00
clearForm : this . clearForm ,
2018-05-08 20:09:42 -04:00
refetchForm : this . refetchForm ,
isChanged : this . isChanged ,
submitForm : this . submitFormContext , //Change in name because we already have a function
2018-05-11 09:52:04 +09:00
// called submitForm, but no reason for the user to know
// about that
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 ,
2018-06-27 19:53:21 +02:00
getLabel : this . getLabel ,
2018-04-20 14:35:53 -05:00
initialDocument : this . state . initialDocument ,
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 ,
2018-03-25 10:54:45 +09:00
errors : this . state . errors ,
2018-03-25 12:13:30 +09:00
currentValues : this . state . currentValues ,
2018-10-24 15:25:21 +02:00
deletedValues : this . state . deletedValues
2016-11-23 17:22:29 +09:00
} ;
2018-03-24 11:33:28 +09:00
} ;
2016-11-23 17:22:29 +09:00
// --------------------------------------------------------------------- //
2018-03-24 11:33:28 +09:00
// ------------------------------ Lifecycle ---------------------------- //
2016-11-23 17:22:29 +09:00
// --------------------------------------------------------------------- //
2018-08-09 11:41:06 +09:00
/ *
2018-09-07 17:20:41 +02:00
2018-11-26 14:59:57 +01:00
When props change , reinitialize the form state
Triggered only for data related props ( collection , document , currentUser etc . )
@ see https : //reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html
2018-09-07 17:20:41 +02:00
2018-08-09 11:41:06 +09:00
* /
UNSAFE _componentWillReceiveProps ( nextProps ) {
2018-12-31 15:22:17 +09:00
const needReset = ! ! RESET _PROPS . find ( prop => ! isEqual ( this . props [ prop ] , nextProps [ prop ] ) ) ;
2018-11-26 14:59:57 +01:00
if ( needReset ) {
2018-08-09 11:41:06 +09:00
this . setState ( getInitialStateFromProps ( nextProps ) ) ;
}
2018-04-20 14:35:53 -05:00
}
2018-03-24 11:33:28 +09:00
/ *
2018-09-07 17:20:41 +02:00
2018-04-20 14:35:53 -05:00
Manually update the current values of one or more fields ( i . e . on change or blur ) .
2018-09-07 17:20:41 +02:00
2018-03-24 11:33:28 +09:00
* /
2018-08-07 10:13:06 +09:00
updateCurrentValues = ( newValues , options = { } ) => {
// default to overwriting old value with new
const { mode = 'overwrite' } = options ;
2018-10-26 11:32:22 +02:00
const { changeCallback } = this . props ;
2018-09-05 17:08:29 +02:00
2018-03-24 11:33:28 +09:00
// keep the previous ones and extend (with possible replacement) with new ones
this . setState ( prevState => {
2018-08-09 11:41:06 +09:00
// keep only the relevant properties
2018-10-24 15:25:21 +02:00
const { currentValues , currentDocument , deletedValues } = cloneDeep (
prevState
) ;
const newState = {
currentValues ,
currentDocument ,
deletedValues ,
foo : { }
} ;
2018-08-09 11:41:06 +09:00
2018-03-24 11:33:28 +09:00
Object . keys ( newValues ) . forEach ( key => {
const path = key ;
const value = newValues [ key ] ;
2018-08-07 16:42:45 +09:00
if ( isEmptyValue ( value ) ) {
2018-03-24 11:33:28 +09:00
// delete value
2018-03-26 14:27:45 +09:00
unset ( newState . currentValues , path ) ;
2018-08-07 16:42:45 +09:00
set ( newState . currentDocument , path , null ) ;
2018-03-28 11:51:18 +09:00
newState . deletedValues = [ ... prevState . deletedValues , path ] ;
2018-03-24 11:33:28 +09:00
} else {
2018-08-07 10:13:06 +09:00
// 1. update currentValues
2018-03-25 12:13:30 +09:00
set ( newState . currentValues , path , value ) ;
2018-08-07 10:13:06 +09:00
// 2. update currentDocument
// For arrays and objects, give option to merge instead of overwrite
if ( mode === 'merge' && ( Array . isArray ( value ) || isObject ( value ) ) ) {
2018-08-07 16:42:45 +09:00
const oldValue = get ( newState . currentDocument , path ) ;
set ( newState . currentDocument , path , merge ( oldValue , value ) ) ;
2018-08-07 10:13:06 +09:00
} else {
2018-08-07 16:42:45 +09:00
set ( newState . currentDocument , path , value ) ;
2018-08-07 10:13:06 +09:00
}
// 3. in case value had previously been deleted, "undelete" it
2018-03-28 11:51:18 +09:00
newState . deletedValues = _ . without ( prevState . deletedValues , path ) ;
2018-03-24 11:33:28 +09:00
}
} ) ;
2018-10-26 11:32:22 +02:00
if ( changeCallback ) changeCallback ( newState . currentDocument ) ;
2018-03-25 12:13:30 +09:00
return newState ;
2018-03-24 11:33:28 +09:00
} ) ;
} ;
2018-05-11 09:52:04 +09:00
2018-05-08 20:09:42 -04:00
/ *
2018-09-07 17:20:41 +02:00
2018-05-08 20:09:42 -04:00
Warn the user if there are unsaved changes
2018-09-07 17:20:41 +02:00
2018-05-08 20:09:42 -04:00
* /
handleRouteLeave = ( ) => {
if ( this . isChanged ( ) ) {
const message = this . context . intl . formatMessage ( {
id : 'forms.confirm_discard' ,
2018-10-24 15:25:21 +02:00
defaultMessage : 'Are you sure you want to discard your changes?'
2018-05-08 20:09:42 -04:00
} ) ;
return message ;
}
} ;
2018-07-24 18:29:27 +02:00
2018-05-22 09:51:36 +02:00
//see https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onbeforeunload
//the message returned is actually ignored by most browsers and a default message 'Are you sure you want to leave this page? You might have unsaved changes' is displayed. See the Notes section on the mozilla docs above
2018-10-24 15:25:21 +02:00
handlePageLeave = event => {
2018-07-24 18:29:27 +02:00
if ( this . isChanged ( ) ) {
2018-05-22 09:51:36 +02:00
const message = this . context . intl . formatMessage ( {
id : 'forms.confirm_discard' ,
defaultMessage : 'Are you sure you want to discard your changes?'
} ) ;
if ( event ) {
event . returnValue = message ;
}
2018-07-24 18:29:27 +02:00
2018-05-22 09:51:36 +02:00
return message ;
}
} ;
2018-07-24 18:29:27 +02:00
2018-05-08 20:09:42 -04:00
/ *
2018-09-07 17:20:41 +02:00
2018-05-08 20:09:42 -04:00
Install a route leave hook to warn the user if there are unsaved changes
2018-09-07 17:20:41 +02:00
2018-05-08 20:09:42 -04:00
* /
componentDidMount = ( ) => {
let warnUnsavedChanges = getSetting ( 'forms.warnUnsavedChanges' ) ;
if ( typeof this . props . warnUnsavedChanges === 'boolean' ) {
warnUnsavedChanges = this . props . warnUnsavedChanges ;
}
if ( warnUnsavedChanges ) {
const routes = this . props . router . routes ;
const currentRoute = routes [ routes . length - 1 ] ;
this . props . router . setRouteLeaveHook ( currentRoute , this . handleRouteLeave ) ;
2018-07-24 18:29:27 +02:00
2018-05-22 09:51:36 +02:00
//check for closing the browser with unsaved changes
window . onbeforeunload = this . handlePageLeave ;
2018-05-08 20:09:42 -04:00
}
} ;
2018-05-11 09:52:04 +09:00
2018-05-22 09:51:36 +02:00
/ *
Remove the closing browser check on component unmount
see https : //gist.github.com/mknabe/bfcb6db12ef52323954a28655801792d
* /
componentWillUnmount = ( ) => {
let warnUnsavedChanges = getSetting ( 'forms.warnUnsavedChanges' ) ;
if ( typeof this . props . warnUnsavedChanges === 'boolean' ) {
warnUnsavedChanges = this . props . warnUnsavedChanges ;
}
if ( warnUnsavedChanges ) {
window . onbeforeunload = undefined ; //undefined instead of null to support IE
}
} ;
2018-07-24 18:29:27 +02:00
2018-04-20 14:35:53 -05:00
/ *
2018-09-07 17:20:41 +02:00
2018-05-08 20:09:42 -04:00
Returns true if there are any differences between the initial document and the current one
2018-09-07 17:20:41 +02:00
2018-05-08 20:09:42 -04:00
* /
isChanged = ( ) => {
const initialDocument = this . state . initialDocument ;
const changedDocument = this . getDocument ( ) ;
2018-05-11 09:52:04 +09:00
2018-05-08 20:09:42 -04:00
const changedValue = find ( changedDocument , ( value , key , collection ) => {
return ! isEqualWith ( value , initialDocument [ key ] , ( objValue , othValue ) => {
if ( ! objValue && ! othValue ) return true ;
} ) ;
} ) ;
2018-05-11 09:52:04 +09:00
2018-05-08 20:09:42 -04:00
return typeof changedValue !== 'undefined' ;
} ;
2018-05-11 09:52:04 +09:00
2018-05-08 20:09:42 -04:00
/ *
2018-09-07 17:20:41 +02:00
2018-05-08 20:09:42 -04:00
Refetch the document from the database ( in case it was updated by another process or to reset the form )
2018-09-07 17:20:41 +02:00
2018-05-08 20:09:42 -04:00
* /
refetchForm = ( ) => {
if ( this . props . data && this . props . data . refetch ) {
this . props . data . refetch ( ) ;
}
} ;
2018-05-11 09:52:04 +09:00
2018-09-14 11:12:45 +02:00
/ * *
2018-10-04 20:08:14 +02:00
* Clears form errors and values .
*
* @ example Clear form
* // form will be fully emptied, with exception of prefilled values
* clearForm ( { document : { } } ) ;
*
* @ example Reset / revert form
* // form will be reverted to its initial state
* clearForm ( ) ;
*
* @ example Clear with new values
* // form will be cleared but initialized with the new document
* const document = {
* // ... some values
* } ;
* clearForm ( { document } ) ;
2018-09-14 11:12:45 +02:00
*
* @ param { Object = } options
2018-10-04 20:08:14 +02:00
* @ param { Object = } options . document
2018-09-14 11:12:45 +02:00
* Document to use as new initial document when values are cleared instead of
2018-10-04 20:08:14 +02:00
* the existing one . Note that prefilled props will be merged
2018-09-14 11:12:45 +02:00
* /
2018-10-04 20:08:14 +02:00
clearForm = ( { document } = { } ) => {
2018-05-08 20:09:42 -04:00
document = document ? merge ( { } , this . props . prefilledProps , document ) : null ;
2018-03-24 11:33:28 +09:00
this . setState ( prevState => ( {
2018-10-04 20:08:14 +02:00
errors : [ ] ,
currentValues : { } ,
deletedValues : [ ] ,
currentDocument : document || prevState . initialDocument ,
initialDocument : document || prevState . initialDocument ,
2018-10-24 15:25:21 +02:00
disabled : false
2018-03-24 11:33:28 +09:00
} ) ) ;
} ;
/ *
2018-09-07 17:20:41 +02:00
2018-03-24 11:33:28 +09:00
Key down handler
2018-09-07 17:20:41 +02:00
2018-03-24 11:33:28 +09:00
* /
formKeyDown = event => {
if ( ( event . ctrlKey || event . metaKey ) && event . keyCode === 13 ) {
2018-06-30 11:34:17 +02:00
this . submitForm ( this . form . getModel ( ) ) ;
2018-03-24 11:33:28 +09:00
}
} ;
newMutationSuccessCallback = result => {
2016-11-25 12:22:13 +09:00
this . mutationSuccessCallback ( result , 'new' ) ;
2018-03-24 11:33:28 +09:00
} ;
2016-11-25 12:22:13 +09:00
2018-03-24 11:33:28 +09:00
editMutationSuccessCallback = result => {
2018-10-24 15:25:21 +02:00
this . mutationSuccessCallback ( result , 'edit' ) ;
2018-03-24 11:33:28 +09:00
} ;
2016-11-25 12:22:13 +09:00
2018-03-24 11:21:39 +09:00
mutationSuccessCallback = ( result , mutationType ) => {
2018-06-13 18:28:01 +09:00
this . setState ( prevState => ( { disabled : false } ) ) ;
2018-12-02 22:33:45 +09:00
let document = result . data [ Object . keys ( result . data ) [ 0 ] ] . data ; // document is always on first property
2016-11-23 17:22:29 +09:00
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 ( ) ;
2018-05-11 09:52:04 +09:00
// call the clear form method (i.e. trigger setState) only if the form has not been unmounted
2018-05-08 20:09:42 -04:00
// (we are in an async callback, everything can happen!)
2018-07-07 18:49:04 +02:00
if ( this . form ) {
2018-06-30 11:34:17 +02:00
this . form . reset ( this . getDocument ( ) ) ;
2018-10-24 15:25:21 +02:00
this . clearForm ( {
document : mutationType === 'edit' ? document : undefined
} ) ;
2017-01-13 18:17:08 +01:00
}
2017-01-10 17:49:03 +09:00
2017-07-06 12:49:28 -07:00
// run document through mutation success callbacks
2018-12-15 18:25:15 +09:00
document = runCallbacks ( { callbacks : this . successFormCallbacks , iterator : document , properties : { form : this } } ) ;
2017-07-06 12:49:28 -07:00
2016-11-23 17:22:29 +09:00
// run success callback if it exists
2018-12-02 22:33:45 +09:00
if ( this . props . successCallback ) this . props . successCallback ( document , { form : this } ) ;
2018-03-24 11:33:28 +09:00
} ;
2016-11-23 17:22:29 +09:00
// catch graphql errors
2018-04-30 15:39:33 -03:00
mutationErrorCallback = ( document , error ) => {
2018-03-24 11:33:28 +09: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
2018-03-24 11:33:28 +09:00
console . log ( '// graphQL Error' ) ;
2018-01-25 15:03:03 -06:00
// 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
2018-12-15 18:25:15 +09:00
runCallbacks ( { callbacks : this . failureFormCallbacks , iterator : error , properties : { error , form : this } } ) ;
2017-07-06 12:49:28 -07:00
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
}
2018-05-11 09:52:04 +09:00
2016-11-23 17:22:29 +09:00
// run error callback if it exists
2018-12-02 22:33:45 +09:00
if ( this . props . errorCallback ) this . props . errorCallback ( document , error , { form : this } ) ;
2018-05-11 09:52:04 +09:00
2018-05-10 10:18:55 +09:00
// scroll back up to show error messages
2018-05-08 20:09:42 -04:00
Utils . scrollIntoView ( '.flash-message' ) ;
2018-03-24 11:33:28 +09:00
} ;
2016-11-23 17:22:29 +09:00
2018-04-20 14:35:53 -05:00
/ *
2018-09-07 17:20:41 +02:00
2018-03-24 11:33:28 +09:00
Submit form handler
2018-09-07 17:20:41 +02:00
2018-03-24 11:33:28 +09:00
* /
2019-01-24 18:01:53 +09:00
submitForm = event => {
2018-03-23 15:46:31 +09:00
2019-01-24 18:01:53 +09:00
event . preventDefault ( ) ;
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
2018-03-24 11:33:28 +09:00
this . setState ( prevState => ( { errors : [ ] , disabled : true } ) ) ;
2016-11-23 17:22:29 +09:00
2019-01-24 18:01:53 +09:00
// complete the data with values from custom components
2016-12-20 09:27:16 +09:00
// note: it follows the same logic as SmartForm's getDocument method
2019-01-24 18:01:53 +09:00
let data = this . getData ( { replaceIntlFields : true , addExtraFields : false } ) ;
2016-11-23 17:22:29 +09:00
// if there's a submit callback, run it
if ( this . props . submitCallback ) {
2018-09-05 17:08:29 +02:00
data = this . props . submitCallback ( data ) || data ;
2016-11-23 17:22:29 +09:00
}
2018-03-24 11:33:28 +09:00
if ( this . getFormType ( ) === 'new' ) {
2018-07-10 10:02:08 +02:00
// create document form
2018-10-29 22:34:29 +01:00
this . props [ ` create ${ this . props . typeName } ` ] ( { data } )
2018-03-24 11:33:28 +09:00
. then ( this . newMutationSuccessCallback )
2018-04-30 15:39:33 -03:00
. catch ( error => this . mutationErrorCallback ( document , error ) ) ;
2018-03-24 11:33:28 +09:00
} else {
2018-07-10 10:02:08 +02:00
// update document form
const documentId = this . getDocument ( ) . _id ;
2018-10-29 22:34:29 +01:00
this . props [ ` update ${ this . props . typeName } ` ] ( {
2018-10-24 15:25:21 +02:00
selector : { documentId } ,
data
} )
2018-03-24 11:33:28 +09:00
. then ( this . editMutationSuccessCallback )
2018-05-02 10:35:13 -03:00
. catch ( error => this . mutationErrorCallback ( document , error ) ) ;
2016-11-23 17:22:29 +09:00
}
2018-03-24 11:33:28 +09:00
} ;
2016-11-23 17:22:29 +09:00
2018-03-24 11:33:28 +09:00
/ *
2018-09-07 17:20:41 +02:00
2018-03-24 11:33:28 +09:00
Delete document handler
2018-09-07 17:20:41 +02:00
2018-03-24 11:33:28 +09:00
* /
2018-03-24 11:21:39 +09:00
deleteDocument = ( ) => {
2016-11-23 17:22:29 +09:00
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
2018-03-24 11:33:28 +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 ) ) {
2018-03-24 11:33:28 +09:00
this . props
. removeMutation ( { documentId } )
. then ( mutationResult => {
// the mutation result looks like {data:{collectionRemove: null}} if succeeded
2018-10-24 15:25:21 +02: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
} )
2018-05-02 13:39:52 -03:00
. catch ( error => {
// eslint-disable-next-line no-console
console . log ( error ) ;
} ) ;
2016-11-23 17:22:29 +09:00
}
2018-03-24 11:33:28 +09:00
} ;
2016-11-23 17:22:29 +09:00
2019-01-23 09:56:15 +09:00
// --------------------------------------------------------------------- //
// ------------------------- Props to Pass ----------------------------- //
// --------------------------------------------------------------------- //
getWrapperProps = ( ) => ( {
className : 'document-' + this . getFormType ( ) ,
} ) ;
getFormProps = ( ) => ( {
id : this . props . id ,
onSubmit : this . submitForm ,
onKeyDown : this . formKeyDown ,
ref : e => {
this . form = e ;
} ,
} ) ;
getFormErrorsProps = ( ) => ( {
errors : this . state . errors
} ) ;
getFormGroupProps = group => ( {
key : group . name ,
... group ,
errors : this . state . errors ,
throwError : this . throwError ,
currentValues : this . state . currentValues ,
updateCurrentValues : this . updateCurrentValues ,
deletedValues : this . state . deletedValues ,
addToDeletedValues : this . addToDeletedValues ,
clearFieldErrors : this . clearFieldErrors ,
formType : this . getFormType ( ) ,
currentUser : this . props . currentUser ,
disabled : this . state . disabled ,
formComponents : mergeWithComponents ( this . props . formComponents ) ,
} ) ;
getFormSubmitProps = ( ) => ( {
submitLabel : this . props . submitLabel ,
cancelLabel : this . props . cancelLabel ,
revertLabel : this . props . revertLabel ,
cancelCallback : this . props . cancelCallback ,
revertCallback : this . props . revertCallback ,
document : this . getDocument ( ) ,
deleteDocument :
( this . getFormType ( ) === 'edit' &&
this . props . showRemove &&
this . deleteDocument ) ||
null ,
collectionName : this . props . collectionName ,
currentValues : this . state . currentValues ,
deletedValues : this . state . deletedValues ,
errors : this . state . errors ,
} ) ;
2016-11-23 17:22:29 +09:00
// --------------------------------------------------------------------- //
2018-03-24 11:33:28 +09:00
// ----------------------------- Render -------------------------------- //
2016-11-23 17:22:29 +09:00
// --------------------------------------------------------------------- //
2018-05-11 09:52:04 +09:00
render ( ) {
2018-10-25 12:07:50 +02:00
const FormComponents = mergeWithComponents ( this . props . formComponents ) ;
2016-11-23 17:22:29 +09:00
return (
2019-01-23 09:56:15 +09:00
< div { ...this.getWrapperProps ( ) } >
2019-01-24 18:01:53 +09:00
< form { ...this.getFormProps ( ) } >
2019-01-23 09:56:15 +09:00
< FormComponents.FormErrors { ...this.getFormErrorsProps ( ) } / >
{ this . getFieldGroups ( ) . map ( group => (
< FormComponents.FormGroup { ...this.getFormGroupProps ( group ) } / >
2018-03-24 11:33:28 +09:00
) ) }
2018-01-26 17:41:15 -06:00
2017-11-09 10:01:22 +09:00
{ this . props . repeatErrors && this . renderErrors ( ) }
2019-01-23 09:56:15 +09:00
< FormComponents.FormSubmit { ...this.getFormSubmitProps ( ) } / >
2019-01-24 18:01:53 +09:00
< / form >
2016-11-23 17:22:29 +09:00
< / div >
2018-03-24 11:33:28 +09:00
) ;
2016-11-23 17:22:29 +09:00
}
}
2018-06-20 10:23:54 +09:00
SmartForm . propTypes = {
2016-11-23 17:22:29 +09:00
// main options
2018-10-29 22:08:41 +01:00
collection : PropTypes . object . isRequired ,
collectionName : PropTypes . string . isRequired ,
typeName : PropTypes . string . isRequired ,
2018-10-29 22:34:29 +01:00
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 ) ,
2018-12-28 08:52:40 +09:00
addFields : PropTypes . arrayOf ( PropTypes . string ) ,
2018-07-10 10:02:08 +02:00
removeFields : PropTypes . arrayOf ( PropTypes . string ) ,
hideFields : PropTypes . arrayOf ( PropTypes . string ) , // OpenCRUD backwards compatibility
2017-02-02 15:15:51 +01:00
showRemove : PropTypes . bool ,
2018-09-25 09:42:56 -04:00
submitLabel : PropTypes . node ,
cancelLabel : PropTypes . node ,
revertLabel : PropTypes . node ,
2017-11-09 10:01:22 +09:00
repeatErrors : PropTypes . bool ,
2018-05-08 20:09:42 -04:00
warnUnsavedChanges : PropTypes . bool ,
2018-09-23 08:13:09 +09:00
formComponents : PropTypes . object ,
2016-11-23 17:22:29 +09:00
// callbacks
2018-10-29 22:34:29 +01:00
... callbackProps ,
2017-02-02 15:15:51 +01:00
currentUser : PropTypes . object ,
2018-10-24 15:25:21 +02:00
client : PropTypes . object
2018-03-24 11:33:28 +09:00
} ;
2016-11-23 17:22:29 +09:00
2018-06-20 10:23:54 +09:00
SmartForm . defaultProps = {
2017-11-09 10:01:22 +09:00
layout : 'horizontal' ,
2018-03-23 08:51:24 +09:00
prefilledProps : { } ,
2017-11-09 10:01:22 +09:00
repeatErrors : false ,
2018-10-24 15:25:21 +02:00
showRemove : true
2018-03-24 11:33:28 +09:00
} ;
2016-11-23 17:22:29 +09:00
2018-06-20 10:23:54 +09:00
SmartForm . contextTypes = {
2018-10-24 15:25:21 +02:00
intl : intlShape
2018-03-24 11:33:28 +09:00
} ;
2016-11-23 17:22:29 +09:00
2018-06-20 10:23:54 +09:00
SmartForm . childContextTypes = {
2017-05-06 16:08:01 +09:00
addToDeletedValues : PropTypes . func ,
2018-03-24 11:16:11 +09:00
deletedValues : PropTypes . array ,
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 ,
2018-05-08 20:09:42 -04:00
refetchForm : PropTypes . func ,
isChanged : PropTypes . func ,
2018-03-26 14:27:45 +09:00
initialDocument : PropTypes . object ,
2017-09-08 22:52:54 -07:00
getDocument : PropTypes . func ,
2018-06-27 19:53:21 +02:00
getLabel : PropTypes . func ,
2017-09-08 22:52:54 -07:00
submitForm : PropTypes . func ,
2018-03-25 10:54:45 +09:00
errors : PropTypes . array ,
2018-10-24 15:25:21 +02:00
currentValues : PropTypes . object
2018-03-24 11:33:28 +09:00
} ;
2016-11-23 17:22:29 +09:00
2018-06-20 10:23:54 +09:00
module . exports = SmartForm ;
2018-03-26 17:50:03 +09:00
2018-10-29 22:08:41 +01:00
registerComponent ( {
name : 'Form' ,
component : SmartForm ,
hocs : [ withCollectionProps ]
} ) ;