diff --git a/src/openforms/js/compiled-lang/en.json b/src/openforms/js/compiled-lang/en.json index e2f7fe336b..c4c3900c1f 100644 --- a/src/openforms/js/compiled-lang/en.json +++ b/src/openforms/js/compiled-lang/en.json @@ -1973,6 +1973,12 @@ "value": "The text that will be displayed in the overview page to confirm the form is filled in correctly. Leave blank to get value from global configuration." } ], + "Ggu4I8": [ + { + "type": 0, + "value": "Whether to map the specific subfield of addressNl component" + } + ], "Gi+cvN": [ { "type": 0, @@ -2127,6 +2133,12 @@ "value": "Successful Submissions Removal Method field label" } ], + "I3bGFV": [ + { + "type": 0, + "value": "House number Schema target" + } + ], "I3zyib": [ { "type": 0, @@ -4375,6 +4387,12 @@ "value": "Attachments" } ], + "eD5x1W": [ + { + "type": 0, + "value": "House letter Schema target" + } + ], "eJEu3L": [ { "type": 0, @@ -4585,6 +4603,12 @@ "value": "Are you sure that you want to remove this mapping?" } ], + "gdEbdr": [ + { + "type": 0, + "value": "Map specific subfields" + } + ], "gheRkp": [ { "type": 0, @@ -5303,6 +5327,12 @@ "value": "Something went wrong while retrieving the available object type versions." } ], + "ndWy6O": [ + { + "type": 0, + "value": "City Schema target" + } + ], "neCqv9": [ { "type": 0, @@ -5327,6 +5357,12 @@ "value": "Tooltip" } ], + "o4AUIi": [ + { + "type": 0, + "value": "Multiple target paths configured" + } + ], "o5Mfn3": [ { "type": 0, @@ -5339,6 +5375,12 @@ "value": "The email addresses to which the submission details will be sent" } ], + "o5iEgm": [ + { + "type": 0, + "value": "JSON Schema object target" + } + ], "o83CBr": [ { "type": 0, @@ -5735,6 +5777,12 @@ "value": "If specified, the user must check at most this many options." } ], + "sTZk2h": [ + { + "type": 0, + "value": "Street name Schema target" + } + ], "sU/n0S": [ { "type": 0, @@ -5879,6 +5927,12 @@ "value": "Footer" } ], + "u7m/jP": [ + { + "type": 0, + "value": "Postcode Schema target" + } + ], "uBO/Al": [ { "type": 0, @@ -6161,6 +6215,12 @@ "value": "Create definition" } ], + "xhwyFo": [ + { + "type": 0, + "value": "House number addition Schema target" + } + ], "xxVNeU": [ { "type": 0, diff --git a/src/openforms/js/compiled-lang/nl.json b/src/openforms/js/compiled-lang/nl.json index 7354935a39..91920943b7 100644 --- a/src/openforms/js/compiled-lang/nl.json +++ b/src/openforms/js/compiled-lang/nl.json @@ -1994,6 +1994,12 @@ "value": "De tekst om de ingevulde gegevens te bevestigen op de overzichtspagina. Laat dit veld leeg om de standaardinstelling te gebruiken." } ], + "Ggu4I8": [ + { + "type": 0, + "value": "Whether to map the specific subfield of addressNl component" + } + ], "Gi+cvN": [ { "type": 0, @@ -2148,6 +2154,12 @@ "value": "Opschoonmethode voor voltooide inzendingen" } ], + "I3bGFV": [ + { + "type": 0, + "value": "House number Schema target" + } + ], "I3zyib": [ { "type": 0, @@ -4397,6 +4409,12 @@ "value": "Attachments" } ], + "eD5x1W": [ + { + "type": 0, + "value": "House letter Schema target" + } + ], "eJEu3L": [ { "type": 0, @@ -4607,6 +4625,12 @@ "value": "Ben je zeker dat je deze koppeling wil verwijderen?" } ], + "gdEbdr": [ + { + "type": 0, + "value": "Map specific subfields" + } + ], "gheRkp": [ { "type": 0, @@ -5325,6 +5349,12 @@ "value": "Er ging iets fout bij het ophalen van de objecttypeversies." } ], + "ndWy6O": [ + { + "type": 0, + "value": "City Schema target" + } + ], "neCqv9": [ { "type": 0, @@ -5349,6 +5379,12 @@ "value": "Tooltip" } ], + "o4AUIi": [ + { + "type": 0, + "value": "Multiple target paths configured" + } + ], "o5Mfn3": [ { "type": 0, @@ -5361,6 +5397,12 @@ "value": "The email addresses to which the submission details will be sent" } ], + "o5iEgm": [ + { + "type": 0, + "value": "JSON Schema object target" + } + ], "o83CBr": [ { "type": 0, @@ -5757,6 +5799,12 @@ "value": "Indien opgegeven, dan mag de gebruiker maximaal dit aantal opties aanvinken." } ], + "sTZk2h": [ + { + "type": 0, + "value": "Street name Schema target" + } + ], "sU/n0S": [ { "type": 0, @@ -5901,6 +5949,12 @@ "value": "Voettekst" } ], + "u7m/jP": [ + { + "type": 0, + "value": "Postcode Schema target" + } + ], "uBO/Al": [ { "type": 0, @@ -6183,6 +6237,12 @@ "value": "Formulierdefinitie aanmaken" } ], + "xhwyFo": [ + { + "type": 0, + "value": "House number addition Schema target" + } + ], "xxVNeU": [ { "type": 0, diff --git a/src/openforms/js/components/admin/form_design/registrations/objectsapi/AddressNlObjectsApiVariableConfigurationEditor.js b/src/openforms/js/components/admin/form_design/registrations/objectsapi/AddressNlObjectsApiVariableConfigurationEditor.js new file mode 100644 index 0000000000..7fc71c11c3 --- /dev/null +++ b/src/openforms/js/components/admin/form_design/registrations/objectsapi/AddressNlObjectsApiVariableConfigurationEditor.js @@ -0,0 +1,340 @@ +import {FieldArray, useFormikContext} from 'formik'; +import isEqual from 'lodash/isEqual'; +import PropTypes from 'prop-types'; +import React, {useContext, useState} from 'react'; +import {FormattedMessage} from 'react-intl'; +import {useAsync, useToggle} from 'react-use'; + +import {APIContext} from 'components/admin/form_design/Context'; +import {REGISTRATION_OBJECTS_TARGET_PATHS} from 'components/admin/form_design/constants'; +import Field from 'components/admin/forms/Field'; +import Fieldset from 'components/admin/forms/Fieldset'; +import FormRow from 'components/admin/forms/FormRow'; +import {Checkbox} from 'components/admin/forms/Inputs'; +import Select, {LOADING_OPTION} from 'components/admin/forms/Select'; +import ErrorMessage from 'components/errors/ErrorMessage'; +import {post} from 'utils/fetch'; + +const ADDRESSNL_NESTED_PROPERTIES = { + postcode: {type: 'string'}, + houseLetter: {type: 'string'}, + houseNumber: {type: 'string'}, + houseNumberAddition: {type: 'string'}, + city: {type: 'string'}, + streetName: {type: 'string'}, +}; + +const asJsonSchema = isSpecificTargetPaths => { + // Special handling for addressNL component type. + if (!isSpecificTargetPaths) { + return { + type: 'object', + properties: ADDRESSNL_NESTED_PROPERTIES, + }; + } else { + return { + type: 'string', + }; + } +}; + +export const AddressNlEditor = ({ + variable, + components, + namePrefix, + isGeometry, + index, + mappedVariable, + objecttype, + objectsApiGroup, + objecttypeVersion, + TargetPathDisplay, +}) => { + const {csrftoken} = useContext(APIContext); + const {values, setValues, setFieldValue} = useFormikContext(); + const [jsonSchemaVisible, toggleJsonSchemaVisible] = useToggle(false); + const {specificTargetPaths} = values; + const [isSpecificTargetPaths, setIsSpecificTargetPaths] = useState(specificTargetPaths || false); + + const deriveAddress = components[variable?.key]['deriveAddress']; + + const { + loading, + value: targetPaths, + error, + } = useAsync(async () => { + const response = await post(REGISTRATION_OBJECTS_TARGET_PATHS, csrftoken, { + objectsApiGroup, + objecttype, + objecttypeVersion, + variableJsonSchema: asJsonSchema(isSpecificTargetPaths), + }); + if (!response.ok) { + throw new Error('Error when loading target paths'); + } + + return response.data; + }, [isSpecificTargetPaths]); + + const getTargetPath = pathSegment => targetPaths.find(t => isEqual(t.targetPath, pathSegment)); + + const choices = + loading || error + ? LOADING_OPTION + : targetPaths.map(t => [JSON.stringify(t.targetPath), ]); + + if (error) + return ( + + + + ); + + const onSpecificTargetPathsChange = event => { + setIsSpecificTargetPaths(event.target.checked); + + if (event.target.checked) { + setFieldValue(`${namePrefix}.targetPath`, undefined); + } + + // update Formik's state in order to be able to have this value as the default one for the React's state + setValues({ + ...values, + specificTargetPaths: event.target.checked, + }); + }; + + return ( + <> + + + } + disabled={isSpecificTargetPaths} + > + + + + } + helpText={ + + } + disabled={!!mappedVariable.targetPath} + > + + + + {isSpecificTargetPaths && ( +
+ + + } + > + + + + + + } + > + + + + + + } + > + + + + + + } + > + + + + + + } + disabled={!deriveAddress} + > + + + + + + } + disabled={!deriveAddress} + > + + + +
+ )} +
+ e.preventDefault() || toggleJsonSchemaVisible()}> + + + {jsonSchemaVisible && ( +
+            {loading || (!mappedVariable.targetPath && !isSpecificTargetPaths) ? (
+              
+            ) : isSpecificTargetPaths ? (
+              JSON.stringify(ADDRESSNL_NESTED_PROPERTIES, null, 2)
+            ) : (
+              JSON.stringify(getTargetPath(mappedVariable.targetPath).jsonSchema, null, 2)
+            )}
+          
+ )} +
+ + ); +}; + +const TargetPathSelect = ({id, name, index, choices, mappedVariable, disabled}) => { + // To avoid having an incomplete variable mapping added in the `variablesMapping` array, + // It is added only when an actual target path is selected. This way, having the empty + // option selected means the variable is unmapped (hence the `arrayHelpers.remove` call below). + const { + values: {variablesMapping}, + getFieldProps, + setFieldValue, + } = useFormikContext(); + const props = getFieldProps(name); + const isNew = variablesMapping.length === index; + + return ( + ( + { + if (event.target.value === '') { + arrayHelpers.remove(index); + } else { + if (isNew) { + arrayHelpers.push({...mappedVariable, targetPath: JSON.parse(event.target.value)}); + } else { + setFieldValue(name, JSON.parse(event.target.value)); + } + } + }} + /> + )} + /> + ); +}; + +TargetPathSelect.propTypes = { + name: PropTypes.string.isRequired, + index: PropTypes.number.isRequired, + choices: PropTypes.array.isRequired, + mappedVariable: PropTypes.object.isRequired, +}; diff --git a/src/openforms/js/components/admin/form_design/registrations/objectsapi/ObjectsApiSummaryHandler.js b/src/openforms/js/components/admin/form_design/registrations/objectsapi/ObjectsApiSummaryHandler.js index a132077f71..adeb03641f 100644 --- a/src/openforms/js/components/admin/form_design/registrations/objectsapi/ObjectsApiSummaryHandler.js +++ b/src/openforms/js/components/admin/form_design/registrations/objectsapi/ObjectsApiSummaryHandler.js @@ -40,6 +40,15 @@ const ObjectsApiSummaryHandler = ({variable, backendOptions}) => { ); } + if (!variableMapping.targetPath) { + return ( + + ); + } + return variableMapping.targetPath .map(p => {p}) .reduce((res, item) => ( diff --git a/src/openforms/js/components/admin/form_design/registrations/objectsapi/ObjectsApiVariableConfigurationEditor.js b/src/openforms/js/components/admin/form_design/registrations/objectsapi/ObjectsApiVariableConfigurationEditor.js index 069a2b6cf1..f7d410508f 100644 --- a/src/openforms/js/components/admin/form_design/registrations/objectsapi/ObjectsApiVariableConfigurationEditor.js +++ b/src/openforms/js/components/admin/form_design/registrations/objectsapi/ObjectsApiVariableConfigurationEditor.js @@ -1,24 +1,44 @@ -import {FieldArray, useFormikContext} from 'formik'; -import isEqual from 'lodash/isEqual'; +import {useFormikContext} from 'formik'; import PropTypes from 'prop-types'; import React, {useContext} from 'react'; import {FormattedMessage} from 'react-intl'; -import {useAsync, useToggle} from 'react-use'; -import {APIContext, FormContext} from 'components/admin/form_design/Context'; -import {REGISTRATION_OBJECTS_TARGET_PATHS} from 'components/admin/form_design/constants'; +import {FormContext} from 'components/admin/form_design/Context'; import Field from 'components/admin/forms/Field'; import Fieldset from 'components/admin/forms/Fieldset'; import FormRow from 'components/admin/forms/FormRow'; import {Checkbox, TextInput} from 'components/admin/forms/Inputs'; -import Select, {LOADING_OPTION} from 'components/admin/forms/Select'; -import ErrorMessage from 'components/errors/ErrorMessage'; -import {post} from 'utils/fetch'; -import {asJsonSchema} from './utils'; +import {AddressNlEditor} from './AddressNlObjectsApiVariableConfigurationEditor'; +import {GenericEditor} from './GenericObjectsApiVariableConfigurationEditor'; + +// This can be updated with component-specific variable configuration options which do not +// adhere to the generic behaviour (GenericEditor) +const VARIABLE_CONFIGURATION_OPTIONS = { + addressNL: AddressNlEditor, +}; + +const TargetPathDisplay = ({target}) => { + const path = target.targetPath.length ? target.targetPath.join(' > ') : '/ (root)'; + return ( + + ); +}; + +TargetPathDisplay.propTypes = { + target: PropTypes.shape({ + targetPath: PropTypes.arrayOf(PropTypes.string).isRequired, + isRequired: PropTypes.bool.isRequired, + }).isRequired, +}; /** - * Returns the Objects API Configuration editor modal for a specific variable. This only applies to V2 Options + * Returns the Objects API Configuration editor modal for a specific variable and a specific + * component type. This only applies to V2 Options * * @typedef {{ * version: 1 | 2; @@ -34,10 +54,8 @@ import {asJsonSchema} from './utils'; * @returns {JSX.Element} - The configuration form for the Objects API */ const ObjectsApiVariableConfigurationEditor = ({variable}) => { - const {csrftoken} = useContext(APIContext); - const {components} = useContext(FormContext); - const [jsonSchemaVisible, toggleJsonSchemaVisible] = useToggle(false); const {values: backendOptions, getFieldProps, setFieldValue} = useFormikContext(); + const {components} = useContext(FormContext); /** @type {ObjectsAPIRegistrationBackendOptions} */ const { @@ -71,126 +89,72 @@ const ObjectsApiVariableConfigurationEditor = ({variable}) => { // relative to that const namePrefix = `variablesMapping.${index}`; - const { - loading, - value: targetPaths, - error, - } = useAsync( - async () => { - const response = await post(REGISTRATION_OBJECTS_TARGET_PATHS, csrftoken, { - objectsApiGroup, - objecttype, - objecttypeVersion, - variableJsonSchema: asJsonSchema(variable, components), - }); - if (!response.ok) { - throw new Error('Error when loading target paths'); - } - - return response.data; - }, - // Load only once: - [] - ); - - const getTargetPath = pathSegment => targetPaths.find(t => isEqual(t.targetPath, pathSegment)); - - const choices = - loading || error - ? LOADING_OPTION - : targetPaths.map(t => [JSON.stringify(t.targetPath), ]); - - if (error) - return ( - - - - ); + // check if there is a specific ConfigurationEditor according to the variable type, + // if not, fallback to the default/generic one + const componentType = components[variable?.key]?.type; + const VariableConfigurationEditor = + VARIABLE_CONFIGURATION_OPTIONS?.[componentType] ?? GenericEditor; return ( -
- - - } - > - - - - - - } - helpText={ - record.geometry}} + <> +
+ + + } + > + - } - name="geometryVariableKey" - disabled={!!mappedVariable.targetPath} - > - { - const newValue = event.target.checked ? variable.key : undefined; - setFieldValue('geometryVariableKey', newValue); - }} - /> - - - - + + + + } + helpText={ + record.geometry}} + /> + } + name="geometryVariableKey" + disabled={!!mappedVariable.targetPath} + > + { + const newValue = event.target.checked ? variable.key : undefined; + setFieldValue('geometryVariableKey', newValue); + }} /> - } - disabled={isGeometry} - > - - - -
- e.preventDefault() || toggleJsonSchemaVisible()}> - - - {jsonSchemaVisible && ( -
-            {loading || !mappedVariable.targetPath ? (
-              
-            ) : (
-              JSON.stringify(getTargetPath(mappedVariable.targetPath).jsonSchema, null, 2)
-            )}
-          
- )} -
-
+
+
+
+
+ +
+ ); }; @@ -200,70 +164,4 @@ ObjectsApiVariableConfigurationEditor.propTypes = { }).isRequired, }; -const TargetPathSelect = ({name, index, choices, mappedVariable, disabled}) => { - // To avoid having an incomplete variable mapping added in the `variablesMapping` array, - // It is added only when an actual target path is selected. This way, having the empty - // option selected means the variable is unmapped (hence the `arrayHelpers.remove` call below). - const { - values: {variablesMapping}, - getFieldProps, - setFieldValue, - } = useFormikContext(); - const props = getFieldProps(name); - const isNew = variablesMapping.length === index; - - return ( - ( -