diff --git a/src/formio/components/AddressNL.jsx b/src/formio/components/AddressNL.jsx
index e2ab54c93..9e1331532 100644
--- a/src/formio/components/AddressNL.jsx
+++ b/src/formio/components/AddressNL.jsx
@@ -3,7 +3,7 @@
*/
import {Formik, useFormikContext} from 'formik';
import debounce from 'lodash/debounce';
-import {useContext, useEffect} from 'react';
+import {useContext, useEffect, useState} from 'react';
import {createRoot} from 'react-dom/client';
import {Formio} from 'react-formio';
import {FormattedMessage, IntlProvider, defineMessages, useIntl} from 'react-intl';
@@ -61,6 +61,50 @@ export default class AddressNL extends Field {
if (this.component.validate.plugins && this.component.validate.plugins.length) {
updatedOptions.async = true;
}
+
+ if (!dirty) {
+ return super.checkComponentValidity(data, dirty, row, updatedOptions);
+ }
+
+ const {postcode, houseNumber, city, streetName} = this.dataValue;
+ if (this.component?.validate?.required) {
+ if (
+ this.component.deriveAddress &&
+ [postcode, houseNumber, city, streetName].some(value => value === '')
+ ) {
+ const messages = [
+ {
+ message: this.t('Required fields can not be empty.'),
+ level: 'error',
+ },
+ ];
+ this.setComponentValidity(messages, true, false);
+ return false;
+ } else if (
+ !this.component.deriveAddress &&
+ [postcode, houseNumber].some(value => value === '')
+ ) {
+ const messages = [
+ {
+ message: this.t('Required fields can not be empty.'),
+ level: 'error',
+ },
+ ];
+ this.setComponentValidity(messages, true, false);
+ return false;
+ }
+ } else {
+ if ((postcode && !houseNumber) || (!postcode && houseNumber)) {
+ const messages = [
+ {
+ message: this.t('Both postcode and housenumber fields are required or none of them.'),
+ level: 'error',
+ },
+ ];
+ this.setComponentValidity(messages, true, false);
+ return false;
+ }
+ }
return super.checkComponentValidity(data, dirty, row, updatedOptions);
}
@@ -77,6 +121,7 @@ export default class AddressNL extends Field {
city: '',
streetName: '',
secretStreetCity: '',
+ autoPopulated: false,
};
}
@@ -199,6 +244,22 @@ const FIELD_LABELS = defineMessages({
description: 'Label for addressNL houseNumber input',
defaultMessage: 'House number',
},
+ houseLetter: {
+ description: 'Label for addressNL houseLetter input',
+ defaultMessage: 'House letter',
+ },
+ houseNumberAddition: {
+ description: 'Label for addressNL houseNumberAddition input',
+ defaultMessage: 'House number addition',
+ },
+ streetName: {
+ description: 'Label for addressNL streetName input',
+ defaultMessage: 'Street name',
+ },
+ city: {
+ description: 'Label for addressNL city input',
+ defaultMessage: 'City',
+ },
});
const addressNLSchema = (required, intl, {postcode = {}, city = {}}) => {
@@ -216,6 +277,7 @@ const addressNLSchema = (required, intl, {postcode = {}, city = {}}) => {
});
let postcodeSchema = z.string().regex(postcodeRegex, {message: postcodeErrorMessage});
+ let streetNameSchema = z.string();
const {pattern: cityPattern = '', errorMessage: cityErrorMessage = ''} = city;
let citySchema = z.string();
if (cityPattern) {
@@ -235,12 +297,15 @@ const addressNLSchema = (required, intl, {postcode = {}, city = {}}) => {
if (!required) {
postcodeSchema = postcodeSchema.optional();
houseNumberSchema = houseNumberSchema.optional();
+ streetNameSchema = streetNameSchema.optional();
+ citySchema = citySchema.optional();
}
return z
.object({
postcode: postcodeSchema,
- city: citySchema.optional(),
+ streetName: streetNameSchema,
+ city: citySchema,
houseNumber: houseNumberSchema,
houseLetter: z
.string()
@@ -340,6 +405,7 @@ const AddressNLForm = ({initialValues, required, deriveAddress, layout, setFormi
postcode: true,
houseNumber: true,
city: true,
+ streetName: true,
}}
validationSchema={toFormikValidationSchema(
addressNLSchema(required, intl, {
@@ -368,6 +434,7 @@ const AddressNLForm = ({initialValues, required, deriveAddress, layout, setFormi
const FormikAddress = ({required, setFormioValues, deriveAddress, layout}) => {
const {values, isValid, setFieldValue} = useFormikContext();
const {baseUrl} = useContext(ConfigContext);
+ const [isAddressAutoFilled, setAddressAutoFilled] = useState(true);
const useColumns = layout === 'doubleColumn';
useEffect(() => {
@@ -394,6 +461,12 @@ const FormikAddress = ({required, setFormioValues, deriveAddress, layout}) => {
setFieldValue('city', data['city']);
setFieldValue('streetName', data['streetName']);
setFieldValue('secretStreetCity', data['secretStreetCity']);
+
+ // mark the auto-filled fields as populated and disabled when they have been both
+ // retrieved from the API and they do have a value
+ const dataRetrieved = !!(data['city'] && data['streetName']);
+ setAddressAutoFilled(dataRetrieved);
+ setFieldValue('autoPopulated', dataRetrieved);
};
return (
@@ -406,45 +479,24 @@ const FormikAddress = ({required, setFormioValues, deriveAddress, layout}) => {
>
-
- }
- />
+ } />
- }
+ label={}
/>
{deriveAddress && (
<>
- }
- disabled
+ label={}
+ disabled={isAddressAutoFilled}
+ isRequired={required}
/>
- }
- disabled
+ label={}
+ disabled={isAddressAutoFilled}
+ isRequired={required}
/>
>
)}
diff --git a/src/formio/components/AddressNL.stories.js b/src/formio/components/AddressNL.stories.js
index 20bd619c8..8db81161b 100644
--- a/src/formio/components/AddressNL.stories.js
+++ b/src/formio/components/AddressNL.stories.js
@@ -1,6 +1,7 @@
import {expect, userEvent, waitFor, within} from '@storybook/test';
import {ConfigDecorator, withUtrechtDocument} from 'story-utils/decorators';
+import {sleep} from 'utils';
import {
mockBAGDataGet,
@@ -84,6 +85,28 @@ export const ClientSideValidation = {
},
};
+export const Required = {
+ args: {
+ extraComponentProperties: {
+ validate: {
+ required: true,
+ },
+ },
+ },
+ render: SingleFormioComponent,
+ play: async ({canvasElement}) => {
+ await sleep(500);
+ const canvas = within(canvasElement);
+
+ (await canvas.findByLabelText('Huisnummer')).focus();
+ await userEvent.tab();
+
+ expect(await canvas.findByText('Postcode is verplicht.')).toBeVisible();
+ expect(await canvas.findByText('Huisnummer is verplicht.')).toBeVisible();
+ expect(await canvas.findByText('required')).toBeVisible();
+ },
+};
+
export const NotRequired = {
args: {
extraComponentProperties: {
@@ -195,7 +218,7 @@ export const WithFailedBRKValidation = {
// },
};
-export const WithDeriveCityStreetNameWithData = {
+export const WithDeriveCityStreetNameWithDataNotRequired = {
render: SingleFormioComponent,
parameters: {
msw: {
@@ -234,7 +257,7 @@ export const WithDeriveCityStreetNameWithData = {
const houseNumberInput = await canvas.findByLabelText('Huisnummer');
await userEvent.type(houseNumberInput, '1');
- const city = await canvas.findByLabelText('Stad');
+ const city = await canvas.findByLabelText('Plaats');
const streetName = await canvas.findByLabelText('Straatnaam');
await userEvent.tab();
@@ -322,7 +345,7 @@ export const WithDeriveCityStreetNameWithDataIncorrectCity = {
const houseNumberInput = await canvas.findByLabelText('Huisnummer');
await userEvent.type(houseNumberInput, '1');
- const city = await canvas.findByLabelText('Stad');
+ const city = await canvas.findByLabelText('Plaats');
const streetName = await canvas.findByLabelText('Straatnaam');
await userEvent.tab();
@@ -338,7 +361,7 @@ export const WithDeriveCityStreetNameWithDataIncorrectCity = {
},
};
-export const WithDeriveCityStreetNameNoData = {
+export const WithDeriveCityStreetNameNoDataAndNotRequired = {
render: SingleFormioComponent,
parameters: {
msw: {
@@ -365,14 +388,16 @@ export const WithDeriveCityStreetNameNoData = {
const houseNumberInput = await canvas.findByLabelText('Huisnummer');
await userEvent.type(houseNumberInput, '1');
- const city = await canvas.findByLabelText('Stad');
+ const city = await canvas.findByLabelText('Plaats');
const streetName = await canvas.findByLabelText('Straatnaam');
await userEvent.tab();
await waitFor(() => {
expect(city.value).toBe('');
+ expect(city).not.toBeDisabled();
expect(streetName.value).toBe('');
+ expect(streetName).not.toBeDisabled();
});
},
};
diff --git a/src/i18n/compiled/en.json b/src/i18n/compiled/en.json
index 69d4d9e5d..cd7ed52c5 100644
--- a/src/i18n/compiled/en.json
+++ b/src/i18n/compiled/en.json
@@ -397,6 +397,12 @@
"value": "Check and confirm"
}
],
+ "AKhmW+": [
+ {
+ "type": 0,
+ "value": "Street name"
+ }
+ ],
"AM6xqd": [
{
"type": 0,
@@ -491,12 +497,6 @@
"value": "Remove"
}
],
- "DEetjI": [
- {
- "type": 0,
- "value": "Street name"
- }
- ],
"DK2ewv": [
{
"type": 0,
@@ -1925,12 +1925,6 @@
"value": "."
}
],
- "osSl3z": [
- {
- "type": 0,
- "value": "City"
- }
- ],
"ovI+W7": [
{
"type": 0,
@@ -2069,6 +2063,12 @@
"value": "Send code"
}
],
+ "s4+4p2": [
+ {
+ "type": 0,
+ "value": "City"
+ }
+ ],
"sSmY1N": [
{
"type": 0,
diff --git a/src/i18n/compiled/nl.json b/src/i18n/compiled/nl.json
index a57a85100..4ab79b983 100644
--- a/src/i18n/compiled/nl.json
+++ b/src/i18n/compiled/nl.json
@@ -397,6 +397,12 @@
"value": "Controleer en bevestig"
}
],
+ "AKhmW+": [
+ {
+ "type": 0,
+ "value": "Straatnaam"
+ }
+ ],
"AM6xqd": [
{
"type": 0,
@@ -491,12 +497,6 @@
"value": "Verwijderen"
}
],
- "DEetjI": [
- {
- "type": 0,
- "value": "Straatnaam"
- }
- ],
"DK2ewv": [
{
"type": 0,
@@ -1929,12 +1929,6 @@
"value": " zijn."
}
],
- "osSl3z": [
- {
- "type": 0,
- "value": "Stad"
- }
- ],
"ovI+W7": [
{
"type": 0,
@@ -2073,6 +2067,12 @@
"value": "Verstuur code"
}
],
+ "s4+4p2": [
+ {
+ "type": 0,
+ "value": "Plaats"
+ }
+ ],
"sSmY1N": [
{
"type": 0,
diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json
index 965f9c360..2e5b3ab7b 100644
--- a/src/i18n/messages/en.json
+++ b/src/i18n/messages/en.json
@@ -174,6 +174,11 @@
"description": "Check overview and confirm",
"originalDefault": "Check and confirm"
},
+ "AKhmW+": {
+ "defaultMessage": "Street name",
+ "description": "Label for addressNL streetName input",
+ "originalDefault": "Street name"
+ },
"AM6xqd": {
"defaultMessage": "House number must be a number with up to five digits (e.g. 456).",
"description": "ZOD error message when AddressNL house number does not match the house number regular expression",
@@ -249,11 +254,6 @@
"description": "Appointments: remove product/service button text",
"originalDefault": "Remove"
},
- "DEetjI": {
- "defaultMessage": "Street name",
- "description": "Label for addressNL streetName read only result",
- "originalDefault": "Street name"
- },
"DK2ewv": {
"defaultMessage": "Authentication problem",
"description": "'Permission denied' error title",
@@ -934,11 +934,6 @@
"description": "ZOD 'too_big' error message, for BigInt",
"originalDefault": "BigInt must be {exact, select, true {exactly equal to} other {{inclusive, select, true {less than or equal to} other {less than}}} } {maximum}."
},
- "osSl3z": {
- "defaultMessage": "City",
- "description": "Label for addressNL city read only result",
- "originalDefault": "City"
- },
"ovI+W7": {
"defaultMessage": "Use ⌘ + scroll to zoom the map",
"description": "Gesturehandeling mac scroll message.",
@@ -984,6 +979,11 @@
"description": "Email verification: send code button text",
"originalDefault": "Send code"
},
+ "s4+4p2": {
+ "defaultMessage": "City",
+ "description": "Label for addressNL city input",
+ "originalDefault": "City"
+ },
"sSmY1N": {
"defaultMessage": "Find address",
"description": "The leaflet map's input fields placeholder message.",
diff --git a/src/i18n/messages/nl.json b/src/i18n/messages/nl.json
index 4ef35f536..73fed4ab6 100644
--- a/src/i18n/messages/nl.json
+++ b/src/i18n/messages/nl.json
@@ -176,6 +176,11 @@
"description": "Check overview and confirm",
"originalDefault": "Check and confirm"
},
+ "AKhmW+": {
+ "defaultMessage": "Straatnaam",
+ "description": "Label for addressNL streetName input",
+ "originalDefault": "Street name"
+ },
"AM6xqd": {
"defaultMessage": "Huisnummer moet een nummer zijn met maximaal 5 cijfers (bijv. 456).",
"description": "ZOD error message when AddressNL house number does not match the house number regular expression",
@@ -252,11 +257,6 @@
"description": "Appointments: remove product/service button text",
"originalDefault": "Remove"
},
- "DEetjI": {
- "defaultMessage": "Straatnaam",
- "description": "Label for addressNL streetName read only result",
- "originalDefault": "Street name"
- },
"DK2ewv": {
"defaultMessage": "Inlogprobleem",
"description": "'Permission denied' error title",
@@ -946,11 +946,6 @@
"description": "ZOD 'too_big' error message, for BigInt",
"originalDefault": "BigInt must be {exact, select, true {exactly equal to} other {{inclusive, select, true {less than or equal to} other {less than}}} } {maximum}."
},
- "osSl3z": {
- "defaultMessage": "Stad",
- "description": "Label for addressNL city read only result",
- "originalDefault": "City"
- },
"ovI+W7": {
"defaultMessage": "Gebruik ⌘ + scroll om te zoomen in de kaart",
"description": "Gesturehandeling mac scroll message.",
@@ -996,6 +991,11 @@
"description": "Email verification: send code button text",
"originalDefault": "Send code"
},
+ "s4+4p2": {
+ "defaultMessage": "Plaats",
+ "description": "Label for addressNL city input",
+ "originalDefault": "City"
+ },
"sSmY1N": {
"defaultMessage": "Zoek adres",
"description": "The leaflet map's input fields placeholder message.",