enables conditional field logic
This commit is contained in:
10
src/client/components/forms/Form/flattenFilters.js
Normal file
10
src/client/components/forms/Form/flattenFilters.js
Normal file
@@ -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;
|
||||
@@ -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);
|
||||
|
||||
@@ -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':
|
||||
|
||||
@@ -61,7 +61,7 @@ const Number = (props) => {
|
||||
/>
|
||||
<input
|
||||
value={value || ''}
|
||||
onChange={onFieldChange}
|
||||
onChange={e => onFieldChange(parseInt(e.target.value, 10))}
|
||||
disabled={formProcessing ? 'disabled' : undefined}
|
||||
placeholder={placeholder}
|
||||
type="number"
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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]);
|
||||
|
||||
54
src/client/components/forms/withCondition/index.js
Normal file
54
src/client/components/forms/withCondition/index.js
Normal file
@@ -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 <Field {...props} />;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return <Field {...props} />;
|
||||
};
|
||||
|
||||
WithCondition.defaultProps = {
|
||||
condition: null,
|
||||
};
|
||||
|
||||
WithCondition.propTypes = {
|
||||
condition: PropTypes.func,
|
||||
name: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
return WithCondition;
|
||||
};
|
||||
|
||||
export default withCondition;
|
||||
@@ -50,12 +50,12 @@ const EditView = (props) => {
|
||||
<Form
|
||||
className={`${baseClass}__form`}
|
||||
method={data ? 'put' : 'post'}
|
||||
action={`${serverURL}/globals/${global.slug}`}
|
||||
action={`${serverURL}${api}/globals/${global.slug}`}
|
||||
>
|
||||
<StickyHeader
|
||||
showStatus
|
||||
content={
|
||||
<APIURL url={`${serverURL}/globals/${global.slug}`} />
|
||||
<APIURL url={`${serverURL}${api}/globals/${global.slug}`} />
|
||||
}
|
||||
action={(
|
||||
<>
|
||||
@@ -77,6 +77,7 @@ EditView.propTypes = {
|
||||
global: PropTypes.shape({
|
||||
label: PropTypes.string,
|
||||
slug: PropTypes.string,
|
||||
fields: PropTypes.arrayOf(PropTypes.shape({})),
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
|
||||
@@ -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}`,
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user