@planning-inspectorate/dynamic-forms 3.16.0 → 3.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +124 -189
- package/docs/Email Validation.md +86 -0
- package/docs/Getting Started.md +299 -0
- package/package.json +1 -1
- package/src/components/address/question.d.ts +1 -9
- package/src/components/address/question.d.ts.map +1 -1
- package/src/components/address/question.js +6 -17
- package/src/components/checkbox/question.d.ts +1 -21
- package/src/components/checkbox/question.d.ts.map +1 -1
- package/src/components/checkbox/question.js +38 -50
- package/src/components/checkbox/question.test.js +177 -26
- package/src/components/date/question.d.ts +0 -8
- package/src/components/date/question.d.ts.map +1 -1
- package/src/components/date/question.js +6 -14
- package/src/components/date-period/question.d.ts +1 -5
- package/src/components/date-period/question.d.ts.map +1 -1
- package/src/components/date-period/question.js +1 -1
- package/src/components/date-time/question.d.ts +0 -8
- package/src/components/date-time/question.d.ts.map +1 -1
- package/src/components/date-time/question.js +4 -12
- package/src/components/email/question.d.ts +0 -9
- package/src/components/email/question.d.ts.map +1 -1
- package/src/components/email/question.js +2 -13
- package/src/components/email/question.test.js +19 -0
- package/src/components/manage-list/answer-summary-list.njk +1 -1
- package/src/components/manage-list/question.d.ts +1 -5
- package/src/components/manage-list/question.d.ts.map +1 -1
- package/src/components/manage-list/question.js +1 -1
- package/src/components/multi-field-input/question.d.ts +4 -19
- package/src/components/multi-field-input/question.d.ts.map +1 -1
- package/src/components/multi-field-input/question.js +89 -23
- package/src/components/multi-field-input/question.test.js +441 -3
- package/src/components/number-entry/question.d.ts +0 -28
- package/src/components/number-entry/question.d.ts.map +1 -1
- package/src/components/number-entry/question.js +3 -20
- package/src/components/number-entry/question.test.js +17 -0
- package/src/components/radio/question.d.ts +1 -9
- package/src/components/radio/question.d.ts.map +1 -1
- package/src/components/radio/question.js +29 -46
- package/src/components/radio/question.test.js +139 -60
- package/src/components/select/question.d.ts +1 -15
- package/src/components/select/question.d.ts.map +1 -1
- package/src/components/select/question.js +4 -47
- package/src/components/single-line-input/question.d.ts +0 -4
- package/src/components/single-line-input/question.d.ts.map +1 -1
- package/src/components/single-line-input/question.js +1 -1
- package/src/components/text-entry/question.d.ts +0 -4
- package/src/components/text-entry/question.d.ts.map +1 -1
- package/src/components/text-entry/question.js +1 -1
- package/src/components/text-entry-redact/question.d.ts +2 -6
- package/src/components/text-entry-redact/question.d.ts.map +1 -1
- package/src/components/text-entry-redact/question.js +1 -1
- package/src/components/unit-option-entry/question.d.ts.map +1 -1
- package/src/components/unit-option-entry/question.js +3 -1
- package/src/components/unit-option-entry/question.test.js +15 -0
- package/src/components/utils/question-has-answer.js +1 -1
- package/src/components/utils/question-utils.d.ts +19 -1
- package/src/components/utils/question-utils.d.ts.map +1 -1
- package/src/components/utils/question-utils.js +31 -2
- package/src/components/utils/question-utils.test.js +89 -2
- package/src/questions/options-question.d.ts +4 -4
- package/src/questions/options-question.d.ts.map +1 -1
- package/src/questions/options-question.js +27 -2
- package/src/questions/options-question.test.js +106 -0
- package/src/questions/question.d.ts +16 -1
- package/src/questions/question.d.ts.map +1 -1
- package/src/questions/question.js +64 -9
- package/src/questions/question.test.js +86 -0
- package/test/journey.js +2 -0
- package/test/questions.js +50 -0
- package/test/snapshots/check-your-answers-not-started.html +22 -0
- package/test/snapshots/check-your-answers-with-answers.html +23 -1
- package/test/snapshots/favourite-activity-reason.html +54 -0
- package/test/snapshots/travel-requirements.html +48 -0
- package/test/utils/question-test-utils.js +38 -7
- package/typedefs/question-props.d.ts +33 -0
- package/typedefs/question-types.d.ts +4 -0
package/README.md
CHANGED
|
@@ -1,142 +1,86 @@
|
|
|
1
1
|
# Dynamics Forms
|
|
2
2
|
|
|
3
|
-
This package is for building [
|
|
3
|
+
This package is for building [GOV.UK Frontend](https://design-system.service.gov.uk/) forms using a configuration-based approach. It allows you to define a set of questions, and combine them into journeys. Questions can be configured to be included conditionally, based on the answers to other questions.
|
|
4
4
|
|
|
5
|
-
The functionality for 'check-your-answers' pages can also be useful for generating pages outside the context of a journey or form, such as for managing data. Each row shown on the page can include a link to edit that data.
|
|
5
|
+
The functionality for ['check-your-answers'](https://design-system.service.gov.uk/patterns/check-answers/) pages can also be useful for generating pages outside the context of a journey or form, such as for managing data. Each row shown on the page can include a link to edit that data.
|
|
6
6
|
|
|
7
7
|
> Note this is ported from Appeals: [dynamic forms](https://github.com/Planning-Inspectorate/appeal-planning-decision/tree/a46f945047dc1f13d523a0853b4fcbb4bd0f6d6e/packages/forms-web-app/src/dynamic-forms), but transformed for ES6 modules and Node Test Runner. Not all functionality has been brought across, such as 'add more'.
|
|
8
8
|
> It is hoped that this version can be developed over time so Appeals can move to this version.
|
|
9
9
|
|
|
10
|
+
## Getting started
|
|
11
|
+
|
|
12
|
+
In summary:
|
|
13
|
+
|
|
14
|
+
1. Install the package: `npm install --save @planning-inspectorate/dynamic-forms`
|
|
15
|
+
2. Configure nunjucks
|
|
16
|
+
3. (optional) Configure accessible-autocomplete
|
|
17
|
+
4. Add the question and check-your-answers templates
|
|
18
|
+
5. Configure questions, journeys, and routes
|
|
19
|
+
|
|
20
|
+
For more details, see the [Getting started](./docs/Getting%20Started.md) guide.
|
|
21
|
+
|
|
10
22
|
## Terminology
|
|
11
23
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
24
|
+
| Term | Description |
|
|
25
|
+
|------------|----------------------------------------------------------------------------------------------------------------------|
|
|
26
|
+
| Component | A reusable part of a user interface. Usually forming the main content of a page in the context of a user journey. |
|
|
27
|
+
| Question | An instantiated component with associated configuration, a specific question within a journey |
|
|
28
|
+
| Section | A group of questions, configured with any conditional logic |
|
|
29
|
+
| Journey | An entire set of questions and sections which make up a user journey |
|
|
30
|
+
| Answer | Data input by a user against a specific Question |
|
|
31
|
+
| Response | A collection of answers submitted as part of a Journey |
|
|
32
|
+
| Validation | Verification that an individual Answer meets the criteria of that Question. i.e. String is greater than 3 characters |
|
|
21
33
|
|
|
22
|
-
##
|
|
34
|
+
## Usage
|
|
23
35
|
|
|
24
|
-
|
|
36
|
+
The test directory includes a very basic journey in `test/journey.js`. This may be a useful guide for setting up a journey and associated questions. Also in `test/questions.test.js` there is a function `createAppWithQuestions` which may be a useful guide for setting up the appropriate controllers and routes for a journey.
|
|
25
37
|
|
|
26
|
-
|
|
38
|
+
All exports are named exports from the root module:
|
|
27
39
|
|
|
28
|
-
|
|
40
|
+
`import {COMPONENT_TYPES} from '@planning-inspectorate/dynamic-forms`
|
|
29
41
|
|
|
30
|
-
|
|
31
|
-
import { createRequire } from 'node:module';
|
|
32
|
-
import path from 'node:path';
|
|
33
|
-
import nunjucks from 'nunjucks';
|
|
34
|
-
|
|
35
|
-
export function configureNunjucks() {
|
|
36
|
-
// get the require function, see https://nodejs.org/api/module.html#modulecreaterequirefilename
|
|
37
|
-
const require = createRequire(import.meta.url);
|
|
38
|
-
// path to dynamic forms folder
|
|
39
|
-
const dynamicFormsRoot = path.resolve(require.resolve('@planning-inspectorate/dynamic-forms'), '..');
|
|
40
|
-
// get the path to the govuk-frontend folder, in node_modules, using the node require resolution
|
|
41
|
-
const govukFrontendRoot = path.resolve(require.resolve('govuk-frontend'), '../..');
|
|
42
|
-
const appDir = path.join('some/path/to/the', 'app');
|
|
43
|
-
|
|
44
|
-
// configure nunjucks
|
|
45
|
-
return nunjucks.configure(
|
|
46
|
-
// ensure nunjucks templates can use govuk-frontend components, and templates we've defined in `web/src/app`
|
|
47
|
-
[dynamicFormsRoot, govukFrontendRoot, appDir],
|
|
48
|
-
{
|
|
49
|
-
// output with dangerous characters are escaped automatically
|
|
50
|
-
autoescape: true,
|
|
51
|
-
// automatically remove trailing newlines from a block/tag
|
|
52
|
-
trimBlocks: true,
|
|
53
|
-
// automatically remove leading whitespace from a block/tag
|
|
54
|
-
lstripBlocks: true
|
|
55
|
-
}
|
|
56
|
-
);
|
|
57
|
-
}
|
|
42
|
+
### Components
|
|
58
43
|
|
|
59
|
-
|
|
44
|
+
Components available are exported via the `COMPONENT_TYPES` constant.
|
|
60
45
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
**Journey template**
|
|
73
|
-
|
|
74
|
-
The journey template is used for question pages. It must include a block called `dynQuestionContent`, and a block called `head` (which the default gov.uk template has).
|
|
75
|
-
It should also include the rendering of the errorSummary component. The path to this template is configured via the `journeyTemplate` property on a Journey.
|
|
76
|
-
|
|
77
|
-
e.g.
|
|
78
|
-
|
|
79
|
-
```nunjucks
|
|
80
|
-
{% extends "govuk/template.njk" %}
|
|
81
|
-
|
|
82
|
-
{% block beforeContent %}
|
|
83
|
-
{% if errorSummary %}
|
|
84
|
-
<div class="govuk-grid-row">
|
|
85
|
-
<div class="govuk-grid-column-two-thirds">
|
|
86
|
-
{{ govukErrorSummary({
|
|
87
|
-
titleText: "There is a problem",
|
|
88
|
-
errorList: errorSummary,
|
|
89
|
-
attributes: {"data-cy": "error-wrapper"}
|
|
90
|
-
}) }}
|
|
91
|
-
</div>
|
|
92
|
-
</div>
|
|
93
|
-
{% endif %}
|
|
94
|
-
{% endblock %}
|
|
95
|
-
|
|
96
|
-
{% block content %}
|
|
97
|
-
<div class="govuk-grid-row">
|
|
98
|
-
<div class="govuk-grid-column-full">
|
|
99
|
-
<h1 class="govuk-heading-xl">
|
|
100
|
-
{% if pageCaption %}
|
|
101
|
-
<span class="govuk-caption-xl">{{ pageCaption }}</span>
|
|
102
|
-
{% endif %}
|
|
103
|
-
{{ pageHeading }}
|
|
104
|
-
</h1>
|
|
105
|
-
</div>
|
|
106
|
-
</div>
|
|
107
|
-
|
|
108
|
-
{% block dynQuestionContent %}{% endblock %}
|
|
109
|
-
{% endblock %}
|
|
110
|
-
```
|
|
46
|
+
#### Custom components
|
|
47
|
+
|
|
48
|
+
Components can be created and used with Dynamic Forms with some configuration. Extend the base `Question` class.
|
|
49
|
+
|
|
50
|
+
1. Ensure nunjucks is configured with the folder containing your custom components
|
|
51
|
+
2. Export the custom component type names and classes
|
|
52
|
+
3. Merge the exported question classes object with the custom classes
|
|
53
|
+
|
|
54
|
+
See also [Contributing > new components](#new-components)
|
|
55
|
+
|
|
56
|
+
### Controllers
|
|
111
57
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
</h1>
|
|
130
|
-
</div>
|
|
131
|
-
</div>
|
|
132
|
-
|
|
133
|
-
{% block dynTaskList %}{% endblock %}
|
|
134
|
-
{% endblock %}
|
|
58
|
+
Dynamic forms includes several Express handlers/controllers which implement the core logic. They all have prerequisites and expect particular properties have been added to the `res.locals` object. These are:
|
|
59
|
+
|
|
60
|
+
* `res.locals.journey` must be an instance of the `Journey` class
|
|
61
|
+
* `res.locals.journeyResponse` must be an instance of the `JourneyResponse` class
|
|
62
|
+
|
|
63
|
+
They can be added using middleware, and utility functions are available for this, for example:
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
// get the answers from the session, and add JourneyResponse to `res.locals`
|
|
67
|
+
const getJourneyResponse = buildGetJourneyResponseFromSession(JOURNEY_ID);
|
|
68
|
+
// create the journey with the given journeyResponse and questions, and add to `res.locals`
|
|
69
|
+
const getJourney = buildGetJourney((req, journeyResponse) => createJourney(req, journeyResponse, questions));
|
|
70
|
+
|
|
71
|
+
router.use(
|
|
72
|
+
getJourneyResponse,
|
|
73
|
+
getJourney
|
|
74
|
+
);
|
|
135
75
|
```
|
|
136
76
|
|
|
137
|
-
|
|
77
|
+
The controllers available are:
|
|
138
78
|
|
|
139
|
-
|
|
79
|
+
| Controller | Purpose |
|
|
80
|
+
|----------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
81
|
+
| question | To render a question page as part of a journey |
|
|
82
|
+
| buildSave | Returns a controller to handle saving the answer and either redirecting to the next question or show any validation errors if present. Accepts a save function. |
|
|
83
|
+
| list/buildList | Renders a check-your-answers page showing all the answers, links to change any answer, and a button to submit the answers. |
|
|
140
84
|
|
|
141
85
|
### Conditions
|
|
142
86
|
|
|
@@ -199,93 +143,84 @@ These conditions combine with individual conditions and section conditions. In t
|
|
|
199
143
|
| q6 | q1 = 'yes' | q5 = 90 | N/A |
|
|
200
144
|
| q7 | q1 = 'yes' | q5 = 90 | N/A |
|
|
201
145
|
|
|
202
|
-
###
|
|
146
|
+
### Validators
|
|
203
147
|
|
|
204
|
-
|
|
148
|
+
Many validators are available such as `RequiredValidator` and `AddressValidator`. More detail on email validation is available in [docs/Email Validation](./docs/Email%20Validation.md).
|
|
149
|
+
|
|
150
|
+
### Custom Summary Formatting
|
|
151
|
+
|
|
152
|
+
The `formatSummaryValue` option allows you to customise how answers are displayed on check-your-answers pages without needing to create a custom question class.
|
|
205
153
|
|
|
206
154
|
#### Basic Usage
|
|
207
155
|
|
|
156
|
+
Pass a `formatSummaryValue` function when creating a question:
|
|
157
|
+
|
|
208
158
|
```javascript
|
|
209
|
-
import {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
question: 'What is your email address?',
|
|
222
|
-
fieldName: 'contactEmail',
|
|
223
|
-
url: 'contact-email',
|
|
224
|
-
label: 'Email address',
|
|
225
|
-
validators: [
|
|
226
|
-
new EmailValidator({
|
|
227
|
-
errorMessage: 'Enter an email address in the correct format, like name@example.com'
|
|
228
|
-
})
|
|
229
|
-
]
|
|
230
|
-
}
|
|
231
|
-
};
|
|
232
|
-
|
|
233
|
-
// Create questions using the factory function
|
|
234
|
-
const questions = createQuestions(questionProps, questionClasses, {});
|
|
235
|
-
const emailQuestion = questions.contactEmail;
|
|
159
|
+
import { RadioQuestion } from '@planning-inspectorate/dynamic-forms';
|
|
160
|
+
|
|
161
|
+
new RadioQuestion({
|
|
162
|
+
fieldName: 'status',
|
|
163
|
+
title: 'Application status',
|
|
164
|
+
question: 'What is the application status?',
|
|
165
|
+
options: [
|
|
166
|
+
{ text: 'Approved', value: 'approved' },
|
|
167
|
+
{ text: 'Rejected', value: 'rejected' }
|
|
168
|
+
],
|
|
169
|
+
formatSummaryValue: ({ formattedAnswer }) => `<strong>${formattedAnswer}</strong>`
|
|
170
|
+
});
|
|
236
171
|
```
|
|
237
172
|
|
|
238
|
-
|
|
173
|
+
#### Formatter Context
|
|
174
|
+
|
|
175
|
+
The formatter function receives a context object with the following properties:
|
|
239
176
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
177
|
+
| Property | Type | Description |
|
|
178
|
+
|----------|------|------------------------------------------------|
|
|
179
|
+
| `answer` | `unknown` | The raw answer value |
|
|
180
|
+
| `formattedAnswer` | `string` | The default formatted display value |
|
|
181
|
+
| `question` | `Question` | The question instance |
|
|
182
|
+
| `journey` | `Journey` | The journey instance |
|
|
183
|
+
| `sectionSegment` | `string` | The current section segment |
|
|
243
184
|
|
|
244
|
-
####
|
|
185
|
+
#### Examples
|
|
245
186
|
|
|
246
|
-
|
|
187
|
+
**Bold formatting:**
|
|
247
188
|
|
|
248
189
|
```javascript
|
|
249
|
-
|
|
250
|
-
options: {
|
|
251
|
-
allowDisplayName: false, // Reject "Name <email@domain.com>" format
|
|
252
|
-
requireTld: true, // Require top-level domain (default: true)
|
|
253
|
-
allowUtf8LocalPart: false, // Only ASCII characters in local part
|
|
254
|
-
allowIpDomain: false // Don't allow IP addresses as domain
|
|
255
|
-
},
|
|
256
|
-
errorMessage: 'Enter a valid business email address'
|
|
257
|
-
});
|
|
190
|
+
formatSummaryValue: ({ formattedAnswer }) => `<strong>${escapeHtml(formattedAnswer)}</strong>`
|
|
258
191
|
```
|
|
259
192
|
|
|
260
|
-
|
|
193
|
+
**Conditional styling based on answer:**
|
|
194
|
+
|
|
195
|
+
```javascript
|
|
196
|
+
formatSummaryValue: ({ answer, formattedAnswer }) => {
|
|
197
|
+
const color = answer === 'approved' ? 'green' : 'red';
|
|
198
|
+
return `<span style="color: ${color}">${escapeHtml(formattedAnswer)}</span>`;
|
|
199
|
+
}
|
|
200
|
+
```
|
|
261
201
|
|
|
262
|
-
|
|
202
|
+
**Adding a suffix from another answer:**
|
|
263
203
|
|
|
264
204
|
```javascript
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
const questionProps = {
|
|
273
|
-
contactEmail: {
|
|
274
|
-
type: COMPONENT_TYPES.SINGLE_LINE_INPUT,
|
|
275
|
-
title: 'Contact Information',
|
|
276
|
-
question: 'What is your email address?',
|
|
277
|
-
fieldName: 'contactEmail',
|
|
278
|
-
url: 'contact-email',
|
|
279
|
-
label: 'Email address',
|
|
280
|
-
inputAttributes: { type: 'email', spellcheck: 'false' },
|
|
281
|
-
autocomplete: 'email',
|
|
282
|
-
validators: [new EmailValidator()]
|
|
283
|
-
}
|
|
284
|
-
};
|
|
285
|
-
|
|
286
|
-
const questions = createQuestions(questionProps, questionClasses, {});
|
|
205
|
+
const suffixes = { solid: ' (tonnes)', liquid: ' (litres)' };
|
|
206
|
+
|
|
207
|
+
formatSummaryValue: ({ answer, formattedAnswer }) => {
|
|
208
|
+
const suffix = suffixes[answer] ?? '';
|
|
209
|
+
return `${formattedAnswer}${suffix}`;
|
|
210
|
+
}
|
|
287
211
|
```
|
|
288
212
|
|
|
213
|
+
**Accessing other journey answers:**
|
|
214
|
+
|
|
215
|
+
```javascript
|
|
216
|
+
formatSummaryValue: ({ formattedAnswer, journey }) => {
|
|
217
|
+
const capacity = journey.response.answers.wasteCapacity;
|
|
218
|
+
return capacity ? `${formattedAnswer}: ${capacity}` : formattedAnswer;
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
> **Note:** `formattedAnswer` is already escaped by dynamic-forms; you are responsible for escaping any additional user-controlled values you include (for example `answer` or values read from `journey.response.answers`).
|
|
223
|
+
|
|
289
224
|
## Contributing
|
|
290
225
|
|
|
291
226
|
When contributing to this package, ensure changes are generic and not service-specific. Speak to the R&D devs if you are not sure. Prefer configuration over hardcoding values, and ensure the code is well documented.
|
|
@@ -317,11 +252,11 @@ When adding a new question type, be sure to add an example question into `test/q
|
|
|
317
252
|
|
|
318
253
|
To update any snapshots with rendering changes (or new questions), run `node --test --test-update-snapshots`.
|
|
319
254
|
|
|
320
|
-
###
|
|
255
|
+
### New components
|
|
321
256
|
|
|
322
257
|
When implement a new component, extend the base `Question` class. Common methods to override are:
|
|
323
258
|
|
|
324
|
-
* `
|
|
259
|
+
* `formatAnswer` - used for check-your-answers display
|
|
325
260
|
* `getDataToSave` - answer data to save
|
|
326
261
|
* `addCustomDataToViewModel` - customise the view model with extra data/configuration
|
|
327
262
|
* `answerForViewModel` - customise the view model answer value
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Email Validation
|
|
2
|
+
|
|
3
|
+
Email validation is provided through the `EmailValidator` class and `EmailQuestion` component. The validator uses express-validator's robust email validation with configurable options.
|
|
4
|
+
|
|
5
|
+
## Basic Usage
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
import {
|
|
9
|
+
COMPONENT_TYPES,
|
|
10
|
+
createQuestions,
|
|
11
|
+
questionClasses,
|
|
12
|
+
EmailValidator
|
|
13
|
+
} from '@planning-inspectorate/dynamic-forms';
|
|
14
|
+
|
|
15
|
+
// Define question configuration
|
|
16
|
+
const questionProps = {
|
|
17
|
+
contactEmail: {
|
|
18
|
+
type: COMPONENT_TYPES.EMAIL,
|
|
19
|
+
title: 'Contact Information',
|
|
20
|
+
question: 'What is your email address?',
|
|
21
|
+
fieldName: 'contactEmail',
|
|
22
|
+
url: 'contact-email',
|
|
23
|
+
label: 'Email address',
|
|
24
|
+
validators: [
|
|
25
|
+
new EmailValidator({
|
|
26
|
+
errorMessage: 'Enter an email address in the correct format, like name@example.com'
|
|
27
|
+
})
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// Create questions using the factory function
|
|
33
|
+
const questions = createQuestions(questionProps, questionClasses, {});
|
|
34
|
+
const emailQuestion = questions.contactEmail;
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The EmailQuestion uses the following default input attributes:
|
|
38
|
+
|
|
39
|
+
- `type="email"`
|
|
40
|
+
- `spellcheck="false"`
|
|
41
|
+
- `autocomplete="email"`
|
|
42
|
+
|
|
43
|
+
## Advanced Validation Options
|
|
44
|
+
|
|
45
|
+
For stricter email validation requirements:
|
|
46
|
+
|
|
47
|
+
```javascript
|
|
48
|
+
const businessEmailValidator = new EmailValidator({
|
|
49
|
+
options: {
|
|
50
|
+
allowDisplayName: false, // Reject "Name <email@domain.com>" format
|
|
51
|
+
requireTld: true, // Require top-level domain (default: true)
|
|
52
|
+
allowUtf8LocalPart: false, // Only ASCII characters in local part
|
|
53
|
+
allowIpDomain: false // Don't allow IP addresses as domain
|
|
54
|
+
},
|
|
55
|
+
errorMessage: 'Enter a valid business email address'
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Alternative: Single Line Input with Email Attributes
|
|
60
|
+
|
|
61
|
+
You can also configure a single line input with email-specific attributes:
|
|
62
|
+
|
|
63
|
+
```javascript
|
|
64
|
+
import {
|
|
65
|
+
COMPONENT_TYPES,
|
|
66
|
+
createQuestions,
|
|
67
|
+
questionClasses,
|
|
68
|
+
EmailValidator
|
|
69
|
+
} from '@planning-inspectorate/dynamic-forms';
|
|
70
|
+
|
|
71
|
+
const questionProps = {
|
|
72
|
+
contactEmail: {
|
|
73
|
+
type: COMPONENT_TYPES.SINGLE_LINE_INPUT,
|
|
74
|
+
title: 'Contact Information',
|
|
75
|
+
question: 'What is your email address?',
|
|
76
|
+
fieldName: 'contactEmail',
|
|
77
|
+
url: 'contact-email',
|
|
78
|
+
label: 'Email address',
|
|
79
|
+
inputAttributes: { type: 'email', spellcheck: 'false' },
|
|
80
|
+
autocomplete: 'email',
|
|
81
|
+
validators: [new EmailValidator()]
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const questions = createQuestions(questionProps, questionClasses, {});
|
|
86
|
+
```
|