diff --git a/docs/manual/forms/basics.rst b/docs/manual/forms/basics.rst index b4134f4bcb..8cf25b73c9 100644 --- a/docs/manual/forms/basics.rst +++ b/docs/manual/forms/basics.rst @@ -142,7 +142,7 @@ stap. In deze sectie kunt u velden (ook wel componenten genoemd) naar de formulier stap slepen en configureren. Kijk voor alle mogelijkheden naar het overzicht van -:ref:`formulier velden ` en naar de +:ref:`formuliervelden ` en naar de :ref:`voorbeelden `. Bevestiging diff --git a/docs/manual/forms/form_fields.rst b/docs/manual/forms/form_fields.rst index 7ffae425a7..75e6828349 100644 --- a/docs/manual/forms/form_fields.rst +++ b/docs/manual/forms/form_fields.rst @@ -108,7 +108,7 @@ Validatie --------- * **Verplicht**: Indien aangevinkt dan is dit veld verplicht voor de - eindgebruiker. + eindgebruiker. Sommige velden ondersteunen :ref:`niet-blokkerende verplichting `. * **Plugin**: U kunt gebruik maken van een externe plugin om een veld te valideren. De waarde van het veld wordt naar de plugin gestuurd en diff --git a/docs/manual/forms/index.rst b/docs/manual/forms/index.rst index 685a9fb122..651f3e372e 100644 --- a/docs/manual/forms/index.rst +++ b/docs/manual/forms/index.rst @@ -11,6 +11,7 @@ De kern van Open Formulieren is uiteraard het beheren van formulieren. variables form_fields logic + soft_required_fields logic_dmn translations registrator diff --git a/docs/manual/forms/soft_required_fields.rst b/docs/manual/forms/soft_required_fields.rst new file mode 100644 index 0000000000..9b55904fa2 --- /dev/null +++ b/docs/manual/forms/soft_required_fields.rst @@ -0,0 +1,56 @@ +.. _manual_forms_soft_required_fields: + +================================================= +Formuliervelden met niet-blokkerende verplichting +================================================= + +Sommige velden laten toe om ze als "aangeraden" te markeren. In deze configuratie wordt +de gebruiker er van bewust gemaakt dat ze velden onbedoeld leeg laten, maar het blokkeert +ze niet om het formulier in te zenden. + +.. note:: De meeste formuliervelden ondersteunen een + :ref:`validatie-optie ` om het veld verplicht te + maken. Deze kan echter soms (om juridische redenen) niet gebruikt worden omdat een + gebruiker de aanvraag móet kunnen insturen en de organisatie verplicht is om + deze te beoordelen. Het is dan netjes om de gebruiker te kunnen wijzen op eventuele + kosten en gevolgen van het niet aanleveren van alle uitgevraagde gegevens. + +Formulierconfiguratie +===================== + +.. note:: deze documentatie gaat ervan uit dat je bekend met de basis van + :ref:`formulieren beheren `. + +Het is belangrijk dat je in de betreffende formulierstap(pen) een component toevoegt +die de foutmeldingen weergeeft, anders krijgt de gebruiker geen feedback van eventuele +ontbrekende gegevens. + +Voor elke relevante formulierstap: + +1. Sleep zoals je gewend bent de velden uit het menu aan de linkerkant, en stel de + relevante configuraties in. +2. In het instellingenscherm, klik op de "Validatie"-tab. +3. Schakel vervolgens het selectievakje "Aangeraden (niet-blokkerend verplicht)" in. + Indien je deze niet kan aanvinken, controleer dan dat de optie "Verplicht" uitgevinkt + is - een veld kan namelijk niet tegelijk blokkerend en niet-blokkerend verplicht zijn. +4. Sla de veldinstellingen op. +5. Herhaal stap 1 tot en met 4 voor alle benodigde velden. +6. Klik in het menu aan de linkerkant de categorie "Opmaak" open. +7. Sleep het component "Foutmeldingen aangeraden velden" in de formulierstap. + + * De inhoud zal enkel getoond worden als er "aangeraden velden" een lege waarde + hebben. + * Je kan het bericht voor de gebruiker naar wens instellen. Hierin kan je de + sjabloonvariabele ``{{ missingFields }}`` gebruiken die getoond wordt als lijst + van veld-labels met ontbrekende waarde. + * Andere sjabloonvariabelen of -uitdrukkingen kunnen niet gebruikt worden. + * Je kan hier ook vertalingen toepassen. + +Ondersteunde velden +=================== + +De velden die "Aangeraden (niet-blokkerend verplicht)"-validatie ondersteunen zijn: + +* Bestandsupload + +Neem contact op indien je deze functionaliteit ook bij andere veldsoorten wenst. diff --git a/package-lock.json b/package-lock.json index 03536e4c86..3141c1c30e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "@fortawesome/fontawesome-free": "^6.1.1", "@open-formulieren/design-tokens": "^0.53.0", - "@open-formulieren/formio-builder": "^0.30.0", + "@open-formulieren/formio-builder": "^0.31.0", "@open-formulieren/leaflet-tools": "^1.0.0", "@open-formulieren/monaco-json-editor": "^0.2.0", "@rjsf/core": "^4.2.1", @@ -4429,16 +4429,16 @@ "integrity": "sha512-3Pv32ULCuFOJZ2GaqcpvB45u6xScr0lmW5ETB9P1Ox9TG5nvMcVSwuwYe/GwxbzmvtZgiMQRMKRFT9lNYLeREQ==" }, "node_modules/@open-formulieren/formio-builder": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/@open-formulieren/formio-builder/-/formio-builder-0.30.0.tgz", - "integrity": "sha512-O7gLZ/WZNkpwVdWxDWTIYFSXlJoYzj9C+Tzf91IbcAyGjsyjhHg5hgH4I7xIc9DRM2xYPwnR91G8y7/3AwivfA==", + "version": "0.31.0", + "resolved": "https://registry.npmjs.org/@open-formulieren/formio-builder/-/formio-builder-0.31.0.tgz", + "integrity": "sha512-gMM3EYU+04fIE0XjiNLTmNfAUl9yNzCcBERwH9hsWVPgduFTYFC0v+kB/SCkg/Zab2bKtv3IugJIksgm1sX36Q==", "dependencies": { "@ckeditor/ckeditor5-react": "^6.2.0", "@floating-ui/react": "^0.26.4", "@open-formulieren/ckeditor5-build-classic": "^1.0.1", "@open-formulieren/leaflet-tools": "^1.0.0", "@open-formulieren/monaco-json-editor": "^0.2.0", - "@storybook/test": "^8.2.6", + "@storybook/test": "^8.3.5", "clsx": "^1.2.1", "formik": "^2.4.5", "leaflet": "^1.9.4", @@ -23567,16 +23567,16 @@ "integrity": "sha512-3Pv32ULCuFOJZ2GaqcpvB45u6xScr0lmW5ETB9P1Ox9TG5nvMcVSwuwYe/GwxbzmvtZgiMQRMKRFT9lNYLeREQ==" }, "@open-formulieren/formio-builder": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/@open-formulieren/formio-builder/-/formio-builder-0.30.0.tgz", - "integrity": "sha512-O7gLZ/WZNkpwVdWxDWTIYFSXlJoYzj9C+Tzf91IbcAyGjsyjhHg5hgH4I7xIc9DRM2xYPwnR91G8y7/3AwivfA==", + "version": "0.31.0", + "resolved": "https://registry.npmjs.org/@open-formulieren/formio-builder/-/formio-builder-0.31.0.tgz", + "integrity": "sha512-gMM3EYU+04fIE0XjiNLTmNfAUl9yNzCcBERwH9hsWVPgduFTYFC0v+kB/SCkg/Zab2bKtv3IugJIksgm1sX36Q==", "requires": { "@ckeditor/ckeditor5-react": "^6.2.0", "@floating-ui/react": "^0.26.4", "@open-formulieren/ckeditor5-build-classic": "^1.0.1", "@open-formulieren/leaflet-tools": "^1.0.0", "@open-formulieren/monaco-json-editor": "^0.2.0", - "@storybook/test": "^8.2.6", + "@storybook/test": "^8.3.5", "clsx": "^1.2.1", "formik": "^2.4.5", "leaflet": "^1.9.4", diff --git a/package.json b/package.json index 6e6cb33c28..d7eab6655b 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "dependencies": { "@fortawesome/fontawesome-free": "^6.1.1", "@open-formulieren/design-tokens": "^0.53.0", - "@open-formulieren/formio-builder": "^0.30.0", + "@open-formulieren/formio-builder": "^0.31.0", "@open-formulieren/leaflet-tools": "^1.0.0", "@open-formulieren/monaco-json-editor": "^0.2.0", "@rjsf/core": "^4.2.1", diff --git a/src/openforms/forms/models/form_definition.py b/src/openforms/forms/models/form_definition.py index bd20e6f019..d8b389dd0e 100644 --- a/src/openforms/forms/models/form_definition.py +++ b/src/openforms/forms/models/form_definition.py @@ -141,6 +141,8 @@ def used_in(self) -> models.QuerySet: to be used in bulk Form Definition querysets, you should use prefetch queries for that. """ + if not self.pk: + return Form.objects.none() return ( Form.objects.filter( _is_deleted=False, diff --git a/src/openforms/forms/tests/test_models.py b/src/openforms/forms/tests/test_models.py index 4eab36ba7c..b112a7d2fe 100644 --- a/src/openforms/forms/tests/test_models.py +++ b/src/openforms/forms/tests/test_models.py @@ -435,6 +435,14 @@ def test_num_components_calculated_on_save(self): self.assertEqual(fd._num_components, 2) + def test_used_in_for_unsaved_fds(self): + FormFactory.create(generate_minimal_setup=False) + fd = FormDefinitionFactory.build() + + used_in_num = fd.used_in.count() + + self.assertEqual(used_in_num, 0) + class FormStepTestCase(TestCase): def test_str(self): diff --git a/src/openforms/js/compiled-lang/en.json b/src/openforms/js/compiled-lang/en.json index 67bd566692..94ec4e51ce 100644 --- a/src/openforms/js/compiled-lang/en.json +++ b/src/openforms/js/compiled-lang/en.json @@ -929,6 +929,12 @@ "value": "The text that will be displayed in the form step to save the current information. Leave blank to get value from global configuration." } ], + "8M403q": [ + { + "type": 0, + "value": "Soft required fields should be filled out, but empty values don't block the users' progress. Sometimes this is needed for legal reasons. A component cannot be hard and soft required at the same time." + } + ], "8NbOpb": [ { "type": 0, @@ -2739,6 +2745,12 @@ "value": "Select existing form definition" } ], + "QL4SGQ": [ + { + "type": 0, + "value": "Soft required" + } + ], "QLTh2N": [ { "type": 0, @@ -2757,12 +2769,6 @@ "value": "Whether the user is allowed to submit this form or not, and whether the overview page should be shown if they are not." } ], - "QXsAuR": [ - { - "type": 0, - "value": "Product to fetch prices for" - } - ], "QaPqxz": [ { "type": 0, @@ -5729,12 +5735,6 @@ "value": "Do not display the configured label and top line as the header in the fieldset." } ], - "uuKVtO": [ - { - "type": 0, - "value": "Product" - } - ], "vAZDY4": [ { "type": 0, diff --git a/src/openforms/js/compiled-lang/nl.json b/src/openforms/js/compiled-lang/nl.json index c065ec774a..5c986d0962 100644 --- a/src/openforms/js/compiled-lang/nl.json +++ b/src/openforms/js/compiled-lang/nl.json @@ -933,6 +933,12 @@ "value": "De tekst op de knop om de gegevens van de huidige stap op te slaan en het formulier te onderbreken. Laat dit veld leeg om de standaardinstelling te gebruiken." } ], + "8M403q": [ + { + "type": 0, + "value": "Aangeraden velden moeten in principe ingevuld worden, maar ontbrekende waarden blokkeren de voortgang niet. Dit is soms nodig voor juridische redenen. Een component kan niet tegelijk verplicht en aangeraden zijn." + } + ], "8NbOpb": [ { "type": 0, @@ -2756,6 +2762,12 @@ "value": "Selecteer een bestaande formulierdefinitie" } ], + "QL4SGQ": [ + { + "type": 0, + "value": "Aangeraden (niet-blokkerend verplicht)" + } + ], "QLTh2N": [ { "type": 0, @@ -2774,12 +2786,6 @@ "value": "Geeft aan of de gebruiker het formulier kan inzenden of niet, en of een overzichtspagina getoond wordt indien niet." } ], - "QXsAuR": [ - { - "type": 0, - "value": "Product waarvoor prijzen worden opgehaald" - } - ], "QaPqxz": [ { "type": 0, @@ -5751,12 +5757,6 @@ "value": "Verberg de koptekst en de lijn boven de veldengroep in het formulier." } ], - "uuKVtO": [ - { - "type": 0, - "value": "Product" - } - ], "vAZDY4": [ { "type": 0, diff --git a/src/openforms/js/components/form/softRequiredErrors.js b/src/openforms/js/components/form/softRequiredErrors.js new file mode 100644 index 0000000000..12246ac35f --- /dev/null +++ b/src/openforms/js/components/form/softRequiredErrors.js @@ -0,0 +1,35 @@ +import {Formio} from 'formiojs'; + +import {localiseSchema} from './i18n'; + +const FormioContentField = Formio.Components.components.content; + +const Component = Formio.Components.components.component; + +class SoftRequiredErrors extends FormioContentField { + static schema(...extend) { + const schema = Component.schema( + { + type: 'softRequiredErrors', + key: 'softRequiredErrors', + label: '', // not displayed anyway + input: false, + html: undefined, // use default defined in formio-builder + }, + ...extend + ); + return localiseSchema(schema); + } + + static get builderInfo() { + return { + title: 'Soft required errors', + icon: 'triangle-exclamation', + group: 'custom_layout', + weight: 900, + schema: this.schema(), + }; + } +} + +export default SoftRequiredErrors; diff --git a/src/openforms/js/components/formio_builder/builder.js b/src/openforms/js/components/formio_builder/builder.js index 3381b55538..915e32ebf5 100644 --- a/src/openforms/js/components/formio_builder/builder.js +++ b/src/openforms/js/components/formio_builder/builder.js @@ -70,6 +70,7 @@ const getBuilderOptions = () => { content: true, fieldset: true, columns: true, + softRequiredErrors: true, }, }, custom_preset: { diff --git a/src/openforms/js/formio_module.js b/src/openforms/js/formio_module.js index d57c1c37c0..6bf1c778e7 100644 --- a/src/openforms/js/formio_module.js +++ b/src/openforms/js/formio_module.js @@ -26,6 +26,7 @@ import RadioField from './components/form/radio'; import SelectField from './components/form/select'; import SelectBoxesField from './components/form/selectBoxes'; import SignatureField from './components/form/signature'; +import SoftRequiredErrors from './components/form/softRequiredErrors'; import TextArea from './components/form/textarea'; import TextField from './components/form/textfield'; import TimeField from './components/form/time'; @@ -64,6 +65,7 @@ const FormIOModule = { editgrid: EditGrid, datamap: Datamap, addressNL: AddressNL, + softRequiredErrors: SoftRequiredErrors, }, builders: { webform: WebformBuilder, diff --git a/src/openforms/js/lang/formio/nl.json b/src/openforms/js/lang/formio/nl.json index 2d79534983..e9750be174 100644 --- a/src/openforms/js/lang/formio/nl.json +++ b/src/openforms/js/lang/formio/nl.json @@ -405,5 +405,6 @@ "Verify": "Bevestigen", "You must verify this email address to continue.": "Om door te gaan moet je dit e-mailadres bevestigen.", "You must select at least {{minCount}} items.": "Zorg dat dit veld {{minCount}} of meer opties aangevinkt heeft.", + "Soft required errors": "Foutmeldingen aangeraden velden", "": "" } diff --git a/src/openforms/scss/components/builder/_builder.scss b/src/openforms/scss/components/builder/_builder.scss index 83e7e80c0b..531646cf19 100644 --- a/src/openforms/scss/components/builder/_builder.scss +++ b/src/openforms/scss/components/builder/_builder.scss @@ -67,7 +67,7 @@ div.flatpickr-calendar.open { .tab-pane.active { min-block-size: 50vh; - &:has(.formio-component-content) { + &:has(.formio-component-content, .formio-component-softRequiredErrors) { min-block-size: 0; } }