@planning-inspectorate/dynamic-forms 3.20.5 → 3.21.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 +49 -0
- package/docs/Getting Started.md +8 -2
- package/package.json +1 -1
- package/src/components/boolean/question.d.ts +1 -1
- package/src/components/boolean/question.js +1 -1
- package/src/components/date/question.d.ts +1 -1
- package/src/components/date/question.js +1 -1
- package/src/components/date-period/question.d.ts +1 -1
- package/src/components/date-period/question.js +1 -1
- package/src/components/date-time/question.d.ts +1 -1
- package/src/components/date-time/question.js +1 -1
- package/src/components/multi-field-input/question.js +1 -1
- package/src/components/single-line-input/index.njk +4 -0
- package/src/components/single-line-input/question.d.ts +2 -0
- package/src/components/single-line-input/question.d.ts.map +1 -1
- package/src/components/single-line-input/question.js +4 -0
- package/src/components/single-line-input/question.test.js +6 -0
- package/src/components/unit-option-entry/question.d.ts +1 -1
- package/src/components/unit-option-entry/question.js +1 -1
- package/src/questions/options-question.js +1 -1
- package/typedefs/question-props.d.ts +3 -1
package/README.md
CHANGED
|
@@ -82,6 +82,55 @@ The controllers available are:
|
|
|
82
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
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. |
|
|
84
84
|
|
|
85
|
+
#### Saving answers
|
|
86
|
+
|
|
87
|
+
The `buildSave` controller accepts a `SaveDataFn` which is passed the context required to save the answer(s). To save answers to session during a journey, a `SaveDataFn` implementation is provided called `saveDataToSession`.
|
|
88
|
+
|
|
89
|
+
For other use cases, write a custom `SaveDataFn`. A basic implementation (if you are not using ManageList) needs to save the answers passed in as `data.answers`, which is an object whose keys are the question field names, and the values are the answers. For example:
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
const update: SaveDataFn = async ({data}) => {
|
|
93
|
+
const answers = data.answers;
|
|
94
|
+
|
|
95
|
+
console.log('saving', answers);
|
|
96
|
+
|
|
97
|
+
// TODO: map to database
|
|
98
|
+
// TODO: write to database
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
To support `ManageList` questions as well, handle the `isManageListItem`, `manageListQuestionFieldName`, `manageListItemRemove` parameters. For example:
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
const update: SaveDataFn = async ({data, req, isManageListItem, manageListQuestionFieldName, manageListItemRemove}) => {
|
|
106
|
+
if (isManageListItem) {
|
|
107
|
+
const update = {
|
|
108
|
+
id: req.params.manageListItemId,
|
|
109
|
+
...data.answers
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
console.log('saving', update);
|
|
113
|
+
// TODO: map to database
|
|
114
|
+
// TODO: write to database
|
|
115
|
+
return;
|
|
116
|
+
} else if (manageListItemRemove) {
|
|
117
|
+
const id = req.params.manageListItemId;
|
|
118
|
+
// identify which type of item it is via the field name
|
|
119
|
+
const type = manageListQuestionFieldName;
|
|
120
|
+
console.log('removing item', {id, type});
|
|
121
|
+
// TODO: delete from database
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const answers = data.answers;
|
|
125
|
+
|
|
126
|
+
console.log('saving', answers);
|
|
127
|
+
|
|
128
|
+
// TODO: map to database
|
|
129
|
+
// TODO: write to database
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
|
|
85
134
|
### Conditions
|
|
86
135
|
|
|
87
136
|
Conditions are used to show/hide questions based on the answers to other questions. There are fluent methods on a Section
|
package/docs/Getting Started.md
CHANGED
|
@@ -78,7 +78,7 @@ Dynamic forms requires two templates to work for rendering questions and renderi
|
|
|
78
78
|
### Journey (or question) template
|
|
79
79
|
|
|
80
80
|
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).
|
|
81
|
-
It should also include the rendering of the errorSummary component. The path to this template is configured via the `journeyTemplate` property on a Journey.
|
|
81
|
+
It should also include the rendering of the errorSummary component, and optionally the back link. The path to this template is configured via the `journeyTemplate` property on a Journey.
|
|
82
82
|
|
|
83
83
|
For example:
|
|
84
84
|
|
|
@@ -87,7 +87,13 @@ e.g.
|
|
|
87
87
|
```nunjucks
|
|
88
88
|
{% extends "govuk/template.njk" %}
|
|
89
89
|
|
|
90
|
-
{%
|
|
90
|
+
{% from "govuk/components/back-link/macro.njk" import govukBackLink %}
|
|
91
|
+
{% from "govuk/components/error-summary/macro.njk" import govukErrorSummary %}
|
|
92
|
+
|
|
93
|
+
{% block containerStart %}
|
|
94
|
+
{% if backLink %}
|
|
95
|
+
{{ govukBackLink({ href: backLink }) }}
|
|
96
|
+
{% endif %}
|
|
91
97
|
{% if errorSummary %}
|
|
92
98
|
<div class="govuk-grid-row">
|
|
93
99
|
<div class="govuk-grid-column-two-thirds">
|
package/package.json
CHANGED
|
@@ -17,7 +17,7 @@ export class BooleanQuestion extends RadioQuestion {
|
|
|
17
17
|
/**
|
|
18
18
|
* Get the data to save from the request, returns an object of answers
|
|
19
19
|
* @param {import('express').Request} req
|
|
20
|
-
* @param {JourneyResponse} journeyResponse - current journey response
|
|
20
|
+
* @param {JourneyResponse} journeyResponse - current journey response
|
|
21
21
|
* @returns {Promise<{ answers: Record<string, unknown> }>}
|
|
22
22
|
*/ getDataToSave(req: import("express").Request, journeyResponse: JourneyResponse): Promise<{
|
|
23
23
|
answers: Record<string, unknown>;
|
|
@@ -97,7 +97,7 @@ export class BooleanQuestion extends RadioQuestion {
|
|
|
97
97
|
/**
|
|
98
98
|
* Get the data to save from the request, returns an object of answers
|
|
99
99
|
* @param {import('express').Request} req
|
|
100
|
-
* @param {JourneyResponse} journeyResponse - current journey response
|
|
100
|
+
* @param {JourneyResponse} journeyResponse - current journey response
|
|
101
101
|
* @returns {Promise<{ answers: Record<string, unknown> }>}
|
|
102
102
|
*/ //eslint-disable-next-line no-unused-vars -- journeyResponse kept for other questions to use
|
|
103
103
|
async getDataToSave(req, journeyResponse) {
|
|
@@ -10,7 +10,7 @@ export class DateQuestion extends Question {
|
|
|
10
10
|
/**
|
|
11
11
|
* Get the data to save from the request, returns an object of answers
|
|
12
12
|
* @param {import('express').Request} req
|
|
13
|
-
* @param {import('#journey-response').JourneyResponse} journeyResponse - current journey response
|
|
13
|
+
* @param {import('#journey-response').JourneyResponse} journeyResponse - current journey response
|
|
14
14
|
* @returns {Promise.<Object>}
|
|
15
15
|
*/ getDataToSave(req: import("express").Request, journeyResponse: import("#journey-response").JourneyResponse): Promise<Object>;
|
|
16
16
|
answerForViewModel(answers: any, isPayload: any): {
|
|
@@ -29,7 +29,7 @@ export class DateQuestion extends Question {
|
|
|
29
29
|
/**
|
|
30
30
|
* Get the data to save from the request, returns an object of answers
|
|
31
31
|
* @param {import('express').Request} req
|
|
32
|
-
* @param {import('#journey-response').JourneyResponse} journeyResponse - current journey response
|
|
32
|
+
* @param {import('#journey-response').JourneyResponse} journeyResponse - current journey response
|
|
33
33
|
* @returns {Promise.<Object>}
|
|
34
34
|
*/ //eslint-disable-next-line no-unused-vars -- journeyResponse kept for other questions to use
|
|
35
35
|
async getDataToSave(req, journeyResponse) {
|
|
@@ -19,7 +19,7 @@ export class DatePeriodQuestion extends Question {
|
|
|
19
19
|
/**
|
|
20
20
|
* Get the data to save from the request, returns an object of answers
|
|
21
21
|
* @param {import('express').Request} req
|
|
22
|
-
* @param {import('#journey-response').JourneyResponse} journeyResponse - current journey response
|
|
22
|
+
* @param {import('#journey-response').JourneyResponse} journeyResponse - current journey response
|
|
23
23
|
* @returns {Promise.<Object>}
|
|
24
24
|
*/ getDataToSave(req: import("express").Request, journeyResponse: import("#journey-response").JourneyResponse): Promise<Object>;
|
|
25
25
|
answerForViewModel(answers: any, isPayload: any): {
|
|
@@ -54,7 +54,7 @@ export class DatePeriodQuestion extends Question {
|
|
|
54
54
|
/**
|
|
55
55
|
* Get the data to save from the request, returns an object of answers
|
|
56
56
|
* @param {import('express').Request} req
|
|
57
|
-
* @param {import('#journey-response').JourneyResponse} journeyResponse - current journey response
|
|
57
|
+
* @param {import('#journey-response').JourneyResponse} journeyResponse - current journey response
|
|
58
58
|
* @returns {Promise.<Object>}
|
|
59
59
|
*/ //eslint-disable-next-line no-unused-vars -- journeyResponse kept for other questions to use
|
|
60
60
|
async getDataToSave(req, journeyResponse) {
|
|
@@ -10,7 +10,7 @@ export class DateTimeQuestion extends Question {
|
|
|
10
10
|
/**
|
|
11
11
|
* Get the data to save from the request, returns an object of answers
|
|
12
12
|
* @param {import('express').Request} req
|
|
13
|
-
* @param {JourneyResponse} journeyResponse - current journey response
|
|
13
|
+
* @param {JourneyResponse} journeyResponse - current journey response
|
|
14
14
|
* @returns {Promise.<Object>}
|
|
15
15
|
*/ getDataToSave(req: import("express").Request, journeyResponse: JourneyResponse): Promise<Object>;
|
|
16
16
|
answerForViewModel(answers: any, isPayload: any): {
|
|
@@ -38,7 +38,7 @@ export class DateTimeQuestion extends Question {
|
|
|
38
38
|
/**
|
|
39
39
|
* Get the data to save from the request, returns an object of answers
|
|
40
40
|
* @param {import('express').Request} req
|
|
41
|
-
* @param {JourneyResponse} journeyResponse - current journey response
|
|
41
|
+
* @param {JourneyResponse} journeyResponse - current journey response
|
|
42
42
|
* @returns {Promise.<Object>}
|
|
43
43
|
*/ //eslint-disable-next-line no-unused-vars -- journeyResponse kept for other questions to use
|
|
44
44
|
async getDataToSave(req, journeyResponse) {
|
|
@@ -44,7 +44,7 @@ export class MultiFieldInputQuestion extends Question {
|
|
|
44
44
|
/**
|
|
45
45
|
* Get the data to save from the request, returns an object of answers
|
|
46
46
|
* @param {import('express').Request} req
|
|
47
|
-
* @param {import('#journey-response').JourneyResponse} journeyResponse - current journey response
|
|
47
|
+
* @param {import('#journey-response').JourneyResponse} journeyResponse - current journey response
|
|
48
48
|
* @returns {Promise<{ answers: Record<string, unknown> }>}
|
|
49
49
|
*/ //eslint-disable-next-line no-unused-vars -- journeyResponse kept for other questions to use
|
|
50
50
|
async getDataToSave(req, journeyResponse) {
|
|
@@ -38,6 +38,8 @@
|
|
|
38
38
|
text: errors[question.fieldName].msg
|
|
39
39
|
},
|
|
40
40
|
classes: question.classes,
|
|
41
|
+
prefix: question.prefix,
|
|
42
|
+
suffix: question.suffix,
|
|
41
43
|
attributes: question.attributes,
|
|
42
44
|
type: question.type,
|
|
43
45
|
autocomplete: question.autocomplete
|
|
@@ -62,6 +64,8 @@
|
|
|
62
64
|
text: errors[question.fieldName].msg
|
|
63
65
|
},
|
|
64
66
|
classes: question.classes,
|
|
67
|
+
prefix: question.prefix,
|
|
68
|
+
suffix: question.suffix,
|
|
65
69
|
attributes: question.attributes,
|
|
66
70
|
type: question.type,
|
|
67
71
|
autocomplete: question.autocomplete
|
|
@@ -10,6 +10,8 @@ export class SingleLineInputQuestion extends Question {
|
|
|
10
10
|
inputAttributes: Record<string, string>;
|
|
11
11
|
label: string | undefined;
|
|
12
12
|
classes: string;
|
|
13
|
+
prefix: import("#typedefs/question-props.d.ts").Affix | undefined;
|
|
14
|
+
suffix: import("#typedefs/question-props.d.ts").Affix | undefined;
|
|
13
15
|
}
|
|
14
16
|
export default SingleLineInputQuestion;
|
|
15
17
|
import { Question } from '#question';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"question.d.ts","sourceRoot":"","sources":["question.js"],"names":[],"mappings":"AAEA;;GAEG;AACH;IAIC;;OAEG;IACH,oBAFW,OAAO,+BAA+B,EAAE,6BAA6B,
|
|
1
|
+
{"version":3,"file":"question.d.ts","sourceRoot":"","sources":["question.js"],"names":[],"mappings":"AAEA;;GAEG;AACH;IAIC;;OAEG;IACH,oBAFW,OAAO,+BAA+B,EAAE,6BAA6B,EAa/E;IAjBD,qCAAqC;IACrC,iBADW,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CACjB;IAWf,0BAAyB;IAEzB,gBAAmC;IACnC,kEAA2B;IAC3B,kEAA2B;CAiB5B;;yBAvCwB,WAAW"}
|
|
@@ -19,6 +19,8 @@ export class SingleLineInputQuestion extends Question {
|
|
|
19
19
|
this.label = params.label;
|
|
20
20
|
this.inputAttributes = params.inputAttributes || {};
|
|
21
21
|
this.classes = params.classes || '';
|
|
22
|
+
this.prefix = params.prefix;
|
|
23
|
+
this.suffix = params.suffix;
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
/**
|
|
@@ -32,6 +34,8 @@ export class SingleLineInputQuestion extends Question {
|
|
|
32
34
|
viewModel.question.type = type;
|
|
33
35
|
|
|
34
36
|
viewModel.question.classes = this.classes;
|
|
37
|
+
viewModel.question.prefix = this.prefix;
|
|
38
|
+
viewModel.question.suffix = this.suffix;
|
|
35
39
|
}
|
|
36
40
|
}
|
|
37
41
|
|
|
@@ -11,6 +11,8 @@ describe('./src/dynamic-forms/components/single-line-input/question.js', () => {
|
|
|
11
11
|
const HTML = '/path/to/html.njk';
|
|
12
12
|
const HINT = 'hint';
|
|
13
13
|
const LABEL = 'A label';
|
|
14
|
+
const PREFIX = { text: '£', classes: 'govuk-!-font-weight-bold' };
|
|
15
|
+
const SUFFIX = { text: 'per year' };
|
|
14
16
|
|
|
15
17
|
const question = new SingleLineInputQuestion({
|
|
16
18
|
title: TITLE,
|
|
@@ -20,6 +22,8 @@ describe('./src/dynamic-forms/components/single-line-input/question.js', () => {
|
|
|
20
22
|
html: HTML,
|
|
21
23
|
hint: HINT,
|
|
22
24
|
label: LABEL,
|
|
25
|
+
prefix: PREFIX,
|
|
26
|
+
suffix: SUFFIX,
|
|
23
27
|
autocomplete: FIELDNAME
|
|
24
28
|
});
|
|
25
29
|
|
|
@@ -31,6 +35,8 @@ describe('./src/dynamic-forms/components/single-line-input/question.js', () => {
|
|
|
31
35
|
assert.strictEqual(question.html, HTML);
|
|
32
36
|
assert.strictEqual(question.hint, HINT);
|
|
33
37
|
assert.strictEqual(question.label, LABEL);
|
|
38
|
+
assert.strictEqual(question.prefix, PREFIX);
|
|
39
|
+
assert.strictEqual(question.suffix, SUFFIX);
|
|
34
40
|
assert.strictEqual(question.autocomplete, FIELDNAME);
|
|
35
41
|
});
|
|
36
42
|
});
|
|
@@ -54,7 +54,7 @@ export class UnitOptionEntryQuestion extends Question {
|
|
|
54
54
|
/**
|
|
55
55
|
* Get the data to save from the request, returns an object of answers
|
|
56
56
|
* @param {import('express').Request} req
|
|
57
|
-
* @param {JourneyResponse} journeyResponse - current journey response
|
|
57
|
+
* @param {JourneyResponse} journeyResponse - current journey response
|
|
58
58
|
* @returns {Promise<{ answers: Record<string, unknown> }>}
|
|
59
59
|
*/ getDataToSave(req: import("express").Request, journeyResponse: JourneyResponse): Promise<{
|
|
60
60
|
answers: Record<string, unknown>;
|
|
@@ -125,7 +125,7 @@ export class UnitOptionEntryQuestion extends Question {
|
|
|
125
125
|
/**
|
|
126
126
|
* Get the data to save from the request, returns an object of answers
|
|
127
127
|
* @param {import('express').Request} req
|
|
128
|
-
* @param {JourneyResponse} journeyResponse - current journey response
|
|
128
|
+
* @param {JourneyResponse} journeyResponse - current journey response
|
|
129
129
|
* @returns {Promise<{ answers: Record<string, unknown> }>}
|
|
130
130
|
*/ //eslint-disable-next-line no-unused-vars -- journeyResponse kept for other questions to use
|
|
131
131
|
async getDataToSave(req, journeyResponse) {
|
|
@@ -147,7 +147,7 @@ export class OptionsQuestion extends Question {
|
|
|
147
147
|
/**
|
|
148
148
|
* Get the data to save from the request, returns an object of answers
|
|
149
149
|
* @param {import('express').Request} req
|
|
150
|
-
* @param {import('../journey/journey-response.js').JourneyResponse} journeyResponse - current journey response
|
|
150
|
+
* @param {import('../journey/journey-response.js').JourneyResponse} journeyResponse - current journey response
|
|
151
151
|
* @returns {Promise<{ answers: Record<string, unknown> }>}
|
|
152
152
|
*/ //eslint-disable-next-line no-unused-vars -- journeyResponse kept for other questions to use
|
|
153
153
|
async getDataToSave(req, journeyResponse) {
|
|
@@ -68,7 +68,7 @@ export type SelectableOption = {
|
|
|
68
68
|
attributes?: Record<string, string>;
|
|
69
69
|
behaviour?: 'exclusive';
|
|
70
70
|
conditional?: {
|
|
71
|
-
question
|
|
71
|
+
question?: string;
|
|
72
72
|
type: string;
|
|
73
73
|
fieldName: string;
|
|
74
74
|
inputClasses?: string;
|
|
@@ -261,6 +261,8 @@ export type SingleLineInputQuestionParams = CommonQuestionParams & {
|
|
|
261
261
|
inputAttributes?: Record<string, string>; // HTML attributes to add to the input
|
|
262
262
|
label?: string; // if defined this will show as a label for the input and the question will just be a standard h1
|
|
263
263
|
classes?: string; // HTML classes to add to the input
|
|
264
|
+
prefix?: Affix;
|
|
265
|
+
suffix?: Affix;
|
|
264
266
|
};
|
|
265
267
|
|
|
266
268
|
type SingleLineInputQuestionProps = SingleLineInputQuestionParams & {
|