@defra/forms-engine-plugin 3.0.1 → 3.0.2
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/.server/server/plugins/engine/pageControllers/RepeatPageController.d.ts +1 -0
- package/.server/server/plugins/engine/pageControllers/RepeatPageController.js +8 -1
- package/.server/server/plugins/engine/pageControllers/RepeatPageController.js.map +1 -1
- package/.server/server/plugins/engine/types.d.ts +1 -0
- package/.server/server/plugins/engine/types.js.map +1 -1
- package/.server/server/plugins/engine/views/repeat-list-summary.html +15 -5
- package/package.json +1 -1
- package/src/server/plugins/engine/pageControllers/RepeatPageController.ts +8 -1
- package/src/server/plugins/engine/types.ts +1 -0
- package/src/server/plugins/engine/views/repeat-list-summary.html +15 -5
|
@@ -8,6 +8,7 @@ export declare class RepeatPageController extends QuestionPageController {
|
|
|
8
8
|
listSummaryViewName: string;
|
|
9
9
|
listDeleteViewName: string;
|
|
10
10
|
repeat: Repeat;
|
|
11
|
+
allowSaveAndExit: boolean;
|
|
11
12
|
constructor(model: FormModel, pageDef: PageRepeat);
|
|
12
13
|
get keys(): string[];
|
|
13
14
|
getFormParams(request?: FormContextRequest): import("~/src/server/plugins/engine/types.js").FormPayloadParams;
|
|
@@ -9,6 +9,7 @@ export class RepeatPageController extends QuestionPageController {
|
|
|
9
9
|
listSummaryViewName = 'repeat-list-summary';
|
|
10
10
|
listDeleteViewName = 'item-delete';
|
|
11
11
|
repeat;
|
|
12
|
+
allowSaveAndExit = true;
|
|
12
13
|
constructor(model, pageDef) {
|
|
13
14
|
super(model, pageDef);
|
|
14
15
|
this.repeat = pageDef.repeat;
|
|
@@ -198,6 +199,11 @@ export class RepeatPageController extends QuestionPageController {
|
|
|
198
199
|
});
|
|
199
200
|
return super.proceed(request, h, nextPath);
|
|
200
201
|
}
|
|
202
|
+
|
|
203
|
+
// Check if this is a save-and-exit action
|
|
204
|
+
if (action === FormAction.SaveAndExit) {
|
|
205
|
+
return this.handleSaveAndExit(request, context, h);
|
|
206
|
+
}
|
|
201
207
|
const nextPath = this.getNextPath(context);
|
|
202
208
|
return super.proceed(request, h, nextPath);
|
|
203
209
|
};
|
|
@@ -350,7 +356,8 @@ export class RepeatPageController extends QuestionPageController {
|
|
|
350
356
|
errors,
|
|
351
357
|
checkAnswers: [{
|
|
352
358
|
summaryList
|
|
353
|
-
}]
|
|
359
|
+
}],
|
|
360
|
+
allowSaveAndExit: this.shouldShowSaveAndExit(request.server)
|
|
354
361
|
};
|
|
355
362
|
}
|
|
356
363
|
getSummaryPath(request) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RepeatPageController.js","names":["randomUUID","Boom","Joi","isRepeatState","redirectPath","QuestionPageController","FormAction","RepeatPageController","listSummaryViewName","listDeleteViewName","repeat","constructor","model","pageDef","options","schema","itemId","string","uuid","required","collection","formSchema","append","stateSchema","object","keys","name","array","items","min","max","label","title","getFormParams","request","params","payload","getFormDataFromState","state","list","getListFromState","getItemId","item","getItemFromList","getStateFromValidForm","badRequest","itemState","updated","newList","push","indexOf","proceed","h","nextPath","getSummaryPath","find","values","makeGetRouteHandler","context","path","query","summaryPath","returnUrl","force","length","makeGetListSummaryRouteHandler","viewModel","getListSummaryViewModel","view","makePostListSummaryRouteHandler","action","hasErrorMin","Continue","hasErrorMax","AddAnother","count","itemTitle","errors","href","text","getNextPath","makeGetItemDeleteRouteHandler","notFound","backLink","getBackLink","pageTitle","buttonConfirm","buttonCancel","makePostItemDeleteRouteHandler","confirm","splice","update","mergeState","getViewModel","itemNumber","repeatCaption","sectionTitle","isForceAccess","summaryList","classes","rows","Array","isArray","forEach","index","getHref","visuallyHiddenText","itemDisplayText","fields","getDisplayStringFromState","key","value","actions","repeatTitle","showTitle","checkAnswers"],"sources":["../../../../../src/server/plugins/engine/pageControllers/RepeatPageController.ts"],"sourcesContent":["import { randomUUID } from 'crypto'\n\nimport { type PageRepeat, type Repeat } from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport Joi from 'joi'\n\nimport { isRepeatState } from '~/src/server/plugins/engine/components/FormComponent.js'\nimport { redirectPath } from '~/src/server/plugins/engine/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { QuestionPageController } from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js'\nimport {\n type FormContext,\n type FormContextRequest,\n type FormPageViewModel,\n type FormPayload,\n type FormSubmissionState,\n type ItemDeletePageViewModel,\n type RepeatItemState,\n type RepeatListState,\n type RepeaterSummaryPageViewModel,\n type SummaryList,\n type SummaryListAction\n} from '~/src/server/plugins/engine/types.js'\nimport {\n FormAction,\n type FormRequest,\n type FormRequestPayload,\n type FormResponseToolkit\n} from '~/src/server/routes/types.js'\n\nexport class RepeatPageController extends QuestionPageController {\n declare pageDef: PageRepeat\n\n listSummaryViewName = 'repeat-list-summary'\n listDeleteViewName = 'item-delete'\n repeat: Repeat\n\n constructor(model: FormModel, pageDef: PageRepeat) {\n super(model, pageDef)\n\n this.repeat = pageDef.repeat\n\n const { options, schema } = this.repeat\n const itemId = Joi.string().uuid().required()\n\n this.collection.formSchema = this.collection.formSchema.append({ itemId })\n this.collection.stateSchema = Joi.object<RepeatItemState>().keys({\n [options.name]: Joi.array()\n .items(this.collection.stateSchema.append({ itemId }))\n .min(schema.min)\n .max(schema.max)\n .label(`${options.title} list`)\n .required()\n })\n }\n\n get keys() {\n const { repeat } = this\n return [repeat.options.name, ...super.keys]\n }\n\n getFormParams(request?: FormContextRequest) {\n const params = super.getFormParams(request)\n\n // Apply an itemId to the form payload\n if (request?.payload) {\n params.itemId = request.params.itemId ?? randomUUID()\n }\n\n return params\n }\n\n getFormDataFromState(\n request: FormContextRequest | undefined,\n state: FormSubmissionState\n ) {\n const { repeat } = this\n\n const params = this.getFormParams(request)\n const list = this.getListFromState(state)\n const itemId = this.getItemId(request)\n\n // Create payload with repeater list state\n if (!itemId) {\n return {\n ...params,\n [repeat.options.name]: list\n }\n }\n\n // Create payload with repeater item state\n const item = this.getItemFromList(list, itemId)\n\n return {\n ...params,\n ...item\n }\n }\n\n getStateFromValidForm(\n request: FormContextRequest,\n state: FormSubmissionState,\n payload: FormPayload\n ) {\n const itemId = this.getItemId(request)\n\n if (!itemId) {\n throw Boom.badRequest('No item ID found')\n }\n\n const list = this.getListFromState(state)\n const item = this.getItemFromList(list, itemId)\n\n const itemState = super.getStateFromValidForm(request, state, payload)\n const updated: RepeatItemState = { ...itemState, itemId }\n const newList = [...list]\n\n if (!item) {\n // Adding a new item\n newList.push(updated)\n } else {\n // Update an existing item\n newList[list.indexOf(item)] = updated\n }\n\n return {\n [this.repeat.options.name]: newList\n }\n }\n\n proceed(request: FormContextRequest, h: FormResponseToolkit) {\n const nextPath = this.getSummaryPath(request)\n return super.proceed(request, h, nextPath)\n }\n\n getItemFromList(list: RepeatListState, itemId?: string) {\n return list.find((item) => item.itemId === itemId)\n }\n\n getListFromState(state: FormSubmissionState) {\n const { name } = this.repeat.options\n const values = state[name]\n\n return isRepeatState(values) ? values : []\n }\n\n makeGetRouteHandler() {\n return async (\n request: FormRequest,\n context: FormContext,\n h: FormResponseToolkit\n ) => {\n const { path } = this\n const { query } = request\n const { state } = context\n\n const itemId = this.getItemId(request)\n const list = this.getListFromState(state)\n\n if (!itemId) {\n const summaryPath = this.getSummaryPath(request)\n const nextPath = redirectPath(`${path}/${randomUUID()}`, {\n returnUrl: query.returnUrl,\n force: query.force\n })\n\n // Only redirect to new item when list is empty\n return super.proceed(request, h, list.length ? summaryPath : nextPath)\n }\n\n return super.makeGetRouteHandler()(request, context, h)\n }\n }\n\n makeGetListSummaryRouteHandler() {\n return (\n request: FormRequest,\n context: FormContext,\n h: FormResponseToolkit\n ) => {\n const { path } = this\n const { query } = request\n const { state } = context\n\n const list = this.getListFromState(state)\n\n if (!list.length) {\n const nextPath = redirectPath(`${path}/${randomUUID()}`, {\n returnUrl: query.returnUrl\n })\n\n return super.proceed(request, h, nextPath)\n }\n\n const viewModel = this.getListSummaryViewModel(request, context, list)\n\n return h.view(this.listSummaryViewName, viewModel)\n }\n }\n\n makePostListSummaryRouteHandler() {\n return (\n request: FormRequestPayload,\n context: FormContext,\n h: FormResponseToolkit\n ) => {\n const { path, repeat } = this\n const { query } = request\n const { schema, options } = repeat\n const { state } = context\n\n const list = this.getListFromState(state)\n\n if (!list.length) {\n const nextPath = redirectPath(`${path}/${randomUUID()}`, {\n returnUrl: query.returnUrl\n })\n\n return super.proceed(request, h, nextPath)\n }\n\n const { action } = this.getFormParams(request)\n\n const hasErrorMin =\n action === FormAction.Continue && list.length < schema.min\n\n const hasErrorMax =\n (action === FormAction.AddAnother && list.length >= schema.max) ||\n (action === FormAction.Continue && list.length > schema.max)\n\n // Show error if repeat limits apply\n if (hasErrorMin || hasErrorMax) {\n const count = hasErrorMax ? schema.max : schema.min\n const itemTitle = `${options.title}${count === 1 ? '' : 's'}`\n\n context.errors = [\n {\n path: [],\n href: '',\n name: '',\n text: hasErrorMax\n ? `You can only add up to ${count} ${itemTitle}`\n : `You must add at least ${count} ${itemTitle}`\n }\n ]\n\n const viewModel = this.getListSummaryViewModel(request, context, list)\n\n return h.view(this.listSummaryViewName, viewModel)\n }\n\n if (action === FormAction.AddAnother) {\n const nextPath = redirectPath(`${path}/${randomUUID()}`, {\n returnUrl: query.returnUrl\n })\n\n return super.proceed(request, h, nextPath)\n }\n\n const nextPath = this.getNextPath(context)\n return super.proceed(request, h, nextPath)\n }\n }\n\n makeGetItemDeleteRouteHandler() {\n return (\n request: FormRequest,\n context: FormContext,\n h: FormResponseToolkit\n ) => {\n const { viewModel } = this\n const { state } = context\n\n const list = this.getListFromState(state)\n const itemId = this.getItemId(request)\n const item = this.getItemFromList(list, itemId)\n\n if (!item || list.length === 1) {\n throw Boom.notFound(\n item\n ? 'Last list item cannot be removed'\n : 'List item to remove not found'\n )\n }\n\n const { title } = this.repeat.options\n\n return h.view(this.listDeleteViewName, {\n ...viewModel,\n context,\n backLink: this.getBackLink(request, context),\n pageTitle: `Are you sure you want to remove this ${title}?`,\n itemTitle: `${title} ${list.indexOf(item) + 1}`,\n buttonConfirm: { text: `Remove ${title}` },\n buttonCancel: { text: 'Cancel' }\n } satisfies ItemDeletePageViewModel)\n }\n }\n\n makePostItemDeleteRouteHandler() {\n return async (\n request: FormRequestPayload,\n context: FormContext,\n h: FormResponseToolkit\n ) => {\n const { repeat } = this\n const { state } = context\n\n const { confirm } = this.getFormParams(request)\n\n const list = this.getListFromState(state)\n const itemId = this.getItemId(request)\n const item = this.getItemFromList(list, itemId)\n\n if (!item || list.length === 1) {\n throw Boom.notFound(\n item\n ? 'Last list item cannot be removed'\n : 'List item to remove not found'\n )\n }\n\n // Remove the item from the list\n if (confirm) {\n list.splice(list.indexOf(item), 1)\n\n const update = {\n [repeat.options.name]: list\n }\n\n await this.mergeState(request, state, update)\n }\n\n return this.proceed(request, h)\n }\n }\n\n getViewModel(\n request: FormContextRequest,\n context: FormContext\n ): FormPageViewModel {\n const { state } = context\n\n const list = this.getListFromState(state)\n const itemId = this.getItemId(request)\n const item = this.getItemFromList(list, itemId)\n\n const viewModel = super.getViewModel(request, context)\n const itemNumber = item ? list.indexOf(item) + 1 : list.length + 1\n const repeatCaption = `${this.repeat.options.title} ${itemNumber}`\n\n return {\n ...viewModel,\n\n sectionTitle: viewModel.sectionTitle\n ? `${viewModel.sectionTitle}: ${repeatCaption}`\n : repeatCaption\n }\n }\n\n getListSummaryViewModel(\n request: FormContextRequest,\n context: FormContext,\n list: RepeatListState\n ): RepeaterSummaryPageViewModel {\n const { collection, href, repeat } = this\n const { query } = request\n const { isForceAccess, errors } = context\n\n const { title } = repeat.options\n\n const summaryList: SummaryList = {\n classes: 'govuk-summary-list--long-actions',\n rows: []\n }\n\n let count = 0\n\n if (Array.isArray(list)) {\n count = list.length\n\n const summaryPath = this.getSummaryPath(request)\n\n list.forEach((item, index) => {\n const items: SummaryListAction[] = []\n\n // Remove summary list actions from previews\n if (!isForceAccess) {\n items.push({\n href: redirectPath(`${href}/${item.itemId}`, {\n returnUrl: query.returnUrl ?? this.getHref(summaryPath)\n }),\n text: 'Change',\n classes: 'govuk-link--no-visited-state',\n visuallyHiddenText: `item ${index + 1}`\n })\n\n if (count > 1) {\n items.push({\n href: redirectPath(`${href}/${item.itemId}/confirm-delete`, {\n returnUrl: query.returnUrl\n }),\n text: 'Remove',\n classes: 'govuk-link--no-visited-state',\n visuallyHiddenText: `item ${index + 1}`\n })\n }\n }\n\n const itemDisplayText = collection.fields.length\n ? collection.fields[0].getDisplayStringFromState(item)\n : ''\n\n summaryList.rows.push({\n key: {\n text: `${title} ${index + 1}`\n },\n value: {\n text: itemDisplayText || 'Not supplied'\n },\n actions: {\n items\n }\n })\n })\n }\n\n return {\n ...this.viewModel,\n backLink: this.getBackLink(request, context),\n repeatTitle: title,\n pageTitle: `You have added ${count} ${title}${count === 1 ? '' : 's'}`,\n showTitle: true,\n context,\n errors,\n checkAnswers: [{ summaryList }]\n }\n }\n\n getSummaryPath(request?: FormContextRequest) {\n const { path } = this\n\n const summaryPath = super.getSummaryPath()\n\n if (!request) {\n return summaryPath\n }\n\n const { query } = request\n\n return redirectPath(`${path}${summaryPath}`, {\n returnUrl: query.returnUrl\n })\n }\n}\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"RepeatPageController.js","names":["randomUUID","Boom","Joi","isRepeatState","redirectPath","QuestionPageController","FormAction","RepeatPageController","listSummaryViewName","listDeleteViewName","repeat","allowSaveAndExit","constructor","model","pageDef","options","schema","itemId","string","uuid","required","collection","formSchema","append","stateSchema","object","keys","name","array","items","min","max","label","title","getFormParams","request","params","payload","getFormDataFromState","state","list","getListFromState","getItemId","item","getItemFromList","getStateFromValidForm","badRequest","itemState","updated","newList","push","indexOf","proceed","h","nextPath","getSummaryPath","find","values","makeGetRouteHandler","context","path","query","summaryPath","returnUrl","force","length","makeGetListSummaryRouteHandler","viewModel","getListSummaryViewModel","view","makePostListSummaryRouteHandler","action","hasErrorMin","Continue","hasErrorMax","AddAnother","count","itemTitle","errors","href","text","SaveAndExit","handleSaveAndExit","getNextPath","makeGetItemDeleteRouteHandler","notFound","backLink","getBackLink","pageTitle","buttonConfirm","buttonCancel","makePostItemDeleteRouteHandler","confirm","splice","update","mergeState","getViewModel","itemNumber","repeatCaption","sectionTitle","isForceAccess","summaryList","classes","rows","Array","isArray","forEach","index","getHref","visuallyHiddenText","itemDisplayText","fields","getDisplayStringFromState","key","value","actions","repeatTitle","showTitle","checkAnswers","shouldShowSaveAndExit","server"],"sources":["../../../../../src/server/plugins/engine/pageControllers/RepeatPageController.ts"],"sourcesContent":["import { randomUUID } from 'crypto'\n\nimport { type PageRepeat, type Repeat } from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport Joi from 'joi'\n\nimport { isRepeatState } from '~/src/server/plugins/engine/components/FormComponent.js'\nimport { redirectPath } from '~/src/server/plugins/engine/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { QuestionPageController } from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js'\nimport {\n type FormContext,\n type FormContextRequest,\n type FormPageViewModel,\n type FormPayload,\n type FormSubmissionState,\n type ItemDeletePageViewModel,\n type RepeatItemState,\n type RepeatListState,\n type RepeaterSummaryPageViewModel,\n type SummaryList,\n type SummaryListAction\n} from '~/src/server/plugins/engine/types.js'\nimport {\n FormAction,\n type FormRequest,\n type FormRequestPayload,\n type FormResponseToolkit\n} from '~/src/server/routes/types.js'\n\nexport class RepeatPageController extends QuestionPageController {\n declare pageDef: PageRepeat\n\n listSummaryViewName = 'repeat-list-summary'\n listDeleteViewName = 'item-delete'\n repeat: Repeat\n allowSaveAndExit = true\n\n constructor(model: FormModel, pageDef: PageRepeat) {\n super(model, pageDef)\n\n this.repeat = pageDef.repeat\n\n const { options, schema } = this.repeat\n const itemId = Joi.string().uuid().required()\n\n this.collection.formSchema = this.collection.formSchema.append({ itemId })\n this.collection.stateSchema = Joi.object<RepeatItemState>().keys({\n [options.name]: Joi.array()\n .items(this.collection.stateSchema.append({ itemId }))\n .min(schema.min)\n .max(schema.max)\n .label(`${options.title} list`)\n .required()\n })\n }\n\n get keys() {\n const { repeat } = this\n return [repeat.options.name, ...super.keys]\n }\n\n getFormParams(request?: FormContextRequest) {\n const params = super.getFormParams(request)\n\n // Apply an itemId to the form payload\n if (request?.payload) {\n params.itemId = request.params.itemId ?? randomUUID()\n }\n\n return params\n }\n\n getFormDataFromState(\n request: FormContextRequest | undefined,\n state: FormSubmissionState\n ) {\n const { repeat } = this\n\n const params = this.getFormParams(request)\n const list = this.getListFromState(state)\n const itemId = this.getItemId(request)\n\n // Create payload with repeater list state\n if (!itemId) {\n return {\n ...params,\n [repeat.options.name]: list\n }\n }\n\n // Create payload with repeater item state\n const item = this.getItemFromList(list, itemId)\n\n return {\n ...params,\n ...item\n }\n }\n\n getStateFromValidForm(\n request: FormContextRequest,\n state: FormSubmissionState,\n payload: FormPayload\n ) {\n const itemId = this.getItemId(request)\n\n if (!itemId) {\n throw Boom.badRequest('No item ID found')\n }\n\n const list = this.getListFromState(state)\n const item = this.getItemFromList(list, itemId)\n\n const itemState = super.getStateFromValidForm(request, state, payload)\n const updated: RepeatItemState = { ...itemState, itemId }\n const newList = [...list]\n\n if (!item) {\n // Adding a new item\n newList.push(updated)\n } else {\n // Update an existing item\n newList[list.indexOf(item)] = updated\n }\n\n return {\n [this.repeat.options.name]: newList\n }\n }\n\n proceed(request: FormContextRequest, h: FormResponseToolkit) {\n const nextPath = this.getSummaryPath(request)\n return super.proceed(request, h, nextPath)\n }\n\n getItemFromList(list: RepeatListState, itemId?: string) {\n return list.find((item) => item.itemId === itemId)\n }\n\n getListFromState(state: FormSubmissionState) {\n const { name } = this.repeat.options\n const values = state[name]\n\n return isRepeatState(values) ? values : []\n }\n\n makeGetRouteHandler() {\n return async (\n request: FormRequest,\n context: FormContext,\n h: FormResponseToolkit\n ) => {\n const { path } = this\n const { query } = request\n const { state } = context\n\n const itemId = this.getItemId(request)\n const list = this.getListFromState(state)\n\n if (!itemId) {\n const summaryPath = this.getSummaryPath(request)\n const nextPath = redirectPath(`${path}/${randomUUID()}`, {\n returnUrl: query.returnUrl,\n force: query.force\n })\n\n // Only redirect to new item when list is empty\n return super.proceed(request, h, list.length ? summaryPath : nextPath)\n }\n\n return super.makeGetRouteHandler()(request, context, h)\n }\n }\n\n makeGetListSummaryRouteHandler() {\n return (\n request: FormRequest,\n context: FormContext,\n h: FormResponseToolkit\n ) => {\n const { path } = this\n const { query } = request\n const { state } = context\n\n const list = this.getListFromState(state)\n\n if (!list.length) {\n const nextPath = redirectPath(`${path}/${randomUUID()}`, {\n returnUrl: query.returnUrl\n })\n\n return super.proceed(request, h, nextPath)\n }\n\n const viewModel = this.getListSummaryViewModel(request, context, list)\n\n return h.view(this.listSummaryViewName, viewModel)\n }\n }\n\n makePostListSummaryRouteHandler() {\n return (\n request: FormRequestPayload,\n context: FormContext,\n h: FormResponseToolkit\n ) => {\n const { path, repeat } = this\n const { query } = request\n const { schema, options } = repeat\n const { state } = context\n\n const list = this.getListFromState(state)\n\n if (!list.length) {\n const nextPath = redirectPath(`${path}/${randomUUID()}`, {\n returnUrl: query.returnUrl\n })\n\n return super.proceed(request, h, nextPath)\n }\n\n const { action } = this.getFormParams(request)\n\n const hasErrorMin =\n action === FormAction.Continue && list.length < schema.min\n\n const hasErrorMax =\n (action === FormAction.AddAnother && list.length >= schema.max) ||\n (action === FormAction.Continue && list.length > schema.max)\n\n // Show error if repeat limits apply\n if (hasErrorMin || hasErrorMax) {\n const count = hasErrorMax ? schema.max : schema.min\n const itemTitle = `${options.title}${count === 1 ? '' : 's'}`\n\n context.errors = [\n {\n path: [],\n href: '',\n name: '',\n text: hasErrorMax\n ? `You can only add up to ${count} ${itemTitle}`\n : `You must add at least ${count} ${itemTitle}`\n }\n ]\n\n const viewModel = this.getListSummaryViewModel(request, context, list)\n\n return h.view(this.listSummaryViewName, viewModel)\n }\n\n if (action === FormAction.AddAnother) {\n const nextPath = redirectPath(`${path}/${randomUUID()}`, {\n returnUrl: query.returnUrl\n })\n\n return super.proceed(request, h, nextPath)\n }\n\n // Check if this is a save-and-exit action\n if (action === FormAction.SaveAndExit) {\n return this.handleSaveAndExit(request, context, h)\n }\n\n const nextPath = this.getNextPath(context)\n return super.proceed(request, h, nextPath)\n }\n }\n\n makeGetItemDeleteRouteHandler() {\n return (\n request: FormRequest,\n context: FormContext,\n h: FormResponseToolkit\n ) => {\n const { viewModel } = this\n const { state } = context\n\n const list = this.getListFromState(state)\n const itemId = this.getItemId(request)\n const item = this.getItemFromList(list, itemId)\n\n if (!item || list.length === 1) {\n throw Boom.notFound(\n item\n ? 'Last list item cannot be removed'\n : 'List item to remove not found'\n )\n }\n\n const { title } = this.repeat.options\n\n return h.view(this.listDeleteViewName, {\n ...viewModel,\n context,\n backLink: this.getBackLink(request, context),\n pageTitle: `Are you sure you want to remove this ${title}?`,\n itemTitle: `${title} ${list.indexOf(item) + 1}`,\n buttonConfirm: { text: `Remove ${title}` },\n buttonCancel: { text: 'Cancel' }\n } satisfies ItemDeletePageViewModel)\n }\n }\n\n makePostItemDeleteRouteHandler() {\n return async (\n request: FormRequestPayload,\n context: FormContext,\n h: FormResponseToolkit\n ) => {\n const { repeat } = this\n const { state } = context\n\n const { confirm } = this.getFormParams(request)\n\n const list = this.getListFromState(state)\n const itemId = this.getItemId(request)\n const item = this.getItemFromList(list, itemId)\n\n if (!item || list.length === 1) {\n throw Boom.notFound(\n item\n ? 'Last list item cannot be removed'\n : 'List item to remove not found'\n )\n }\n\n // Remove the item from the list\n if (confirm) {\n list.splice(list.indexOf(item), 1)\n\n const update = {\n [repeat.options.name]: list\n }\n\n await this.mergeState(request, state, update)\n }\n\n return this.proceed(request, h)\n }\n }\n\n getViewModel(\n request: FormContextRequest,\n context: FormContext\n ): FormPageViewModel {\n const { state } = context\n\n const list = this.getListFromState(state)\n const itemId = this.getItemId(request)\n const item = this.getItemFromList(list, itemId)\n\n const viewModel = super.getViewModel(request, context)\n const itemNumber = item ? list.indexOf(item) + 1 : list.length + 1\n const repeatCaption = `${this.repeat.options.title} ${itemNumber}`\n\n return {\n ...viewModel,\n\n sectionTitle: viewModel.sectionTitle\n ? `${viewModel.sectionTitle}: ${repeatCaption}`\n : repeatCaption\n }\n }\n\n getListSummaryViewModel(\n request: FormContextRequest,\n context: FormContext,\n list: RepeatListState\n ): RepeaterSummaryPageViewModel {\n const { collection, href, repeat } = this\n const { query } = request\n const { isForceAccess, errors } = context\n\n const { title } = repeat.options\n\n const summaryList: SummaryList = {\n classes: 'govuk-summary-list--long-actions',\n rows: []\n }\n\n let count = 0\n\n if (Array.isArray(list)) {\n count = list.length\n\n const summaryPath = this.getSummaryPath(request)\n\n list.forEach((item, index) => {\n const items: SummaryListAction[] = []\n\n // Remove summary list actions from previews\n if (!isForceAccess) {\n items.push({\n href: redirectPath(`${href}/${item.itemId}`, {\n returnUrl: query.returnUrl ?? this.getHref(summaryPath)\n }),\n text: 'Change',\n classes: 'govuk-link--no-visited-state',\n visuallyHiddenText: `item ${index + 1}`\n })\n\n if (count > 1) {\n items.push({\n href: redirectPath(`${href}/${item.itemId}/confirm-delete`, {\n returnUrl: query.returnUrl\n }),\n text: 'Remove',\n classes: 'govuk-link--no-visited-state',\n visuallyHiddenText: `item ${index + 1}`\n })\n }\n }\n\n const itemDisplayText = collection.fields.length\n ? collection.fields[0].getDisplayStringFromState(item)\n : ''\n\n summaryList.rows.push({\n key: {\n text: `${title} ${index + 1}`\n },\n value: {\n text: itemDisplayText || 'Not supplied'\n },\n actions: {\n items\n }\n })\n })\n }\n\n return {\n ...this.viewModel,\n backLink: this.getBackLink(request, context),\n repeatTitle: title,\n pageTitle: `You have added ${count} ${title}${count === 1 ? '' : 's'}`,\n showTitle: true,\n context,\n errors,\n checkAnswers: [{ summaryList }],\n allowSaveAndExit: this.shouldShowSaveAndExit(request.server)\n }\n }\n\n getSummaryPath(request?: FormContextRequest) {\n const { path } = this\n\n const summaryPath = super.getSummaryPath()\n\n if (!request) {\n return summaryPath\n }\n\n const { query } = request\n\n return redirectPath(`${path}${summaryPath}`, {\n returnUrl: query.returnUrl\n })\n }\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -240,6 +240,7 @@ export interface RepeaterSummaryPageViewModel extends PageViewModelBase {
|
|
|
240
240
|
errors?: FormSubmissionError[];
|
|
241
241
|
checkAnswers: CheckAnswers[];
|
|
242
242
|
repeatTitle: string;
|
|
243
|
+
allowSaveAndExit: boolean;
|
|
243
244
|
}
|
|
244
245
|
export interface FeaturedFormPageViewModel extends FormPageViewModel {
|
|
245
246
|
formAction?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","names":["FileStatus","UploadStatus"],"sources":["../../../../src/server/plugins/engine/types.ts"],"sourcesContent":["import {\n type ComponentDef,\n type Event,\n type FormDefinition,\n type FormMetadata,\n type FormVersionMetadata,\n type Item,\n type List,\n type Page\n} from '@defra/forms-model'\nimport {\n type PluginProperties,\n type Request,\n type ResponseObject\n} from '@hapi/hapi'\nimport { type JoiExpression, type ValidationErrorItem } from 'joi'\n\nimport { FormComponent } from '~/src/server/plugins/engine/components/FormComponent.js'\nimport { type UkAddressState } from '~/src/server/plugins/engine/components/UkAddressField.js'\nimport { type Component } from '~/src/server/plugins/engine/components/helpers/components.js'\nimport { type FileUploadField } from '~/src/server/plugins/engine/components/index.js'\nimport {\n type BackLink,\n type ComponentText,\n type ComponentViewModel,\n type DatePartsState,\n type MonthYearState\n} from '~/src/server/plugins/engine/components/types.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type DetailItemField } from '~/src/server/plugins/engine/models/types.js'\nimport { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'\nimport {\n type FileStatus,\n type FormAdapterSubmissionSchemaVersion,\n type UploadStatus\n} from '~/src/server/plugins/engine/types/enums.js'\nimport { type ViewContext } from '~/src/server/plugins/nunjucks/types.js'\nimport {\n type FormAction,\n type FormParams,\n type FormRequest,\n type FormRequestPayload,\n type FormResponseToolkit,\n type FormStatus\n} from '~/src/server/routes/types.js'\nimport { type CacheService } from '~/src/server/services/cacheService.js'\nimport { type RequestOptions } from '~/src/server/services/httpService.js'\nimport { type Services } from '~/src/server/types.js'\n\nexport type AnyFormRequest = FormRequest | FormRequestPayload\nexport type AnyRequest = Request | AnyFormRequest\n\n/**\n * Form submission state stores the following in Redis:\n * Props containing user's submitted values as `{ [inputId]: value }` or as `{ [sectionName]: { [inputName]: value } }`\n * a) . e.g:\n * ```ts\n * {\n * _C9PRHmsgt: 'Ben',\n * WfLk9McjzX: 'Music',\n * IK7jkUFCBL: 'Royal Academy of Music'\n * }\n * ```\n *\n * b)\n * ```ts\n * {\n * checkBeforeYouStart: { ukPassport: true },\n * applicantDetails: {\n * numberOfApplicants: 1,\n * phoneNumber: '77777777',\n * emailAddress: 'aaa@aaa.com'\n * },\n * applicantOneDetails: {\n * firstName: 'a',\n * middleName: 'a',\n * lastName: 'a',\n * address: { addressLine1: 'a', addressLine2: 'a', town: 'a', postcode: 'a' }\n * }\n * }\n * ```\n */\n\n/**\n * Form submission state\n */\nexport type FormSubmissionState = {\n upload?: Record<string, TempFileState>\n} & FormState\n\nexport interface FormSubmissionError\n extends Pick<ValidationErrorItem, 'context' | 'path'> {\n href: string // e.g: '#dateField__day'\n name: string // e.g: 'dateField__day'\n text: string // e.g: 'Date field must be a real date'\n}\n\nexport interface FormPayloadParams {\n action?: FormAction\n confirm?: true\n crumb?: string\n itemId?: string\n}\n\n/**\n * Form POST for question pages\n * (after Joi has converted value types)\n */\nexport type FormPayload = FormPayloadParams & Partial<Record<string, FormValue>>\n\nexport type FormValue =\n | Item['value']\n | Item['value'][]\n | UploadState\n | RepeatListState\n | undefined\n\nexport type FormState = Partial<Record<string, FormStateValue>>\nexport type FormStateValue = Exclude<FormValue, undefined> | null\n\nexport interface FormValidationResult<\n ValueType extends FormPayload | FormSubmissionState\n> {\n value: ValueType\n errors: FormSubmissionError[] | undefined\n}\n\nexport interface FormContext {\n /**\n * Evaluation form state only (filtered by visited paths),\n * with values formatted for condition evaluation using\n * {@link FormComponent.getContextValueFromState}\n */\n evaluationState: FormState\n\n /**\n * Relevant form state only (filtered by visited paths)\n */\n relevantState: FormState\n\n /**\n * Relevant pages only (filtered by visited paths)\n */\n relevantPages: PageControllerClass[]\n\n /**\n * Form submission payload (single page)\n */\n payload: FormPayload\n\n /**\n * Form submission state (entire form)\n */\n state: FormSubmissionState\n\n /**\n * Validation errors (entire form)\n */\n errors?: FormSubmissionError[]\n\n /**\n * Visited paths evaluated from form state\n */\n paths: string[]\n\n /**\n * Preview URL direct access is allowed\n */\n isForceAccess: boolean\n\n /**\n * Miscellaneous extra data from event responses\n */\n data: object\n\n pageDefMap: Map<string, Page>\n listDefMap: Map<string, List>\n componentDefMap: Map<string, ComponentDef>\n pageMap: Map<string, PageControllerClass>\n componentMap: Map<string, Component>\n referenceNumber: string\n submittedVersionNumber?: number\n}\n\nexport type FormContextRequest = (\n | {\n method: 'get'\n payload?: undefined\n }\n | {\n method: 'post'\n payload: FormPayload\n }\n | {\n method: FormRequest['method']\n payload?: object | undefined\n }\n) &\n Pick<\n FormRequest,\n 'app' | 'method' | 'params' | 'path' | 'query' | 'url' | 'server'\n >\n\nexport interface UploadInitiateResponse {\n uploadId: string\n uploadUrl: string\n statusUrl: string\n}\n\nexport {\n FileStatus,\n UploadStatus\n} from '~/src/server/plugins/engine/types/enums.js'\n\nexport type UploadState = FileState[]\n\nexport type FileUpload = {\n fileId: string\n filename: string\n contentLength: number\n} & (\n | {\n fileStatus: FileStatus.complete | FileStatus.rejected | FileStatus.pending\n errorMessage?: string\n }\n | {\n fileStatus: FileStatus.complete\n errorMessage?: undefined\n }\n)\n\nexport interface FileUploadMetadata {\n retrievalKey: string\n}\n\nexport type UploadStatusResponse =\n | {\n uploadStatus: UploadStatus.initiated\n metadata: FileUploadMetadata\n form: { file?: undefined }\n }\n | {\n uploadStatus: UploadStatus.pending | UploadStatus.ready\n metadata: FileUploadMetadata\n form: { file: FileUpload }\n numberOfRejectedFiles?: number\n }\n | {\n uploadStatus: UploadStatus.ready\n metadata: FileUploadMetadata\n form: { file: FileUpload }\n numberOfRejectedFiles: 0\n }\n\nexport type UploadStatusFileResponse = Exclude<\n UploadStatusResponse,\n { uploadStatus: UploadStatus.initiated }\n>\n\nexport interface FileState {\n uploadId: string\n status: UploadStatusFileResponse\n}\n\nexport interface TempFileState {\n upload?: UploadInitiateResponse\n files: UploadState\n}\n\nexport interface RepeatItemState extends FormPayload {\n itemId: string\n}\n\nexport type RepeatListState = RepeatItemState[]\n\nexport interface CheckAnswers {\n title?: ComponentText\n summaryList: SummaryList\n}\n\nexport interface SummaryList {\n classes?: string\n rows: SummaryListRow[]\n}\n\nexport interface SummaryListRow {\n key: ComponentText\n value: ComponentText\n actions?: { items: SummaryListAction[] }\n}\n\nexport type SummaryListAction = ComponentText & {\n href: string\n visuallyHiddenText: string\n}\n\nexport interface PageViewModelBase extends Partial<ViewContext> {\n page: PageController\n name?: string\n pageTitle: string\n sectionTitle?: string\n showTitle: boolean\n isStartPage: boolean\n backLink?: BackLink\n feedbackLink?: string\n serviceUrl: string\n phaseTag?: string\n}\n\nexport interface ItemDeletePageViewModel extends PageViewModelBase {\n context: FormContext\n itemTitle: string\n confirmation?: ComponentText\n buttonConfirm: ComponentText\n buttonCancel: ComponentText\n}\n\nexport interface FormPageViewModel extends PageViewModelBase {\n components: ComponentViewModel[]\n context: FormContext\n errors?: FormSubmissionError[]\n hasMissingNotificationEmail?: boolean\n allowSaveAndExit: boolean\n}\n\nexport interface RepeaterSummaryPageViewModel extends PageViewModelBase {\n context: FormContext\n errors?: FormSubmissionError[]\n checkAnswers: CheckAnswers[]\n repeatTitle: string\n}\n\nexport interface FeaturedFormPageViewModel extends FormPageViewModel {\n formAction?: string\n formComponent: ComponentViewModel\n componentsBefore: ComponentViewModel[]\n uploadId: string | undefined\n proxyUrl: string | null\n}\n\nexport type PageViewModel =\n | PageViewModelBase\n | ItemDeletePageViewModel\n | FormPageViewModel\n | RepeaterSummaryPageViewModel\n | FeaturedFormPageViewModel\n\nexport type GlobalFunction = (value: unknown) => unknown\nexport type FilterFunction = (value: unknown) => unknown\nexport interface ErrorMessageTemplate {\n type: string\n template: JoiExpression\n}\n\nexport interface ErrorMessageTemplateList {\n baseErrors: ErrorMessageTemplate[]\n advancedSettingsErrors: ErrorMessageTemplate[]\n}\n\nexport type PreparePageEventRequestOptions = (\n options: RequestOptions,\n event: Event,\n page: PageControllerClass,\n context: FormContext\n) => void\n\nexport type OnRequestCallback = (\n request: AnyFormRequest,\n params: FormParams,\n definition: FormDefinition,\n metadata: FormMetadata\n) => void\n\nexport type SaveAndExitHandler = (\n request: FormRequestPayload,\n h: FormResponseToolkit,\n context: FormContext\n) => ResponseObject\n\nexport interface PluginOptions {\n model?: FormModel\n services?: Services\n controllers?: Record<string, typeof PageController>\n cache?: CacheService | string\n globals?: Record<string, GlobalFunction>\n filters?: Record<string, FilterFunction>\n saveAndExit?: SaveAndExitHandler\n pluginPath?: string\n nunjucks: {\n baseLayoutPath: string\n paths: string[]\n }\n viewContext: PluginProperties['forms-engine-plugin']['viewContext']\n preparePageEventRequestOptions?: PreparePageEventRequestOptions\n onRequest?: OnRequestCallback\n baseUrl: string // base URL of the application, protocol and hostname e.g. \"https://myapp.com\"\n}\n\nexport interface FormAdapterSubmissionMessageMeta {\n schemaVersion: FormAdapterSubmissionSchemaVersion\n timestamp: Date\n referenceNumber: string\n formName: string\n formId: string\n formSlug: string\n status: FormStatus\n isPreview: boolean\n notificationEmail: string\n versionMetadata?: FormVersionMetadata\n}\n\nexport type FormAdapterSubmissionMessageMetaSerialised = Omit<\n FormAdapterSubmissionMessageMeta,\n 'schemaVersion' | 'timestamp' | 'status'\n> & {\n schemaVersion: number\n status: string\n timestamp: string\n}\nexport interface FormAdapterFile {\n fileName: string\n fileId: string\n userDownloadLink: string\n}\n\nexport interface FormAdapterSubmissionMessageResult {\n files: {\n main: string\n repeaters: Record<string, string>\n }\n}\n\n/**\n * A detail item specifically for files\n */\nexport type FileUploadFieldDetailitem = Omit<DetailItemField, 'field'> & {\n field: FileUploadField\n}\nexport type RichFormValue =\n | FormValue\n | FormPayload\n | DatePartsState\n | MonthYearState\n | UkAddressState\n\nexport interface FormAdapterSubmissionMessageData {\n main: Record<string, RichFormValue>\n repeaters: Record<string, Record<string, RichFormValue>[]>\n files: Record<string, FormAdapterFile[]>\n}\n\nexport interface FormAdapterSubmissionMessagePayload {\n meta: FormAdapterSubmissionMessageMeta\n data: FormAdapterSubmissionMessageData\n result: FormAdapterSubmissionMessageResult\n}\n\nexport interface FormAdapterSubmissionMessage\n extends FormAdapterSubmissionMessagePayload {\n messageId: string\n recordCreatedAt: Date\n}\n\nexport interface FormAdapterSubmissionService {\n handleFormSubmission: (\n submissionMessage: FormAdapterSubmissionMessage\n ) => unknown\n}\n"],"mappings":"AAqDA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAmBA;AACA;AACA;AACA;;AAsGA,SACEA,UAAU,EACVC,YAAY;;AA6Nd;AACA;AACA","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"types.js","names":["FileStatus","UploadStatus"],"sources":["../../../../src/server/plugins/engine/types.ts"],"sourcesContent":["import {\n type ComponentDef,\n type Event,\n type FormDefinition,\n type FormMetadata,\n type FormVersionMetadata,\n type Item,\n type List,\n type Page\n} from '@defra/forms-model'\nimport {\n type PluginProperties,\n type Request,\n type ResponseObject\n} from '@hapi/hapi'\nimport { type JoiExpression, type ValidationErrorItem } from 'joi'\n\nimport { FormComponent } from '~/src/server/plugins/engine/components/FormComponent.js'\nimport { type UkAddressState } from '~/src/server/plugins/engine/components/UkAddressField.js'\nimport { type Component } from '~/src/server/plugins/engine/components/helpers/components.js'\nimport { type FileUploadField } from '~/src/server/plugins/engine/components/index.js'\nimport {\n type BackLink,\n type ComponentText,\n type ComponentViewModel,\n type DatePartsState,\n type MonthYearState\n} from '~/src/server/plugins/engine/components/types.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type DetailItemField } from '~/src/server/plugins/engine/models/types.js'\nimport { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'\nimport {\n type FileStatus,\n type FormAdapterSubmissionSchemaVersion,\n type UploadStatus\n} from '~/src/server/plugins/engine/types/enums.js'\nimport { type ViewContext } from '~/src/server/plugins/nunjucks/types.js'\nimport {\n type FormAction,\n type FormParams,\n type FormRequest,\n type FormRequestPayload,\n type FormResponseToolkit,\n type FormStatus\n} from '~/src/server/routes/types.js'\nimport { type CacheService } from '~/src/server/services/cacheService.js'\nimport { type RequestOptions } from '~/src/server/services/httpService.js'\nimport { type Services } from '~/src/server/types.js'\n\nexport type AnyFormRequest = FormRequest | FormRequestPayload\nexport type AnyRequest = Request | AnyFormRequest\n\n/**\n * Form submission state stores the following in Redis:\n * Props containing user's submitted values as `{ [inputId]: value }` or as `{ [sectionName]: { [inputName]: value } }`\n * a) . e.g:\n * ```ts\n * {\n * _C9PRHmsgt: 'Ben',\n * WfLk9McjzX: 'Music',\n * IK7jkUFCBL: 'Royal Academy of Music'\n * }\n * ```\n *\n * b)\n * ```ts\n * {\n * checkBeforeYouStart: { ukPassport: true },\n * applicantDetails: {\n * numberOfApplicants: 1,\n * phoneNumber: '77777777',\n * emailAddress: 'aaa@aaa.com'\n * },\n * applicantOneDetails: {\n * firstName: 'a',\n * middleName: 'a',\n * lastName: 'a',\n * address: { addressLine1: 'a', addressLine2: 'a', town: 'a', postcode: 'a' }\n * }\n * }\n * ```\n */\n\n/**\n * Form submission state\n */\nexport type FormSubmissionState = {\n upload?: Record<string, TempFileState>\n} & FormState\n\nexport interface FormSubmissionError\n extends Pick<ValidationErrorItem, 'context' | 'path'> {\n href: string // e.g: '#dateField__day'\n name: string // e.g: 'dateField__day'\n text: string // e.g: 'Date field must be a real date'\n}\n\nexport interface FormPayloadParams {\n action?: FormAction\n confirm?: true\n crumb?: string\n itemId?: string\n}\n\n/**\n * Form POST for question pages\n * (after Joi has converted value types)\n */\nexport type FormPayload = FormPayloadParams & Partial<Record<string, FormValue>>\n\nexport type FormValue =\n | Item['value']\n | Item['value'][]\n | UploadState\n | RepeatListState\n | undefined\n\nexport type FormState = Partial<Record<string, FormStateValue>>\nexport type FormStateValue = Exclude<FormValue, undefined> | null\n\nexport interface FormValidationResult<\n ValueType extends FormPayload | FormSubmissionState\n> {\n value: ValueType\n errors: FormSubmissionError[] | undefined\n}\n\nexport interface FormContext {\n /**\n * Evaluation form state only (filtered by visited paths),\n * with values formatted for condition evaluation using\n * {@link FormComponent.getContextValueFromState}\n */\n evaluationState: FormState\n\n /**\n * Relevant form state only (filtered by visited paths)\n */\n relevantState: FormState\n\n /**\n * Relevant pages only (filtered by visited paths)\n */\n relevantPages: PageControllerClass[]\n\n /**\n * Form submission payload (single page)\n */\n payload: FormPayload\n\n /**\n * Form submission state (entire form)\n */\n state: FormSubmissionState\n\n /**\n * Validation errors (entire form)\n */\n errors?: FormSubmissionError[]\n\n /**\n * Visited paths evaluated from form state\n */\n paths: string[]\n\n /**\n * Preview URL direct access is allowed\n */\n isForceAccess: boolean\n\n /**\n * Miscellaneous extra data from event responses\n */\n data: object\n\n pageDefMap: Map<string, Page>\n listDefMap: Map<string, List>\n componentDefMap: Map<string, ComponentDef>\n pageMap: Map<string, PageControllerClass>\n componentMap: Map<string, Component>\n referenceNumber: string\n submittedVersionNumber?: number\n}\n\nexport type FormContextRequest = (\n | {\n method: 'get'\n payload?: undefined\n }\n | {\n method: 'post'\n payload: FormPayload\n }\n | {\n method: FormRequest['method']\n payload?: object | undefined\n }\n) &\n Pick<\n FormRequest,\n 'app' | 'method' | 'params' | 'path' | 'query' | 'url' | 'server'\n >\n\nexport interface UploadInitiateResponse {\n uploadId: string\n uploadUrl: string\n statusUrl: string\n}\n\nexport {\n FileStatus,\n UploadStatus\n} from '~/src/server/plugins/engine/types/enums.js'\n\nexport type UploadState = FileState[]\n\nexport type FileUpload = {\n fileId: string\n filename: string\n contentLength: number\n} & (\n | {\n fileStatus: FileStatus.complete | FileStatus.rejected | FileStatus.pending\n errorMessage?: string\n }\n | {\n fileStatus: FileStatus.complete\n errorMessage?: undefined\n }\n)\n\nexport interface FileUploadMetadata {\n retrievalKey: string\n}\n\nexport type UploadStatusResponse =\n | {\n uploadStatus: UploadStatus.initiated\n metadata: FileUploadMetadata\n form: { file?: undefined }\n }\n | {\n uploadStatus: UploadStatus.pending | UploadStatus.ready\n metadata: FileUploadMetadata\n form: { file: FileUpload }\n numberOfRejectedFiles?: number\n }\n | {\n uploadStatus: UploadStatus.ready\n metadata: FileUploadMetadata\n form: { file: FileUpload }\n numberOfRejectedFiles: 0\n }\n\nexport type UploadStatusFileResponse = Exclude<\n UploadStatusResponse,\n { uploadStatus: UploadStatus.initiated }\n>\n\nexport interface FileState {\n uploadId: string\n status: UploadStatusFileResponse\n}\n\nexport interface TempFileState {\n upload?: UploadInitiateResponse\n files: UploadState\n}\n\nexport interface RepeatItemState extends FormPayload {\n itemId: string\n}\n\nexport type RepeatListState = RepeatItemState[]\n\nexport interface CheckAnswers {\n title?: ComponentText\n summaryList: SummaryList\n}\n\nexport interface SummaryList {\n classes?: string\n rows: SummaryListRow[]\n}\n\nexport interface SummaryListRow {\n key: ComponentText\n value: ComponentText\n actions?: { items: SummaryListAction[] }\n}\n\nexport type SummaryListAction = ComponentText & {\n href: string\n visuallyHiddenText: string\n}\n\nexport interface PageViewModelBase extends Partial<ViewContext> {\n page: PageController\n name?: string\n pageTitle: string\n sectionTitle?: string\n showTitle: boolean\n isStartPage: boolean\n backLink?: BackLink\n feedbackLink?: string\n serviceUrl: string\n phaseTag?: string\n}\n\nexport interface ItemDeletePageViewModel extends PageViewModelBase {\n context: FormContext\n itemTitle: string\n confirmation?: ComponentText\n buttonConfirm: ComponentText\n buttonCancel: ComponentText\n}\n\nexport interface FormPageViewModel extends PageViewModelBase {\n components: ComponentViewModel[]\n context: FormContext\n errors?: FormSubmissionError[]\n hasMissingNotificationEmail?: boolean\n allowSaveAndExit: boolean\n}\n\nexport interface RepeaterSummaryPageViewModel extends PageViewModelBase {\n context: FormContext\n errors?: FormSubmissionError[]\n checkAnswers: CheckAnswers[]\n repeatTitle: string\n allowSaveAndExit: boolean\n}\n\nexport interface FeaturedFormPageViewModel extends FormPageViewModel {\n formAction?: string\n formComponent: ComponentViewModel\n componentsBefore: ComponentViewModel[]\n uploadId: string | undefined\n proxyUrl: string | null\n}\n\nexport type PageViewModel =\n | PageViewModelBase\n | ItemDeletePageViewModel\n | FormPageViewModel\n | RepeaterSummaryPageViewModel\n | FeaturedFormPageViewModel\n\nexport type GlobalFunction = (value: unknown) => unknown\nexport type FilterFunction = (value: unknown) => unknown\nexport interface ErrorMessageTemplate {\n type: string\n template: JoiExpression\n}\n\nexport interface ErrorMessageTemplateList {\n baseErrors: ErrorMessageTemplate[]\n advancedSettingsErrors: ErrorMessageTemplate[]\n}\n\nexport type PreparePageEventRequestOptions = (\n options: RequestOptions,\n event: Event,\n page: PageControllerClass,\n context: FormContext\n) => void\n\nexport type OnRequestCallback = (\n request: AnyFormRequest,\n params: FormParams,\n definition: FormDefinition,\n metadata: FormMetadata\n) => void\n\nexport type SaveAndExitHandler = (\n request: FormRequestPayload,\n h: FormResponseToolkit,\n context: FormContext\n) => ResponseObject\n\nexport interface PluginOptions {\n model?: FormModel\n services?: Services\n controllers?: Record<string, typeof PageController>\n cache?: CacheService | string\n globals?: Record<string, GlobalFunction>\n filters?: Record<string, FilterFunction>\n saveAndExit?: SaveAndExitHandler\n pluginPath?: string\n nunjucks: {\n baseLayoutPath: string\n paths: string[]\n }\n viewContext: PluginProperties['forms-engine-plugin']['viewContext']\n preparePageEventRequestOptions?: PreparePageEventRequestOptions\n onRequest?: OnRequestCallback\n baseUrl: string // base URL of the application, protocol and hostname e.g. \"https://myapp.com\"\n}\n\nexport interface FormAdapterSubmissionMessageMeta {\n schemaVersion: FormAdapterSubmissionSchemaVersion\n timestamp: Date\n referenceNumber: string\n formName: string\n formId: string\n formSlug: string\n status: FormStatus\n isPreview: boolean\n notificationEmail: string\n versionMetadata?: FormVersionMetadata\n}\n\nexport type FormAdapterSubmissionMessageMetaSerialised = Omit<\n FormAdapterSubmissionMessageMeta,\n 'schemaVersion' | 'timestamp' | 'status'\n> & {\n schemaVersion: number\n status: string\n timestamp: string\n}\nexport interface FormAdapterFile {\n fileName: string\n fileId: string\n userDownloadLink: string\n}\n\nexport interface FormAdapterSubmissionMessageResult {\n files: {\n main: string\n repeaters: Record<string, string>\n }\n}\n\n/**\n * A detail item specifically for files\n */\nexport type FileUploadFieldDetailitem = Omit<DetailItemField, 'field'> & {\n field: FileUploadField\n}\nexport type RichFormValue =\n | FormValue\n | FormPayload\n | DatePartsState\n | MonthYearState\n | UkAddressState\n\nexport interface FormAdapterSubmissionMessageData {\n main: Record<string, RichFormValue>\n repeaters: Record<string, Record<string, RichFormValue>[]>\n files: Record<string, FormAdapterFile[]>\n}\n\nexport interface FormAdapterSubmissionMessagePayload {\n meta: FormAdapterSubmissionMessageMeta\n data: FormAdapterSubmissionMessageData\n result: FormAdapterSubmissionMessageResult\n}\n\nexport interface FormAdapterSubmissionMessage\n extends FormAdapterSubmissionMessagePayload {\n messageId: string\n recordCreatedAt: Date\n}\n\nexport interface FormAdapterSubmissionService {\n handleFormSubmission: (\n submissionMessage: FormAdapterSubmissionMessage\n ) => unknown\n}\n"],"mappings":"AAqDA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAmBA;AACA;AACA;AACA;;AAsGA,SACEA,UAAU,EACVC,YAAY;;AA8Nd;AACA;AACA","ignoreList":[]}
|
|
@@ -24,10 +24,10 @@
|
|
|
24
24
|
{{ govukSummaryList(section.summaryList) }}
|
|
25
25
|
{% endfor %}
|
|
26
26
|
|
|
27
|
-
<
|
|
28
|
-
<
|
|
29
|
-
<input type="hidden" name="crumb" value="{{ crumb }}">
|
|
27
|
+
<form method="post" novalidate>
|
|
28
|
+
<input type="hidden" name="crumb" value="{{ crumb }}">
|
|
30
29
|
|
|
30
|
+
<div class="govuk-button-group">
|
|
31
31
|
{{ govukButton({
|
|
32
32
|
text: "Continue",
|
|
33
33
|
name: "action",
|
|
@@ -42,8 +42,18 @@
|
|
|
42
42
|
classes: "govuk-button--secondary",
|
|
43
43
|
preventDoubleClick: true
|
|
44
44
|
}) }}
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
|
|
46
|
+
{% if allowSaveAndExit %}
|
|
47
|
+
{{ govukButton({
|
|
48
|
+
text: "Save and exit",
|
|
49
|
+
classes: "govuk-button--secondary",
|
|
50
|
+
name: "action",
|
|
51
|
+
value: "save-and-exit",
|
|
52
|
+
preventDoubleClick: true
|
|
53
|
+
}) }}
|
|
54
|
+
{% endif %}
|
|
55
|
+
</div>
|
|
56
|
+
</form>
|
|
47
57
|
</div>
|
|
48
58
|
</div>
|
|
49
59
|
|
package/package.json
CHANGED
|
@@ -34,6 +34,7 @@ export class RepeatPageController extends QuestionPageController {
|
|
|
34
34
|
listSummaryViewName = 'repeat-list-summary'
|
|
35
35
|
listDeleteViewName = 'item-delete'
|
|
36
36
|
repeat: Repeat
|
|
37
|
+
allowSaveAndExit = true
|
|
37
38
|
|
|
38
39
|
constructor(model: FormModel, pageDef: PageRepeat) {
|
|
39
40
|
super(model, pageDef)
|
|
@@ -257,6 +258,11 @@ export class RepeatPageController extends QuestionPageController {
|
|
|
257
258
|
return super.proceed(request, h, nextPath)
|
|
258
259
|
}
|
|
259
260
|
|
|
261
|
+
// Check if this is a save-and-exit action
|
|
262
|
+
if (action === FormAction.SaveAndExit) {
|
|
263
|
+
return this.handleSaveAndExit(request, context, h)
|
|
264
|
+
}
|
|
265
|
+
|
|
260
266
|
const nextPath = this.getNextPath(context)
|
|
261
267
|
return super.proceed(request, h, nextPath)
|
|
262
268
|
}
|
|
@@ -433,7 +439,8 @@ export class RepeatPageController extends QuestionPageController {
|
|
|
433
439
|
showTitle: true,
|
|
434
440
|
context,
|
|
435
441
|
errors,
|
|
436
|
-
checkAnswers: [{ summaryList }]
|
|
442
|
+
checkAnswers: [{ summaryList }],
|
|
443
|
+
allowSaveAndExit: this.shouldShowSaveAndExit(request.server)
|
|
437
444
|
}
|
|
438
445
|
}
|
|
439
446
|
|
|
@@ -329,6 +329,7 @@ export interface RepeaterSummaryPageViewModel extends PageViewModelBase {
|
|
|
329
329
|
errors?: FormSubmissionError[]
|
|
330
330
|
checkAnswers: CheckAnswers[]
|
|
331
331
|
repeatTitle: string
|
|
332
|
+
allowSaveAndExit: boolean
|
|
332
333
|
}
|
|
333
334
|
|
|
334
335
|
export interface FeaturedFormPageViewModel extends FormPageViewModel {
|
|
@@ -24,10 +24,10 @@
|
|
|
24
24
|
{{ govukSummaryList(section.summaryList) }}
|
|
25
25
|
{% endfor %}
|
|
26
26
|
|
|
27
|
-
<
|
|
28
|
-
<
|
|
29
|
-
<input type="hidden" name="crumb" value="{{ crumb }}">
|
|
27
|
+
<form method="post" novalidate>
|
|
28
|
+
<input type="hidden" name="crumb" value="{{ crumb }}">
|
|
30
29
|
|
|
30
|
+
<div class="govuk-button-group">
|
|
31
31
|
{{ govukButton({
|
|
32
32
|
text: "Continue",
|
|
33
33
|
name: "action",
|
|
@@ -42,8 +42,18 @@
|
|
|
42
42
|
classes: "govuk-button--secondary",
|
|
43
43
|
preventDoubleClick: true
|
|
44
44
|
}) }}
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
|
|
46
|
+
{% if allowSaveAndExit %}
|
|
47
|
+
{{ govukButton({
|
|
48
|
+
text: "Save and exit",
|
|
49
|
+
classes: "govuk-button--secondary",
|
|
50
|
+
name: "action",
|
|
51
|
+
value: "save-and-exit",
|
|
52
|
+
preventDoubleClick: true
|
|
53
|
+
}) }}
|
|
54
|
+
{% endif %}
|
|
55
|
+
</div>
|
|
56
|
+
</form>
|
|
47
57
|
</div>
|
|
48
58
|
</div>
|
|
49
59
|
|