diff --git a/src/client/components/forms/Form/flattenFilters.js b/src/client/components/forms/Form/flattenFilters.js new file mode 100644 index 0000000000..bfd3972c5b --- /dev/null +++ b/src/client/components/forms/Form/flattenFilters.js @@ -0,0 +1,10 @@ +const flattenFilters = [{ + test: (_, value) => { + const hasValidProperty = Object.prototype.hasOwnProperty.call(value, 'valid'); + const hasValueProperty = Object.prototype.hasOwnProperty.call(value, 'value'); + + return (hasValidProperty && hasValueProperty); + }, +}]; + +export default flattenFilters; diff --git a/src/client/components/forms/Form/index.js b/src/client/components/forms/Form/index.js index fb76f3fc7f..d33da3ae61 100644 --- a/src/client/components/forms/Form/index.js +++ b/src/client/components/forms/Form/index.js @@ -1,4 +1,4 @@ -import React, { useState, useReducer } from 'react'; +import React, { useContext, useState, useReducer } from 'react'; import { useHistory } from 'react-router-dom'; import PropTypes from 'prop-types'; import { unflatten } from 'flatley'; @@ -15,6 +15,8 @@ import './index.scss'; const baseClass = 'form'; +export const useForm = () => useContext(FormContext); + const Form = (props) => { const [fields, dispatchFields] = useReducer(fieldReducer, {}); const [submitted, setSubmitted] = useState(false); diff --git a/src/client/components/forms/Form/reducer.js b/src/client/components/forms/Form/reducer.js index 91c0cce7e6..2b0629a617 100644 --- a/src/client/components/forms/Form/reducer.js +++ b/src/client/components/forms/Form/reducer.js @@ -1,4 +1,5 @@ import { unflatten, flatten } from 'flatley'; +import flattenFilters from './flattenFilters'; const splitRowsFromState = (state, name) => { // Take a copy of state @@ -26,15 +27,6 @@ const splitRowsFromState = (state, name) => { }; }; -const flattenFilters = [{ - test: (_, value) => { - const hasValidProperty = Object.prototype.hasOwnProperty.call(value, 'valid'); - const hasValueProperty = Object.prototype.hasOwnProperty.call(value, 'value'); - - return (hasValidProperty && hasValueProperty); - }, -}]; - function fieldReducer(state, action) { switch (action.type) { case 'REPLACE_ALL': diff --git a/src/client/components/forms/field-types/Number/index.js b/src/client/components/forms/field-types/Number/index.js index 37db01e8f2..cc36341d74 100644 --- a/src/client/components/forms/field-types/Number/index.js +++ b/src/client/components/forms/field-types/Number/index.js @@ -61,7 +61,7 @@ const Number = (props) => { /> onFieldChange(parseInt(e.target.value, 10))} disabled={formProcessing ? 'disabled' : undefined} placeholder={placeholder} type="number" diff --git a/src/client/components/forms/field-types/Textarea/index.js b/src/client/components/forms/field-types/Textarea/index.js index 715668308b..9d17a868a7 100644 --- a/src/client/components/forms/field-types/Textarea/index.js +++ b/src/client/components/forms/field-types/Textarea/index.js @@ -1,6 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import useFieldType from '../../useFieldType'; +import withCondition from '../../withCondition'; import Label from '../../Label'; import Error from '../../Error'; @@ -79,6 +80,7 @@ Textarea.defaultProps = { errorMessage: defaultError, width: 100, style: {}, + placeholder: null, }; Textarea.propTypes = { @@ -90,6 +92,7 @@ Textarea.propTypes = { width: PropTypes.number, style: PropTypes.shape({}), label: PropTypes.string, + placeholder: PropTypes.string, }; -export default Textarea; +export default withCondition(Textarea); diff --git a/src/client/components/forms/useFieldType/index.js b/src/client/components/forms/useFieldType/index.js index c74c3fcc49..6e967ce877 100644 --- a/src/client/components/forms/useFieldType/index.js +++ b/src/client/components/forms/useFieldType/index.js @@ -26,14 +26,18 @@ const useFieldType = (options) => { }); }, [name, required, dispatchFields, validate]); + // Send value up to form on mount and when value changes useEffect(() => { sendField(mountValue); }, [sendField, mountValue]); + // Remove field from state on "unmount" useEffect(() => { return () => dispatchFields({ name, type: 'REMOVE' }); }, [dispatchFields, name]); + // Send up new value when default is loaded + // only if it's not null useEffect(() => { if (defaultValue != null) sendField(defaultValue); }, [defaultValue, sendField]); diff --git a/src/client/components/forms/withCondition/index.js b/src/client/components/forms/withCondition/index.js new file mode 100644 index 0000000000..dcb2f6f32e --- /dev/null +++ b/src/client/components/forms/withCondition/index.js @@ -0,0 +1,54 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import { useForm } from '../Form'; + +const withCondition = (Field) => { + const WithCondition = (props) => { + const { condition, name } = props; + const { fields } = useForm(); + + if (condition) { + let siblingFields = {}; + + // 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/views/globals/Edit/index.js b/src/client/components/views/globals/Edit/index.js index 22ddd4f0b2..9a16a7a1cb 100644 --- a/src/client/components/views/globals/Edit/index.js +++ b/src/client/components/views/globals/Edit/index.js @@ -50,12 +50,12 @@ const EditView = (props) => {
+ } action={( <> @@ -77,6 +77,7 @@ EditView.propTypes = { global: PropTypes.shape({ label: PropTypes.string, slug: PropTypes.string, + fields: PropTypes.arrayOf(PropTypes.shape({})), }).isRequired, }; diff --git a/src/client/securedConfig.js b/src/client/securedConfig.js index ee6e5c5ce2..5473fdbb71 100644 --- a/src/client/securedConfig.js +++ b/src/client/securedConfig.js @@ -1,12 +1,39 @@ const sanitizeConfig = require('../utilities/sanitizeConfig'); const secureConfig = require('../utilities/secureConfig'); +function convertToText(obj) { + const string = []; + + if (obj === undefined || obj === null) { + return String(obj); + } if (typeof (obj) === 'object' && (obj.join === undefined || obj.join === null)) { + Object.keys(obj).forEach((prop) => { + if (Object.prototype.hasOwnProperty.call(obj, prop)) string.push(`${prop}: ${convertToText(obj[prop])}`); + }); + + return `{${string.join(',')}}`; + } if (typeof (obj) === 'object' && !(obj.join === undefined || obj.join === null)) { + Object.keys(obj).forEach((prop) => { + string.push(convertToText(obj[prop])); + }); + + return `[${string.join(',')}]`; + } if (typeof (obj) === 'function') { + string.push(obj.toString()); + } else { + string.push(JSON.stringify(obj)); + } + + return string.join(','); +} + module.exports = (config) => { const sanitizedConfig = sanitizeConfig(config); const securedConfig = secureConfig(sanitizedConfig); + const stringifiedConfig = convertToText(securedConfig); return { code: ` - module.exports = ${JSON.stringify(securedConfig)}`, + module.exports = ${stringifiedConfig}`, }; };