hof 20.1.2 → 20.1.4
Sign up to get free protection for your applications and to get access to all the features.
- package/README.md +1 -0
- package/components/combine-and-loop-fields/Readme.md +3 -1
- package/components/combine-and-loop-fields/index.js +6 -6
- package/controller/validation/validators.js +4 -0
- package/frontend/template-mixins/mixins/template-mixins.js +11 -0
- package/frontend/template-mixins/partials/forms/textarea-group.html +35 -32
- package/package.json +2 -2
- package/sandbox/apps/sandbox/fields.js +16 -7
- package/sandbox/apps/sandbox/index.js +4 -0
- package/sandbox/apps/sandbox/sections/summary-data-sections.js +3 -0
- package/sandbox/apps/sandbox/translations/src/en/fields.json +6 -2
- package/sandbox/apps/sandbox/translations/src/en/pages.json +9 -0
- package/sandbox/apps/sandbox/translations/src/en/validation.json +4 -0
package/README.md
CHANGED
@@ -1530,6 +1530,7 @@ To render a specific fields in your templates use the mixin name (matching those
|
|
1530
1530
|
- `required`: Value applied to `aria-required` HTML attribute.
|
1531
1531
|
- `hint`: This adds context to the label, which it is a part of, for input text, radio groups and textarea. It is used within the input by aria-describedby for screen readers.
|
1532
1532
|
- `maxlength`: Applicable to text-based fields and mapped to the `maxlength` HTML attribute.
|
1533
|
+
- `maxword`: Applicable to textarea fields.
|
1533
1534
|
- `options`: Applicable to HTML `select` and `radio` controls and used to generate the items of either HTML element.
|
1534
1535
|
- `selected`: Applicable to `select`, `checkbox`, and `radio` controls. Will render the selected HTML option/element selected or checked.
|
1535
1536
|
- `legend`: Applicable to `radio` button controls, which are wrapped in a HTML `fieldset` with a `legend` element.
|
@@ -25,6 +25,7 @@ This allows you to specify fields to loop over and add as objects to a parent ar
|
|
25
25
|
],
|
26
26
|
removePrefix: 'storage-',
|
27
27
|
combineValuesToSingleField: 'address',
|
28
|
+
groupOptional: true,
|
28
29
|
returnTo: '/add-address'
|
29
30
|
}),
|
30
31
|
next: '/confirm'
|
@@ -35,5 +36,6 @@ Here are the fields you call this behaviour first to set config for it:
|
|
35
36
|
`fieldsToGroup`: (Required) the fields being specified for an object, e.g. house number, street, postcode, that are grouped together,
|
36
37
|
`removePrefix`: (Optional) a string which is used to remove consistent prefixes from a collection of fields that are grouped together,
|
37
38
|
`combineValuesToSingleField`: (Optional) a new field that is created with its value being the concatenation of values of the fields specified in `fieldsToGroup`,
|
38
|
-
`
|
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
|
39
41
|
```
|
@@ -5,7 +5,7 @@ const path = require('path');
|
|
5
5
|
const express = require('express');
|
6
6
|
|
7
7
|
module.exports = config => {
|
8
|
-
const { returnTo, groupName, fieldsToGroup, combineValuesToSingleField, removePrefix } = config;
|
8
|
+
const { returnTo, groupName, fieldsToGroup, combineValuesToSingleField, removePrefix, groupOptional } = config;
|
9
9
|
|
10
10
|
if (removePrefix && typeof removePrefix !== 'string') {
|
11
11
|
throw new Error('removePrefix is a string and is optional for loops');
|
@@ -71,7 +71,7 @@ module.exports = config => {
|
|
71
71
|
});
|
72
72
|
}
|
73
73
|
|
74
|
-
const target = items.length ? req.form.options.route : returnTo;
|
74
|
+
const target = (items.length || groupOptional) ? req.form.options.route : returnTo;
|
75
75
|
const action = req.params.action || '';
|
76
76
|
res.redirect(path.join(req.baseUrl, target, action));
|
77
77
|
}
|
@@ -84,7 +84,10 @@ module.exports = config => {
|
|
84
84
|
validate: ['required'],
|
85
85
|
options: [
|
86
86
|
'yes', 'no'
|
87
|
-
]
|
87
|
+
],
|
88
|
+
legend: {
|
89
|
+
className: 'visuallyhidden'
|
90
|
+
}
|
88
91
|
}, req.form.options.fieldSettings);
|
89
92
|
|
90
93
|
// add conditonal fork
|
@@ -95,9 +98,6 @@ module.exports = config => {
|
|
95
98
|
condition: {
|
96
99
|
field: field,
|
97
100
|
value: 'yes'
|
98
|
-
},
|
99
|
-
legend: {
|
100
|
-
className: 'visuallyhidden'
|
101
101
|
}
|
102
102
|
});
|
103
103
|
next();
|
@@ -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
|
},
|
@@ -42,6 +42,15 @@ module.exports = function (options) {
|
|
42
42
|
return null;
|
43
43
|
}
|
44
44
|
|
45
|
+
function maxword(field) {
|
46
|
+
const validation = field.validate || [];
|
47
|
+
const mw = _.findWhere(validation, { type: 'maxword' });
|
48
|
+
if (mw) {
|
49
|
+
return _.isArray(mw.arguments) ? mw.arguments[0] : mw.arguments;
|
50
|
+
}
|
51
|
+
return null;
|
52
|
+
}
|
53
|
+
|
45
54
|
function type(field) {
|
46
55
|
return field.type || 'text';
|
47
56
|
}
|
@@ -208,6 +217,7 @@ module.exports = function (options) {
|
|
208
217
|
hintId: extension.hintId || (hint ? key + '-hint' : null),
|
209
218
|
error: this.errors && this.errors[key],
|
210
219
|
maxlength: maxlength(field) || extension.maxlength,
|
220
|
+
maxword: maxword(field) || extension.maxword,
|
211
221
|
required: required,
|
212
222
|
pattern: extension.pattern,
|
213
223
|
date: extension.date,
|
@@ -216,6 +226,7 @@ module.exports = function (options) {
|
|
216
226
|
isPageHeading: field.isPageHeading,
|
217
227
|
attributes: field.attributes,
|
218
228
|
isPrefixOrSuffix: _.map(field.attributes, item => {if (item.prefix || item.suffix !== undefined) return true;}),
|
229
|
+
isMaxlengthOrMaxword: maxlength(field) || extension.maxlength || maxword(field) || extension.maxword,
|
219
230
|
renderChild: renderChild.bind(this)
|
220
231
|
});
|
221
232
|
}
|
@@ -1,32 +1,35 @@
|
|
1
|
-
{{#
|
2
|
-
|
3
|
-
{{/
|
4
|
-
<div id="{{id}}-group" class="
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
id="{{id}}"
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
{{
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
1
|
+
{{#isMaxlengthOrMaxword}}<div class="govuk-character-count" data-module="govuk-character-count"
|
2
|
+
{{#maxlength}}data-maxlength="{{maxlength}}" {{/maxlength}} {{#maxword}}data-maxwords="{{maxword}}" {{/maxword}}>
|
3
|
+
{{/isMaxlengthOrMaxword}}
|
4
|
+
<div id="{{id}}-group" class="govuk-form-group {{#formGroupClassName}}{{formGroupClassName}}{{/formGroupClassName}}{{#error}} govuk-form-group--error{{/error}}">
|
5
|
+
{{#isPageHeading}}<h1 class="govuk-label-wrapper">{{/isPageHeading}}
|
6
|
+
<label for="{{id}}"
|
7
|
+
class="{{labelClassName}}{{#isPageHeading}}govuk-label--l{{/isPageHeading}}">
|
8
|
+
{{{label}}}
|
9
|
+
{{#error}}
|
10
|
+
<p id="{{id}}-error" class="govuk-error-message">
|
11
|
+
<span id="{{id}}-error" class="govuk-visually-hidden">Error:</span>{{error.message}}
|
12
|
+
</p>
|
13
|
+
{{/error}}
|
14
|
+
</label>
|
15
|
+
{{#isPageHeading}}</h1>{{/isPageHeading}}
|
16
|
+
{{#hint}}<div {{$hintId}}id="{{hintId}}" {{/hintId}}class="govuk-hint">{{hint}}</div>{{/hint}}
|
17
|
+
{{#renderChild}}{{/renderChild}}
|
18
|
+
<textarea name="{{id}}" id="{{id}}"
|
19
|
+
class="govuk-textarea {{#isMaxlengthOrMaxword}}govuk-js-character-count{{/isMaxlengthOrMaxword}} {{#className}}{{className}}{{/className}} {{#error}}govuk-input--error{{/error}}"
|
20
|
+
aria-describedby="{{id}}-info"
|
21
|
+
{{#attributes}}
|
22
|
+
{{attribute}}="{{value}}"
|
23
|
+
{{/attributes}}
|
24
|
+
{{^error}}{{#hintId}}{{#maxlength}} aria-describedby="{{id}}-maxlength-hint {{hintId}}"{{/maxlength}}{{/hintId}}{{/error}}
|
25
|
+
{{^error}}{{#hintId}}{{^maxlength}} aria-describedby="{{hintId}}"{{/maxlength}}{{/hintId}}{{/error}}
|
26
|
+
{{^error}}{{^hintId}}{{#maxlength}}aria-describedby="{{id}}-maxlength-hint" {{/maxlength}}{{/hintId}}{{/error}}
|
27
|
+
{{#error}} aria-invalid="true" aria-describedby="{{id}}-error" {{/error}}
|
28
|
+
>{{value}}</textarea>
|
29
|
+
{{#isMaxlengthOrMaxword}}
|
30
|
+
<div id="{{id}}-info" class=" govuk-hint govuk-character-count__message" aria-live="polite">You have {{#maxlength}}{{maxlength}} characters{{/maxlength}}{{#maxword}}{{maxword}}
|
31
|
+
words{{/maxword}} remaining
|
32
|
+
</div>
|
33
|
+
{{/isMaxlengthOrMaxword}}
|
34
|
+
</div>
|
35
|
+
{{#isMaxlengthOrMaxword}}</div>{{/isMaxlengthOrMaxword}}
|
package/package.json
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"name": "hof",
|
3
3
|
"description": "A bootstrap for HOF projects",
|
4
|
-
"version": "20.1.
|
4
|
+
"version": "20.1.4",
|
5
5
|
"license": "MIT",
|
6
6
|
"main": "index.js",
|
7
7
|
"author": "HomeOffice",
|
@@ -135,7 +135,7 @@
|
|
135
135
|
"reporter": "spec",
|
136
136
|
"require": "test/common.js",
|
137
137
|
"recursive": "true",
|
138
|
-
"timeout": "
|
138
|
+
"timeout": "7000",
|
139
139
|
"exit": "true"
|
140
140
|
},
|
141
141
|
"resolutions": {
|
@@ -16,9 +16,6 @@ module.exports = {
|
|
16
16
|
},
|
17
17
|
name: {
|
18
18
|
validate: ['required', 'notUrl', { type: 'maxlength', arguments: 200 }],
|
19
|
-
// need to remove this for the heading to go
|
20
|
-
labelClassName: ['govuk-label--l'],
|
21
|
-
isPageHeading: 'true'
|
22
19
|
},
|
23
20
|
'dateOfBirth': dateComponent('dateOfBirth', {
|
24
21
|
mixin: 'input-date',
|
@@ -70,8 +67,6 @@ module.exports = {
|
|
70
67
|
]
|
71
68
|
},
|
72
69
|
email: {
|
73
|
-
isPageHeading: 'true',
|
74
|
-
labelClassName: ['govuk-label--l'],
|
75
70
|
validate: ['required', 'email']
|
76
71
|
},
|
77
72
|
phone: {
|
@@ -95,11 +90,10 @@ module.exports = {
|
|
95
90
|
mixin: 'textarea',
|
96
91
|
// we want to ignore default formatters as we want
|
97
92
|
// to preserve white space
|
98
|
-
isPageHeading: 'true',
|
99
93
|
'ignore-defaults': true,
|
100
94
|
// apply the other default formatters
|
101
95
|
formatter: ['trim', 'hyphens'],
|
102
|
-
|
96
|
+
isPageHeading: 'true',
|
103
97
|
// attributes here are passed to the field element
|
104
98
|
validate: ['required', { type: 'maxlength', arguments: 10 }],
|
105
99
|
attributes: [{
|
@@ -107,6 +101,21 @@ module.exports = {
|
|
107
101
|
value: 8
|
108
102
|
}]
|
109
103
|
},
|
104
|
+
whatHappened: {
|
105
|
+
mixin: 'textarea',
|
106
|
+
// we want to ignore default formatters as we want
|
107
|
+
// to preserve white space
|
108
|
+
'ignore-defaults': true,
|
109
|
+
// apply the other default formatters
|
110
|
+
formatter: ['trim', 'hyphens'],
|
111
|
+
isPageHeading: 'true',
|
112
|
+
// attributes here are passed to the field element
|
113
|
+
validate: ['required', { type: 'maxword', arguments: 10 }],
|
114
|
+
attributes: [{
|
115
|
+
attribute: 'rows',
|
116
|
+
value: 8
|
117
|
+
}]
|
118
|
+
},
|
110
119
|
appealStages: {
|
111
120
|
mixin: 'select',
|
112
121
|
isPageHeading: 'true',
|
@@ -15,7 +15,7 @@
|
|
15
15
|
}
|
16
16
|
},
|
17
17
|
"name": {
|
18
|
-
"label": "
|
18
|
+
"label": "Full name"
|
19
19
|
},
|
20
20
|
"dateOfBirth": {
|
21
21
|
"legend": "What is your date of birth?",
|
@@ -70,7 +70,7 @@
|
|
70
70
|
}
|
71
71
|
},
|
72
72
|
"email" : {
|
73
|
-
"label": "
|
73
|
+
"label": "Email address"
|
74
74
|
},
|
75
75
|
"phone": {
|
76
76
|
"label": "Phone number",
|
@@ -83,6 +83,10 @@
|
|
83
83
|
"label": "Complaint details",
|
84
84
|
"hint": "Briefly summarise your complaint. Include anything that can help our investigation."
|
85
85
|
},
|
86
|
+
"whatHappened": {
|
87
|
+
"label": "What happened",
|
88
|
+
"hint": "Briefly summarise what happened."
|
89
|
+
},
|
86
90
|
"countrySelect": {
|
87
91
|
"label": "Which country are you based in?",
|
88
92
|
"hint": "Start to type the country name and options will appear"
|
@@ -10,6 +10,12 @@
|
|
10
10
|
"header": "What is your address in the UK?",
|
11
11
|
"intro": "If you have no fixed address, enter an address where we can contact you."
|
12
12
|
},
|
13
|
+
"name": {
|
14
|
+
"header": "What is your full name?"
|
15
|
+
},
|
16
|
+
"email": {
|
17
|
+
"header": "Enter your email address"
|
18
|
+
},
|
13
19
|
"phone-number": {
|
14
20
|
"header": "Enter your phone number"
|
15
21
|
},
|
@@ -36,6 +42,9 @@
|
|
36
42
|
},
|
37
43
|
"complaintDetails": {
|
38
44
|
"header": "Complaint details"
|
45
|
+
},
|
46
|
+
"whatHappened": {
|
47
|
+
"header": "What happened"
|
39
48
|
}
|
40
49
|
}
|
41
50
|
},
|
@@ -44,6 +44,10 @@
|
|
44
44
|
"default": "Enter details about why you are making a complaint",
|
45
45
|
"maxlength": "Keep to the {{maxlength}} character limit"
|
46
46
|
},
|
47
|
+
"whatHappened": {
|
48
|
+
"default": "Enter details about what happened",
|
49
|
+
"maxword": "Keep to the {{maxword}} word limit"
|
50
|
+
},
|
47
51
|
"appealStages": {
|
48
52
|
"required": "Select an appeal stage from the list"
|
49
53
|
}
|