hof 21.0.0-instrumentation-beta.0 → 21.0.1-axios-beta
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/.github/workflows/automate-publish.yml +1 -1
- package/.github/workflows/automate-tag.yml +4 -4
- package/.nyc_output/4fc007c9-d6c8-4614-89ce-04c7d6ce9fe5.json +1 -0
- package/.nyc_output/processinfo/4fc007c9-d6c8-4614-89ce-04c7d6ce9fe5.json +1 -0
- package/.nyc_output/processinfo/index.json +1 -1
- package/README.md +340 -256
- package/build/tasks/sass/index.js +3 -1
- package/build/tasks/watch/index.js +1 -1
- package/components/combine-and-loop-fields/Readme.md +42 -0
- package/components/combine-and-loop-fields/index.js +156 -0
- package/components/date/index.js +3 -1
- package/components/date/templates/date.html +15 -12
- package/components/homeoffice-countries/index.js +22 -0
- package/components/index.js +2 -0
- package/components/notify/notify.js +2 -2
- package/components/summary/index.js +3 -2
- package/config/builder-defaults.js +3 -1
- package/config/component-defaults.js +13 -0
- package/controller/controller.js +57 -1
- package/controller/formatting/formatters.js +12 -0
- package/controller/validation/index.js +2 -1
- package/controller/validation/validators.js +4 -0
- package/frontend/govuk-template/build/config.js +2 -2
- package/frontend/govuk-template/build/govuk_template.html +104 -0
- package/frontend/govuk-template/build/index.js +2 -2
- package/frontend/govuk-template/index.js +4 -4
- package/frontend/template-mixins/mixins/template-mixins.js +39 -11
- package/frontend/template-mixins/partials/forms/checkbox-group.html +47 -0
- package/frontend/template-mixins/partials/forms/checkbox.html +4 -4
- package/frontend/template-mixins/partials/forms/input-submit.html +1 -1
- package/frontend/template-mixins/partials/forms/input-text-date.html +37 -0
- package/frontend/template-mixins/partials/forms/input-text-group.html +15 -10
- package/frontend/template-mixins/partials/forms/option-group.html +42 -26
- package/frontend/template-mixins/partials/forms/select.html +10 -5
- package/frontend/template-mixins/partials/forms/textarea-group.html +37 -23
- package/frontend/template-mixins/partials/mixins/panel.html +3 -4
- package/frontend/template-partials/views/accessibility.html +4 -4
- package/frontend/template-partials/views/cookies.html +1 -1
- package/frontend/template-partials/views/layout.html +24 -17
- package/frontend/template-partials/views/partials/back.html +1 -1
- package/frontend/template-partials/views/partials/bullet-list.html +1 -1
- package/frontend/template-partials/views/partials/confirmation-alert.html +4 -3
- package/frontend/template-partials/views/partials/continue.html +1 -1
- package/frontend/template-partials/views/partials/cookie-banner.html +27 -24
- package/frontend/template-partials/views/partials/cookie-settings-radio.html +6 -6
- package/frontend/template-partials/views/partials/external-link.html +1 -1
- package/frontend/template-partials/views/partials/form.html +2 -1
- package/frontend/template-partials/views/partials/maincontent-left.html +4 -4
- package/frontend/template-partials/views/partials/navigation.html +7 -6
- package/frontend/template-partials/views/partials/session-cookies-table.html +6 -6
- package/frontend/template-partials/views/partials/summary-table-row.html +2 -2
- package/frontend/template-partials/views/partials/table.html +7 -7
- package/frontend/template-partials/views/partials/validation-list.html +2 -2
- package/frontend/template-partials/views/partials/validation-summary.html +14 -13
- package/frontend/template-partials/views/partials/warn.html +7 -0
- package/frontend/template-partials/views/session-timeout.html +3 -2
- package/frontend/themes/gov-uk/client-js/cookieSettings.js +1 -1
- package/frontend/themes/gov-uk/client-js/govuk-cookies.js +121 -0
- package/frontend/themes/gov-uk/client-js/index.js +6 -1
- package/frontend/themes/gov-uk/client-js/skip-to-main.js +19 -0
- package/frontend/themes/gov-uk/styles/_cookie-banner.scss +51 -1
- package/frontend/themes/gov-uk/styles/govuk.scss +4 -0
- package/frontend/themes/gov-uk/styles/modules/_validation.scss +5 -5
- package/frontend/toolkit/assets/javascript/character-count.js +4 -4
- package/frontend/toolkit/assets/javascript/progressive-reveal.js +3 -1
- package/frontend/toolkit/assets/javascript/validation.js +5 -1
- package/frontend/toolkit/assets/stylesheets/modules/_validation.scss +3 -3
- package/index.js +15 -2
- package/lib/ga-tag.js +1 -1
- package/lib/settings.js +18 -2
- package/middleware/errors.js +2 -3
- package/middleware/not-found.js +0 -3
- package/middleware/rate-limiter.js +1 -0
- package/model/apis/html-to-pdf-converter.js +9 -8
- package/model/index.js +27 -28
- package/package.json +16 -14
- package/sandbox/README.md +3 -3
- package/sandbox/apps/sandbox/fields.js +33 -11
- package/sandbox/apps/sandbox/index.js +4 -0
- package/sandbox/apps/sandbox/sections/summary-data-sections.js +3 -0
- package/sandbox/apps/sandbox/translations/en/default.json +224 -0
- package/sandbox/apps/sandbox/translations/src/en/fields.json +11 -4
- package/sandbox/apps/sandbox/translations/src/en/journey.json +4 -1
- package/sandbox/apps/sandbox/translations/src/en/pages.json +7 -25
- package/sandbox/apps/sandbox/translations/src/en/validation.json +5 -1
- package/sandbox/assets/js/index.js +1 -1
- package/sandbox/assets/scss/app.scss +16 -16
- package/sandbox/package.json +6 -1
- package/sandbox/public/css/app.css +2793 -0
- package/sandbox/public/images/icons/icon-caret-left.png +0 -0
- package/sandbox/public/images/icons/icon-complete.png +0 -0
- package/sandbox/public/images/icons/icon-cross-remove-sign.png +0 -0
- package/sandbox/public/js/bundle.js +32888 -0
- package/sandbox/server.js +2 -1
- package/sandbox/yarn.lock +243 -1
- package/wizard/index.js +0 -13
- package/wizard/middleware/check-progress.js +36 -1
- package/.nyc_output/4d5a4574-78fc-4fcb-9412-3658f6ce33ff.json +0 -1
- package/.nyc_output/processinfo/4d5a4574-78fc-4fcb-9412-3658f6ce33ff.json +0 -1
- package/frontend/govuk-template/govuk_template.html +0 -109
- package/frontend/themes/gov-uk/views/partials/form.html +0 -9
- package/frontend/themes/gov-uk/views/partials/forms/option-group.html +0 -28
- package/frontend/themes/gov-uk/views/partials/mixins/panel.html +0 -3
- package/frontend/themes/gov-uk/views/partials/validation-summary.html +0 -24
- package/middleware/monitor.js +0 -20
- package/sandbox/apps/sandbox/views/confirmation.html +0 -15
@@ -26,7 +26,9 @@ module.exports = config => {
|
|
26
26
|
sass.render({
|
27
27
|
file: config.sass.src,
|
28
28
|
importer: importer({ aliases }),
|
29
|
-
aliases
|
29
|
+
aliases,
|
30
|
+
outputStyle: config.sass.outputStyle,
|
31
|
+
quietDeps: config.sass.quietDeps
|
30
32
|
}, (err, result) => err ? reject(err) : resolve(result.css));
|
31
33
|
}))
|
32
34
|
.then(css => new Promise((resolve, reject) => {
|
@@ -116,7 +116,7 @@ module.exports = config => {
|
|
116
116
|
|
117
117
|
if (process.env.HOF_SANDBOX === 'true') {
|
118
118
|
const rootDir = require('path').resolve(__dirname, '../../../');
|
119
|
-
ignored.push(`${rootDir}/frontend/govuk-template/
|
119
|
+
ignored.push(`${rootDir}/frontend/govuk-template/govuk_template_generated.html`);
|
120
120
|
watchLocation = [rootDir, '.'];
|
121
121
|
}
|
122
122
|
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# Combine & Loop Fields Behaviour
|
2
|
+
|
3
|
+
## What this does
|
4
|
+
This allows you to specify fields to loop over and add as objects to a parent array. You can use this for adding multiple addresses, criminal offences, names etc. You can see here in this example, we ask for a set of details prefixed with the word `storage-` for getting address details for multiple storage addresses (see the Home Office's firearms repository). We then can aggregate these addresses as objects into an array called `all-storage-addresses` and get redirected back to `/add-address` whenever we selected `yes` to adding another address,
|
5
|
+
```
|
6
|
+
'/add-address': {
|
7
|
+
fields: [
|
8
|
+
'storage-building',
|
9
|
+
'storage-street',
|
10
|
+
'storage-townOrCity',
|
11
|
+
'storage-postcodeOrZIPCode'
|
12
|
+
],
|
13
|
+
next: '/add-another-address-with-list',
|
14
|
+
continueOnEdit: true
|
15
|
+
},
|
16
|
+
'/add-another-address-with-list': {
|
17
|
+
template: 'add-another-address-loop.html',
|
18
|
+
behaviours: CombineAndLoopFields({
|
19
|
+
groupName: 'all-storage-addresses',
|
20
|
+
fieldsToGroup: [
|
21
|
+
'storage-building',
|
22
|
+
'storage-street',
|
23
|
+
'storage-townOrCity',
|
24
|
+
'storage-postcodeOrZIPCode'
|
25
|
+
],
|
26
|
+
removePrefix: 'storage-',
|
27
|
+
combineValuesToSingleField: 'address',
|
28
|
+
groupOptional: true,
|
29
|
+
returnTo: '/add-address'
|
30
|
+
}),
|
31
|
+
next: '/confirm'
|
32
|
+
```
|
33
|
+
Here are the fields you call this behaviour first to set config for it:
|
34
|
+
```
|
35
|
+
`groupName`: (Required) a parent array for storing details for each object you are collecting information for,
|
36
|
+
`fieldsToGroup`: (Required) the fields being specified for an object, e.g. house number, street, postcode, that are grouped together,
|
37
|
+
`removePrefix`: (Optional) a string which is used to remove consistent prefixes from a collection of fields that are grouped together,
|
38
|
+
`combineValuesToSingleField`: (Optional) a new field that is created with its value being the concatenation of values of the fields specified in `fieldsToGroup`,
|
39
|
+
`groupOptional`: (Optional) set this to true if you want to land on the radio button question if all records in the group are deleted after creation,
|
40
|
+
`returnTo`: (Required) the next step if you want to add another object to this group
|
41
|
+
```
|
42
|
+
N.B. in the above example we use `continueOnEdit: true` on the individual record step (i.e. `/add-address`) to ensure we revisit the grouped fields page otherwise it will not be added to the group and the user will be returned to the 'Check Your Answers' page upon a field edit.
|
@@ -0,0 +1,156 @@
|
|
1
|
+
|
2
|
+
const _ = require('lodash');
|
3
|
+
const uuid = require('uuid').v1;
|
4
|
+
const path = require('path');
|
5
|
+
const express = require('express');
|
6
|
+
|
7
|
+
module.exports = config => {
|
8
|
+
const { returnTo, groupName, fieldsToGroup, combineValuesToSingleField, removePrefix, groupOptional } = config;
|
9
|
+
|
10
|
+
if (removePrefix && typeof removePrefix !== 'string') {
|
11
|
+
throw new Error('removePrefix is a string and is optional for loops');
|
12
|
+
}
|
13
|
+
|
14
|
+
if (combineValuesToSingleField && typeof combineValuesToSingleField !== 'string') {
|
15
|
+
throw new Error('combineValuesToSingleField is a string and is optional for loops');
|
16
|
+
}
|
17
|
+
|
18
|
+
if (!returnTo || typeof returnTo !== 'string') {
|
19
|
+
throw new Error('returnTo is a string and is required for loops');
|
20
|
+
}
|
21
|
+
|
22
|
+
if (!groupName || typeof groupName !== 'string') {
|
23
|
+
throw new Error('groupName is a string and is required for loops');
|
24
|
+
}
|
25
|
+
|
26
|
+
if (!fieldsToGroup ||
|
27
|
+
!fieldsToGroup.length ||
|
28
|
+
!Array.isArray(fieldsToGroup) ||
|
29
|
+
_.some(fieldsToGroup, field => typeof field !== 'string')) {
|
30
|
+
throw new Error('fieldsToGroup is an array of strings and is required for loops');
|
31
|
+
}
|
32
|
+
|
33
|
+
return superclass => class extends superclass {
|
34
|
+
get(req, res, next) {
|
35
|
+
if (req.query.delete) {
|
36
|
+
const router = express.Router({ mergeParams: true });
|
37
|
+
router.use([
|
38
|
+
// eslint-disable-next-line no-underscore-dangle
|
39
|
+
this._configure.bind(this),
|
40
|
+
this.removeItem.bind(this),
|
41
|
+
this.reload.bind(this)
|
42
|
+
]);
|
43
|
+
return router.handle(req, res, next);
|
44
|
+
}
|
45
|
+
return super.get(req, res, next);
|
46
|
+
}
|
47
|
+
|
48
|
+
getLoopFields(req) {
|
49
|
+
let loopedFields = _.pick(req.sessionModel.toJSON(), fieldsToGroup);
|
50
|
+
|
51
|
+
if (removePrefix) {
|
52
|
+
loopedFields = _.mapKeys(loopedFields, (value, key) => key.replace(removePrefix, ''));
|
53
|
+
}
|
54
|
+
return loopedFields;
|
55
|
+
}
|
56
|
+
|
57
|
+
removeItem(req, res, next) {
|
58
|
+
const id = req.query.delete;
|
59
|
+
const items = req.sessionModel.get(groupName).filter(item => item.id !== id);
|
60
|
+
req.sessionModel.set(groupName, items);
|
61
|
+
next();
|
62
|
+
}
|
63
|
+
|
64
|
+
// eslint-disable-next-line no-unused-vars
|
65
|
+
reload(req, res, next) {
|
66
|
+
const items = req.sessionModel.get(groupName);
|
67
|
+
if (!items.length) {
|
68
|
+
req.sessionModel.set(`${groupName}-saved`, false);
|
69
|
+
fieldsToGroup.forEach(field => {
|
70
|
+
req.sessionModel.unset(field);
|
71
|
+
});
|
72
|
+
}
|
73
|
+
|
74
|
+
const target = (items.length || groupOptional) ? req.form.options.route : returnTo;
|
75
|
+
const action = req.params.action || '';
|
76
|
+
res.redirect(path.join(req.baseUrl, target, action));
|
77
|
+
}
|
78
|
+
|
79
|
+
configure(req, res, next) {
|
80
|
+
const field = `${groupName}-add-another`;
|
81
|
+
// add yes/no field
|
82
|
+
req.form.options.fields[field] = Object.assign({
|
83
|
+
mixin: 'radio-group',
|
84
|
+
validate: ['required'],
|
85
|
+
options: [
|
86
|
+
'yes', 'no'
|
87
|
+
],
|
88
|
+
legend: {
|
89
|
+
className: 'visuallyhidden'
|
90
|
+
}
|
91
|
+
}, req.form.options.fieldSettings);
|
92
|
+
|
93
|
+
// add conditonal fork
|
94
|
+
req.form.options.forks = req.form.options.forks || [];
|
95
|
+
req.form.options.forks.push({
|
96
|
+
target: returnTo,
|
97
|
+
continueOnEdit: true,
|
98
|
+
condition: {
|
99
|
+
field: field,
|
100
|
+
value: 'yes'
|
101
|
+
}
|
102
|
+
});
|
103
|
+
next();
|
104
|
+
}
|
105
|
+
|
106
|
+
getValues(req, res, next) {
|
107
|
+
const fieldsGroup = req.sessionModel.get(groupName) || [];
|
108
|
+
const added = req.sessionModel.get(`${groupName}-saved`);
|
109
|
+
return super.getValues(req, res, (err, values) => {
|
110
|
+
if (err) {
|
111
|
+
return next(err);
|
112
|
+
}
|
113
|
+
if (!added) {
|
114
|
+
const fields = this.getLoopFields(req);
|
115
|
+
if (!_.isEmpty(fields)) {
|
116
|
+
const newField = Object.assign({id: uuid()}, fields);
|
117
|
+
|
118
|
+
if (combineValuesToSingleField) {
|
119
|
+
const combinedValues = _.filter(fieldsToGroup.map(field => req.sessionModel.get(field))).join(', ');
|
120
|
+
newField[combineValuesToSingleField] = combinedValues;
|
121
|
+
}
|
122
|
+
|
123
|
+
fieldsGroup.push(newField);
|
124
|
+
values[groupName] = fieldsGroup;
|
125
|
+
fieldsToGroup.forEach(field => req.sessionModel.unset(field));
|
126
|
+
|
127
|
+
req.sessionModel.set(groupName, fieldsGroup);
|
128
|
+
req.sessionModel.set(`${groupName}-saved`, true);
|
129
|
+
}
|
130
|
+
}
|
131
|
+
return next(null, values);
|
132
|
+
});
|
133
|
+
}
|
134
|
+
|
135
|
+
locals(req, res) {
|
136
|
+
const items = req.form.values[groupName] || [];
|
137
|
+
return Object.assign({}, super.locals(req, res), {
|
138
|
+
items,
|
139
|
+
hasItems: items.length > 0,
|
140
|
+
field: groupName
|
141
|
+
});
|
142
|
+
}
|
143
|
+
|
144
|
+
saveValues(req, res, next) {
|
145
|
+
// remove "yes" value from session so it is no pre-populated next time around
|
146
|
+
super.saveValues(req, res, err => {
|
147
|
+
const field = `${groupName}-add-another`;
|
148
|
+
if (req.form.values[field] === 'yes') {
|
149
|
+
req.sessionModel.unset(field);
|
150
|
+
req.sessionModel.set(`${groupName}-saved`, false);
|
151
|
+
}
|
152
|
+
next(err);
|
153
|
+
});
|
154
|
+
}
|
155
|
+
};
|
156
|
+
};
|
package/components/date/index.js
CHANGED
@@ -40,6 +40,7 @@ const conditionalTranslate = (key, translate) => {
|
|
40
40
|
};
|
41
41
|
|
42
42
|
const getLegendClassName = field => field && field.legend && field.legend.className || '';
|
43
|
+
const getIsPageHeading = field => field && field.isPageHeading || '';
|
43
44
|
|
44
45
|
module.exports = (key, opts) => {
|
45
46
|
if (!key) {
|
@@ -144,8 +145,9 @@ module.exports = (key, opts) => {
|
|
144
145
|
const legend = conditionalTranslate(`fields.${key}.legend`, req.translate);
|
145
146
|
const hint = conditionalTranslate(`fields.${key}.hint`, req.translate);
|
146
147
|
const legendClassName = getLegendClassName(options);
|
148
|
+
const isPageHeading = getIsPageHeading(options);
|
147
149
|
const error = req.form.errors && req.form.errors[key];
|
148
|
-
res.render(template, { key, legend, legendClassName, hint, error }, (err, html) => {
|
150
|
+
res.render(template, { key, legend, legendClassName, isPageHeading, hint, error }, (err, html) => {
|
149
151
|
if (err) {
|
150
152
|
next(err);
|
151
153
|
} else {
|
@@ -1,17 +1,20 @@
|
|
1
|
-
<
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
1
|
+
<div class="govuk-form-group {{#error}}govuk-form-group--error{{/error}}">
|
2
|
+
<fieldset id="{{key}}-group" class="govuk-fieldset{{#className}} {{className}}{{/className}}" role="group">
|
3
|
+
<legend class="govuk-fieldset__legend {{#isPageHeading}}govuk-fieldset__legend--l{{/isPageHeading}}{{#legendClassName}} {{legendClassName}}{{/legendClassName}}">
|
4
|
+
{{#isPageHeading}}<h1 class="govuk-fieldset__heading">{{/isPageHeading}}
|
5
|
+
{{legend}}
|
6
|
+
{{#isPageHeading}}</h1>{{/isPageHeading}}
|
7
|
+
</legend>
|
7
8
|
{{#hint}}
|
8
|
-
<span id="{{key}}-hint" class="
|
9
|
+
<span id="{{key}}-hint" class="govuk-hint">{{hint}}</span>
|
9
10
|
{{/hint}}
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
11
|
+
{{#error}}
|
12
|
+
<p id="{{key}}-error" class="govuk-error-message">
|
13
|
+
<span class="govuk-visually-hidden">Error:</span> {{error.message}}
|
14
|
+
</p>
|
15
|
+
{{/error}}
|
16
|
+
<div id="{{key}}" class="govuk-date-input">
|
15
17
|
{{#input-date}}{{key}}{{/input-date}}
|
16
18
|
</div>
|
17
19
|
</fieldset>
|
20
|
+
</div>
|
@@ -0,0 +1,22 @@
|
|
1
|
+
|
2
|
+
const _ = require('lodash');
|
3
|
+
const componentDefaults = require('../../config/component-defaults');
|
4
|
+
|
5
|
+
module.exports = superclass => class extends superclass {
|
6
|
+
configure(req, res, next) {
|
7
|
+
const homeOfficeCountries = [''].concat(require('homeoffice-countries').allCountries);
|
8
|
+
|
9
|
+
const nationalityFields = componentDefaults.homeOfficeCountries;
|
10
|
+
|
11
|
+
nationalityFields.forEach(field => {
|
12
|
+
if (_.get(req, `form.options.fields[${field}].options`)) {
|
13
|
+
req.form.options.fields[field].options = homeOfficeCountries.map(country => {
|
14
|
+
const labelString = country !== '' ? country : 'Please select a country';
|
15
|
+
return { label: labelString, value: country };
|
16
|
+
});
|
17
|
+
}
|
18
|
+
});
|
19
|
+
|
20
|
+
next();
|
21
|
+
}
|
22
|
+
};
|
package/components/index.js
CHANGED
@@ -3,8 +3,10 @@
|
|
3
3
|
module.exports = {
|
4
4
|
addressLookup: require('./address-lookup'),
|
5
5
|
clearSession: require('./clear-session'),
|
6
|
+
combineAndLoopFields: require('./combine-and-loop-fields'),
|
6
7
|
date: require('./date'),
|
7
8
|
emailer: require('./emailer'),
|
9
|
+
homeOfficeCountries: require('./homeoffice-countries'),
|
8
10
|
notify: require('./notify'),
|
9
11
|
summary: require('./summary')
|
10
12
|
};
|
@@ -1,6 +1,6 @@
|
|
1
1
|
'use strict';
|
2
2
|
const NotifyClient = require('notifications-node-client').NotifyClient;
|
3
|
-
const
|
3
|
+
const { v4: uuidv4 } = require('uuid');
|
4
4
|
|
5
5
|
module.exports = class Notify {
|
6
6
|
constructor(opts) {
|
@@ -11,7 +11,7 @@ module.exports = class Notify {
|
|
11
11
|
}
|
12
12
|
|
13
13
|
send(email) {
|
14
|
-
const reference =
|
14
|
+
const reference = uuidv4();
|
15
15
|
|
16
16
|
return this.notifyClient.sendEmail(this.notifyTemplate, email.recipient, {
|
17
17
|
personalisation: {
|
@@ -59,7 +59,7 @@ module.exports = SuperClass => class extends SuperClass {
|
|
59
59
|
fieldData.value = fieldSpec.derivation ?
|
60
60
|
this.runCombinerForDerivedField(fieldSpec, req) : fieldData.value;
|
61
61
|
fieldData.value = (typeof fieldSpec.parse === 'function') ?
|
62
|
-
fieldSpec.parse(fieldData.value) : fieldData.value;
|
62
|
+
fieldSpec.parse(fieldData.value, req) : fieldData.value;
|
63
63
|
}
|
64
64
|
|
65
65
|
return fieldData;
|
@@ -121,7 +121,8 @@ module.exports = SuperClass => class extends SuperClass {
|
|
121
121
|
}
|
122
122
|
|
123
123
|
getStepForField(key, steps) {
|
124
|
-
|
124
|
+
const keyName = Array.isArray(key) ? key[0] : key;
|
125
|
+
return Object.keys(steps).filter(step => steps[step].fields && steps[step].fields.indexOf(keyName) > -1)[0];
|
125
126
|
}
|
126
127
|
|
127
128
|
|
@@ -12,7 +12,9 @@ module.exports = {
|
|
12
12
|
src: 'assets/scss/app.scss',
|
13
13
|
out: 'public/css/app.css',
|
14
14
|
match: 'assets/scss/**/*.scss',
|
15
|
-
restart: false
|
15
|
+
restart: false,
|
16
|
+
quietDeps: false,
|
17
|
+
outputStyle: 'expanded'
|
16
18
|
},
|
17
19
|
translate: {
|
18
20
|
src: 'apps/**/translations/src',
|
package/controller/controller.js
CHANGED
@@ -103,15 +103,23 @@ module.exports = class Controller extends BaseController {
|
|
103
103
|
const locals = super.locals(req, res);
|
104
104
|
const stepLocals = req.form.options.locals || {};
|
105
105
|
|
106
|
-
|
106
|
+
let fields = _.map(req.form.options.fields, (field, key) =>
|
107
107
|
Object.assign({}, field, { key })
|
108
108
|
);
|
109
|
+
// only include fields that aren't dependents to mitigate duplicate fields on the page
|
110
|
+
fields = fields.filter(field => !req.form.options.fields[field.key].dependent);
|
109
111
|
|
110
112
|
return _.extend({}, locals, {
|
111
113
|
fields,
|
112
114
|
route,
|
113
115
|
baseUrl: req.baseUrl,
|
116
|
+
skipToMain: this.getFirstFormItem(req.form.options.fields),
|
114
117
|
title: this.getTitle(route, lookup, req.form.options.fields, res.locals),
|
118
|
+
journeyHeaderURL: this.getJourneyHeaderURL(req.baseUrl),
|
119
|
+
header: this.getHeader(route, lookup, res.locals),
|
120
|
+
captionHeading: this.getCaptionHeading(route, lookup, res.locals),
|
121
|
+
warning: this.getWarning(route, lookup, res.locals),
|
122
|
+
subHeading: this.getSubHeading(route, lookup, res.locals),
|
115
123
|
intro: this.getIntro(route, lookup, res.locals),
|
116
124
|
backLink: this.getBackLink(req, res),
|
117
125
|
nextPage: this.getNextStep(req, res),
|
@@ -119,6 +127,34 @@ module.exports = class Controller extends BaseController {
|
|
119
127
|
}, stepLocals);
|
120
128
|
}
|
121
129
|
|
130
|
+
getJourneyHeaderURL(url) {
|
131
|
+
return url === '' ? '/' : url;
|
132
|
+
}
|
133
|
+
|
134
|
+
getFirstFormItem(fields) {
|
135
|
+
let firstFieldKey;
|
136
|
+
if (_.size(fields)) {
|
137
|
+
firstFieldKey = Object.keys(fields)[0];
|
138
|
+
}
|
139
|
+
return firstFieldKey | 'main-content';
|
140
|
+
}
|
141
|
+
|
142
|
+
getHeader(route, lookup, locals) {
|
143
|
+
return lookup(`pages.${route}.header`, locals);
|
144
|
+
}
|
145
|
+
|
146
|
+
getCaptionHeading(route, lookup, locals) {
|
147
|
+
return lookup(`pages.${route}.captionHeading`, locals);
|
148
|
+
}
|
149
|
+
|
150
|
+
getSubHeading(route, lookup, locals) {
|
151
|
+
return lookup(`pages.${route}.subHeading`, locals);
|
152
|
+
}
|
153
|
+
|
154
|
+
getWarning(route, lookup, locals) {
|
155
|
+
return lookup(`pages.${route}.warning`, locals);
|
156
|
+
}
|
157
|
+
|
122
158
|
getTitle(route, lookup, fields, locals) {
|
123
159
|
let fieldName = '';
|
124
160
|
if (_.size(fields)) {
|
@@ -140,6 +176,26 @@ module.exports = class Controller extends BaseController {
|
|
140
176
|
_getErrors(req, res, callback) {
|
141
177
|
super._getErrors(req, res, () => {
|
142
178
|
Object.keys(req.form.errors).forEach(key => {
|
179
|
+
if (req.form && req.form.options && req.form.options.fields) {
|
180
|
+
const field = req.form.options.fields[key];
|
181
|
+
// get first option for radios and checkbox
|
182
|
+
if (field.mixin === 'radio-group' || field.mixin === 'checkbox-group') {
|
183
|
+
// get first option for radios and checkbox where there is a toggle
|
184
|
+
if(typeof field.options[0] === 'object') {
|
185
|
+
req.form.errors[key].errorLinkId = key + '-' + field.options[0].value;
|
186
|
+
} else {
|
187
|
+
req.form.errors[key].errorLinkId = key + '-' + field.options[0];
|
188
|
+
}
|
189
|
+
// eslint-disable-next-line brace-style
|
190
|
+
}
|
191
|
+
// get first field for date input control
|
192
|
+
else if (field && field.mixin === 'input-date') {
|
193
|
+
req.form.errors[key].errorLinkId = key + '-day';
|
194
|
+
} else {
|
195
|
+
req.form.errors[key].errorLinkId = key;
|
196
|
+
}
|
197
|
+
}
|
198
|
+
|
143
199
|
req.form.errors[key].message = this.getErrorMessage(req.form.errors[key], req, res);
|
144
200
|
});
|
145
201
|
callback();
|
@@ -53,6 +53,18 @@ module.exports = {
|
|
53
53
|
|
54
54
|
base64decode(value) {
|
55
55
|
return Buffer.from(value, 'base64').toString();
|
56
|
+
},
|
57
|
+
|
58
|
+
ukPostcode(value) {
|
59
|
+
if (typeof value !== 'string' || value === '') {
|
60
|
+
return value;
|
61
|
+
}
|
62
|
+
|
63
|
+
const postcode = this.uppercase(this.removespaces(value));
|
64
|
+
const firstPart = postcode.slice(0, -3);
|
65
|
+
const secondPart = postcode.slice(-3);
|
66
|
+
|
67
|
+
return `${firstPart} ${secondPart}`;
|
56
68
|
}
|
57
69
|
|
58
70
|
};
|
@@ -63,7 +63,8 @@ function validate(fields) {
|
|
63
63
|
debug(`Validating field: "${key}" with value: "${value}"`);
|
64
64
|
|
65
65
|
function shouldValidate() {
|
66
|
-
|
66
|
+
// validationLink used to validates multiple dependent fields in checkbox-group
|
67
|
+
let dependent = fields[key].dependent || fields[key].validationLink;
|
67
68
|
|
68
69
|
if (typeof dependent === 'string') {
|
69
70
|
dependent = {
|
@@ -62,6 +62,10 @@ module.exports = Validators = {
|
|
62
62
|
return Validators.string(value) && (value === '' || value.length <= length);
|
63
63
|
},
|
64
64
|
|
65
|
+
maxword(value, length) {
|
66
|
+
return Validators.string(value) && (value === '' || value.split(/\s+/).length <= length);
|
67
|
+
},
|
68
|
+
|
65
69
|
exactlength(value, length) {
|
66
70
|
return Validators.string(value) && (value === '' || value.length === length);
|
67
71
|
},
|
@@ -5,6 +5,7 @@ module.exports = {
|
|
5
5
|
assetPath: '{{govukAssetPath}}',
|
6
6
|
afterHeader: '{{$afterHeader}}{{/afterHeader}}',
|
7
7
|
bodyClasses: '{{$bodyClasses}}{{/bodyClasses}}',
|
8
|
+
bodyStart: '{{$bodyStart}}{{/bodyStart}}',
|
8
9
|
bodyEnd: '{{$bodyEnd}}{{/bodyEnd}}',
|
9
10
|
content: '{{$main}}{{/main}}',
|
10
11
|
cookieMessage: '{{$cookieMessage}}{{/cookieMessage}}',
|
@@ -17,8 +18,7 @@ module.exports = {
|
|
17
18
|
insideHeader: '{{$insideHeader}}{{/insideHeader}}',
|
18
19
|
pageTitle: '{{$pageTitle}}{{/pageTitle}}',
|
19
20
|
propositionHeader: '{{$propositionHeader}}{{/propositionHeader}}',
|
20
|
-
skipLinkMessage: '{{$skipLinkMessage}}Skip to main content{{/skipLinkMessage}}',
|
21
21
|
globalHeaderText: '{{$globalHeaderText}}GOV.UK{{/globalHeaderText}}',
|
22
|
-
licenceMessage: '{{$licenceMessage}}
|
22
|
+
licenceMessage: '{{$licenceMessage}}All content is available under the <a href="https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/" id="open-government-licence" class="govuk-footer__link" target="_blank" rel="license">Open Government Licence v3.0</a>, except where otherwise stated{{/licenceMessage}}',
|
23
23
|
crownCopyrightMessage: '{{$crownCopyrightMessage}}© Crown copyright{{/crownCopyrightMessage}}'
|
24
24
|
};
|
@@ -0,0 +1,104 @@
|
|
1
|
+
|
2
|
+
<!DOCTYPE html>
|
3
|
+
<!--[if lt IE 9]><html class="lte-ie8" lang="{{ htmlLang }}"><![endif]-->
|
4
|
+
<!--[if gt IE 8]><!--><html lang="{{ htmlLang }}" class="govuk-template"><!--<![endif]-->
|
5
|
+
<head>
|
6
|
+
<meta charset="utf-8" />
|
7
|
+
<title>{{ pageTitle }}</title>
|
8
|
+
|
9
|
+
<link rel="shortcut icon" sizes="16x16 32x32 48x48" href="{{assetPath}}images/favicon.ico" type="image/x-icon">
|
10
|
+
<link rel="mask-icon" href="{{assetPath}}images/govuk-mask-icon.svg" color="#0b0c0c">
|
11
|
+
<link rel="apple-touch-icon" sizes="180x180" href="{{assetPath}}images/govuk-apple-touch-icon-180x180.png">
|
12
|
+
<link rel="apple-touch-icon" sizes="167x167" href="{{assetPath}}images/govuk-apple-touch-icon-167x167.png">
|
13
|
+
<link rel="apple-touch-icon" sizes="152x152" href="{{assetPath}}images/govuk-apple-touch-icon-152x152.png">
|
14
|
+
<link rel="apple-touch-icon" href="{{assetPath}}images/govuk-apple-touch-icon.png">
|
15
|
+
|
16
|
+
|
17
|
+
<meta name="theme-color" content="#0b0c0c" />
|
18
|
+
|
19
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
20
|
+
|
21
|
+
{{{ head }}}
|
22
|
+
|
23
|
+
|
24
|
+
<meta property="og:image" content="{{assetPath}}images/opengraph-image.png">
|
25
|
+
</head>
|
26
|
+
|
27
|
+
<body class="{{ bodyClasses }} govuk-template__body js-enabled" >
|
28
|
+
<script>document.body.className = ((document.body.className) ? document.body.className + ' js-enabled' : 'js-enabled');</script>
|
29
|
+
|
30
|
+
|
31
|
+
|
32
|
+
<div id="global-cookie-message" class="gem-c-cookie-banner govuk-clearfix" data-module="cookie-banner" role="region" aria-label="cookie banner" data-nosnippet="">
|
33
|
+
{{{ cookieMessage }}}
|
34
|
+
</div>
|
35
|
+
|
36
|
+
{{{ bodyStart }}}
|
37
|
+
|
38
|
+
<header role="banner" id="govuk-header" class="{{{ headerClass }}}">
|
39
|
+
<div class="govuk-header__container govuk-width-container">
|
40
|
+
|
41
|
+
<div class="govuk-header__logo">
|
42
|
+
<a href="{{{ homepageUrl }}}" title="{{ logoLinkTitle }}" id="logo" class="govuk-header__link govuk-header__link--homepage" target="_blank" data-module="track-click" data-track-category="homeLinkClicked" data-track-action="homeHeader">
|
43
|
+
<span class="govuk-header__logotype">
|
44
|
+
<!--[if gt IE 8]><!-->
|
45
|
+
<svg aria-hidden="true" focusable="false" class="govuk-header__logotype-crown" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 132 97" height="30" width="36">
|
46
|
+
<path fill="currentColor" fill-rule="evenodd" d="M25 30.2c3.5 1.5 7.7-.2 9.1-3.7 1.5-3.6-.2-7.8-3.9-9.2-3.6-1.4-7.6.3-9.1 3.9-1.4 3.5.3 7.5 3.9 9zM9 39.5c3.6 1.5 7.8-.2 9.2-3.7 1.5-3.6-.2-7.8-3.9-9.1-3.6-1.5-7.6.2-9.1 3.8-1.4 3.5.3 7.5 3.8 9zM4.4 57.2c3.5 1.5 7.7-.2 9.1-3.8 1.5-3.6-.2-7.7-3.9-9.1-3.5-1.5-7.6.3-9.1 3.8-1.4 3.5.3 7.6 3.9 9.1zm38.3-21.4c3.5 1.5 7.7-.2 9.1-3.8 1.5-3.6-.2-7.7-3.9-9.1-3.6-1.5-7.6.3-9.1 3.8-1.3 3.6.4 7.7 3.9 9.1zm64.4-5.6c-3.6 1.5-7.8-.2-9.1-3.7-1.5-3.6.2-7.8 3.8-9.2 3.6-1.4 7.7.3 9.2 3.9 1.3 3.5-.4 7.5-3.9 9zm15.9 9.3c-3.6 1.5-7.7-.2-9.1-3.7-1.5-3.6.2-7.8 3.7-9.1 3.6-1.5 7.7.2 9.2 3.8 1.5 3.5-.3 7.5-3.8 9zm4.7 17.7c-3.6 1.5-7.8-.2-9.2-3.8-1.5-3.6.2-7.7 3.9-9.1 3.6-1.5 7.7.3 9.2 3.8 1.3 3.5-.4 7.6-3.9 9.1zM89.3 35.8c-3.6 1.5-7.8-.2-9.2-3.8-1.4-3.6.2-7.7 3.9-9.1 3.6-1.5 7.7.3 9.2 3.8 1.4 3.6-.3 7.7-3.9 9.1zM69.7 17.7l8.9 4.7V9.3l-8.9 2.8c-.2-.3-.5-.6-.9-.9L72.4 0H59.6l3.5 11.2c-.3.3-.6.5-.9.9l-8.8-2.8v13.1l8.8-4.7c.3.3.6.7.9.9l-5 15.4v.1c-.2.8-.4 1.6-.4 2.4 0 4.1 3.1 7.5 7 8.1h.2c.3 0 .7.1 1 .1.4 0 .7 0 1-.1h.2c4-.6 7.1-4.1 7.1-8.1 0-.8-.1-1.7-.4-2.4V34l-5.1-15.4c.4-.2.7-.6 1-.9zM66 92.8c16.9 0 32.8 1.1 47.1 3.2 4-16.9 8.9-26.7 14-33.5l-9.6-3.4c1 4.9 1.1 7.2 0 10.2-1.5-1.4-3-4.3-4.2-8.7L108.6 76c2.8-2 5-3.2 7.5-3.3-4.4 9.4-10 11.9-13.6 11.2-4.3-.8-6.3-4.6-5.6-7.9 1-4.7 5.7-5.9 8-.5 4.3-8.7-3-11.4-7.6-8.8 7.1-7.2 7.9-13.5 2.1-21.1-8 6.1-8.1 12.3-4.5 20.8-4.7-5.4-12.1-2.5-9.5 6.2 3.4-5.2 7.9-2 7.2 3.1-.6 4.3-6.4 7.8-13.5 7.2-10.3-.9-10.9-8-11.2-13.8 2.5-.5 7.1 1.8 11 7.3L80.2 60c-4.1 4.4-8 5.3-12.3 5.4 1.4-4.4 8-11.6 8-11.6H55.5s6.4 7.2 7.9 11.6c-4.2-.1-8-1-12.3-5.4l1.4 16.4c3.9-5.5 8.5-7.7 10.9-7.3-.3 5.8-.9 12.8-11.1 13.8-7.2.6-12.9-2.9-13.5-7.2-.7-5 3.8-8.3 7.1-3.1 2.7-8.7-4.6-11.6-9.4-6.2 3.7-8.5 3.6-14.7-4.6-20.8-5.8 7.6-5 13.9 2.2 21.1-4.7-2.6-11.9.1-7.7 8.8 2.3-5.5 7.1-4.2 8.1.5.7 3.3-1.3 7.1-5.7 7.9-3.5.7-9-1.8-13.5-11.2 2.5.1 4.7 1.3 7.5 3.3l-4.7-15.4c-1.2 4.4-2.7 7.2-4.3 8.7-1.1-3-.9-5.3 0-10.2l-9.5 3.4c5 6.9 9.9 16.7 14 33.5 14.8-2.1 30.8-3.2 47.7-3.2z"></path>
|
47
|
+
</svg>
|
48
|
+
<!--<![endif]-->
|
49
|
+
<!--<![endif]-->
|
50
|
+
<!--[if IE 8]>
|
51
|
+
<img src="{{ assetPath }}images/govuk-logotype-crown.png" class="govuk-header__logotype-crown-fallback-image" width="36" height="32">
|
52
|
+
<![endif]-->
|
53
|
+
</span>
|
54
|
+
<span class="govuk-header__logotype-text">
|
55
|
+
{{{ globalHeaderText }}}
|
56
|
+
</span>
|
57
|
+
</a>
|
58
|
+
</div>
|
59
|
+
{{{ insideHeader }}}
|
60
|
+
|
61
|
+
{{{ propositionHeader }}}
|
62
|
+
</div>
|
63
|
+
</header>
|
64
|
+
|
65
|
+
|
66
|
+
{{{ afterHeader }}}
|
67
|
+
|
68
|
+
|
69
|
+
{{{ content }}}
|
70
|
+
|
71
|
+
<footer class="govuk-footer" id="footer" role="contentinfo">
|
72
|
+
|
73
|
+
<div class="govuk-width-container">
|
74
|
+
{{{ footerTop }}}
|
75
|
+
|
76
|
+
<div class="govuk-footer__meta">
|
77
|
+
<div class="govuk-footer__meta-item govuk-footer__meta-item--grow">
|
78
|
+
<h2 class="govuk-visually-hidden">Support links</h2>
|
79
|
+
{{{ footerSupportLinks }}}
|
80
|
+
|
81
|
+
<svg aria-hidden="true" focusable="false" class="govuk-footer__licence-logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 483.2 195.7" height="17" width="41">
|
82
|
+
<path fill="currentColor" d="M421.5 142.8V.1l-50.7 32.3v161.1h112.4v-50.7zm-122.3-9.6A47.12 47.12 0 0 1 221 97.8c0-26 21.1-47.1 47.1-47.1 16.7 0 31.4 8.7 39.7 21.8l42.7-27.2A97.63 97.63 0 0 0 268.1 0c-36.5 0-68.3 20.1-85.1 49.7A98 98 0 0 0 97.8 0C43.9 0 0 43.9 0 97.8s43.9 97.8 97.8 97.8c36.5 0 68.3-20.1 85.1-49.7a97.76 97.76 0 0 0 149.6 25.4l19.4 22.2h3v-87.8h-80l24.3 27.5zM97.8 145c-26 0-47.1-21.1-47.1-47.1s21.1-47.1 47.1-47.1 47.2 21 47.2 47S123.8 145 97.8 145"></path>
|
83
|
+
</svg>
|
84
|
+
|
85
|
+
<span class="govuk-footer__licence-description">{{{ licenceMessage }}}</span>
|
86
|
+
</div>
|
87
|
+
|
88
|
+
<div class="govuk-footer__meta-item">
|
89
|
+
<a class="govuk-footer__link govuk-footer__copyright-logo" id="copyright-logo" target="_blank" href="https://www.nationalarchives.gov.uk/information-management/re-using-public-sector-information/uk-government-licensing-framework/crown-copyright/">{{{ crownCopyrightMessage }}}</a>
|
90
|
+
</div>
|
91
|
+
</div>
|
92
|
+
</div>
|
93
|
+
</footer>
|
94
|
+
|
95
|
+
<div id="global-app-error" class="app-error hidden"></div>
|
96
|
+
|
97
|
+
|
98
|
+
{{{ bodyEnd }}}
|
99
|
+
|
100
|
+
|
101
|
+
<script {{#nonce}}nonce="{{nonce}}"{{/nonce}}>if (typeof window.GOVUK === 'undefined') document.body.className = document.body.className.replace('js-enabled', '');</script>
|
102
|
+
|
103
|
+
</body>
|
104
|
+
</html>
|
@@ -12,12 +12,12 @@ function addNonceValueAttributeToInlineScripts(compiledTemplateString) {
|
|
12
12
|
}
|
13
13
|
|
14
14
|
module.exports = () => {
|
15
|
-
const template = require.resolve('
|
15
|
+
const template = require.resolve('./govuk_template.html');
|
16
16
|
|
17
17
|
const govukTemplate = fs.readFileSync(template, { encoding: 'utf-8' });
|
18
18
|
const compiledTemplate = Hogan.compile(govukTemplate).render(govukConfig);
|
19
19
|
const parsedTemplate = addNonceValueAttributeToInlineScripts(compiledTemplate);
|
20
|
-
const output = path.resolve(__dirname, '../
|
20
|
+
const output = path.resolve(__dirname, '../govuk_template_generated.html');
|
21
21
|
|
22
22
|
fs.writeFileSync(output, parsedTemplate, { encoding: 'utf-8' });
|
23
23
|
};
|