From 4e2c0900012ec404c6f5f75d794f6dbf31efbaef Mon Sep 17 00:00:00 2001 From: James Date: Wed, 20 May 2020 22:28:41 -0400 Subject: [PATCH] adjusts withConditions due to not supporting functions passed from config to client side code --- demo/collections/Conditions.js | 96 +++++++++++++++ demo/payload.config.js | 2 + src/auth/baseFields.js | 6 +- .../forms/field-types/Checkbox/index.js | 4 +- .../forms/field-types/DateTime/index.js | 4 +- .../forms/field-types/Email/index.js | 4 +- .../forms/field-types/Flexible/index.js | 4 +- .../forms/field-types/Group/index.js | 4 +- .../forms/field-types/HiddenInput/index.js | 4 +- .../forms/field-types/Number/index.js | 4 +- .../forms/field-types/Password/index.js | 4 +- .../forms/field-types/Relationship/index.js | 4 +- .../forms/field-types/Repeater/index.js | 4 +- .../forms/field-types/Select/index.js | 4 +- .../forms/field-types/Text/index.js | 4 +- .../forms/field-types/Textarea/index.js | 4 +- .../forms/field-types/Upload/index.js | 4 +- .../components/forms/useFieldType/index.js | 4 +- .../components/forms/withCondition/index.js | 54 --------- .../components/forms/withConditions/index.js | 112 ++++++++++++++++++ src/fields/schemaMap.js | 2 +- 21 files changed, 246 insertions(+), 86 deletions(-) create mode 100644 demo/collections/Conditions.js delete mode 100644 src/client/components/forms/withCondition/index.js create mode 100644 src/client/components/forms/withConditions/index.js diff --git a/demo/collections/Conditions.js b/demo/collections/Conditions.js new file mode 100644 index 0000000000..d9dfd5dad2 --- /dev/null +++ b/demo/collections/Conditions.js @@ -0,0 +1,96 @@ +const Conditions = { + slug: 'conditions', + labels: { + singular: 'Conditions', + plural: 'Conditions', + }, + fields: [ + { + name: 'title', + type: 'text', + label: 'Title', + required: true, + }, + { + name: 'enable-test', + type: 'checkbox', + label: 'Enable Test', + }, + { + name: 'number', + type: 'number', + label: 'Number Field', + }, + { + name: 'simple-condition', + type: 'text', + label: 'Enable Test is checked', + required: true, + conditions: { + 'enable-test': { + equals: true, + }, + }, + }, + { + name: 'or-condition', + type: 'text', + label: 'Number is greater than 20 OR enable-test is checked', + required: true, + conditions: { + or: [ + { + number: { + greater_than: 20, + }, + }, + { + 'enable-test': { + equals: true, + }, + }, + ], + + }, + }, + { + name: 'nested-conditions', + type: 'text', + label: 'Number is either greater than 20 AND enable-test is checked, OR number is less than 20 and enable-test is NOT checked', + conditions: { + or: [ + { + and: [ + { + number: { + greater_than: 20, + }, + }, + { + 'enable-test': { + equals: true, + }, + }, + ], + }, + { + and: [ + { + number: { + less_than: 20, + }, + }, + { + 'enable-test': { + equals: false, + }, + }, + ], + }, + ], + }, + }, + ], +}; + +module.exports = Conditions; diff --git a/demo/payload.config.js b/demo/payload.config.js index 5562228e4a..fbc14be9eb 100644 --- a/demo/payload.config.js +++ b/demo/payload.config.js @@ -2,6 +2,7 @@ const path = require('path'); const Admin = require('./collections/Admin'); const AllFields = require('./collections/AllFields'); const Code = require('./collections/Code'); +const Conditions = require('./collections/Conditions'); const CustomComponents = require('./collections/CustomComponents'); const File = require('./collections/File'); const FlexibleContent = require('./collections/FlexibleContent'); @@ -30,6 +31,7 @@ module.exports = { Admin, AllFields, Code, + Conditions, CustomComponents, File, FlexibleContent, diff --git a/src/auth/baseFields.js b/src/auth/baseFields.js index e68d801b08..ae1dc4e837 100644 --- a/src/auth/baseFields.js +++ b/src/auth/baseFields.js @@ -24,8 +24,10 @@ module.exports = [ name: 'apiKey', type: 'text', label: 'User API Key', - condition: (_, siblings) => { - return siblings.enableAPIKey && siblings.enableAPIKey.value; + conditions: { + enableAPIKey: { + equals: true, + }, }, }, ]; diff --git a/src/client/components/forms/field-types/Checkbox/index.js b/src/client/components/forms/field-types/Checkbox/index.js index 5f96fcaaf1..0d920f00ac 100644 --- a/src/client/components/forms/field-types/Checkbox/index.js +++ b/src/client/components/forms/field-types/Checkbox/index.js @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import useFieldType from '../../useFieldType'; -import withCondition from '../../withCondition'; +import withConditions from '../../withConditions'; import Error from '../../Error'; import StyledCheckbox from './StyledCheckbox'; @@ -87,4 +87,4 @@ Checkbox.propTypes = { label: PropTypes.string, }; -export default withCondition(Checkbox); +export default withConditions(Checkbox); diff --git a/src/client/components/forms/field-types/DateTime/index.js b/src/client/components/forms/field-types/DateTime/index.js index bc3db12e56..d6f453cf6f 100644 --- a/src/client/components/forms/field-types/DateTime/index.js +++ b/src/client/components/forms/field-types/DateTime/index.js @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import DatePicker from 'react-datepicker'; -import withCondition from '../../withCondition'; +import withConditions from '../../withConditions'; import useFieldType from '../../useFieldType'; import Label from '../../Label'; import Error from '../../Error'; @@ -156,4 +156,4 @@ DateTime.propTypes = { timeFormat: PropTypes.string, }; -export default withCondition(DateTime); +export default withConditions(DateTime); diff --git a/src/client/components/forms/field-types/Email/index.js b/src/client/components/forms/field-types/Email/index.js index 64b9c3682a..2bb7dc4803 100644 --- a/src/client/components/forms/field-types/Email/index.js +++ b/src/client/components/forms/field-types/Email/index.js @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import withCondition from '../../withCondition'; +import withConditions from '../../withConditions'; import useFieldType from '../../useFieldType'; import Label from '../../Label'; import Error from '../../Error'; @@ -108,4 +108,4 @@ Email.propTypes = { autoComplete: PropTypes.string, }; -export default withCondition(Email); +export default withConditions(Email); diff --git a/src/client/components/forms/field-types/Flexible/index.js b/src/client/components/forms/field-types/Flexible/index.js index eb0ce584f9..6199150036 100644 --- a/src/client/components/forms/field-types/Flexible/index.js +++ b/src/client/components/forms/field-types/Flexible/index.js @@ -6,7 +6,7 @@ import { DragDropContext, Droppable } from 'react-beautiful-dnd'; import { useModal } from '@trbl/react-modal'; import { RowModifiedProvider, useRowModified } from '../../Form/RowModified'; -import withCondition from '../../withCondition'; +import withConditions from '../../withConditions'; import Button from '../../../elements/Button'; import FormContext from '../../Form/Context'; import AddRowModal from './AddRowModal'; @@ -210,4 +210,4 @@ Flexible.propTypes = { fieldTypes: PropTypes.shape({}).isRequired, }; -export default withCondition(Flexible); +export default withConditions(Flexible); diff --git a/src/client/components/forms/field-types/Group/index.js b/src/client/components/forms/field-types/Group/index.js index 1ecdab2957..0cf440e1be 100644 --- a/src/client/components/forms/field-types/Group/index.js +++ b/src/client/components/forms/field-types/Group/index.js @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import RenderFields from '../../RenderFields'; -import withCondition from '../../withCondition'; +import withConditions from '../../withConditions'; import './index.scss'; @@ -41,4 +41,4 @@ Group.propTypes = { fieldTypes: PropTypes.shape({}).isRequired, }; -export default withCondition(Group); +export default withConditions(Group); diff --git a/src/client/components/forms/field-types/HiddenInput/index.js b/src/client/components/forms/field-types/HiddenInput/index.js index b96ce31f64..2911dcd033 100644 --- a/src/client/components/forms/field-types/HiddenInput/index.js +++ b/src/client/components/forms/field-types/HiddenInput/index.js @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import useFieldType from '../../useFieldType'; -import withCondition from '../../withCondition'; +import withConditions from '../../withConditions'; const HiddenInput = (props) => { const { @@ -38,4 +38,4 @@ HiddenInput.propTypes = { defaultValue: PropTypes.string, }; -export default withCondition(HiddenInput); +export default withConditions(HiddenInput); diff --git a/src/client/components/forms/field-types/Number/index.js b/src/client/components/forms/field-types/Number/index.js index 1dd59eecb5..9606e96bd5 100644 --- a/src/client/components/forms/field-types/Number/index.js +++ b/src/client/components/forms/field-types/Number/index.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import useFieldType from '../../useFieldType'; import Label from '../../Label'; import Error from '../../Error'; -import withCondition from '../../withCondition'; +import withConditions from '../../withConditions'; import './index.scss'; @@ -96,4 +96,4 @@ NumberField.propTypes = { label: PropTypes.string, }; -export default withCondition(NumberField); +export default withConditions(NumberField); diff --git a/src/client/components/forms/field-types/Password/index.js b/src/client/components/forms/field-types/Password/index.js index 9a739eda00..5ac4b37acb 100644 --- a/src/client/components/forms/field-types/Password/index.js +++ b/src/client/components/forms/field-types/Password/index.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import useFieldType from '../../useFieldType'; import Label from '../../Label'; import Error from '../../Error'; -import withCondition from '../../withCondition'; +import withConditions from '../../withConditions'; import './index.scss'; @@ -92,4 +92,4 @@ Password.propTypes = { validate: PropTypes.func, }; -export default withCondition(Password); +export default withConditions(Password); diff --git a/src/client/components/forms/field-types/Relationship/index.js b/src/client/components/forms/field-types/Relationship/index.js index 41d2f2cb65..72b4ca3a8b 100644 --- a/src/client/components/forms/field-types/Relationship/index.js +++ b/src/client/components/forms/field-types/Relationship/index.js @@ -2,7 +2,7 @@ import React, { Component, useState, useEffect } from 'react'; import PropTypes from 'prop-types'; import Cookies from 'universal-cookie'; import some from 'async-some'; -import withCondition from '../../withCondition'; +import withConditions from '../../withConditions'; import ReactSelect from '../../../elements/ReactSelect'; import useFieldType from '../../useFieldType'; import Label from '../../Label'; @@ -338,4 +338,4 @@ const RelationshipFieldType = (props) => { ); }; -export default withCondition(RelationshipFieldType); +export default withConditions(RelationshipFieldType); diff --git a/src/client/components/forms/field-types/Repeater/index.js b/src/client/components/forms/field-types/Repeater/index.js index 2b3412a33b..8a1d2e7c37 100644 --- a/src/client/components/forms/field-types/Repeater/index.js +++ b/src/client/components/forms/field-types/Repeater/index.js @@ -5,7 +5,7 @@ import PropTypes from 'prop-types'; import { DragDropContext, Droppable } from 'react-beautiful-dnd'; import { RowModifiedProvider, useRowModified } from '../../Form/RowModified'; -import withCondition from '../../withCondition'; +import withConditions from '../../withConditions'; import Button from '../../../elements/Button'; import FormContext from '../../Form/Context'; import DraggableSection from '../../DraggableSection'; @@ -163,4 +163,4 @@ Repeater.propTypes = { fieldTypes: PropTypes.shape({}).isRequired, }; -export default withCondition(Repeater); +export default withConditions(Repeater); diff --git a/src/client/components/forms/field-types/Select/index.js b/src/client/components/forms/field-types/Select/index.js index e2de794599..f9264ede34 100644 --- a/src/client/components/forms/field-types/Select/index.js +++ b/src/client/components/forms/field-types/Select/index.js @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import withCondition from '../../withCondition'; +import withConditions from '../../withConditions'; import ReactSelect from '../../../elements/ReactSelect'; import useFieldType from '../../useFieldType'; import Label from '../../Label'; @@ -153,4 +153,4 @@ Select.propTypes = { hasMany: PropTypes.bool, }; -export default withCondition(Select); +export default withConditions(Select); diff --git a/src/client/components/forms/field-types/Text/index.js b/src/client/components/forms/field-types/Text/index.js index c56f3e239b..6f024a2e47 100644 --- a/src/client/components/forms/field-types/Text/index.js +++ b/src/client/components/forms/field-types/Text/index.js @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import useFieldType from '../../useFieldType'; -import withCondition from '../../withCondition'; +import withConditions from '../../withConditions'; import Label from '../../Label'; import Error from '../../Error'; @@ -96,4 +96,4 @@ Text.propTypes = { label: PropTypes.string, }; -export default withCondition(Text); +export default withConditions(Text); diff --git a/src/client/components/forms/field-types/Textarea/index.js b/src/client/components/forms/field-types/Textarea/index.js index 9d17a868a7..487c523c5f 100644 --- a/src/client/components/forms/field-types/Textarea/index.js +++ b/src/client/components/forms/field-types/Textarea/index.js @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import useFieldType from '../../useFieldType'; -import withCondition from '../../withCondition'; +import withConditions from '../../withConditions'; import Label from '../../Label'; import Error from '../../Error'; @@ -95,4 +95,4 @@ Textarea.propTypes = { placeholder: PropTypes.string, }; -export default withCondition(Textarea); +export default withConditions(Textarea); diff --git a/src/client/components/forms/field-types/Upload/index.js b/src/client/components/forms/field-types/Upload/index.js index ccd08d0f2f..2b32d9f217 100644 --- a/src/client/components/forms/field-types/Upload/index.js +++ b/src/client/components/forms/field-types/Upload/index.js @@ -1,6 +1,6 @@ import React, { Component } from 'react'; import useFieldType from '../../useFieldType'; -import withCondition from '../../withCondition'; +import withConditions from '../../withConditions'; import UploadMedia from '../../../modules/UploadMedia'; import './index.scss'; @@ -60,4 +60,4 @@ class Media extends Component { } } -export default withCondition(fieldType(Media, 'media', validate, error)); +export default withConditions(fieldType(Media, 'media', validate, error)); diff --git a/src/client/components/forms/useFieldType/index.js b/src/client/components/forms/useFieldType/index.js index 6e967ce877..6cc86e981e 100644 --- a/src/client/components/forms/useFieldType/index.js +++ b/src/client/components/forms/useFieldType/index.js @@ -14,7 +14,9 @@ const useFieldType = (options) => { const formContext = useContext(FormContext); const { dispatchFields, submitted, processing } = formContext; - const mountValue = formContext.fields[name]?.value || null; + let mountValue = formContext.fields[name]?.value; + + if (!mountValue && mountValue !== false) mountValue = null; const sendField = useCallback((valueToSend) => { dispatchFields({ diff --git a/src/client/components/forms/withCondition/index.js b/src/client/components/forms/withCondition/index.js deleted file mode 100644 index 798c77899f..0000000000 --- a/src/client/components/forms/withCondition/index.js +++ /dev/null @@ -1,54 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -import useForm from '../Form/useForm'; - -const withCondition = (Field) => { - const WithCondition = (props) => { - const { condition, name } = props; - const { fields } = useForm(); - - if (condition) { - let siblingFields = fields; - - // If this field is nested - // We can provide a list of sibling fields - if (name.indexOf('.') > 0) { - const parentFieldPath = name.substring(0, name.lastIndexOf('.') + 1); - siblingFields = Object.keys(fields).reduce((siblings, fieldKey) => { - if (fieldKey.indexOf(parentFieldPath) === 0) { - return { - ...siblings, - [fieldKey.replace(parentFieldPath, '')]: fields[fieldKey], - }; - } - - return siblings; - }, {}); - } - - const passesCondition = condition ? condition(fields, siblingFields) : true; - - if (passesCondition) { - return ; - } - - return null; - } - - return ; - }; - - WithCondition.defaultProps = { - condition: null, - }; - - WithCondition.propTypes = { - condition: PropTypes.func, - name: PropTypes.string.isRequired, - }; - - return WithCondition; -}; - -export default withCondition; diff --git a/src/client/components/forms/withConditions/index.js b/src/client/components/forms/withConditions/index.js new file mode 100644 index 0000000000..24990d553d --- /dev/null +++ b/src/client/components/forms/withConditions/index.js @@ -0,0 +1,112 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import useForm from '../Form/useForm'; + +const recursiveCheckCondition = (object, siblingFields) => Object.keys(object).every((key) => { + if (key.toLowerCase() === 'and') { + return object[key].every((andCondition) => { + const result = recursiveCheckCondition(andCondition, siblingFields); + return result; + }); + } + + if (key.toLowerCase() === 'or') { + return object[key].some((orCondition) => { + const result = recursiveCheckCondition(orCondition, siblingFields); + return result; + }); + } + + if (typeof object[key] === 'object') { + return Object.keys(object[key]).every((operator) => { + const value = siblingFields?.[key]?.value; + const valueToCompare = object[key][operator]; + + let result = false; + + if (operator === 'equals') { + result = value === valueToCompare; + } + + if (operator === 'not_equals') { + result = value !== valueToCompare; + } + + if (typeof value === 'number') { + if (operator === 'greater_than') { + result = value > valueToCompare; + } + + if (operator === 'greater_than_equals') { + result = value >= valueToCompare; + } + + if (operator === 'less_than') { + result = value < valueToCompare; + } + + if (operator === 'less_than_equals') { + result = value <= valueToCompare; + } + } + + return result; + }); + } + + return false; +}); + +const withConditions = (Field) => { + const WithConditions = (props) => { + const { conditions, name } = props; + const { fields } = useForm(); + + if (conditions) { + let siblingFields = fields; + + // If this field is nested + // We can provide a list of sibling fields + if (name.indexOf('.') > 0) { + const parentFieldPath = name.substring(0, name.lastIndexOf('.') + 1); + siblingFields = Object.keys(fields).reduce((siblings, fieldKey) => { + if (fieldKey.indexOf(parentFieldPath) === 0) { + return { + ...siblings, + [fieldKey.replace(parentFieldPath, '')]: fields[fieldKey], + }; + } + + return siblings; + }, {}); + } + + const passesConditions = recursiveCheckCondition(conditions, siblingFields); + + if (passesConditions) { + return ; + } + + return null; + } + + return ; + }; + + WithConditions.defaultProps = { + conditions: null, + }; + + WithConditions.propTypes = { + conditions: PropTypes.oneOfType([ + PropTypes.shape({}), + PropTypes.array, + ]), + name: PropTypes.string.isRequired, + }; + + return WithConditions; +}; + +export default withConditions; diff --git a/src/fields/schemaMap.js b/src/fields/schemaMap.js index 85053197d2..fb74b669c1 100644 --- a/src/fields/schemaMap.js +++ b/src/fields/schemaMap.js @@ -5,7 +5,7 @@ const formatBaseSchema = (field) => { hide: field.hidden === 'api' || field.hidden === true, localized: field.localized || false, unique: field.unique || false, - required: (field.required && !field.localized && !field.hidden) || false, + required: (field.required && !field.localized && !field.hidden && !field.conditions) || false, default: field.defaultValue || undefined, }; };