@defra/forms-engine-plugin 5.0.0-alpha.10 → 5.0.0-alpha.12
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/.public/javascripts/application.min.js +1 -1
- package/.public/javascripts/application.min.js.LICENSE.txt +7 -0
- package/.public/javascripts/application.min.js.map +1 -1
- package/.public/javascripts/shared.min.js +1 -1
- package/.public/javascripts/shared.min.js.LICENSE.txt +7 -0
- package/.public/javascripts/shared.min.js.map +1 -1
- package/.server/client/javascripts/file-upload.js +1 -0
- package/.server/client/javascripts/file-upload.js.map +1 -1
- package/.server/client/javascripts/geospatial-map.js +2 -28
- package/.server/client/javascripts/geospatial-map.js.map +1 -1
- package/.server/client/javascripts/govuk.js +2 -1
- package/.server/client/javascripts/govuk.js.map +1 -1
- package/.server/client/javascripts/location-map.js +3 -2
- package/.server/client/javascripts/location-map.js.map +1 -1
- package/.server/client/javascripts/map.d.ts +6 -0
- package/.server/client/javascripts/map.js +37 -0
- package/.server/client/javascripts/map.js.map +1 -1
- package/.server/server/plugins/engine/components/CheckboxesField.d.ts +20 -2
- package/.server/server/plugins/engine/components/CheckboxesField.js +24 -12
- package/.server/server/plugins/engine/components/CheckboxesField.js.map +1 -1
- package/.server/server/plugins/engine/components/EastingNorthingField.d.ts +5 -0
- package/.server/server/plugins/engine/components/EastingNorthingField.js +17 -0
- package/.server/server/plugins/engine/components/EastingNorthingField.js.map +1 -1
- package/.server/server/plugins/engine/components/FileUploadField.d.ts +10 -1
- package/.server/server/plugins/engine/components/FileUploadField.js +21 -7
- package/.server/server/plugins/engine/components/FileUploadField.js.map +1 -1
- package/.server/server/plugins/engine/components/GeospatialField.d.ts +21 -0
- package/.server/server/plugins/engine/components/GeospatialField.js +32 -9
- package/.server/server/plugins/engine/components/GeospatialField.js.map +1 -1
- package/.server/server/plugins/engine/components/LatLongField.d.ts +4 -0
- package/.server/server/plugins/engine/components/LocationFieldBase.js +4 -1
- package/.server/server/plugins/engine/components/LocationFieldBase.js.map +1 -1
- package/.server/server/plugins/engine/components/LocationFieldHelpers.d.ts +5 -1
- package/.server/server/plugins/engine/components/LocationFieldHelpers.js +10 -2
- package/.server/server/plugins/engine/components/LocationFieldHelpers.js.map +1 -1
- package/.server/server/plugins/engine/components/helpers/geospatial.d.ts +8 -1
- package/.server/server/plugins/engine/components/helpers/geospatial.js +56 -15
- package/.server/server/plugins/engine/components/helpers/geospatial.js.map +1 -1
- package/.server/server/plugins/engine/form-availability.d.ts +3 -2
- package/.server/server/plugins/engine/form-availability.js +3 -2
- package/.server/server/plugins/engine/form-availability.js.map +1 -1
- package/.server/server/plugins/engine/form-context.js +2 -2
- package/.server/server/plugins/engine/form-context.js.map +1 -1
- package/.server/server/plugins/engine/i18n/buildValidationMessages.d.ts +1 -1
- package/.server/server/plugins/engine/i18n/buildValidationMessages.js +30 -9
- package/.server/server/plugins/engine/i18n/buildValidationMessages.js.map +1 -1
- package/.server/server/plugins/engine/i18n/translations/cy.json +79 -13
- package/.server/server/plugins/engine/i18n/translations/en-GB.json +42 -13
- package/.server/server/plugins/engine/i18n/types.d.ts +1 -0
- package/.server/server/plugins/engine/i18n/types.js.map +1 -1
- package/.server/server/plugins/engine/models/unavailable-view-model.d.ts +11 -2
- package/.server/server/plugins/engine/models/unavailable-view-model.js +23 -4
- package/.server/server/plugins/engine/models/unavailable-view-model.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/FileUploadPageController.d.ts +6 -0
- package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js +34 -1
- package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/validationOptions.d.ts +1 -1
- package/.server/server/plugins/engine/pageControllers/validationOptions.js +1 -1
- package/.server/server/plugins/engine/pageControllers/validationOptions.js.map +1 -1
- package/.server/server/plugins/engine/types.d.ts +4 -0
- package/.server/server/plugins/engine/types.js.map +1 -1
- package/.server/server/plugins/engine/unavailable-response.js +5 -2
- package/.server/server/plugins/engine/unavailable-response.js.map +1 -1
- package/.server/server/plugins/engine/views/components/_location-field-base.html +3 -2
- package/.server/server/plugins/engine/views/file-upload.html +1 -1
- package/.server/server/plugins/engine/views/unavailable.html +14 -11
- package/.server/server/utils/utils.d.ts +6 -0
- package/.server/server/utils/utils.js +18 -1
- package/.server/server/utils/utils.js.map +1 -1
- package/.server/server/utils/utils.test.js +21 -1
- package/.server/server/utils/utils.test.js.map +1 -1
- package/package.json +2 -1
- package/src/client/javascripts/file-upload.js +1 -0
- package/src/client/javascripts/geospatial-map.js +2 -31
- package/src/client/javascripts/govuk.js +2 -0
- package/src/client/javascripts/location-map.js +3 -1
- package/src/client/javascripts/map.js +42 -0
- package/src/server/plugins/engine/components/CheckboxesField.ts +34 -13
- package/src/server/plugins/engine/components/EastingNorthingField.ts +31 -0
- package/src/server/plugins/engine/components/FileUploadField.ts +29 -7
- package/src/server/plugins/engine/components/GeospatialField.ts +43 -11
- package/src/server/plugins/engine/components/LocationFieldBase.ts +3 -1
- package/src/server/plugins/engine/components/LocationFieldHelpers.ts +13 -3
- package/src/server/plugins/engine/components/helpers/geospatial.ts +84 -21
- package/src/server/plugins/engine/form-availability.ts +8 -2
- package/src/server/plugins/engine/form-context.ts +4 -2
- package/src/server/plugins/engine/i18n/buildValidationMessages.ts +15 -10
- package/src/server/plugins/engine/i18n/translations/cy.json +79 -13
- package/src/server/plugins/engine/i18n/translations/en-GB.json +42 -13
- package/src/server/plugins/engine/i18n/types.ts +1 -0
- package/src/server/plugins/engine/models/unavailable-view-model.ts +26 -5
- package/src/server/plugins/engine/pageControllers/FileUploadPageController.ts +42 -1
- package/src/server/plugins/engine/pageControllers/validationOptions.ts +2 -2
- package/src/server/plugins/engine/types.ts +4 -0
- package/src/server/plugins/engine/unavailable-response.ts +6 -2
- package/src/server/plugins/engine/views/components/_location-field-base.html +3 -2
- package/src/server/plugins/engine/views/file-upload.html +1 -1
- package/src/server/plugins/engine/views/unavailable.html +14 -11
- package/src/server/utils/utils.js +19 -1
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { type CheckboxesFieldComponent, type Item } from '@defra/forms-model'
|
|
2
|
-
import joi, { type ArraySchema
|
|
2
|
+
import joi, { type ArraySchema } from 'joi'
|
|
3
3
|
|
|
4
|
+
import { EN_GB } from '../../../constants.js'
|
|
4
5
|
import { isFormValue } from './FormComponent.js'
|
|
5
6
|
import { SelectionControlField } from './SelectionControlField.js'
|
|
6
|
-
import {
|
|
7
|
+
import { t as tPlugin } from '../i18n/index.js'
|
|
7
8
|
import { type Translator } from '../i18n/types.js'
|
|
8
9
|
import { type FormModel } from '../models/FormModel.js'
|
|
9
10
|
import { type QuestionPageController } from '../pageControllers/QuestionPageController.js'
|
|
@@ -20,6 +21,7 @@ export class CheckboxesField extends SelectionControlField {
|
|
|
20
21
|
declare schema: CheckboxesFieldComponent['schema']
|
|
21
22
|
declare formSchema: ArraySchema<string> | ArraySchema<number>
|
|
22
23
|
declare stateSchema: ArraySchema<string> | ArraySchema<number>
|
|
24
|
+
declare limits: { min?: number; max?: number; length?: number }
|
|
23
25
|
|
|
24
26
|
constructor(
|
|
25
27
|
def: CheckboxesFieldComponent,
|
|
@@ -42,11 +44,8 @@ export class CheckboxesField extends SelectionControlField {
|
|
|
42
44
|
.single()
|
|
43
45
|
.label(this.label)
|
|
44
46
|
.required()
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
'array.max': messageTemplate.arrayMax,
|
|
48
|
-
'array.length': messageTemplate.arrayLength
|
|
49
|
-
})
|
|
47
|
+
|
|
48
|
+
const limits: { min?: number; max?: number; length?: number } = {}
|
|
50
49
|
|
|
51
50
|
if (options.required === false) {
|
|
52
51
|
formSchema = formSchema.optional()
|
|
@@ -54,16 +53,25 @@ export class CheckboxesField extends SelectionControlField {
|
|
|
54
53
|
|
|
55
54
|
if (typeof schema?.length === 'number') {
|
|
56
55
|
formSchema = formSchema.length(schema.length)
|
|
56
|
+
limits.length = schema.length
|
|
57
57
|
} else {
|
|
58
58
|
if (typeof schema?.min === 'number') {
|
|
59
59
|
formSchema = formSchema.min(schema.min)
|
|
60
|
+
limits.min = schema.min
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
if (typeof schema?.max === 'number') {
|
|
63
64
|
formSchema = formSchema.max(schema.max)
|
|
65
|
+
limits.max = schema.max
|
|
64
66
|
}
|
|
65
67
|
}
|
|
66
68
|
|
|
69
|
+
this.limits = limits
|
|
70
|
+
|
|
71
|
+
formSchema = formSchema.messages(
|
|
72
|
+
CheckboxesField.buildErrorMessages(EN_GB, limits)
|
|
73
|
+
)
|
|
74
|
+
|
|
67
75
|
this.formSchema = formSchema.default([])
|
|
68
76
|
this.stateSchema = formSchema.default(null).allow(null)
|
|
69
77
|
this.options = options
|
|
@@ -135,13 +143,9 @@ export class CheckboxesField extends SelectionControlField {
|
|
|
135
143
|
}
|
|
136
144
|
|
|
137
145
|
getValidationMessagesOverride(translator: Translator) {
|
|
138
|
-
const {
|
|
146
|
+
const { language } = translator
|
|
139
147
|
return {
|
|
140
|
-
[this.name]:
|
|
141
|
-
'array.min': buildValidationMessages(t).arrayMin,
|
|
142
|
-
'array.max': buildValidationMessages(t).arrayMax,
|
|
143
|
-
'array.length': buildValidationMessages(t).arrayLength
|
|
144
|
-
} as LanguageMessages
|
|
148
|
+
[this.name]: CheckboxesField.buildErrorMessages(language, this.limits)
|
|
145
149
|
}
|
|
146
150
|
}
|
|
147
151
|
|
|
@@ -181,4 +185,21 @@ export class CheckboxesField extends SelectionControlField {
|
|
|
181
185
|
|
|
182
186
|
return value.every(isFormValue)
|
|
183
187
|
}
|
|
188
|
+
|
|
189
|
+
static buildErrorMessages(
|
|
190
|
+
language: string,
|
|
191
|
+
limits: { min?: number; max?: number; length?: number } = {}
|
|
192
|
+
) {
|
|
193
|
+
return {
|
|
194
|
+
'array.min': tPlugin('validation.arrayMin', language, {
|
|
195
|
+
count: limits.min ?? 1
|
|
196
|
+
}),
|
|
197
|
+
'array.max': tPlugin('validation.arrayMax', language, {
|
|
198
|
+
count: limits.max ?? 1
|
|
199
|
+
}),
|
|
200
|
+
'array.length': tPlugin('validation.arrayLength', language, {
|
|
201
|
+
count: limits.length ?? 1
|
|
202
|
+
})
|
|
203
|
+
}
|
|
204
|
+
}
|
|
184
205
|
}
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
getLocationFieldViewModel
|
|
16
16
|
} from './LocationFieldHelpers.js'
|
|
17
17
|
import { NumberField } from './NumberField.js'
|
|
18
|
+
import { getEastingNorthingCountryValidator } from './helpers/geospatial.js'
|
|
18
19
|
import {
|
|
19
20
|
createLowerFirstExpression,
|
|
20
21
|
getTranslatedLabel
|
|
@@ -112,6 +113,18 @@ export class EastingNorthingField extends FormComponent {
|
|
|
112
113
|
)
|
|
113
114
|
|
|
114
115
|
this.options = options
|
|
116
|
+
|
|
117
|
+
const country = options.countries?.at(0)
|
|
118
|
+
|
|
119
|
+
if (country) {
|
|
120
|
+
this.collection.formSchema = this.collection.formSchema.custom(
|
|
121
|
+
getEastingNorthingCountryValidator(this, country)
|
|
122
|
+
)
|
|
123
|
+
this.collection.stateSchema = this.collection.stateSchema.custom(
|
|
124
|
+
getEastingNorthingCountryValidator(this, country)
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
|
|
115
128
|
this.formSchema = this.collection.formSchema
|
|
116
129
|
this.stateSchema = this.collection.stateSchema
|
|
117
130
|
}
|
|
@@ -164,6 +177,24 @@ export class EastingNorthingField extends FormComponent {
|
|
|
164
177
|
return getLocationFieldViewModel(this, viewModel, context)
|
|
165
178
|
}
|
|
166
179
|
|
|
180
|
+
getErrors(
|
|
181
|
+
translator: Translator,
|
|
182
|
+
errors?: FormSubmissionError[]
|
|
183
|
+
): FormSubmissionError[] | undefined {
|
|
184
|
+
const fieldErrors = super.getErrors(translator, errors)
|
|
185
|
+
|
|
186
|
+
fieldErrors?.forEach((err) => {
|
|
187
|
+
if (err.context?.country) {
|
|
188
|
+
err.text = translator.t(
|
|
189
|
+
'components.eastingNorthingField.wrongCountry',
|
|
190
|
+
{ country: translator.t(`common.${err.context.country}`) }
|
|
191
|
+
)
|
|
192
|
+
}
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
return fieldErrors
|
|
196
|
+
}
|
|
197
|
+
|
|
167
198
|
getViewErrors(
|
|
168
199
|
translator: Translator,
|
|
169
200
|
errors?: FormSubmissionError[]
|
|
@@ -52,7 +52,9 @@ export const tempFileSchema = fileSchema.append({
|
|
|
52
52
|
.string()
|
|
53
53
|
.valid(FileStatus.complete, FileStatus.rejected, FileStatus.pending)
|
|
54
54
|
.required(),
|
|
55
|
-
errorMessage: joi.string().optional()
|
|
55
|
+
errorMessage: joi.string().optional(),
|
|
56
|
+
errorCode: joi.string().optional(),
|
|
57
|
+
errorParams: joi.any().optional()
|
|
56
58
|
})
|
|
57
59
|
|
|
58
60
|
export const formFileSchema = fileSchema.append({
|
|
@@ -111,6 +113,7 @@ export class FileUploadField extends FormComponent {
|
|
|
111
113
|
declare schema: FileUploadFieldComponent['schema']
|
|
112
114
|
declare formSchema: ArraySchema<FileState>
|
|
113
115
|
declare stateSchema: ArraySchema<FileState>
|
|
116
|
+
declare limits: { min?: number; max?: number; length?: number }
|
|
114
117
|
|
|
115
118
|
constructor(
|
|
116
119
|
def: FileUploadFieldComponent,
|
|
@@ -130,21 +133,31 @@ export class FileUploadField extends FormComponent {
|
|
|
130
133
|
formSchema = formSchema.optional()
|
|
131
134
|
}
|
|
132
135
|
|
|
136
|
+
const limits: { min?: number; max?: number; length?: number } = {}
|
|
137
|
+
|
|
133
138
|
if (typeof schema.length !== 'number') {
|
|
134
139
|
if (typeof schema.max === 'number') {
|
|
135
140
|
formSchema = formSchema.max(schema.max)
|
|
141
|
+
limits.max = schema.max
|
|
136
142
|
}
|
|
137
143
|
|
|
138
144
|
if (typeof schema.min === 'number') {
|
|
139
145
|
formSchema = formSchema.min(schema.min)
|
|
146
|
+
limits.min = schema.min
|
|
140
147
|
} else if (options.required !== false) {
|
|
141
148
|
formSchema = formSchema.min(1)
|
|
149
|
+
limits.min = 1
|
|
142
150
|
}
|
|
143
151
|
} else {
|
|
144
152
|
formSchema = formSchema.length(schema.length)
|
|
153
|
+
limits.length = schema.length
|
|
145
154
|
}
|
|
146
155
|
|
|
147
|
-
|
|
156
|
+
this.limits = limits
|
|
157
|
+
|
|
158
|
+
formSchema = formSchema.messages(
|
|
159
|
+
FileUploadField.buildErrorMessages(EN_GB, limits)
|
|
160
|
+
)
|
|
148
161
|
|
|
149
162
|
this.formSchema = formSchema.items(formItemSchema)
|
|
150
163
|
this.stateSchema = formSchema
|
|
@@ -307,7 +320,7 @@ export class FileUploadField extends FormComponent {
|
|
|
307
320
|
getValidationMessagesOverride(translator: Translator) {
|
|
308
321
|
const { language } = translator
|
|
309
322
|
return {
|
|
310
|
-
[this.name]: FileUploadField.buildErrorMessages(language)
|
|
323
|
+
[this.name]: FileUploadField.buildErrorMessages(language, this.limits)
|
|
311
324
|
}
|
|
312
325
|
}
|
|
313
326
|
|
|
@@ -415,11 +428,20 @@ export class FileUploadField extends FormComponent {
|
|
|
415
428
|
}
|
|
416
429
|
}
|
|
417
430
|
|
|
418
|
-
static buildErrorMessages(
|
|
431
|
+
static buildErrorMessages(
|
|
432
|
+
language: string,
|
|
433
|
+
limits: { min?: number; max?: number; length?: number } = {}
|
|
434
|
+
) {
|
|
419
435
|
return {
|
|
420
|
-
'array.min': tPlugin('validation.filesMin', language
|
|
421
|
-
|
|
422
|
-
|
|
436
|
+
'array.min': tPlugin('validation.filesMin', language, {
|
|
437
|
+
count: limits.min ?? 1
|
|
438
|
+
}),
|
|
439
|
+
'array.max': tPlugin('validation.filesMax', language, {
|
|
440
|
+
count: limits.max ?? 1
|
|
441
|
+
}),
|
|
442
|
+
'array.length': tPlugin('validation.filesLength', language, {
|
|
443
|
+
count: limits.length ?? 1
|
|
444
|
+
})
|
|
423
445
|
}
|
|
424
446
|
}
|
|
425
447
|
}
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import { type GeospatialFieldComponent } from '@defra/forms-model'
|
|
2
2
|
import { type ArraySchema } from 'joi'
|
|
3
3
|
|
|
4
|
+
import { EN_GB } from '../../../constants.js'
|
|
4
5
|
import { type ComponentBase } from './ComponentBase.js'
|
|
5
6
|
import {
|
|
6
7
|
FormComponent,
|
|
7
8
|
isGeospatialState
|
|
8
9
|
} from './FormComponent.js'
|
|
9
10
|
import { getMapLayers } from './LocationFieldHelpers.js'
|
|
10
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
determineLimits,
|
|
13
|
+
getGeospatialSchema
|
|
14
|
+
} from './helpers/geospatial.js'
|
|
11
15
|
import { type RenderContext } from './types.js'
|
|
16
|
+
import { t as tPlugin } from '../i18n/index.js'
|
|
12
17
|
import { type Translator } from '../i18n/types.js'
|
|
13
18
|
import { messageTemplate } from '../pageControllers/validationOptions.js'
|
|
14
19
|
import {
|
|
@@ -24,6 +29,7 @@ export class GeospatialField extends FormComponent {
|
|
|
24
29
|
declare options: GeospatialFieldComponent['options']
|
|
25
30
|
declare formSchema: ArraySchema<GeospatialState>
|
|
26
31
|
declare stateSchema: ArraySchema<GeospatialState>
|
|
32
|
+
declare limits: { min?: number; max?: number; length?: number }
|
|
27
33
|
|
|
28
34
|
constructor(
|
|
29
35
|
def: GeospatialFieldComponent,
|
|
@@ -33,20 +39,24 @@ export class GeospatialField extends FormComponent {
|
|
|
33
39
|
|
|
34
40
|
const { options } = def
|
|
35
41
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
42
|
+
let formSchema = getGeospatialSchema(def).label(this.label)
|
|
43
|
+
|
|
44
|
+
const isOptional = options.required === false
|
|
45
|
+
const limits = determineLimits(def, isOptional)
|
|
46
|
+
|
|
47
|
+
formSchema = formSchema.messages(
|
|
48
|
+
GeospatialField.buildErrorMessages(EN_GB, limits)
|
|
49
|
+
)
|
|
41
50
|
|
|
42
51
|
this.formSchema = formSchema
|
|
43
52
|
this.stateSchema = formSchema.default(null)
|
|
44
53
|
|
|
45
|
-
if (
|
|
54
|
+
if (isOptional) {
|
|
46
55
|
this.stateSchema = this.stateSchema.allow(null)
|
|
47
56
|
}
|
|
48
57
|
|
|
49
58
|
this.options = options
|
|
59
|
+
this.limits = limits
|
|
50
60
|
}
|
|
51
61
|
|
|
52
62
|
getFormValueFromState(state: FormSubmissionState) {
|
|
@@ -127,9 +137,7 @@ export class GeospatialField extends FormComponent {
|
|
|
127
137
|
'components.geospatialField.validation.wrongCountry',
|
|
128
138
|
{
|
|
129
139
|
count: Number(err.path[1]) + 1,
|
|
130
|
-
country: translator.t(
|
|
131
|
-
`common.${(err.context.country as string).toLowerCase()}`
|
|
132
|
-
)
|
|
140
|
+
country: translator.t(`common.${err.context.country}`)
|
|
133
141
|
}
|
|
134
142
|
)
|
|
135
143
|
}
|
|
@@ -149,6 +157,13 @@ export class GeospatialField extends FormComponent {
|
|
|
149
157
|
return isGeospatialState(value)
|
|
150
158
|
}
|
|
151
159
|
|
|
160
|
+
getValidationMessagesOverride(translator: Translator) {
|
|
161
|
+
const { language } = translator
|
|
162
|
+
return {
|
|
163
|
+
[this.name]: GeospatialField.buildErrorMessages(language, this.limits)
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
152
167
|
/**
|
|
153
168
|
* For error preview page that shows all possible errors on a component
|
|
154
169
|
*/
|
|
@@ -169,11 +184,28 @@ export class GeospatialField extends FormComponent {
|
|
|
169
184
|
{ type: 'required', template: messageTemplate.selectRequired },
|
|
170
185
|
{
|
|
171
186
|
type: 'array.min',
|
|
172
|
-
template: '{{#title}} must contain at least 1
|
|
187
|
+
template: '{{#title}} must contain at least 1 item'
|
|
173
188
|
},
|
|
174
189
|
{ type: 'object.invalidjson', template: messageTemplate.format }
|
|
175
190
|
],
|
|
176
191
|
advancedSettingsErrors: []
|
|
177
192
|
}
|
|
178
193
|
}
|
|
194
|
+
|
|
195
|
+
static buildErrorMessages(
|
|
196
|
+
language: string,
|
|
197
|
+
limits: { min?: number; max?: number; length?: number } = {}
|
|
198
|
+
) {
|
|
199
|
+
return {
|
|
200
|
+
'array.min': tPlugin('validation.featuresMin', language, {
|
|
201
|
+
count: limits.min ?? 1
|
|
202
|
+
}),
|
|
203
|
+
'array.max': tPlugin('validation.featuresMax', language, {
|
|
204
|
+
count: limits.max ?? 1
|
|
205
|
+
}),
|
|
206
|
+
'array.length': tPlugin('validation.featuresLength', language, {
|
|
207
|
+
count: limits.length ?? 1
|
|
208
|
+
})
|
|
209
|
+
}
|
|
210
|
+
}
|
|
179
211
|
}
|
|
@@ -125,9 +125,11 @@ export abstract class LocationFieldBase extends FormComponent {
|
|
|
125
125
|
const viewModel = super.getViewModel(context)
|
|
126
126
|
|
|
127
127
|
if (this.instructionText) {
|
|
128
|
+
const { tComponent } = context.translator
|
|
128
129
|
return {
|
|
129
130
|
...viewModel,
|
|
130
|
-
instructionText:
|
|
131
|
+
instructionText:
|
|
132
|
+
tComponent(this.def, 'instructionText') || this.instructionText
|
|
131
133
|
}
|
|
132
134
|
}
|
|
133
135
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type Context, type CustomValidator } from 'joi'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { EastingNorthingField } from './EastingNorthingField.js'
|
|
4
4
|
import { isFormValue } from './FormComponent.js'
|
|
5
5
|
import { type GeospatialField } from './GeospatialField.js'
|
|
6
6
|
import { type LatLongField } from './LatLongField.js'
|
|
@@ -190,13 +190,23 @@ export function getLocationFieldViewModel(
|
|
|
190
190
|
...viewModel,
|
|
191
191
|
fieldset,
|
|
192
192
|
items,
|
|
193
|
-
mapLayers: getMapLayers(component)
|
|
193
|
+
mapLayers: getMapLayers(component),
|
|
194
|
+
country:
|
|
195
|
+
component instanceof EastingNorthingField
|
|
196
|
+
? component.options.countries?.at(0)
|
|
197
|
+
: undefined
|
|
194
198
|
}
|
|
195
199
|
|
|
196
200
|
if (component.options.instructionText) {
|
|
201
|
+
const { t, tComponent } = context.translator
|
|
202
|
+
|
|
197
203
|
return {
|
|
198
204
|
...result,
|
|
199
|
-
|
|
205
|
+
instructionSummary: t('components.locationFieldBase.howToFind'),
|
|
206
|
+
instructionText:
|
|
207
|
+
tComponent(component.def, 'instructionText') ||
|
|
208
|
+
component.options.instructionText,
|
|
209
|
+
t
|
|
200
210
|
}
|
|
201
211
|
}
|
|
202
212
|
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
|
-
GeospatialFieldOptionsCountryEnum,
|
|
3
2
|
type GeospatialFieldComponent,
|
|
4
3
|
type GeospatialFieldOptionsCountry
|
|
5
4
|
} from '@defra/forms-model'
|
|
6
5
|
import Bourne from '@hapi/bourne'
|
|
7
6
|
import { booleanWithin } from '@turf/boolean-within'
|
|
7
|
+
import { point } from '@turf/helpers'
|
|
8
|
+
// @ts-expect-error - no types
|
|
9
|
+
import OsGridRef from 'geodesy/osgridref.js'
|
|
8
10
|
import JoiBase, { type CustomValidator } from 'joi'
|
|
9
11
|
|
|
12
|
+
import { type EastingNorthingField } from '../EastingNorthingField.js'
|
|
13
|
+
import { type EastingNorthingState } from '../types.js'
|
|
10
14
|
import {
|
|
11
15
|
type Coordinates,
|
|
12
16
|
type Feature,
|
|
@@ -15,13 +19,6 @@ import {
|
|
|
15
19
|
} from '../../types.js'
|
|
16
20
|
import { countries } from '../../../map/routes/index.js'
|
|
17
21
|
|
|
18
|
-
const countriesDesc: Record<GeospatialFieldOptionsCountryEnum, string> = {
|
|
19
|
-
[GeospatialFieldOptionsCountryEnum.England]: 'England',
|
|
20
|
-
[GeospatialFieldOptionsCountryEnum.NorthernIreland]: 'Northern Ireland',
|
|
21
|
-
[GeospatialFieldOptionsCountryEnum.Scotland]: 'Scotland',
|
|
22
|
-
[GeospatialFieldOptionsCountryEnum.Wales]: 'Wales'
|
|
23
|
-
}
|
|
24
|
-
|
|
25
22
|
const Joi = JoiBase.extend({
|
|
26
23
|
type: 'array',
|
|
27
24
|
base: JoiBase.array(),
|
|
@@ -98,25 +95,43 @@ const featureSchema = Joi.object<Feature>().keys({
|
|
|
98
95
|
geometry: featureGeometrySchema
|
|
99
96
|
})
|
|
100
97
|
|
|
101
|
-
function
|
|
102
|
-
|
|
103
|
-
|
|
98
|
+
export function determineLimits(
|
|
99
|
+
def: GeospatialFieldComponent,
|
|
100
|
+
isOptional: boolean
|
|
104
101
|
) {
|
|
105
|
-
const {
|
|
106
|
-
const
|
|
107
|
-
|
|
102
|
+
const { schema: constraints } = def
|
|
103
|
+
const limits: { min?: number; max?: number; length?: number } = {}
|
|
108
104
|
if (typeof constraints?.length === 'number') {
|
|
109
|
-
|
|
105
|
+
limits.length = constraints.length
|
|
110
106
|
} else {
|
|
111
107
|
if (typeof constraints?.min === 'number') {
|
|
112
|
-
|
|
108
|
+
limits.min = constraints.min
|
|
113
109
|
} else if (!isOptional) {
|
|
114
|
-
|
|
110
|
+
limits.min = 1
|
|
115
111
|
}
|
|
116
112
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
113
|
+
limits.max = typeof constraints?.max === 'number' ? constraints.max : 50
|
|
114
|
+
}
|
|
115
|
+
return limits
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function applySchemaConstraints(
|
|
119
|
+
schema: JoiBase.ArraySchema<Feature[]>,
|
|
120
|
+
def: GeospatialFieldComponent
|
|
121
|
+
) {
|
|
122
|
+
const { options } = def
|
|
123
|
+
const isOptional = options.required === false
|
|
124
|
+
|
|
125
|
+
const limits = determineLimits(def, isOptional)
|
|
126
|
+
|
|
127
|
+
if (limits.length !== undefined) {
|
|
128
|
+
schema = schema.length(limits.length)
|
|
129
|
+
}
|
|
130
|
+
if (limits.min !== undefined) {
|
|
131
|
+
schema = schema.min(limits.min)
|
|
132
|
+
}
|
|
133
|
+
if (limits.max !== undefined) {
|
|
134
|
+
schema = schema.max(limits.max)
|
|
120
135
|
}
|
|
121
136
|
|
|
122
137
|
if (isOptional) {
|
|
@@ -156,7 +171,7 @@ export function getGeospatialSchema(
|
|
|
156
171
|
|
|
157
172
|
if (!result) {
|
|
158
173
|
return helpers.error('any.custom', {
|
|
159
|
-
country
|
|
174
|
+
country
|
|
160
175
|
})
|
|
161
176
|
}
|
|
162
177
|
|
|
@@ -171,3 +186,51 @@ export function getGeospatialSchema(
|
|
|
171
186
|
def
|
|
172
187
|
)
|
|
173
188
|
}
|
|
189
|
+
|
|
190
|
+
export function getEastingNorthingCountryValidator(
|
|
191
|
+
field: EastingNorthingField,
|
|
192
|
+
country: GeospatialFieldOptionsCountry
|
|
193
|
+
) {
|
|
194
|
+
const validateCountryBounds: CustomValidator = (value, helpers) => {
|
|
195
|
+
const countryFeature = countries.features.find(
|
|
196
|
+
(feature) => feature.id === country
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
if (!countryFeature) {
|
|
200
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
201
|
+
return value
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const eastingNorthing = field.getFormValueFromState(
|
|
205
|
+
value as EastingNorthingState
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
if (!eastingNorthing) {
|
|
209
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
210
|
+
return value
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const gridRef = new OsGridRef(
|
|
214
|
+
eastingNorthing.easting,
|
|
215
|
+
eastingNorthing.northing
|
|
216
|
+
)
|
|
217
|
+
const latLong = gridRef.toLatLon()
|
|
218
|
+
const coord = point([latLong.longitude, latLong.latitude])
|
|
219
|
+
const within = booleanWithin(coord, countryFeature)
|
|
220
|
+
|
|
221
|
+
if (!within) {
|
|
222
|
+
return helpers.error(
|
|
223
|
+
'any.custom',
|
|
224
|
+
{
|
|
225
|
+
country
|
|
226
|
+
},
|
|
227
|
+
{ path: [field.name] }
|
|
228
|
+
)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
232
|
+
return value
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return validateCountryBounds
|
|
236
|
+
}
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
FormStatus,
|
|
3
|
+
type FormDefinition,
|
|
4
|
+
type FormMetadata
|
|
5
|
+
} from '@defra/forms-model'
|
|
2
6
|
import Boom from '@hapi/boom'
|
|
3
7
|
|
|
4
8
|
export interface OfflineBoomData {
|
|
5
9
|
offline: true
|
|
6
10
|
metadata: FormMetadata
|
|
11
|
+
definition: FormDefinition | undefined
|
|
7
12
|
}
|
|
8
13
|
|
|
9
14
|
/**
|
|
@@ -13,6 +18,7 @@ export interface OfflineBoomData {
|
|
|
13
18
|
*/
|
|
14
19
|
export function assertFormAvailable(
|
|
15
20
|
metadata: FormMetadata,
|
|
21
|
+
definition: FormDefinition | undefined,
|
|
16
22
|
formState: FormStatus,
|
|
17
23
|
isPreview: boolean
|
|
18
24
|
): void {
|
|
@@ -21,7 +27,7 @@ export function assertFormAvailable(
|
|
|
21
27
|
formState === FormStatus.Live &&
|
|
22
28
|
!isPreview
|
|
23
29
|
) {
|
|
24
|
-
const data: OfflineBoomData = { offline: true, metadata }
|
|
30
|
+
const data: OfflineBoomData = { offline: true, metadata, definition }
|
|
25
31
|
throw Boom.boomify(new Error(`Form ${metadata.slug} is offline`), {
|
|
26
32
|
statusCode: 503,
|
|
27
33
|
data
|
|
@@ -56,13 +56,14 @@ export async function getFormModel(
|
|
|
56
56
|
const formState = resolveState(state)
|
|
57
57
|
|
|
58
58
|
const metadata = await formsService.getFormMetadata(slug)
|
|
59
|
-
assertFormAvailable(metadata, formState, isPreview)
|
|
60
59
|
|
|
61
60
|
const definition = await formsService.getFormDefinition(
|
|
62
61
|
metadata.id,
|
|
63
62
|
formState
|
|
64
63
|
)
|
|
65
64
|
|
|
65
|
+
assertFormAvailable(metadata, definition, formState, isPreview)
|
|
66
|
+
|
|
66
67
|
if (!definition) {
|
|
67
68
|
throw Boom.notFound(
|
|
68
69
|
`No definition found for form metadata ${metadata.id} (${slug}) ${state}`
|
|
@@ -145,7 +146,6 @@ export async function resolveFormModel(
|
|
|
145
146
|
const metadata = await formsService.getFormMetadata(slug)
|
|
146
147
|
const formState = resolveState(state)
|
|
147
148
|
const isPreview = options.isPreview ?? isPreviewState(state, options)
|
|
148
|
-
assertFormAvailable(metadata, formState, isPreview)
|
|
149
149
|
const stateMetadata = metadata[formState]
|
|
150
150
|
|
|
151
151
|
if (!stateMetadata) {
|
|
@@ -199,6 +199,8 @@ export async function resolveFormModel(
|
|
|
199
199
|
cache.set(cacheKey, entry)
|
|
200
200
|
}
|
|
201
201
|
|
|
202
|
+
assertFormAvailable(metadata, entry.model.def, formState, isPreview)
|
|
203
|
+
|
|
202
204
|
return entry.model
|
|
203
205
|
}
|
|
204
206
|
|
|
@@ -16,7 +16,9 @@ const opts = {
|
|
|
16
16
|
* because Joi cannot resolve custom functions from a plain string.
|
|
17
17
|
* Plain strings with only built-in tokens ({{#label}}, {{#limit}}) work without wrapping.
|
|
18
18
|
*/
|
|
19
|
-
export function buildValidationMessages(
|
|
19
|
+
export function buildValidationMessages(
|
|
20
|
+
t: (key: string, options?: Record<string, unknown>) => string
|
|
21
|
+
) {
|
|
20
22
|
return {
|
|
21
23
|
// Expressions — contain lowerFirst custom function
|
|
22
24
|
required: joi.expression(t('validation.required'), opts) as JoiExpression,
|
|
@@ -57,15 +59,18 @@ export function buildValidationMessages(t: (key: string) => string) {
|
|
|
57
59
|
dateMax: t('components.dateField.validation.dateMax'),
|
|
58
60
|
|
|
59
61
|
unicode: t('validation.unicode'),
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
62
|
+
// These generic templates are shared across fields with differing limits, so a
|
|
63
|
+
// fixed singular count is used here — components needing accurate singular/plural
|
|
64
|
+
// text (e.g. FileUploadField) build their own per-instance messages instead.
|
|
65
|
+
arrayMin: t('validation.arrayMin', { count: 1 }),
|
|
66
|
+
arrayMax: t('validation.arrayMax', { count: 1 }),
|
|
67
|
+
arrayLength: t('validation.arrayLength', { count: 1 }),
|
|
68
|
+
featuresMin: t('validation.featuresMin', { count: 1 }),
|
|
69
|
+
featuresMax: t('validation.featuresMax', { count: 1 }),
|
|
70
|
+
featuresLength: t('validation.featuresLength', { count: 1 }),
|
|
71
|
+
filesMin: t('validation.filesMin', { count: 1 }),
|
|
72
|
+
filesMax: t('validation.filesMax', { count: 1 }),
|
|
73
|
+
filesLength: t('validation.filesLength', { count: 1 })
|
|
69
74
|
}
|
|
70
75
|
}
|
|
71
76
|
|