@dwp/govuk-casa 8.7.12 → 8.9.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/dist/casa.d.ts +12 -1
- package/dist/casa.js +10 -2
- package/dist/casa.js.map +1 -0
- package/dist/lib/CasaTemplateLoader.js +1 -0
- package/dist/lib/CasaTemplateLoader.js.map +1 -0
- package/dist/lib/JourneyContext.d.ts +12 -3
- package/dist/lib/JourneyContext.js +20 -5
- package/dist/lib/JourneyContext.js.map +1 -0
- package/dist/lib/MutableRouter.js +1 -0
- package/dist/lib/MutableRouter.js.map +1 -0
- package/dist/lib/Plan.d.ts +1 -1
- package/dist/lib/Plan.js +2 -5
- package/dist/lib/Plan.js.map +1 -0
- package/dist/lib/ValidationError.js +1 -0
- package/dist/lib/ValidationError.js.map +1 -0
- package/dist/lib/ValidatorFactory.d.ts +2 -2
- package/dist/lib/ValidatorFactory.js +3 -2
- package/dist/lib/ValidatorFactory.js.map +1 -0
- package/dist/lib/configuration-ingestor.js +1 -0
- package/dist/lib/configuration-ingestor.js.map +1 -0
- package/dist/lib/configure.js +2 -1
- package/dist/lib/configure.js.map +1 -0
- package/dist/lib/constants.d.ts +9 -0
- package/dist/lib/constants.js +13 -0
- package/dist/lib/constants.js.map +1 -0
- package/dist/lib/end-session.js +1 -0
- package/dist/lib/end-session.js.map +1 -0
- package/dist/lib/field.js +1 -0
- package/dist/lib/field.js.map +1 -0
- package/dist/lib/index.js +1 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/logger.js +1 -0
- package/dist/lib/logger.js.map +1 -0
- package/dist/lib/mount.js +3 -2
- package/dist/lib/mount.js.map +1 -0
- package/dist/lib/nunjucks-filters.js +1 -0
- package/dist/lib/nunjucks-filters.js.map +1 -0
- package/dist/lib/nunjucks.js +1 -0
- package/dist/lib/nunjucks.js.map +1 -0
- package/dist/lib/utils.d.ts +45 -27
- package/dist/lib/utils.js +105 -67
- package/dist/lib/utils.js.map +1 -0
- package/dist/lib/validators/dateObject.js +4 -3
- package/dist/lib/validators/dateObject.js.map +1 -0
- package/dist/lib/validators/email.js +1 -0
- package/dist/lib/validators/email.js.map +1 -0
- package/dist/lib/validators/inArray.js +1 -0
- package/dist/lib/validators/inArray.js.map +1 -0
- package/dist/lib/validators/index.js +1 -0
- package/dist/lib/validators/index.js.map +1 -0
- package/dist/lib/validators/nino.js +1 -0
- package/dist/lib/validators/nino.js.map +1 -0
- package/dist/lib/validators/postalAddressObject.d.ts +2 -2
- package/dist/lib/validators/postalAddressObject.js +2 -1
- package/dist/lib/validators/postalAddressObject.js.map +1 -0
- package/dist/lib/validators/regex.js +1 -0
- package/dist/lib/validators/regex.js.map +1 -0
- package/dist/lib/validators/required.js +1 -0
- package/dist/lib/validators/required.js.map +1 -0
- package/dist/lib/validators/strlen.js +1 -0
- package/dist/lib/validators/strlen.js.map +1 -0
- package/dist/lib/validators/wordCount.js +1 -0
- package/dist/lib/validators/wordCount.js.map +1 -0
- package/dist/lib/waypoint-url.js +40 -9
- package/dist/lib/waypoint-url.js.map +1 -0
- package/dist/middleware/body-parser.js +1 -0
- package/dist/middleware/body-parser.js.map +1 -0
- package/dist/middleware/csrf.js +1 -0
- package/dist/middleware/csrf.js.map +1 -0
- package/dist/middleware/data.js +1 -0
- package/dist/middleware/data.js.map +1 -0
- package/dist/middleware/gather-fields.js +10 -2
- package/dist/middleware/gather-fields.js.map +1 -0
- package/dist/middleware/i18n.js +1 -0
- package/dist/middleware/i18n.js.map +1 -0
- package/dist/middleware/post.js +1 -0
- package/dist/middleware/post.js.map +1 -0
- package/dist/middleware/pre.js +1 -0
- package/dist/middleware/pre.js.map +1 -0
- package/dist/middleware/progress-journey.js +7 -2
- package/dist/middleware/progress-journey.js.map +1 -0
- package/dist/middleware/sanitise-fields.js +1 -0
- package/dist/middleware/sanitise-fields.js.map +1 -0
- package/dist/middleware/serve-first-waypoint.js +1 -0
- package/dist/middleware/serve-first-waypoint.js.map +1 -0
- package/dist/middleware/session.js +1 -0
- package/dist/middleware/session.js.map +1 -0
- package/dist/middleware/skip-waypoint.js +1 -0
- package/dist/middleware/skip-waypoint.js.map +1 -0
- package/dist/middleware/steer-journey.js +2 -0
- package/dist/middleware/steer-journey.js.map +1 -0
- package/dist/middleware/strip-proxy-path.js +1 -0
- package/dist/middleware/strip-proxy-path.js.map +1 -0
- package/dist/middleware/validate-fields.js +7 -1
- package/dist/middleware/validate-fields.js.map +1 -0
- package/dist/mjs/esm-wrapper.js +11 -15
- package/dist/routes/ancillary.js +1 -0
- package/dist/routes/ancillary.js.map +1 -0
- package/dist/routes/journey.js +1 -0
- package/dist/routes/journey.js.map +1 -0
- package/dist/routes/static.js +1 -0
- package/dist/routes/static.js.map +1 -0
- package/locales/cy/error.json +1 -1
- package/locales/en/error.json +1 -1
- package/package.json +17 -16
- package/src/casa.js +330 -0
- package/src/lib/CasaTemplateLoader.js +104 -0
- package/src/lib/JourneyContext.js +797 -0
- package/src/lib/MutableRouter.js +310 -0
- package/src/lib/Plan.js +619 -0
- package/src/lib/ValidationError.js +163 -0
- package/src/lib/ValidatorFactory.js +105 -0
- package/src/lib/configuration-ingestor.js +457 -0
- package/src/lib/configure.js +202 -0
- package/src/lib/constants.js +9 -0
- package/src/lib/dirname.cjs +1 -0
- package/src/lib/end-session.js +45 -0
- package/src/lib/field.js +456 -0
- package/src/lib/index.js +33 -0
- package/src/lib/logger.js +16 -0
- package/src/lib/mount.js +127 -0
- package/src/lib/nunjucks-filters.js +150 -0
- package/src/lib/nunjucks.js +53 -0
- package/src/lib/utils.js +232 -0
- package/src/lib/validators/dateObject.js +169 -0
- package/src/lib/validators/email.js +55 -0
- package/src/lib/validators/inArray.js +81 -0
- package/src/lib/validators/index.js +24 -0
- package/src/lib/validators/nino.js +57 -0
- package/src/lib/validators/postalAddressObject.js +162 -0
- package/src/lib/validators/regex.js +48 -0
- package/src/lib/validators/required.js +74 -0
- package/src/lib/validators/strlen.js +66 -0
- package/src/lib/validators/wordCount.js +70 -0
- package/src/lib/waypoint-url.js +126 -0
- package/src/middleware/body-parser.js +31 -0
- package/src/middleware/csrf.js +29 -0
- package/src/middleware/data.js +105 -0
- package/src/middleware/dirname.cjs +1 -0
- package/src/middleware/gather-fields.js +58 -0
- package/src/middleware/i18n.js +106 -0
- package/src/middleware/post.js +61 -0
- package/src/middleware/pre.js +91 -0
- package/src/middleware/progress-journey.js +96 -0
- package/src/middleware/sanitise-fields.js +58 -0
- package/src/middleware/serve-first-waypoint.js +28 -0
- package/src/middleware/session.js +129 -0
- package/src/middleware/skip-waypoint.js +46 -0
- package/src/middleware/steer-journey.js +79 -0
- package/src/middleware/strip-proxy-path.js +56 -0
- package/src/middleware/validate-fields.js +89 -0
- package/src/routes/ancillary.js +29 -0
- package/src/routes/dirname.cjs +1 -0
- package/src/routes/journey.js +212 -0
- package/src/routes/static.js +77 -0
- package/views/casa/components/character-count/README.md +10 -0
- package/views/casa/components/character-count/template.njk +6 -2
- package/views/casa/components/checkboxes/README.md +43 -34
- package/views/casa/components/checkboxes/template.njk +8 -7
- package/views/casa/components/date-input/README.md +11 -1
- package/views/casa/components/date-input/template.njk +6 -4
- package/views/casa/components/input/README.md +9 -0
- package/views/casa/components/input/template.njk +6 -2
- package/views/casa/components/postal-address-object/README.md +10 -0
- package/views/casa/components/postal-address-object/template.njk +20 -5
- package/views/casa/components/radios/README.md +49 -24
- package/views/casa/components/radios/template.njk +6 -3
- package/views/casa/components/select/README.md +65 -0
- package/views/casa/components/select/macro.njk +3 -0
- package/views/casa/components/select/template.njk +49 -0
- package/views/casa/components/textarea/README.md +9 -0
- package/views/casa/components/textarea/template.njk +6 -2
- package/views/casa/layouts/journey.njk +1 -1
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/* eslint-disable object-curly-newline,max-len */
|
|
2
|
+
import MutableRouter from '../lib/MutableRouter.js';
|
|
3
|
+
import skipWaypointMiddlewareFactory from '../middleware/skip-waypoint.js';
|
|
4
|
+
import steerJourneyMiddlewareFactory from '../middleware/steer-journey.js';
|
|
5
|
+
import sanitiseFieldsMiddlewareFactory from '../middleware/sanitise-fields.js';
|
|
6
|
+
import gatherFieldsMiddlewareFactory from '../middleware/gather-fields.js';
|
|
7
|
+
import validateFieldsMiddlewareFactory from '../middleware/validate-fields.js';
|
|
8
|
+
import progressJourneyMiddlewareFactory from '../middleware/progress-journey.js';
|
|
9
|
+
import waypointUrl from '../lib/waypoint-url.js';
|
|
10
|
+
import logger from '../lib/logger.js';
|
|
11
|
+
import { resolveMiddlewareHooks } from '../lib/utils.js';
|
|
12
|
+
|
|
13
|
+
const log = logger('routes:journey');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @access private
|
|
17
|
+
* @param {import('../casa.js').GlobalHook} GlobalHook
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @access private
|
|
22
|
+
* @param {import('../casa.js').Page} Page
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @access private
|
|
27
|
+
* @param {import('../casa.js').Plan} Plan
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @typedef {object} JourneyRouterOptions Options to configure static router
|
|
32
|
+
* @property {GlobalHook[]} globalHooks Global hooks
|
|
33
|
+
* @property {Page[]} pages Page definitions
|
|
34
|
+
* @property {Plan} plan Plan
|
|
35
|
+
* @property {Function[]} csrfMiddleware Middleware for providing CSRF controls
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
const renderMiddlewareFactory = (view, contextFactory) => [
|
|
39
|
+
(req, res, next) => {
|
|
40
|
+
res.render(view, {
|
|
41
|
+
// Common template variables for both GET and POST requests
|
|
42
|
+
inEditMode: req.casa.editMode,
|
|
43
|
+
editOriginUrl: req.casa.editOrigin,
|
|
44
|
+
activeContextId: req.casa.journeyContext.identity.id,
|
|
45
|
+
...contextFactory(req),
|
|
46
|
+
}, (err, templateString) => {
|
|
47
|
+
if (err) {
|
|
48
|
+
next(err);
|
|
49
|
+
} else {
|
|
50
|
+
res.send(templateString);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
},
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Create an instance of the router for all waypoints visited during a Journey
|
|
58
|
+
* through the Plan.
|
|
59
|
+
*
|
|
60
|
+
* @access private
|
|
61
|
+
* @param {JourneyRouterOptions} opts Options
|
|
62
|
+
* @returns {MutableRouter} Router
|
|
63
|
+
*/
|
|
64
|
+
export default function journeyRouter({
|
|
65
|
+
globalHooks,
|
|
66
|
+
pages,
|
|
67
|
+
plan,
|
|
68
|
+
csrfMiddleware,
|
|
69
|
+
}) {
|
|
70
|
+
// Router
|
|
71
|
+
const router = new MutableRouter();
|
|
72
|
+
|
|
73
|
+
// Special "_" route which handles redirecting the user between sub-apps
|
|
74
|
+
// /app1/_/?refmount=app2&route=prev
|
|
75
|
+
router.all('/_', (req, res) => {
|
|
76
|
+
const mountUrl = `${req.baseUrl}/`;
|
|
77
|
+
const refmount = req.query?.refmount;
|
|
78
|
+
const route = req.query?.route;
|
|
79
|
+
log.trace(`App root ${mountUrl}: refmount = ${refmount}, route = ${route}`);
|
|
80
|
+
|
|
81
|
+
let redirectTo;
|
|
82
|
+
const fallback = waypointUrl({
|
|
83
|
+
mountUrl,
|
|
84
|
+
waypoint: plan.traverse(req.casa.journeyContext, {
|
|
85
|
+
stopCondition: () => (true), // we only need one; stop at the first
|
|
86
|
+
})[0],
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
if (route === 'prev') {
|
|
90
|
+
const routes = plan.traversePrevRoutes(req.casa.journeyContext, { startWaypoint: refmount });
|
|
91
|
+
redirectTo = routes.length ? waypointUrl({ mountUrl, waypoint: routes[0].target }) : fallback;
|
|
92
|
+
} else {
|
|
93
|
+
const routes = plan.traverseNextRoutes(req.casa.journeyContext, { startWaypoint: refmount });
|
|
94
|
+
if (routes[0].target !== null) {
|
|
95
|
+
redirectTo = routes.length ? waypointUrl({ mountUrl, waypoint: routes[0].target }) : fallback;
|
|
96
|
+
} else {
|
|
97
|
+
redirectTo = fallback;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Carry over any params
|
|
102
|
+
const url = new URL(redirectTo, 'https://placeholder.test/');
|
|
103
|
+
const searchParams = new URLSearchParams(req.query);
|
|
104
|
+
searchParams.delete('refmount');
|
|
105
|
+
searchParams.delete('route');
|
|
106
|
+
url.search = searchParams.toString();
|
|
107
|
+
redirectTo = `${url.pathname.replace(/\/+/g, '/')}${url.search}`;
|
|
108
|
+
|
|
109
|
+
log.trace(`Redirect to ${redirectTo}`);
|
|
110
|
+
return res.redirect(redirectTo);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Create GET / POST routes for each page
|
|
114
|
+
const commonMiddleware = [
|
|
115
|
+
...csrfMiddleware,
|
|
116
|
+
];
|
|
117
|
+
|
|
118
|
+
pages.forEach((page) => {
|
|
119
|
+
const { waypoint, view, hooks: pageHooks = [], fields } = page;
|
|
120
|
+
const waypointPath = `/${waypoint}`;
|
|
121
|
+
|
|
122
|
+
let commonWaypointMiddleware = [
|
|
123
|
+
(req, res, next) => {
|
|
124
|
+
req.casa.waypoint = waypoint;
|
|
125
|
+
res.locals.casa.waypoint = waypoint;
|
|
126
|
+
next();
|
|
127
|
+
},
|
|
128
|
+
];
|
|
129
|
+
|
|
130
|
+
if (plan.isSkippable(waypoint)) {
|
|
131
|
+
log.info(`Configuring "${waypoint}" as a skippable waypoint`);
|
|
132
|
+
commonWaypointMiddleware = [
|
|
133
|
+
...commonWaypointMiddleware,
|
|
134
|
+
...skipWaypointMiddlewareFactory({ waypoint }),
|
|
135
|
+
];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
router.get(
|
|
139
|
+
waypointPath,
|
|
140
|
+
...commonMiddleware,
|
|
141
|
+
...commonWaypointMiddleware,
|
|
142
|
+
|
|
143
|
+
...resolveMiddlewareHooks('journey.presteer', waypointPath, [...globalHooks, ...pageHooks]),
|
|
144
|
+
...steerJourneyMiddlewareFactory({ waypoint, plan }),
|
|
145
|
+
...resolveMiddlewareHooks('journey.poststeer', waypointPath, [...globalHooks, ...pageHooks]),
|
|
146
|
+
|
|
147
|
+
...resolveMiddlewareHooks('journey.prerender', waypointPath, [...globalHooks, ...pageHooks]),
|
|
148
|
+
renderMiddlewareFactory(view, (req) => ({
|
|
149
|
+
formUrl: waypointUrl({ mountUrl: `${req.baseUrl}/`, waypoint }),
|
|
150
|
+
formData: req.casa.journeyContext.getDataForPage(waypoint),
|
|
151
|
+
})),
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
router.post(
|
|
155
|
+
waypointPath,
|
|
156
|
+
...commonMiddleware,
|
|
157
|
+
...commonWaypointMiddleware,
|
|
158
|
+
|
|
159
|
+
...resolveMiddlewareHooks('journey.presteer', waypointPath, [...globalHooks, ...pageHooks]),
|
|
160
|
+
...steerJourneyMiddlewareFactory({ waypoint, plan }),
|
|
161
|
+
...resolveMiddlewareHooks('journey.poststeer', waypointPath, [...globalHooks, ...pageHooks]),
|
|
162
|
+
|
|
163
|
+
...resolveMiddlewareHooks('journey.presanitise', waypointPath, [...globalHooks, ...pageHooks]),
|
|
164
|
+
...sanitiseFieldsMiddlewareFactory({ waypoint, fields }),
|
|
165
|
+
...resolveMiddlewareHooks('journey.postsanitise', waypointPath, [...globalHooks, ...pageHooks]),
|
|
166
|
+
|
|
167
|
+
...resolveMiddlewareHooks('journey.pregather', waypointPath, [...globalHooks, ...pageHooks]),
|
|
168
|
+
...gatherFieldsMiddlewareFactory({ waypoint, fields }),
|
|
169
|
+
...resolveMiddlewareHooks('journey.postgather', waypointPath, [...globalHooks, ...pageHooks]),
|
|
170
|
+
|
|
171
|
+
...resolveMiddlewareHooks('journey.prevalidate', waypointPath, [...globalHooks, ...pageHooks]),
|
|
172
|
+
...validateFieldsMiddlewareFactory({ waypoint, fields, plan }),
|
|
173
|
+
...resolveMiddlewareHooks('journey.postvalidate', waypointPath, [...globalHooks, ...pageHooks]),
|
|
174
|
+
|
|
175
|
+
// If there were validation errors, jump out of this route and into the
|
|
176
|
+
// next, where the errors will be rendered
|
|
177
|
+
(req, res, next) => (req.casa.journeyContext.hasValidationErrorsForPage(waypoint) ? next('route') : next()),
|
|
178
|
+
|
|
179
|
+
...resolveMiddlewareHooks('journey.preredirect', waypointPath, [...globalHooks, ...pageHooks]),
|
|
180
|
+
...progressJourneyMiddlewareFactory({ waypoint, plan }),
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
router.post(
|
|
184
|
+
waypointPath,
|
|
185
|
+
...resolveMiddlewareHooks('journey.prerender', waypointPath, [...globalHooks, ...pageHooks]),
|
|
186
|
+
renderMiddlewareFactory(view, (req) => {
|
|
187
|
+
const errors = req.casa.journeyContext.getValidationErrorsForPageByField(waypoint) ?? Object.create(null);
|
|
188
|
+
|
|
189
|
+
// This is a convenience for the template. The `govukErrorSummary` macro
|
|
190
|
+
// requires the errors be in a particular format, so here we provide our
|
|
191
|
+
// errors in that format.
|
|
192
|
+
// Where there are multiple errors against a particular field, only the
|
|
193
|
+
// first one is shown.
|
|
194
|
+
// Disabling security/detect-object-injection rule because both `errors`
|
|
195
|
+
// and the `k` property are known entities
|
|
196
|
+
const govukErrors = Object.keys(errors).map((k) => ({
|
|
197
|
+
text: req.t(errors[k][0].summary, errors[k][0].variables), /* eslint-disable-line security/detect-object-injection */
|
|
198
|
+
href: errors[k][0].fieldHref, /* eslint-disable-line security/detect-object-injection */
|
|
199
|
+
}));
|
|
200
|
+
|
|
201
|
+
return {
|
|
202
|
+
formUrl: waypointUrl({ mountUrl: `${req.baseUrl}/`, waypoint }),
|
|
203
|
+
formData: req.body,
|
|
204
|
+
formErrors: Object.keys(errors).length ? errors : null,
|
|
205
|
+
formErrorsGovukArray: govukErrors.length ? govukErrors : null,
|
|
206
|
+
};
|
|
207
|
+
}),
|
|
208
|
+
);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
return router;
|
|
212
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import ExpressJS from 'express';
|
|
2
|
+
import { readFileSync } from 'fs';
|
|
3
|
+
import { URL } from 'url';
|
|
4
|
+
import { resolve } from 'path';
|
|
5
|
+
import { createRequire } from 'module';
|
|
6
|
+
|
|
7
|
+
import dirname from './dirname.cjs';
|
|
8
|
+
import MutableRouter from '../lib/MutableRouter.js';
|
|
9
|
+
import { validateUrlPath } from '../lib/utils.js';
|
|
10
|
+
|
|
11
|
+
const { static: ExpressStatic } = ExpressJS; // CommonJS
|
|
12
|
+
|
|
13
|
+
const oneDay = 86400000;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @typedef {object} StaticOptions Options to configure static router
|
|
17
|
+
* @property {number} [maxAge=3600000] Cache TTL for all assets (optional, default 1 hour)
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Create a router for serving CASA's static assets.
|
|
22
|
+
*
|
|
23
|
+
* @access private
|
|
24
|
+
* @param {StaticOptions} options Options
|
|
25
|
+
* @returns {MutableRouter} ExpressJS Router instance
|
|
26
|
+
*/
|
|
27
|
+
export default function staticRouter({
|
|
28
|
+
maxAge = 3600000,
|
|
29
|
+
} = {}) {
|
|
30
|
+
const router = new MutableRouter();
|
|
31
|
+
|
|
32
|
+
const notFoundHandler = (req, res, next) => {
|
|
33
|
+
// Fall through to a general purpose error handler
|
|
34
|
+
next(new Error('404'));
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const setHeaders = (req, res, next) => {
|
|
38
|
+
res.set('cache-control', 'public');
|
|
39
|
+
res.set('pragma', 'cache');
|
|
40
|
+
res.set('expires', new Date(Date.now() + oneDay).toUTCString());
|
|
41
|
+
const { pathname } = new URL(req?.originalUrl ?? '', 'https://placeholder.test/');
|
|
42
|
+
if (pathname.substr(-4) === '.css') {
|
|
43
|
+
// Just needed for our in-memory CSS assets
|
|
44
|
+
res.set('content-type', 'text/css');
|
|
45
|
+
}
|
|
46
|
+
next();
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const staticConfig = {
|
|
50
|
+
etag: true,
|
|
51
|
+
lastModified: false,
|
|
52
|
+
maxAge,
|
|
53
|
+
setHeaders: (res) => {
|
|
54
|
+
setHeaders(null, res, () => {});
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// The CASA CSS source contains the placeholder `~~~CASA_MOUNT_URL~~~` which
|
|
59
|
+
// must be replaced with the dynamic `mountUrl` to ensure govuk-frontend
|
|
60
|
+
// assets are served from the correct location.
|
|
61
|
+
const casaCss = readFileSync(resolve(dirname, '../../dist/assets/css/casa.css'), { encoding: 'utf8' });
|
|
62
|
+
const casaCssIe8 = readFileSync(resolve(dirname, '../../dist/assets/css/casa-ie8.css'), { encoding: 'utf8' });
|
|
63
|
+
|
|
64
|
+
// The static middleware will only server GET/HEAD requests, so we can mount
|
|
65
|
+
// the middleware using `use()` rather than resorting to `get()`
|
|
66
|
+
const govukFrontendDirectory = resolve(createRequire(dirname).resolve('govuk-frontend'), '../../');
|
|
67
|
+
|
|
68
|
+
router.use('/govuk/assets/js/all.js', ExpressStatic(`${govukFrontendDirectory}/govuk/all.js`, staticConfig));
|
|
69
|
+
router.use('/govuk/assets', ExpressStatic(`${govukFrontendDirectory}/govuk/assets`, staticConfig));
|
|
70
|
+
router.use('/govuk/assets', notFoundHandler);
|
|
71
|
+
|
|
72
|
+
router.get('/casa/assets/css/casa.css', setHeaders, (req, res) => res.send(casaCss.replace(/~~~CASA_MOUNT_URL~~~/g, validateUrlPath(`${req.baseUrl}/`))));
|
|
73
|
+
router.get('/casa/assets/css/casa-ie8.css', setHeaders, (req, res) => res.send(casaCssIe8.replace(/~~~CASA_MOUNT_URL~~~/g, validateUrlPath(`${req.baseUrl}/`))));
|
|
74
|
+
router.use('/casa/assets', notFoundHandler);
|
|
75
|
+
|
|
76
|
+
return router;
|
|
77
|
+
}
|
|
@@ -19,3 +19,13 @@ casaGovukCharacterCount({
|
|
|
19
19
|
casaErrors: formErrors
|
|
20
20
|
})
|
|
21
21
|
```
|
|
22
|
+
|
|
23
|
+
## Google Tag Manager
|
|
24
|
+
|
|
25
|
+
The following attributes will be attached to the error `<p>` tag if `casaWithAnalytics` is `true`:
|
|
26
|
+
|
|
27
|
+
* `data-ga-question`: Holds the fieldset label's text content
|
|
28
|
+
|
|
29
|
+
These are the conventions used by DWP.
|
|
30
|
+
|
|
31
|
+
> **IMPORTANT:** DO NOT ENABLE this option if the question or answer may contain personally-identifiable information as values will be pushed to Google
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
{% from
|
|
1
|
+
{% from 'govuk/components/character-count/macro.njk' import govukCharacterCount %}
|
|
2
2
|
|
|
3
3
|
{% set fieldErrors = params.casaErrors[params.name] %}
|
|
4
4
|
|
|
@@ -10,11 +10,15 @@
|
|
|
10
10
|
}) %}
|
|
11
11
|
{% endif %}
|
|
12
12
|
|
|
13
|
+
{# Merge parameters #}
|
|
13
14
|
{% set mergedParams = mergeObjects(params, {
|
|
14
15
|
id: params.id if params.id else 'f-' + params.name,
|
|
15
16
|
attributes: mergedAttributes,
|
|
16
17
|
errorMessage: {
|
|
17
|
-
text: t(params.casaErrors[params.name][0].inline, params.casaErrors[params.name][0].variables)
|
|
18
|
+
text: t(params.casaErrors[params.name][0].inline, params.casaErrors[params.name][0].variables),
|
|
19
|
+
attributes: {
|
|
20
|
+
'data-ga-question': params.label.text or params.label.html | striptags
|
|
21
|
+
} if params.casaWithAnalytics else {}
|
|
18
22
|
} if params.casaErrors[params.name].length
|
|
19
23
|
}) %}
|
|
20
24
|
|
|
@@ -15,62 +15,67 @@ Custom parameters:
|
|
|
15
15
|
Basic example:
|
|
16
16
|
|
|
17
17
|
```nunjucks
|
|
18
|
-
{% from
|
|
18
|
+
{% from 'casa/components/checkboxes/macro.njk' import casaGovukCheckboxes with context %}
|
|
19
19
|
|
|
20
20
|
casaGovukCheckboxes({
|
|
21
|
-
name:
|
|
21
|
+
name: 'preferences',
|
|
22
22
|
casaValue: formData.preferences,
|
|
23
23
|
casaErrors: formErrors,
|
|
24
24
|
fieldset: {
|
|
25
25
|
legend: {
|
|
26
|
-
text:
|
|
26
|
+
text: 'Choose your preferences',
|
|
27
27
|
isPageHeading: true,
|
|
28
|
-
classes:
|
|
28
|
+
classes: 'govuk-fieldset__legend--xl'
|
|
29
29
|
}
|
|
30
30
|
},
|
|
31
31
|
hint: {
|
|
32
|
-
text:
|
|
32
|
+
text: 'Some instructive hints'
|
|
33
33
|
},
|
|
34
|
-
items: [
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
34
|
+
items: [
|
|
35
|
+
{
|
|
36
|
+
value: 'twoReeds',
|
|
37
|
+
text: 'Two Reeds'
|
|
38
|
+
}, {
|
|
39
|
+
value: 'twistedFlax',
|
|
40
|
+
text: 'Twisted Flax'
|
|
41
|
+
}, {
|
|
42
|
+
value: 'water',
|
|
43
|
+
text: 'Water'
|
|
44
|
+
}, {
|
|
45
|
+
value: 'eyeOfHorus',
|
|
46
|
+
text: 'Eye of Horus'
|
|
47
|
+
}
|
|
48
|
+
]
|
|
47
49
|
})
|
|
48
50
|
```
|
|
49
51
|
|
|
50
|
-
|
|
52
|
+
If you want one of the checkbox items to toggle the display of an element:
|
|
51
53
|
|
|
52
54
|
```nunjucks
|
|
53
|
-
{% from
|
|
55
|
+
{% from 'casa/components/checkboxes/macro.njk' import casaGovukCheckboxes with context %}
|
|
54
56
|
|
|
55
57
|
{% set panel %}
|
|
56
|
-
This panel will remain hidden until the
|
|
58
|
+
This panel will remain hidden until the 'Yes' checkbox is chosen
|
|
57
59
|
{% endset %}
|
|
58
60
|
|
|
59
|
-
casaGovukCheckboxes({
|
|
60
|
-
name:
|
|
61
|
-
casaValue: formData.
|
|
61
|
+
{{ casaGovukCheckboxes({
|
|
62
|
+
name: 'preference',
|
|
63
|
+
casaValue: formData.preference,
|
|
62
64
|
casaErrors: formErrors,
|
|
63
|
-
items: [
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
items: [
|
|
66
|
+
{
|
|
67
|
+
value: 'yes',
|
|
68
|
+
text: 'Yes',
|
|
69
|
+
conditional: {
|
|
70
|
+
html: panel
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
value: 'no',
|
|
75
|
+
text: 'No'
|
|
68
76
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
text: "Second Choice"
|
|
72
|
-
}]
|
|
73
|
-
})
|
|
77
|
+
]
|
|
78
|
+
}) }}
|
|
74
79
|
```
|
|
75
80
|
|
|
76
81
|
## Displaying errors
|
|
@@ -86,6 +91,10 @@ The following attributes will be attached to each `<input>` option if `casaWithA
|
|
|
86
91
|
* `data-ga-question`: Holds the fieldset legend's content
|
|
87
92
|
* `data-ga-answer`: Holds the specific answer's text/html value
|
|
88
93
|
|
|
94
|
+
The following attributes will be attached to the error `<p>` tag if `casaWithAnalytics` is `true`:
|
|
95
|
+
|
|
96
|
+
* `data-ga-question`: Holds the fieldset legend's content after removing all html tags
|
|
97
|
+
|
|
89
98
|
These are the conventions used by DWP.
|
|
90
99
|
|
|
91
100
|
> **IMPORTANT:** DO NOT ENABLE this option if the question or answer may contain personally-identifiable information as values will be pushed to Google
|
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
{% from
|
|
1
|
+
{% from 'govuk/components/checkboxes/macro.njk' import govukCheckboxes %}
|
|
2
2
|
|
|
3
3
|
{# Ensure field name is always suffixed with [] to have it parsed consistently as an array #}
|
|
4
4
|
{% set fieldName = params.name | replace('[]', '') + '[]' %}
|
|
5
5
|
|
|
6
6
|
{% set fieldErrors = params.casaErrors[params.name] %}
|
|
7
7
|
|
|
8
|
-
{#
|
|
8
|
+
{# Generate validation data- attributes #}
|
|
9
9
|
{% set mergedAttributes = params.attributes or {} %}
|
|
10
10
|
{% if fieldErrors %}
|
|
11
11
|
{% set mergedAttributes = mergeObjects(mergedAttributes, {
|
|
12
12
|
'data-validation': {fn: params.name, va: fieldErrors[0].validator} | dump
|
|
13
13
|
}) %}
|
|
14
14
|
{% endif %}
|
|
15
|
+
|
|
15
16
|
{% set mergedAttributes = mergeObjects(mergedAttributes, {
|
|
16
17
|
id: params.id if params.id else 'f-' + params.name + '-wrapper' | safe
|
|
17
18
|
}) %}
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
{# Add checked flag to chosen inputs #}
|
|
20
|
+
{# Add checked flag to chosen items #}
|
|
21
21
|
{% set mergedItems = [] %}
|
|
22
22
|
{% for item in params.items %}
|
|
23
23
|
{% set item = mergeObjects({
|
|
@@ -31,7 +31,6 @@
|
|
|
31
31
|
{% set mergedItems = (mergedItems.push(item), mergedItems) %}
|
|
32
32
|
{% endfor %}
|
|
33
33
|
|
|
34
|
-
|
|
35
34
|
{# Merge parameters #}
|
|
36
35
|
{% set mergedParams = mergeObjects(params, {
|
|
37
36
|
name: fieldName,
|
|
@@ -39,7 +38,10 @@
|
|
|
39
38
|
attributes: mergedAttributes,
|
|
40
39
|
items: mergedItems,
|
|
41
40
|
errorMessage: {
|
|
42
|
-
text: t(params.casaErrors[params.name][0].inline, params.casaErrors[params.name][0].variables)
|
|
41
|
+
text: t(params.casaErrors[params.name][0].inline, params.casaErrors[params.name][0].variables),
|
|
42
|
+
attributes: {
|
|
43
|
+
'data-ga-question': params.fieldset.legend.text or params.fieldset.legend.html | striptags
|
|
44
|
+
} if params.casaWithAnalytics else {}
|
|
43
45
|
} if params.casaErrors[params.name] else null
|
|
44
46
|
}) %}
|
|
45
47
|
|
|
@@ -52,5 +54,4 @@
|
|
|
52
54
|
}) %}
|
|
53
55
|
{% endif %}
|
|
54
56
|
|
|
55
|
-
|
|
56
57
|
{{ govukCheckboxes(mergedParams) }}
|
|
@@ -72,7 +72,7 @@ module.exports = {
|
|
|
72
72
|
myDateField: sf([
|
|
73
73
|
r.required.make({
|
|
74
74
|
errorMsg: {
|
|
75
|
-
summary: 'Your error
|
|
75
|
+
summary: 'Your error message',
|
|
76
76
|
focusSuffix: '[dd]'
|
|
77
77
|
}
|
|
78
78
|
}),
|
|
@@ -102,3 +102,13 @@ You can also pass a locale:
|
|
|
102
102
|
```nunjucks
|
|
103
103
|
1 Ionawr 1980
|
|
104
104
|
```
|
|
105
|
+
|
|
106
|
+
## Google Tag Manager
|
|
107
|
+
|
|
108
|
+
The following attributes will be attached to the error `<p>` tag if `casaWithAnalytics` is `true`:
|
|
109
|
+
|
|
110
|
+
* `data-ga-question`: Holds the fieldset label's text content after removing all html tags
|
|
111
|
+
|
|
112
|
+
These are the conventions used by DWP.
|
|
113
|
+
|
|
114
|
+
> **IMPORTANT:** DO NOT ENABLE this option if the question or answer may contain personally-identifiable information as values will be pushed to Google
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
{% from
|
|
1
|
+
{% from 'govuk/components/date-input/macro.njk' import govukDateInput %}
|
|
2
2
|
|
|
3
3
|
{% set fieldErrors = params.casaErrors[params.namePrefix] %}
|
|
4
4
|
{% set hasSuffixHighlights = true if fieldErrors[0].focusSuffix.length else false %}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
{{ 'govuk-input--error' if fieldErrors }}
|
|
8
8
|
{% endset -%}
|
|
9
9
|
|
|
10
|
-
{#
|
|
10
|
+
{# Generate validation data- attributes #}
|
|
11
11
|
{% set mergedAttributes = params.attributes or {} %}
|
|
12
12
|
{% if fieldErrors %}
|
|
13
13
|
{% set mergedAttributes = mergeObjects(mergedAttributes, {
|
|
@@ -47,9 +47,11 @@
|
|
|
47
47
|
})
|
|
48
48
|
],
|
|
49
49
|
errorMessage: {
|
|
50
|
-
text: t(params.casaErrors[params.namePrefix][0].inline, params.casaErrors[params.namePrefix][0].variables)
|
|
50
|
+
text: t(params.casaErrors[params.namePrefix][0].inline, params.casaErrors[params.namePrefix][0].variables),
|
|
51
|
+
attributes: {
|
|
52
|
+
'data-ga-question': params.fieldset.legend.text or params.fieldset.legend.html | striptags
|
|
53
|
+
} if params.casaWithAnalytics else {}
|
|
51
54
|
} if params.casaErrors[params.namePrefix] else null
|
|
52
55
|
}) %}
|
|
53
56
|
|
|
54
|
-
|
|
55
57
|
{{ govukDateInput(mergedParams) }}
|
|
@@ -19,3 +19,12 @@ casaGovukInput({
|
|
|
19
19
|
casaErrors: formErrors
|
|
20
20
|
})
|
|
21
21
|
```
|
|
22
|
+
## Google Tag Manager
|
|
23
|
+
|
|
24
|
+
The following attributes will be attached to the error `<p>` tag if `casaWithAnalytics` is `true`:
|
|
25
|
+
|
|
26
|
+
* `data-ga-question`: Holds the fieldset label's text content after removing all html tags
|
|
27
|
+
|
|
28
|
+
These are the conventions used by DWP.
|
|
29
|
+
|
|
30
|
+
> **IMPORTANT:** DO NOT ENABLE this option if the question or answer may contain personally-identifiable information as values will be pushed to Google
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
{% from
|
|
1
|
+
{% from 'govuk/components/input/macro.njk' import govukInput %}
|
|
2
2
|
|
|
3
3
|
{% set fieldErrors = params.casaErrors[params.name] %}
|
|
4
4
|
|
|
@@ -10,11 +10,15 @@
|
|
|
10
10
|
}) %}
|
|
11
11
|
{% endif %}
|
|
12
12
|
|
|
13
|
+
{# Merge parameters #}
|
|
13
14
|
{% set mergedParams = mergeObjects(params, {
|
|
14
15
|
id: params.id if params.id else 'f-' + params.name,
|
|
15
16
|
attributes: mergedAttributes,
|
|
16
17
|
errorMessage: {
|
|
17
|
-
text: t(params.casaErrors[params.name][0].inline, params.casaErrors[params.name][0].variables)
|
|
18
|
+
text: t(params.casaErrors[params.name][0].inline, params.casaErrors[params.name][0].variables),
|
|
19
|
+
attributes: {
|
|
20
|
+
'data-ga-question': params.label.text or params.label.html | striptags
|
|
21
|
+
} if params.casaWithAnalytics else {}
|
|
18
22
|
} if params.casaErrors[params.name].length
|
|
19
23
|
}) %}
|
|
20
24
|
|
|
@@ -52,3 +52,13 @@ With configurable items:
|
|
|
52
52
|
There is a companion validation rule - [`postalAddressObject`](../../../../../docs/field-validation-rules.md#postalAddressObject) - that you can use to ensure the addresses are passed in correctly. See this rule for more information on how to control error highlighting among other things.
|
|
53
53
|
|
|
54
54
|
And when using the `required` field validator in conjunction with this macro, you will need to configure the validator to indicate which part of the date input you want to focus on when linked from the error summary, using the `focusSuffix` error option:
|
|
55
|
+
|
|
56
|
+
## Google Tag Manager
|
|
57
|
+
|
|
58
|
+
The following attributes will be attached to the error `<p>` tags if `casaWithAnalytics` is `true`:
|
|
59
|
+
|
|
60
|
+
* `data-ga-question`: Holds the specific item's label's text content after removing all html tags
|
|
61
|
+
|
|
62
|
+
These are the conventions used by DWP.
|
|
63
|
+
|
|
64
|
+
> **IMPORTANT:** DO NOT ENABLE this option if the question or answer may contain personally-identifiable information as values will be pushed to Google
|
|
@@ -20,7 +20,10 @@
|
|
|
20
20
|
},
|
|
21
21
|
attributes: fieldAttributes,
|
|
22
22
|
errorMessage: {
|
|
23
|
-
text: t(fieldAddress1Errors[0].inline, fieldAddress1Errors[0].variables)
|
|
23
|
+
text: t(fieldAddress1Errors[0].inline, fieldAddress1Errors[0].variables),
|
|
24
|
+
attributes: {
|
|
25
|
+
'data-ga-question': t('macros:postalAddressObject.address1') | striptags
|
|
26
|
+
} if params.casaWithAnalytics else {}
|
|
24
27
|
} if fieldAddress1Errors else null
|
|
25
28
|
}, params.address1 if params.address1 else {}, {
|
|
26
29
|
id: 'f-' + params.name + '[address1]',
|
|
@@ -39,7 +42,10 @@
|
|
|
39
42
|
},
|
|
40
43
|
attributes: fieldAttributes,
|
|
41
44
|
errorMessage: {
|
|
42
|
-
text: t(fieldAddress2Errors[0].inline, fieldAddress2Errors[0].variables)
|
|
45
|
+
text: t(fieldAddress2Errors[0].inline, fieldAddress2Errors[0].variables),
|
|
46
|
+
attributes: {
|
|
47
|
+
'data-ga-question': t('macros:postalAddressObject.address2') | striptags
|
|
48
|
+
} if params.casaWithAnalytics else {}
|
|
43
49
|
} if fieldAddress2Errors else null
|
|
44
50
|
}, params.address2 if params.address2 else {}, {
|
|
45
51
|
id: 'f-' + params.name + '[address2]',
|
|
@@ -61,7 +67,10 @@
|
|
|
61
67
|
},
|
|
62
68
|
attributes: fieldAttributes,
|
|
63
69
|
errorMessage: {
|
|
64
|
-
text: t(fieldErrors[0].inline, fieldErrors[0].variables)
|
|
70
|
+
text: t(fieldErrors[0].inline, fieldErrors[0].variables),
|
|
71
|
+
attributes: {
|
|
72
|
+
'data-ga-question': t('macros:postalAddressObject.address3')
|
|
73
|
+
} if params.casaWithAnalytics else {}
|
|
65
74
|
} if fieldErrors else null
|
|
66
75
|
}, params.address3 if params.address3 else {}, {
|
|
67
76
|
id: 'f-' + params.name + '[address3]',
|
|
@@ -82,7 +91,10 @@
|
|
|
82
91
|
},
|
|
83
92
|
attributes: fieldAttributes,
|
|
84
93
|
errorMessage: {
|
|
85
|
-
text: t(fieldErrors[0].inline, fieldErrors[0].variables)
|
|
94
|
+
text: t(fieldErrors[0].inline, fieldErrors[0].variables),
|
|
95
|
+
attributes: {
|
|
96
|
+
'data-ga-question': t('macros:postalAddressObject.address4')
|
|
97
|
+
} if params.casaWithAnalytics else {}
|
|
86
98
|
} if fieldErrors else null
|
|
87
99
|
}, params.address4 if params.address4 else {}, {
|
|
88
100
|
id: 'f-' + params.name + '[address4]',
|
|
@@ -103,7 +115,10 @@
|
|
|
103
115
|
},
|
|
104
116
|
attributes: fieldAttributes,
|
|
105
117
|
errorMessage: {
|
|
106
|
-
text: t(fieldErrors[0].inline, fieldErrors[0].variables)
|
|
118
|
+
text: t(fieldErrors[0].inline, fieldErrors[0].variables),
|
|
119
|
+
attributes: {
|
|
120
|
+
'data-ga-question': t('macros:postalAddressObject.postcode')
|
|
121
|
+
} if params.casaWithAnalytics else {}
|
|
107
122
|
} if fieldErrors else null
|
|
108
123
|
}, params.postcode if params.postcode else {}, {
|
|
109
124
|
id: 'f-' + params.name + '[postcode]',
|