@defra/forms-engine-plugin 0.1.11 → 0.1.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.
Files changed (147) hide show
  1. package/.public/javascripts/file-upload.min.js +1 -1
  2. package/.public/javascripts/file-upload.min.js.map +1 -1
  3. package/.server/client/javascripts/file-upload.js +45 -4
  4. package/.server/client/javascripts/file-upload.js.map +1 -1
  5. package/.server/server/constants.js +2 -0
  6. package/.server/server/constants.js.map +1 -1
  7. package/.server/server/index.js +1 -1
  8. package/.server/server/index.js.map +1 -1
  9. package/.server/server/plugins/engine/components/AutocompleteField.js +2 -0
  10. package/.server/server/plugins/engine/components/AutocompleteField.js.map +1 -1
  11. package/.server/server/plugins/engine/components/CheckboxesField.js +3 -4
  12. package/.server/server/plugins/engine/components/CheckboxesField.js.map +1 -1
  13. package/.server/server/plugins/engine/components/ComponentCollection.js +37 -16
  14. package/.server/server/plugins/engine/components/ComponentCollection.js.map +1 -1
  15. package/.server/server/plugins/engine/components/DatePartsField.js +36 -2
  16. package/.server/server/plugins/engine/components/DatePartsField.js.map +1 -1
  17. package/.server/server/plugins/engine/components/EmailAddressField.js +19 -3
  18. package/.server/server/plugins/engine/components/EmailAddressField.js.map +1 -1
  19. package/.server/server/plugins/engine/components/FileUploadField.js +44 -4
  20. package/.server/server/plugins/engine/components/FileUploadField.js.map +1 -1
  21. package/.server/server/plugins/engine/components/FormComponent.js +14 -2
  22. package/.server/server/plugins/engine/components/FormComponent.js.map +1 -1
  23. package/.server/server/plugins/engine/components/ListFormComponent.js +16 -3
  24. package/.server/server/plugins/engine/components/ListFormComponent.js.map +1 -1
  25. package/.server/server/plugins/engine/components/Markdown.js +24 -0
  26. package/.server/server/plugins/engine/components/Markdown.js.map +1 -0
  27. package/.server/server/plugins/engine/components/MonthYearField.js +30 -2
  28. package/.server/server/plugins/engine/components/MonthYearField.js.map +1 -1
  29. package/.server/server/plugins/engine/components/MultilineTextField.js +32 -3
  30. package/.server/server/plugins/engine/components/MultilineTextField.js.map +1 -1
  31. package/.server/server/plugins/engine/components/NumberField.js +28 -3
  32. package/.server/server/plugins/engine/components/NumberField.js.map +1 -1
  33. package/.server/server/plugins/engine/components/SelectionControlField.js +14 -0
  34. package/.server/server/plugins/engine/components/SelectionControlField.js.map +1 -1
  35. package/.server/server/plugins/engine/components/TelephoneNumberField.js +19 -3
  36. package/.server/server/plugins/engine/components/TelephoneNumberField.js.map +1 -1
  37. package/.server/server/plugins/engine/components/TextField.js +22 -3
  38. package/.server/server/plugins/engine/components/TextField.js.map +1 -1
  39. package/.server/server/plugins/engine/components/UkAddressField.js +29 -0
  40. package/.server/server/plugins/engine/components/UkAddressField.js.map +1 -1
  41. package/.server/server/plugins/engine/components/YesNoField.js +18 -0
  42. package/.server/server/plugins/engine/components/YesNoField.js.map +1 -1
  43. package/.server/server/plugins/engine/components/helpers.js +16 -0
  44. package/.server/server/plugins/engine/components/helpers.js.map +1 -1
  45. package/.server/server/plugins/engine/components/index.js +1 -0
  46. package/.server/server/plugins/engine/components/index.js.map +1 -1
  47. package/.server/server/plugins/engine/configureEnginePlugin.js +3 -1
  48. package/.server/server/plugins/engine/configureEnginePlugin.js.map +1 -1
  49. package/.server/server/plugins/engine/helpers.js +38 -18
  50. package/.server/server/plugins/engine/helpers.js.map +1 -1
  51. package/.server/server/plugins/engine/models/FormModel.js +60 -2
  52. package/.server/server/plugins/engine/models/FormModel.js.map +1 -1
  53. package/.server/server/plugins/engine/models/SummaryViewModel.js +3 -2
  54. package/.server/server/plugins/engine/models/SummaryViewModel.js.map +1 -1
  55. package/.server/server/plugins/engine/outputFormatters/human/v1.js +1 -1
  56. package/.server/server/plugins/engine/outputFormatters/human/v1.js.map +1 -1
  57. package/.server/server/plugins/engine/pageControllers/PageController.js +13 -5
  58. package/.server/server/plugins/engine/pageControllers/PageController.js.map +1 -1
  59. package/.server/server/plugins/engine/pageControllers/QuestionPageController.js +2 -2
  60. package/.server/server/plugins/engine/pageControllers/QuestionPageController.js.map +1 -1
  61. package/.server/server/plugins/engine/pageControllers/SummaryPageController.js +19 -5
  62. package/.server/server/plugins/engine/pageControllers/SummaryPageController.js.map +1 -1
  63. package/.server/server/plugins/engine/pageControllers/validationOptions.js +6 -11
  64. package/.server/server/plugins/engine/pageControllers/validationOptions.js.map +1 -1
  65. package/.server/server/plugins/engine/plugin.js +5 -4
  66. package/.server/server/plugins/engine/plugin.js.map +1 -1
  67. package/.server/server/plugins/engine/services/notifyService.js +1 -4
  68. package/.server/server/plugins/engine/services/notifyService.js.map +1 -1
  69. package/.server/server/plugins/engine/services/uploadService.js +5 -3
  70. package/.server/server/plugins/engine/services/uploadService.js.map +1 -1
  71. package/.server/server/plugins/engine/types.js.map +1 -1
  72. package/.server/server/plugins/engine/views/components/html.html +1 -1
  73. package/.server/server/plugins/engine/views/components/markdown.html +5 -0
  74. package/.server/server/plugins/engine/views/summary.html +7 -1
  75. package/.server/server/plugins/nunjucks/context.js +6 -5
  76. package/.server/server/plugins/nunjucks/context.js.map +1 -1
  77. package/.server/server/plugins/nunjucks/enviroment.test.js +6 -3
  78. package/.server/server/plugins/nunjucks/enviroment.test.js.map +1 -1
  79. package/.server/server/utils/type-utils.js +8 -0
  80. package/.server/server/utils/type-utils.js.map +1 -0
  81. package/.server/typings/joi/index.d.js.map +1 -1
  82. package/package.json +3 -3
  83. package/src/client/javascripts/file-upload.js +60 -4
  84. package/src/server/constants.js +2 -0
  85. package/src/server/index.test.ts +34 -29
  86. package/src/server/index.ts +2 -1
  87. package/src/server/plugins/engine/components/AutocompleteField.test.ts +71 -3
  88. package/src/server/plugins/engine/components/AutocompleteField.ts +6 -2
  89. package/src/server/plugins/engine/components/CheckboxesField.test.ts +40 -8
  90. package/src/server/plugins/engine/components/CheckboxesField.ts +7 -3
  91. package/src/server/plugins/engine/components/ComponentCollection.ts +45 -18
  92. package/src/server/plugins/engine/components/DatePartsField.test.ts +13 -4
  93. package/src/server/plugins/engine/components/DatePartsField.ts +29 -8
  94. package/src/server/plugins/engine/components/EmailAddressField.test.ts +51 -1
  95. package/src/server/plugins/engine/components/EmailAddressField.ts +17 -2
  96. package/src/server/plugins/engine/components/FileUploadField.test.ts +53 -0
  97. package/src/server/plugins/engine/components/FileUploadField.ts +52 -3
  98. package/src/server/plugins/engine/components/FormComponent.ts +24 -2
  99. package/src/server/plugins/engine/components/ListFormComponent.ts +16 -2
  100. package/src/server/plugins/engine/components/Markdown.test.ts +48 -0
  101. package/src/server/plugins/engine/components/Markdown.ts +29 -0
  102. package/src/server/plugins/engine/components/MonthYearField.test.ts +35 -0
  103. package/src/server/plugins/engine/components/MonthYearField.ts +34 -9
  104. package/src/server/plugins/engine/components/MultilineTextField.test.ts +83 -5
  105. package/src/server/plugins/engine/components/MultilineTextField.ts +37 -2
  106. package/src/server/plugins/engine/components/NumberField.test.ts +24 -2
  107. package/src/server/plugins/engine/components/NumberField.ts +23 -3
  108. package/src/server/plugins/engine/components/RadiosField.test.ts +10 -1
  109. package/src/server/plugins/engine/components/SelectField.test.ts +2 -1
  110. package/src/server/plugins/engine/components/SelectionControlField.ts +14 -0
  111. package/src/server/plugins/engine/components/TelephoneNumberField.test.ts +30 -2
  112. package/src/server/plugins/engine/components/TelephoneNumberField.ts +17 -2
  113. package/src/server/plugins/engine/components/TextField.test.ts +33 -1
  114. package/src/server/plugins/engine/components/TextField.ts +17 -2
  115. package/src/server/plugins/engine/components/UkAddressField.test.ts +46 -3
  116. package/src/server/plugins/engine/components/UkAddressField.ts +28 -0
  117. package/src/server/plugins/engine/components/YesNoField.test.ts +9 -1
  118. package/src/server/plugins/engine/components/YesNoField.ts +24 -0
  119. package/src/server/plugins/engine/components/helpers.test.ts +24 -0
  120. package/src/server/plugins/engine/components/helpers.ts +39 -0
  121. package/src/server/plugins/engine/components/index.ts +1 -0
  122. package/src/server/plugins/engine/configureEnginePlugin.ts +13 -3
  123. package/src/server/plugins/engine/helpers.test.ts +71 -20
  124. package/src/server/plugins/engine/helpers.ts +46 -19
  125. package/src/server/plugins/engine/models/FormModel.test.ts +91 -1
  126. package/src/server/plugins/engine/models/FormModel.ts +86 -3
  127. package/src/server/plugins/engine/models/SummaryViewModel.test.ts +46 -7
  128. package/src/server/plugins/engine/models/SummaryViewModel.ts +7 -3
  129. package/src/server/plugins/engine/outputFormatters/human/v1.test.ts +1 -2
  130. package/src/server/plugins/engine/outputFormatters/human/v1.ts +1 -1
  131. package/src/server/plugins/engine/pageControllers/FileUploadPageController.test.ts +1 -0
  132. package/src/server/plugins/engine/pageControllers/PageController.test.ts +9 -6
  133. package/src/server/plugins/engine/pageControllers/PageController.ts +15 -5
  134. package/src/server/plugins/engine/pageControllers/QuestionPageController.ts +2 -2
  135. package/src/server/plugins/engine/pageControllers/SummaryPageController.ts +21 -6
  136. package/src/server/plugins/engine/pageControllers/validationOptions.ts +31 -17
  137. package/src/server/plugins/engine/plugin.ts +9 -5
  138. package/src/server/plugins/engine/services/notifyService.ts +1 -2
  139. package/src/server/plugins/engine/services/uploadService.js +10 -6
  140. package/src/server/plugins/engine/types.ts +10 -1
  141. package/src/server/plugins/engine/views/components/html.html +1 -1
  142. package/src/server/plugins/engine/views/components/markdown.html +5 -0
  143. package/src/server/plugins/engine/views/summary.html +7 -1
  144. package/src/server/plugins/nunjucks/context.js +4 -4
  145. package/src/server/plugins/nunjucks/enviroment.test.js +9 -3
  146. package/src/server/utils/type-utils.ts +15 -0
  147. package/src/typings/joi/index.d.ts +8 -0
@@ -1 +1 @@
1
- {"version":3,"file":"FormModel.js","names":["ComponentType","ConditionsModel","ControllerPath","ControllerType","Engine","formDefinitionSchema","hasComponents","hasRepeater","add","Parser","joi","findPage","getError","getPage","createPage","validationOptions","opts","defaultServices","FormAction","merge","FormModel","engine","def","lists","sections","name","values","basePath","conditions","pages","services","controllers","pageDefMap","listDefMap","componentDefMap","pageMap","componentMap","constructor","options","result","validate","abortEarly","error","structuredClone","value","push","title","type","items","text","forEach","conditionDef","condition","makeCondition","map","pageDef","some","controller","Status","path","Map","page","list","filter","flatMap","components","component","collection","makeSchema","makeFilteredSchema","relevantPages","schema","object","required","concat","stateSchema","parser","operators","logical","Object","assign","functions","dateForComparison","timePeriod","timeUnit","Date","toISOString","displayName","expr","toConditionExpression","fn","evaluationState","ctx","toConditionContext","evaluate","context","key","defineProperty","get","from","parse","toExpression","getList","find","getFormContext","request","state","errors","query","currentPath","startPath","getStartPath","isForceAccess","relevantState","payload","getFormDataFromState","paths","data","referenceNumber","getReferenceNumber","validateFormPayload","nextPage","initialiseContext","assignEvaluationState","assignRelevantState","getNextPath","validateFormState","assignPaths","V2","keys","getContextValueFromState","includes","action","getFormParams","Validate","update","fields","field","CheckboxesField","undefined","formState","getStateFromValidForm","previousPages","relevantPage","model","stripUnknown","errorsState","details","$$__referenceNumber","Error"],"sources":["../../../../../src/server/plugins/engine/models/FormModel.ts"],"sourcesContent":["import {\n ComponentType,\n ConditionsModel,\n ControllerPath,\n ControllerType,\n Engine,\n formDefinitionSchema,\n hasComponents,\n hasRepeater,\n type ComponentDef,\n type ConditionWrapper,\n type ConditionsModelData,\n type DateUnits,\n type FormDefinition,\n type List,\n type Page\n} from '@defra/forms-model'\nimport { add } from 'date-fns'\nimport { Parser, type Value } from 'expr-eval'\nimport joi from 'joi'\n\nimport { type Component } from '~/src/server/plugins/engine/components/helpers.js'\nimport {\n findPage,\n getError,\n getPage\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type ExecutableCondition } from '~/src/server/plugins/engine/models/types.js'\nimport { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport {\n createPage,\n type PageControllerClass\n} from '~/src/server/plugins/engine/pageControllers/helpers.js'\nimport { validationOptions as opts } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'\nimport * as defaultServices from '~/src/server/plugins/engine/services/index.js'\nimport {\n type FormContext,\n type FormContextRequest,\n type FormState,\n type FormSubmissionError,\n type FormSubmissionState\n} from '~/src/server/plugins/engine/types.js'\nimport { FormAction } from '~/src/server/routes/types.js'\nimport { merge } from '~/src/server/services/cacheService.js'\nimport { type Services } from '~/src/server/types.js'\n\nexport class FormModel {\n /** The runtime engine that should be used */\n engine?: Engine\n\n /** the entire form JSON as an object */\n def: FormDefinition\n\n lists: FormDefinition['lists']\n sections: FormDefinition['sections'] = []\n name: string\n values: FormDefinition\n basePath: string\n conditions: Partial<Record<string, ExecutableCondition>>\n pages: PageControllerClass[]\n services: Services\n\n controllers?: Record<string, typeof PageController>\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\n constructor(\n def: typeof this.def,\n options: { basePath: string },\n services: Services = defaultServices,\n controllers?: Record<string, typeof PageController>\n ) {\n const result = formDefinitionSchema.validate(def, { abortEarly: false })\n\n if (result.error) {\n throw result.error\n }\n\n // Make a clone of the shallow copy returned\n // by joi so as not to change the source data.\n def = structuredClone(result.value)\n\n // Add default lists\n def.lists.push({\n name: '__yesNo',\n title: 'Yes/No',\n type: 'boolean',\n items: [\n {\n text: 'Yes',\n value: true\n },\n {\n text: 'No',\n value: false\n }\n ]\n })\n\n this.engine = def.engine\n this.def = def\n this.lists = def.lists\n this.sections = def.sections\n this.name = def.name ?? ''\n this.values = result.value\n this.basePath = options.basePath\n this.conditions = {}\n this.services = services\n this.controllers = controllers\n\n def.conditions.forEach((conditionDef) => {\n const condition = this.makeCondition(conditionDef)\n this.conditions[condition.name] = condition\n })\n\n this.pages = def.pages.map((pageDef) => createPage(this, pageDef))\n\n if (\n !def.pages.some(\n ({ controller }) =>\n // Check for user-provided status page (optional)\n controller === ControllerType.Status\n )\n ) {\n this.pages.push(\n createPage(this, {\n title: 'Form submitted',\n path: ControllerPath.Status,\n controller: ControllerType.Status\n })\n )\n }\n\n this.pageDefMap = new Map(def.pages.map((page) => [page.path, page]))\n this.listDefMap = new Map(def.lists.map((list) => [list.name, list]))\n this.componentDefMap = new Map(\n def.pages\n .filter(hasComponents)\n .flatMap((page) =>\n page.components.map((component) => [component.name, component])\n )\n )\n\n this.pageMap = new Map(this.pages.map((page) => [page.path, page]))\n this.componentMap = new Map(\n this.pages.flatMap((page) =>\n page.collection.components.map((component) => [\n component.name,\n component\n ])\n )\n )\n }\n\n /**\n * build the entire model schema from individual pages/sections\n */\n makeSchema() {\n return this.makeFilteredSchema(this.pages)\n }\n\n /**\n * build the entire model schema from individual pages/sections and filter out answers\n * for pages which are no longer accessible due to an answer that has been changed\n */\n makeFilteredSchema(relevantPages: PageControllerClass[]) {\n // Build the entire model schema\n // from the individual pages/sections\n let schema = joi.object<FormSubmissionState>().required()\n\n relevantPages.forEach((page) => {\n schema = schema.concat(page.collection.stateSchema)\n })\n\n return schema\n }\n\n /**\n * Instantiates a Condition based on {@link ConditionWrapper}\n * @param condition\n */\n makeCondition(condition: ConditionWrapper): ExecutableCondition {\n const parser = new Parser({\n operators: {\n logical: true\n }\n })\n\n Object.assign(parser.functions, {\n dateForComparison(timePeriod: number, timeUnit: DateUnits) {\n return add(new Date(), { [timeUnit]: timePeriod }).toISOString()\n }\n })\n\n const { name, displayName, value } = condition\n const expr = this.toConditionExpression(value, parser)\n\n const fn = (evaluationState: FormState) => {\n const ctx = this.toConditionContext(evaluationState, this.conditions)\n try {\n return expr.evaluate(ctx) as boolean\n } catch {\n return false\n }\n }\n\n return {\n name,\n displayName,\n value,\n expr,\n fn\n }\n }\n\n toConditionContext(\n evaluationState: FormState,\n conditions: Partial<Record<string, ExecutableCondition>>\n ) {\n const context = { ...evaluationState }\n\n for (const key in conditions) {\n Object.defineProperty(context, key, {\n get() {\n return conditions[key]?.fn(evaluationState)\n }\n })\n }\n\n return context as Extract<Value, Record<string, Value>>\n }\n\n toConditionExpression(value: ConditionsModelData, parser: Parser) {\n const conditions = ConditionsModel.from(value)\n return parser.parse(conditions.toExpression())\n }\n\n getList(name: string): List | undefined {\n return this.lists.find((list) => list.name === name)\n }\n\n /**\n * Form context for the current page\n */\n getFormContext(\n request: FormContextRequest,\n state: FormState,\n errors?: FormSubmissionError[]\n ): FormContext {\n const { query } = request\n\n const page = getPage(this, request)\n\n // Determine form paths\n const currentPath = page.path\n const startPath = page.getStartPath()\n\n // Preview URL direct access is allowed\n const isForceAccess = 'force' in query\n\n let context: FormContext = {\n evaluationState: {},\n relevantState: {},\n relevantPages: [],\n payload: page.getFormDataFromState(request, state),\n state,\n paths: [],\n errors,\n isForceAccess,\n data: {},\n pageDefMap: this.pageDefMap,\n listDefMap: this.listDefMap,\n componentDefMap: this.componentDefMap,\n pageMap: this.pageMap,\n componentMap: this.componentMap,\n referenceNumber: getReferenceNumber(state)\n }\n\n // Validate current page\n context = validateFormPayload(request, page, context)\n\n // Find start page\n let nextPage = findPage(this, startPath)\n\n this.initialiseContext(context)\n\n // Walk form pages from start\n while (nextPage) {\n // Add page to context\n context.relevantPages.push(nextPage)\n\n this.assignEvaluationState(context, nextPage)\n\n this.assignRelevantState(context, nextPage)\n\n // Stop at current page\n if (nextPage.path === currentPath) {\n break\n }\n\n // Apply conditions to determine next page\n nextPage = findPage(this, nextPage.getNextPath(context))\n }\n\n // Validate form state\n context = validateFormState(request, page, context)\n\n // Add paths for navigation\n this.assignPaths(context)\n\n return context\n }\n\n private initialiseContext(context: FormContext) {\n // For the V2 engine, we need to initialise `evaluationState` to null\n // for all keys. This is because the current condition evaluation\n // library (eval-expr) will throw if an expression uses a key that is undefined.\n if (this.engine === Engine.V2) {\n for (const page of this.pages) {\n for (const key of page.keys) {\n context.evaluationState[key] = null\n }\n }\n }\n }\n\n private assignEvaluationState(\n context: FormContext,\n page: PageControllerClass\n ) {\n const { collection, pageDef } = page\n // Skip evaluation state for repeater pages\n\n if (!hasRepeater(pageDef)) {\n Object.assign(\n context.evaluationState,\n collection.getContextValueFromState(context.state)\n )\n }\n }\n\n private assignRelevantState(context: FormContext, page: PageControllerClass) {\n // Copy relevant state by expected keys\n for (const key of page.keys) {\n if (typeof context.state[key] !== 'undefined') {\n context.relevantState[key] = context.state[key]\n }\n }\n }\n\n private assignPaths(context: FormContext) {\n for (const { keys, path } of context.relevantPages) {\n context.paths.push(path)\n\n // Stop at page with errors\n if (\n context.errors?.some(({ name, path }) => {\n return keys.includes(name) || keys.some((key) => path.includes(key))\n })\n ) {\n break\n }\n }\n }\n}\n\n/**\n * Validate current page only\n */\nfunction validateFormPayload(\n request: FormContextRequest,\n page: PageControllerClass,\n context: FormContext\n): FormContext {\n const { collection } = page\n const { payload, state } = context\n\n const { action } = page.getFormParams(request)\n\n // Skip validation GET requests or other actions\n if (!request.payload || action !== FormAction.Validate) {\n return context\n }\n\n // For checkbox fields missing in the payload (i.e. unchecked),\n // explicitly set their value to undefined so that any previously\n // stored value is cleared and required field validation is enforced.\n const update = { ...request.payload }\n collection.fields.forEach((field) => {\n if (\n field.type === ComponentType.CheckboxesField &&\n !(field.name in update)\n ) {\n update[field.name] = undefined\n }\n })\n\n const { value, errors } = collection.validate({\n ...payload,\n ...update\n })\n\n // Add sanitised payload (ready to save)\n const formState = page.getStateFromValidForm(request, state, value)\n\n return {\n ...context,\n payload: merge(payload, value),\n state: merge(state, formState),\n errors\n }\n}\n\n/**\n * Validate entire form state\n */\nfunction validateFormState(\n request: FormContextRequest,\n page: PageControllerClass,\n context: FormContext\n): FormContext {\n const { errors = [], relevantPages, relevantState } = context\n\n // Exclude current page\n const previousPages = relevantPages.filter(\n (relevantPage) => relevantPage !== page\n )\n\n // Validate relevant state\n const { error } = page.model\n .makeFilteredSchema(previousPages)\n .validate(relevantState, { ...opts, stripUnknown: true })\n\n // Add relevant state errors\n if (error) {\n const errorsState = error.details.map(getError)\n return { ...context, errors: errors.concat(errorsState) }\n }\n\n return context\n}\n\nfunction getReferenceNumber(state: FormState): string {\n if (\n !state.$$__referenceNumber ||\n typeof state.$$__referenceNumber !== 'string'\n ) {\n throw Error('Reference number not found in form state')\n }\n\n return state.$$__referenceNumber\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"FormModel.js","names":["ComponentType","ConditionsModel","ControllerPath","ControllerType","Engine","formDefinitionSchema","hasComponents","hasRepeater","add","Parser","joi","hasListFormField","findPage","getError","getPage","setPageTitles","createPage","validationOptions","opts","defaultServices","FormAction","merge","FormModel","engine","def","lists","sections","name","values","basePath","conditions","pages","services","controllers","pageDefMap","listDefMap","componentDefMap","pageMap","componentMap","constructor","options","result","validate","abortEarly","error","structuredClone","value","push","title","type","items","text","forEach","conditionDef","condition","makeCondition","map","pageDef","some","controller","Status","path","Map","page","list","filter","flatMap","components","component","collection","makeSchema","makeFilteredSchema","relevantPages","schema","object","required","concat","stateSchema","parser","operators","logical","Object","assign","functions","dateForComparison","timePeriod","timeUnit","Date","toISOString","displayName","expr","toConditionExpression","fn","evaluationState","ctx","toConditionContext","evaluate","context","key","defineProperty","get","from","parse","toExpression","getList","find","getFormContext","request","state","errors","query","currentPath","startPath","getStartPath","isForceAccess","relevantState","payload","getFormDataFromState","paths","data","referenceNumber","getReferenceNumber","validateFormPayload","nextPage","initialiseContext","assignEvaluationState","assignRelevantState","pageStateIsInvalid","getNextPath","validateFormState","assignPaths","V2","keys","getContextValueFromState","listFields","fields","field","undefined","YesNoField","hasOptionalItems","item","length","fieldStateIsInvalid","validValues","fieldState","getFormValueFromState","isInvalid","isArray","Array","every","includes","href","action","getFormParams","Validate","update","CheckboxesField","formState","getStateFromValidForm","previousPages","relevantPage","model","stripUnknown","errorsState","details","$$__referenceNumber","Error"],"sources":["../../../../../src/server/plugins/engine/models/FormModel.ts"],"sourcesContent":["import {\n ComponentType,\n ConditionsModel,\n ControllerPath,\n ControllerType,\n Engine,\n formDefinitionSchema,\n hasComponents,\n hasRepeater,\n type ComponentDef,\n type ConditionWrapper,\n type ConditionsModelData,\n type DateUnits,\n type FormDefinition,\n type List,\n type Page\n} from '@defra/forms-model'\nimport { add } from 'date-fns'\nimport { Parser, type Value } from 'expr-eval'\nimport joi from 'joi'\n\nimport { type ListFormComponent } from '~/src/server/plugins/engine/components/ListFormComponent.js'\nimport {\n hasListFormField,\n type Component\n} from '~/src/server/plugins/engine/components/helpers.js'\nimport {\n findPage,\n getError,\n getPage,\n setPageTitles\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type ExecutableCondition } from '~/src/server/plugins/engine/models/types.js'\nimport { type PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport {\n createPage,\n type PageControllerClass\n} from '~/src/server/plugins/engine/pageControllers/helpers.js'\nimport { validationOptions as opts } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'\nimport * as defaultServices from '~/src/server/plugins/engine/services/index.js'\nimport {\n type FormContext,\n type FormContextRequest,\n type FormState,\n type FormSubmissionError,\n type FormSubmissionState\n} from '~/src/server/plugins/engine/types.js'\nimport { FormAction } from '~/src/server/routes/types.js'\nimport { merge } from '~/src/server/services/cacheService.js'\nimport { type Services } from '~/src/server/types.js'\n\nexport class FormModel {\n /** The runtime engine that should be used */\n engine?: Engine\n\n /** the entire form JSON as an object */\n def: FormDefinition\n\n lists: FormDefinition['lists']\n sections: FormDefinition['sections'] = []\n name: string\n values: FormDefinition\n basePath: string\n conditions: Partial<Record<string, ExecutableCondition>>\n pages: PageControllerClass[]\n services: Services\n\n controllers?: Record<string, typeof PageController>\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\n constructor(\n def: typeof this.def,\n options: { basePath: string },\n services: Services = defaultServices,\n controllers?: Record<string, typeof PageController>\n ) {\n const result = formDefinitionSchema.validate(def, { abortEarly: false })\n\n if (result.error) {\n throw result.error\n }\n\n // Make a clone of the shallow copy returned\n // by joi so as not to change the source data.\n def = structuredClone(result.value)\n\n // Add default lists\n def.lists.push({\n name: '__yesNo',\n title: 'Yes/No',\n type: 'boolean',\n items: [\n {\n text: 'Yes',\n value: true\n },\n {\n text: 'No',\n value: false\n }\n ]\n })\n\n // Fix up page titles\n setPageTitles(def)\n\n this.engine = def.engine\n this.def = def\n this.lists = def.lists\n this.sections = def.sections\n this.name = def.name ?? ''\n this.values = result.value\n this.basePath = options.basePath\n this.conditions = {}\n this.services = services\n this.controllers = controllers\n\n def.conditions.forEach((conditionDef) => {\n const condition = this.makeCondition(conditionDef)\n this.conditions[condition.name] = condition\n })\n\n this.pages = def.pages.map((pageDef) => createPage(this, pageDef))\n\n if (\n !def.pages.some(\n ({ controller }) =>\n // Check for user-provided status page (optional)\n controller === ControllerType.Status\n )\n ) {\n this.pages.push(\n createPage(this, {\n title: 'Form submitted',\n path: ControllerPath.Status,\n controller: ControllerType.Status\n })\n )\n }\n\n this.pageDefMap = new Map(def.pages.map((page) => [page.path, page]))\n this.listDefMap = new Map(def.lists.map((list) => [list.name, list]))\n this.componentDefMap = new Map(\n def.pages\n .filter(hasComponents)\n .flatMap((page) =>\n page.components.map((component) => [component.name, component])\n )\n )\n\n this.pageMap = new Map(this.pages.map((page) => [page.path, page]))\n this.componentMap = new Map(\n this.pages.flatMap((page) =>\n page.collection.components.map((component) => [\n component.name,\n component\n ])\n )\n )\n }\n\n /**\n * build the entire model schema from individual pages/sections\n */\n makeSchema() {\n return this.makeFilteredSchema(this.pages)\n }\n\n /**\n * build the entire model schema from individual pages/sections and filter out answers\n * for pages which are no longer accessible due to an answer that has been changed\n */\n makeFilteredSchema(relevantPages: PageControllerClass[]) {\n // Build the entire model schema\n // from the individual pages/sections\n let schema = joi.object<FormSubmissionState>().required()\n\n relevantPages.forEach((page) => {\n schema = schema.concat(page.collection.stateSchema)\n })\n\n return schema\n }\n\n /**\n * Instantiates a Condition based on {@link ConditionWrapper}\n * @param condition\n */\n makeCondition(condition: ConditionWrapper): ExecutableCondition {\n const parser = new Parser({\n operators: {\n logical: true\n }\n })\n\n Object.assign(parser.functions, {\n dateForComparison(timePeriod: number, timeUnit: DateUnits) {\n return add(new Date(), { [timeUnit]: timePeriod }).toISOString()\n }\n })\n\n const { name, displayName, value } = condition\n const expr = this.toConditionExpression(value, parser)\n\n const fn = (evaluationState: FormState) => {\n const ctx = this.toConditionContext(evaluationState, this.conditions)\n try {\n return expr.evaluate(ctx) as boolean\n } catch {\n return false\n }\n }\n\n return {\n name,\n displayName,\n value,\n expr,\n fn\n }\n }\n\n toConditionContext(\n evaluationState: FormState,\n conditions: Partial<Record<string, ExecutableCondition>>\n ) {\n const context = { ...evaluationState }\n\n for (const key in conditions) {\n Object.defineProperty(context, key, {\n get() {\n return conditions[key]?.fn(evaluationState)\n }\n })\n }\n\n return context as Extract<Value, Record<string, Value>>\n }\n\n toConditionExpression(value: ConditionsModelData, parser: Parser) {\n const conditions = ConditionsModel.from(value)\n return parser.parse(conditions.toExpression())\n }\n\n getList(name: string): List | undefined {\n return this.lists.find((list) => list.name === name)\n }\n\n /**\n * Form context for the current page\n */\n getFormContext(\n request: FormContextRequest,\n state: FormState,\n errors?: FormSubmissionError[]\n ): FormContext {\n const { query } = request\n\n const page = getPage(this, request)\n\n // Determine form paths\n const currentPath = page.path\n const startPath = page.getStartPath()\n\n // Preview URL direct access is allowed\n const isForceAccess = 'force' in query\n\n let context: FormContext = {\n evaluationState: {},\n relevantState: {},\n relevantPages: [],\n payload: page.getFormDataFromState(request, state),\n state,\n paths: [],\n errors,\n isForceAccess,\n data: {},\n pageDefMap: this.pageDefMap,\n listDefMap: this.listDefMap,\n componentDefMap: this.componentDefMap,\n pageMap: this.pageMap,\n componentMap: this.componentMap,\n referenceNumber: getReferenceNumber(state)\n }\n\n // Validate current page\n context = validateFormPayload(request, page, context)\n\n // Find start page\n let nextPage = findPage(this, startPath)\n\n this.initialiseContext(context)\n\n // Walk form pages from start\n while (nextPage) {\n // Add page to context\n context.relevantPages.push(nextPage)\n\n this.assignEvaluationState(context, nextPage)\n\n this.assignRelevantState(context, nextPage)\n\n // Stop at current page\n if (\n this.pageStateIsInvalid(context, nextPage) ||\n nextPage.path === currentPath\n ) {\n break\n }\n\n // Apply conditions to determine next page\n nextPage = findPage(this, nextPage.getNextPath(context))\n }\n\n // Validate form state\n context = validateFormState(request, page, context)\n\n // Add paths for navigation\n this.assignPaths(context)\n\n return context\n }\n\n private initialiseContext(context: FormContext) {\n // For the V2 engine, we need to initialise `evaluationState` to null\n // for all keys. This is because the current condition evaluation\n // library (eval-expr) will throw if an expression uses a key that is undefined.\n if (this.engine === Engine.V2) {\n for (const page of this.pages) {\n for (const key of page.keys) {\n context.evaluationState[key] = null\n }\n }\n }\n }\n\n private assignEvaluationState(\n context: FormContext,\n page: PageControllerClass\n ) {\n const { collection, pageDef } = page\n // Skip evaluation state for repeater pages\n\n if (!hasRepeater(pageDef)) {\n Object.assign(\n context.evaluationState,\n collection.getContextValueFromState(context.state)\n )\n }\n }\n\n private assignRelevantState(context: FormContext, page: PageControllerClass) {\n // Copy relevant state by expected keys\n for (const key of page.keys) {\n if (typeof context.state[key] !== 'undefined') {\n context.relevantState[key] = context.state[key]\n }\n }\n }\n\n private pageStateIsInvalid(context: FormContext, page: PageControllerClass) {\n // Get any list-bound fields on the page\n const listFields = page.collection.fields.filter(hasListFormField)\n\n // For each list field that is bound to a list that contains any conditional items,\n // we need to check any answers are still valid. Do this by evaluating the conditions\n // and ensuring any current answers are all included in the set of valid answers\n for (const field of listFields) {\n const list = field.list\n\n // Filter out YesNo as they can't be conditional\n if (list !== undefined && field.type !== ComponentType.YesNoField) {\n const hasOptionalItems =\n list.items.filter((item) => item.condition).length > 0\n\n if (hasOptionalItems) {\n return this.fieldStateIsInvalid(context, field, list)\n }\n }\n }\n }\n\n private fieldStateIsInvalid(\n context: FormContext,\n field: ListFormComponent,\n list: List\n ) {\n const { evaluationState, state } = context\n\n const validValues = list.items\n .filter((item) =>\n item.condition\n ? this.conditions[item.condition]?.fn(evaluationState)\n : true\n )\n .map((item) => item.value)\n\n // Get the field state\n const fieldState = field.getFormValueFromState(state)\n\n if (fieldState !== undefined) {\n let isInvalid = false\n const isArray = Array.isArray(fieldState)\n\n // Check if any saved state value(s) are still valid\n // and return true if any are invalid\n if (isArray) {\n isInvalid = !fieldState.every((item) => validValues.includes(item))\n } else {\n isInvalid = !validValues.includes(fieldState)\n }\n\n if (isInvalid) {\n if (!context.errors) {\n context.errors = []\n }\n\n const text =\n 'Options are different because you changed a previous answer'\n\n context.errors.push({\n text,\n name: field.name,\n href: `#${field.name}`,\n path: [`#${field.name}`]\n })\n }\n\n return isInvalid\n }\n }\n\n private assignPaths(context: FormContext) {\n for (const { keys, path } of context.relevantPages) {\n context.paths.push(path)\n\n // Stop at page with errors\n if (\n context.errors?.some(({ name, path }) => {\n return keys.includes(name) || keys.some((key) => path.includes(key))\n })\n ) {\n break\n }\n }\n }\n}\n\n/**\n * Validate current page only\n */\nfunction validateFormPayload(\n request: FormContextRequest,\n page: PageControllerClass,\n context: FormContext\n): FormContext {\n const { collection } = page\n const { payload, state } = context\n\n const { action } = page.getFormParams(request)\n\n // Skip validation GET requests or other actions\n if (!request.payload || action !== FormAction.Validate) {\n return context\n }\n\n // For checkbox fields missing in the payload (i.e. unchecked),\n // explicitly set their value to undefined so that any previously\n // stored value is cleared and required field validation is enforced.\n const update = { ...request.payload }\n collection.fields.forEach((field) => {\n if (\n field.type === ComponentType.CheckboxesField &&\n !(field.name in update)\n ) {\n update[field.name] = undefined\n }\n })\n\n const { value, errors } = collection.validate({\n ...payload,\n ...update\n })\n\n // Add sanitised payload (ready to save)\n const formState = page.getStateFromValidForm(request, state, value)\n\n return {\n ...context,\n payload: merge(payload, value),\n state: merge(state, formState),\n errors\n }\n}\n\n/**\n * Validate entire form state\n */\nfunction validateFormState(\n request: FormContextRequest,\n page: PageControllerClass,\n context: FormContext\n): FormContext {\n const { errors = [], relevantPages, relevantState } = context\n\n // Exclude current page\n const previousPages = relevantPages.filter(\n (relevantPage) => relevantPage !== page\n )\n\n // Validate relevant state\n const { error } = page.model\n .makeFilteredSchema(previousPages)\n .validate(relevantState, { ...opts, stripUnknown: true })\n\n // Add relevant state errors\n if (error) {\n const errorsState = error.details.map(getError)\n return { ...context, errors: errors.concat(errorsState) }\n }\n\n return context\n}\n\nfunction getReferenceNumber(state: FormState): string {\n if (\n !state.$$__referenceNumber ||\n typeof state.$$__referenceNumber !== 'string'\n ) {\n throw Error('Reference number not found in form state')\n }\n\n return state.$$__referenceNumber\n}\n"],"mappings":"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","ignoreList":[]}
@@ -20,6 +20,7 @@ export class SummaryViewModel {
20
20
  errors;
21
21
  serviceUrl;
22
22
  hasMissingNotificationEmail;
23
+ components;
23
24
  constructor(request, page, context) {
24
25
  const {
25
26
  model
@@ -169,8 +170,8 @@ function ItemField(page, state, field, options) {
169
170
  return {
170
171
  name: field.name,
171
172
  label: field.title,
172
- title: field.title,
173
- error: field.getError(options.errors),
173
+ title: field.label,
174
+ error: field.getFirstError(options.errors),
174
175
  value: getAnswer(field, state),
175
176
  href: getPageHref(page, options.path, {
176
177
  returnUrl: getPageHref(page, page.getSummaryPath())
@@ -1 +1 @@
1
- {"version":3,"file":"SummaryViewModel.js","names":["getAnswer","evaluateTemplate","getError","getPageHref","RepeatPageController","validationOptions","opts","SummaryViewModel","page","pageTitle","declaration","details","checkAnswers","context","name","backLink","feedbackLink","phaseTag","errors","serviceUrl","hasMissingNotificationEmail","constructor","request","model","basePath","def","sections","isForceAccess","title","result","makeFilteredSchema","relevantPages","validate","relevantState","stripUnknown","error","map","summaryDetails","detail","rows","items","item","push","href","text","classes","visuallyHiddenText","label","key","value","html","actions","undefined","summaryList","state","forEach","section","sectionPages","filter","collection","path","ItemRepeat","getSummaryPath","field","fields","ItemField","length","options","repeat","values","getListFromState","unit","returnUrl","subItems","repeatState"],"sources":["../../../../../src/server/plugins/engine/models/SummaryViewModel.ts"],"sourcesContent":["import { type Section } from '@defra/forms-model'\n\nimport {\n getAnswer,\n type Field\n} from '~/src/server/plugins/engine/components/helpers.js'\nimport { type BackLink } from '~/src/server/plugins/engine/components/types.js'\nimport {\n evaluateTemplate,\n getError,\n getPageHref\n} from '~/src/server/plugins/engine/helpers.js'\nimport {\n type Detail,\n type DetailItem,\n type DetailItemField,\n type DetailItemRepeat\n} from '~/src/server/plugins/engine/models/types.js'\nimport { RepeatPageController } from '~/src/server/plugins/engine/pageControllers/RepeatPageController.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers.js'\nimport { validationOptions as opts } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'\nimport {\n type CheckAnswers,\n type FormContext,\n type FormContextRequest,\n type FormState,\n type FormSubmissionError,\n type SummaryListAction,\n type SummaryListRow\n} from '~/src/server/plugins/engine/types.js'\n\nexport class SummaryViewModel {\n /**\n * Responsible for parsing state values to the govuk-frontend summary list template\n */\n\n page: PageControllerClass\n pageTitle: string\n declaration?: string\n details: Detail[]\n checkAnswers: CheckAnswers[]\n context: FormContext\n name: string | undefined\n backLink?: BackLink\n feedbackLink?: string\n phaseTag?: string\n errors?: FormSubmissionError[]\n serviceUrl: string\n hasMissingNotificationEmail?: boolean\n\n constructor(\n request: FormContextRequest,\n page: PageControllerClass,\n context: FormContext\n ) {\n const { model } = page\n const { basePath, def, sections } = model\n const { isForceAccess } = context\n\n this.page = page\n this.pageTitle = page.title\n this.serviceUrl = `/${basePath}`\n this.name = def.name\n this.declaration = def.declaration\n this.context = context\n\n const result = model\n .makeFilteredSchema(this.context.relevantPages)\n .validate(this.context.relevantState, { ...opts, stripUnknown: true })\n\n // Format errors\n this.errors = result.error?.details.map(getError)\n this.details = this.summaryDetails(request, sections)\n\n // Format check answers\n this.checkAnswers = this.details.map((detail): CheckAnswers => {\n const { title } = detail\n\n const rows = detail.items.map((item): SummaryListRow => {\n const items: SummaryListAction[] = []\n\n // Remove summary list actions from previews\n if (!isForceAccess) {\n items.push({\n href: item.href,\n text: 'Change',\n classes: 'govuk-link--no-visited-state',\n visuallyHiddenText: item.label\n })\n }\n\n return {\n key: {\n text: evaluateTemplate(item.title, context)\n },\n value: {\n classes: 'app-prose-scope',\n html: item.value || 'Not supplied'\n },\n actions: {\n items\n }\n }\n })\n\n return {\n title: title ? { text: title } : undefined,\n summaryList: { rows }\n }\n })\n }\n\n private summaryDetails(request: FormContextRequest, sections: Section[]) {\n const { context, errors } = this\n const { relevantPages, state } = context\n\n const details: Detail[] = []\n\n ;[undefined, ...sections].forEach((section) => {\n const items: DetailItem[] = []\n\n const sectionPages = relevantPages.filter(\n (page) => page.section === section\n )\n\n sectionPages.forEach((page) => {\n const { collection, path } = page\n\n if (page instanceof RepeatPageController) {\n items.push(\n ItemRepeat(page, state, {\n path: page.getSummaryPath(request),\n errors\n })\n )\n } else {\n for (const field of collection.fields) {\n items.push(ItemField(page, state, field, { path, errors }))\n }\n }\n })\n\n if (items.length) {\n details.push({\n name: section?.name,\n title: section?.title,\n items\n })\n }\n })\n\n return details\n }\n}\n\n/**\n * Creates a repeater detail item\n * @see {@link DetailItemField}\n */\nfunction ItemRepeat(\n page: RepeatPageController,\n state: FormState,\n options: {\n path: string\n errors?: FormSubmissionError[]\n }\n): DetailItemRepeat {\n const { collection, repeat } = page\n const { name, title } = repeat.options\n\n const values = page.getListFromState(state)\n const unit = values.length === 1 ? title : `${title}s`\n\n return {\n name,\n label: title,\n title: values.length ? `${unit} added` : unit,\n value: values.length ? `You added ${values.length} ${unit}` : '',\n href: getPageHref(page, options.path, {\n returnUrl: getPageHref(page, page.getSummaryPath())\n }),\n state,\n page,\n\n // Repeater field detail items\n subItems: values.map((repeatState) =>\n collection.fields.map((field) =>\n ItemField(page, repeatState, field, options)\n )\n )\n }\n}\n\n/**\n * Creates a form field detail item\n * @see {@link DetailItemField}\n */\nfunction ItemField(\n page: PageControllerClass,\n state: FormState,\n field: Field,\n options: {\n path: string\n errors?: FormSubmissionError[]\n }\n): DetailItemField {\n return {\n name: field.name,\n label: field.title,\n title: field.title,\n error: field.getError(options.errors),\n value: getAnswer(field, state),\n href: getPageHref(page, options.path, {\n returnUrl: getPageHref(page, page.getSummaryPath())\n }),\n state,\n page,\n field\n }\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"SummaryViewModel.js","names":["getAnswer","evaluateTemplate","getError","getPageHref","RepeatPageController","validationOptions","opts","SummaryViewModel","page","pageTitle","declaration","details","checkAnswers","context","name","backLink","feedbackLink","phaseTag","errors","serviceUrl","hasMissingNotificationEmail","components","constructor","request","model","basePath","def","sections","isForceAccess","title","result","makeFilteredSchema","relevantPages","validate","relevantState","stripUnknown","error","map","summaryDetails","detail","rows","items","item","push","href","text","classes","visuallyHiddenText","label","key","value","html","actions","undefined","summaryList","state","forEach","section","sectionPages","filter","collection","path","ItemRepeat","getSummaryPath","field","fields","ItemField","length","options","repeat","values","getListFromState","unit","returnUrl","subItems","repeatState","getFirstError"],"sources":["../../../../../src/server/plugins/engine/models/SummaryViewModel.ts"],"sourcesContent":["import { type Section } from '@defra/forms-model'\n\nimport {\n getAnswer,\n type Field\n} from '~/src/server/plugins/engine/components/helpers.js'\nimport {\n type BackLink,\n type ComponentViewModel\n} from '~/src/server/plugins/engine/components/types.js'\nimport {\n evaluateTemplate,\n getError,\n getPageHref\n} from '~/src/server/plugins/engine/helpers.js'\nimport {\n type Detail,\n type DetailItem,\n type DetailItemField,\n type DetailItemRepeat\n} from '~/src/server/plugins/engine/models/types.js'\nimport { RepeatPageController } from '~/src/server/plugins/engine/pageControllers/RepeatPageController.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers.js'\nimport { validationOptions as opts } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'\nimport {\n type CheckAnswers,\n type FormContext,\n type FormContextRequest,\n type FormState,\n type FormSubmissionError,\n type SummaryListAction,\n type SummaryListRow\n} from '~/src/server/plugins/engine/types.js'\n\nexport class SummaryViewModel {\n /**\n * Responsible for parsing state values to the govuk-frontend summary list template\n */\n\n page: PageControllerClass\n pageTitle: string\n declaration?: string\n details: Detail[]\n checkAnswers: CheckAnswers[]\n context: FormContext\n name: string | undefined\n backLink?: BackLink\n feedbackLink?: string\n phaseTag?: string\n errors?: FormSubmissionError[]\n serviceUrl: string\n hasMissingNotificationEmail?: boolean\n components?: ComponentViewModel[]\n\n constructor(\n request: FormContextRequest,\n page: PageControllerClass,\n context: FormContext\n ) {\n const { model } = page\n const { basePath, def, sections } = model\n const { isForceAccess } = context\n\n this.page = page\n this.pageTitle = page.title\n this.serviceUrl = `/${basePath}`\n this.name = def.name\n this.declaration = def.declaration\n this.context = context\n\n const result = model\n .makeFilteredSchema(this.context.relevantPages)\n .validate(this.context.relevantState, { ...opts, stripUnknown: true })\n\n // Format errors\n this.errors = result.error?.details.map(getError)\n this.details = this.summaryDetails(request, sections)\n\n // Format check answers\n this.checkAnswers = this.details.map((detail): CheckAnswers => {\n const { title } = detail\n\n const rows = detail.items.map((item): SummaryListRow => {\n const items: SummaryListAction[] = []\n\n // Remove summary list actions from previews\n if (!isForceAccess) {\n items.push({\n href: item.href,\n text: 'Change',\n classes: 'govuk-link--no-visited-state',\n visuallyHiddenText: item.label\n })\n }\n\n return {\n key: {\n text: evaluateTemplate(item.title, context)\n },\n value: {\n classes: 'app-prose-scope',\n html: item.value || 'Not supplied'\n },\n actions: {\n items\n }\n }\n })\n\n return {\n title: title ? { text: title } : undefined,\n summaryList: { rows }\n }\n })\n }\n\n private summaryDetails(request: FormContextRequest, sections: Section[]) {\n const { context, errors } = this\n const { relevantPages, state } = context\n\n const details: Detail[] = []\n\n ;[undefined, ...sections].forEach((section) => {\n const items: DetailItem[] = []\n\n const sectionPages = relevantPages.filter(\n (page) => page.section === section\n )\n\n sectionPages.forEach((page) => {\n const { collection, path } = page\n\n if (page instanceof RepeatPageController) {\n items.push(\n ItemRepeat(page, state, {\n path: page.getSummaryPath(request),\n errors\n })\n )\n } else {\n for (const field of collection.fields) {\n items.push(ItemField(page, state, field, { path, errors }))\n }\n }\n })\n\n if (items.length) {\n details.push({\n name: section?.name,\n title: section?.title,\n items\n })\n }\n })\n\n return details\n }\n}\n\n/**\n * Creates a repeater detail item\n * @see {@link DetailItemField}\n */\nfunction ItemRepeat(\n page: RepeatPageController,\n state: FormState,\n options: {\n path: string\n errors?: FormSubmissionError[]\n }\n): DetailItemRepeat {\n const { collection, repeat } = page\n const { name, title } = repeat.options\n\n const values = page.getListFromState(state)\n const unit = values.length === 1 ? title : `${title}s`\n\n return {\n name,\n label: title,\n title: values.length ? `${unit} added` : unit,\n value: values.length ? `You added ${values.length} ${unit}` : '',\n href: getPageHref(page, options.path, {\n returnUrl: getPageHref(page, page.getSummaryPath())\n }),\n state,\n page,\n\n // Repeater field detail items\n subItems: values.map((repeatState) =>\n collection.fields.map((field) =>\n ItemField(page, repeatState, field, options)\n )\n )\n }\n}\n\n/**\n * Creates a form field detail item\n * @see {@link DetailItemField}\n */\nfunction ItemField(\n page: PageControllerClass,\n state: FormState,\n field: Field,\n options: {\n path: string\n errors?: FormSubmissionError[]\n }\n): DetailItemField {\n return {\n name: field.name,\n label: field.title,\n title: field.label,\n error: field.getFirstError(options.errors),\n value: getAnswer(field, state),\n href: getPageHref(page, options.path, {\n returnUrl: getPageHref(page, page.getSummaryPath())\n }),\n state,\n page,\n field\n }\n}\n"],"mappings":"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","ignoreList":[]}
@@ -21,7 +21,7 @@ export function format(items, model, submitResponse, formStatus) {
21
21
  if (formStatus.isPreview) {
22
22
  lines.push(`This is a test of the ${formName} ${formStatus.state} form.\n`);
23
23
  }
24
- lines.push(`Form submitted at ${escapeMarkdown(formattedNow)}.\n`);
24
+ lines.push(`${formName} form received at ${escapeMarkdown(formattedNow)}.\n`);
25
25
  lines.push('---\n');
26
26
  items.forEach(item => {
27
27
  const label = escapeMarkdown(item.label);
@@ -1 +1 @@
1
- {"version":3,"file":"v1.js","names":["addDays","format","dateFormat","config","escapeMarkdown","getAnswer","designerUrl","get","items","model","submitResponse","formStatus","files","result","formName","name","now","Date","formattedNow","fileExpiryDate","formattedExpiryDate","lines","push","isPreview","state","forEach","item","label","filename","fileId","repeaters","field","main","join"],"sources":["../../../../../../src/server/plugins/engine/outputFormatters/human/v1.ts"],"sourcesContent":["import { type SubmitResponsePayload } from '@defra/forms-model'\nimport { addDays, format as dateFormat } from 'date-fns'\n\nimport { config } from '~/src/config/index.js'\nimport {\n escapeMarkdown,\n getAnswer\n} from '~/src/server/plugins/engine/components/helpers.js'\nimport { type checkFormStatus } from '~/src/server/plugins/engine/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type DetailItem } from '~/src/server/plugins/engine/models/types.js'\n\nconst designerUrl = config.get('designerUrl')\n\nexport function format(\n items: DetailItem[],\n model: FormModel,\n submitResponse: SubmitResponsePayload,\n formStatus: ReturnType<typeof checkFormStatus>\n) {\n const { files } = submitResponse.result\n\n const formName = escapeMarkdown(model.name)\n\n /**\n * @todo Refactor this below but the code to\n * generate the question and answers works for now\n */\n const now = new Date()\n const formattedNow = `${dateFormat(now, 'h:mmaaa')} on ${dateFormat(now, 'd MMMM yyyy')}`\n\n const fileExpiryDate = addDays(now, 90)\n const formattedExpiryDate = `${dateFormat(fileExpiryDate, 'h:mmaaa')} on ${dateFormat(fileExpiryDate, 'eeee d MMMM yyyy')}`\n\n const lines: string[] = []\n\n lines.push(\n `^ For security reasons, the links in this email expire at ${escapeMarkdown(formattedExpiryDate)}\\n`\n )\n\n if (formStatus.isPreview) {\n lines.push(`This is a test of the ${formName} ${formStatus.state} form.\\n`)\n }\n\n lines.push(`Form submitted at ${escapeMarkdown(formattedNow)}.\\n`)\n lines.push('---\\n')\n\n items.forEach((item) => {\n const label = escapeMarkdown(item.label)\n\n lines.push(`## ${label}\\n`)\n\n if ('subItems' in item) {\n const filename = escapeMarkdown(`Download ${label} (CSV)`)\n const fileId = files.repeaters[item.name]\n\n lines.push(`[${filename}](${designerUrl}/file-download/${fileId})\\n`)\n } else {\n lines.push(\n getAnswer(item.field, item.state, {\n format: 'email'\n })\n )\n }\n\n lines.push('---\\n')\n })\n\n const filename = escapeMarkdown('Download main form (CSV)')\n lines.push(`[${filename}](${designerUrl}/file-download/${files.main})\\n`)\n\n return lines.join('\\n')\n}\n"],"mappings":"AACA,SAASA,OAAO,EAAEC,MAAM,IAAIC,UAAU,QAAQ,UAAU;AAExD,SAASC,MAAM;AACf,SACEC,cAAc,EACdC,SAAS;AAMX,MAAMC,WAAW,GAAGH,MAAM,CAACI,GAAG,CAAC,aAAa,CAAC;AAE7C,OAAO,SAASN,MAAMA,CACpBO,KAAmB,EACnBC,KAAgB,EAChBC,cAAqC,EACrCC,UAA8C,EAC9C;EACA,MAAM;IAAEC;EAAM,CAAC,GAAGF,cAAc,CAACG,MAAM;EAEvC,MAAMC,QAAQ,GAAGV,cAAc,CAACK,KAAK,CAACM,IAAI,CAAC;;EAE3C;AACF;AACA;AACA;EACE,MAAMC,GAAG,GAAG,IAAIC,IAAI,CAAC,CAAC;EACtB,MAAMC,YAAY,GAAG,GAAGhB,UAAU,CAACc,GAAG,EAAE,SAAS,CAAC,OAAOd,UAAU,CAACc,GAAG,EAAE,aAAa,CAAC,EAAE;EAEzF,MAAMG,cAAc,GAAGnB,OAAO,CAACgB,GAAG,EAAE,EAAE,CAAC;EACvC,MAAMI,mBAAmB,GAAG,GAAGlB,UAAU,CAACiB,cAAc,EAAE,SAAS,CAAC,OAAOjB,UAAU,CAACiB,cAAc,EAAE,kBAAkB,CAAC,EAAE;EAE3H,MAAME,KAAe,GAAG,EAAE;EAE1BA,KAAK,CAACC,IAAI,CACR,6DAA6DlB,cAAc,CAACgB,mBAAmB,CAAC,IAClG,CAAC;EAED,IAAIT,UAAU,CAACY,SAAS,EAAE;IACxBF,KAAK,CAACC,IAAI,CAAC,yBAAyBR,QAAQ,IAAIH,UAAU,CAACa,KAAK,UAAU,CAAC;EAC7E;EAEAH,KAAK,CAACC,IAAI,CAAC,qBAAqBlB,cAAc,CAACc,YAAY,CAAC,KAAK,CAAC;EAClEG,KAAK,CAACC,IAAI,CAAC,OAAO,CAAC;EAEnBd,KAAK,CAACiB,OAAO,CAAEC,IAAI,IAAK;IACtB,MAAMC,KAAK,GAAGvB,cAAc,CAACsB,IAAI,CAACC,KAAK,CAAC;IAExCN,KAAK,CAACC,IAAI,CAAC,MAAMK,KAAK,IAAI,CAAC;IAE3B,IAAI,UAAU,IAAID,IAAI,EAAE;MACtB,MAAME,QAAQ,GAAGxB,cAAc,CAAC,YAAYuB,KAAK,QAAQ,CAAC;MAC1D,MAAME,MAAM,GAAGjB,KAAK,CAACkB,SAAS,CAACJ,IAAI,CAACX,IAAI,CAAC;MAEzCM,KAAK,CAACC,IAAI,CAAC,IAAIM,QAAQ,KAAKtB,WAAW,kBAAkBuB,MAAM,KAAK,CAAC;IACvE,CAAC,MAAM;MACLR,KAAK,CAACC,IAAI,CACRjB,SAAS,CAACqB,IAAI,CAACK,KAAK,EAAEL,IAAI,CAACF,KAAK,EAAE;QAChCvB,MAAM,EAAE;MACV,CAAC,CACH,CAAC;IACH;IAEAoB,KAAK,CAACC,IAAI,CAAC,OAAO,CAAC;EACrB,CAAC,CAAC;EAEF,MAAMM,QAAQ,GAAGxB,cAAc,CAAC,0BAA0B,CAAC;EAC3DiB,KAAK,CAACC,IAAI,CAAC,IAAIM,QAAQ,KAAKtB,WAAW,kBAAkBM,KAAK,CAACoB,IAAI,KAAK,CAAC;EAEzE,OAAOX,KAAK,CAACY,IAAI,CAAC,IAAI,CAAC;AACzB","ignoreList":[]}
1
+ {"version":3,"file":"v1.js","names":["addDays","format","dateFormat","config","escapeMarkdown","getAnswer","designerUrl","get","items","model","submitResponse","formStatus","files","result","formName","name","now","Date","formattedNow","fileExpiryDate","formattedExpiryDate","lines","push","isPreview","state","forEach","item","label","filename","fileId","repeaters","field","main","join"],"sources":["../../../../../../src/server/plugins/engine/outputFormatters/human/v1.ts"],"sourcesContent":["import { type SubmitResponsePayload } from '@defra/forms-model'\nimport { addDays, format as dateFormat } from 'date-fns'\n\nimport { config } from '~/src/config/index.js'\nimport {\n escapeMarkdown,\n getAnswer\n} from '~/src/server/plugins/engine/components/helpers.js'\nimport { type checkFormStatus } from '~/src/server/plugins/engine/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type DetailItem } from '~/src/server/plugins/engine/models/types.js'\n\nconst designerUrl = config.get('designerUrl')\n\nexport function format(\n items: DetailItem[],\n model: FormModel,\n submitResponse: SubmitResponsePayload,\n formStatus: ReturnType<typeof checkFormStatus>\n) {\n const { files } = submitResponse.result\n\n const formName = escapeMarkdown(model.name)\n\n /**\n * @todo Refactor this below but the code to\n * generate the question and answers works for now\n */\n const now = new Date()\n const formattedNow = `${dateFormat(now, 'h:mmaaa')} on ${dateFormat(now, 'd MMMM yyyy')}`\n\n const fileExpiryDate = addDays(now, 90)\n const formattedExpiryDate = `${dateFormat(fileExpiryDate, 'h:mmaaa')} on ${dateFormat(fileExpiryDate, 'eeee d MMMM yyyy')}`\n\n const lines: string[] = []\n\n lines.push(\n `^ For security reasons, the links in this email expire at ${escapeMarkdown(formattedExpiryDate)}\\n`\n )\n\n if (formStatus.isPreview) {\n lines.push(`This is a test of the ${formName} ${formStatus.state} form.\\n`)\n }\n\n lines.push(`${formName} form received at ${escapeMarkdown(formattedNow)}.\\n`)\n lines.push('---\\n')\n\n items.forEach((item) => {\n const label = escapeMarkdown(item.label)\n\n lines.push(`## ${label}\\n`)\n\n if ('subItems' in item) {\n const filename = escapeMarkdown(`Download ${label} (CSV)`)\n const fileId = files.repeaters[item.name]\n\n lines.push(`[${filename}](${designerUrl}/file-download/${fileId})\\n`)\n } else {\n lines.push(\n getAnswer(item.field, item.state, {\n format: 'email'\n })\n )\n }\n\n lines.push('---\\n')\n })\n\n const filename = escapeMarkdown('Download main form (CSV)')\n lines.push(`[${filename}](${designerUrl}/file-download/${files.main})\\n`)\n\n return lines.join('\\n')\n}\n"],"mappings":"AACA,SAASA,OAAO,EAAEC,MAAM,IAAIC,UAAU,QAAQ,UAAU;AAExD,SAASC,MAAM;AACf,SACEC,cAAc,EACdC,SAAS;AAMX,MAAMC,WAAW,GAAGH,MAAM,CAACI,GAAG,CAAC,aAAa,CAAC;AAE7C,OAAO,SAASN,MAAMA,CACpBO,KAAmB,EACnBC,KAAgB,EAChBC,cAAqC,EACrCC,UAA8C,EAC9C;EACA,MAAM;IAAEC;EAAM,CAAC,GAAGF,cAAc,CAACG,MAAM;EAEvC,MAAMC,QAAQ,GAAGV,cAAc,CAACK,KAAK,CAACM,IAAI,CAAC;;EAE3C;AACF;AACA;AACA;EACE,MAAMC,GAAG,GAAG,IAAIC,IAAI,CAAC,CAAC;EACtB,MAAMC,YAAY,GAAG,GAAGhB,UAAU,CAACc,GAAG,EAAE,SAAS,CAAC,OAAOd,UAAU,CAACc,GAAG,EAAE,aAAa,CAAC,EAAE;EAEzF,MAAMG,cAAc,GAAGnB,OAAO,CAACgB,GAAG,EAAE,EAAE,CAAC;EACvC,MAAMI,mBAAmB,GAAG,GAAGlB,UAAU,CAACiB,cAAc,EAAE,SAAS,CAAC,OAAOjB,UAAU,CAACiB,cAAc,EAAE,kBAAkB,CAAC,EAAE;EAE3H,MAAME,KAAe,GAAG,EAAE;EAE1BA,KAAK,CAACC,IAAI,CACR,6DAA6DlB,cAAc,CAACgB,mBAAmB,CAAC,IAClG,CAAC;EAED,IAAIT,UAAU,CAACY,SAAS,EAAE;IACxBF,KAAK,CAACC,IAAI,CAAC,yBAAyBR,QAAQ,IAAIH,UAAU,CAACa,KAAK,UAAU,CAAC;EAC7E;EAEAH,KAAK,CAACC,IAAI,CAAC,GAAGR,QAAQ,qBAAqBV,cAAc,CAACc,YAAY,CAAC,KAAK,CAAC;EAC7EG,KAAK,CAACC,IAAI,CAAC,OAAO,CAAC;EAEnBd,KAAK,CAACiB,OAAO,CAAEC,IAAI,IAAK;IACtB,MAAMC,KAAK,GAAGvB,cAAc,CAACsB,IAAI,CAACC,KAAK,CAAC;IAExCN,KAAK,CAACC,IAAI,CAAC,MAAMK,KAAK,IAAI,CAAC;IAE3B,IAAI,UAAU,IAAID,IAAI,EAAE;MACtB,MAAME,QAAQ,GAAGxB,cAAc,CAAC,YAAYuB,KAAK,QAAQ,CAAC;MAC1D,MAAME,MAAM,GAAGjB,KAAK,CAACkB,SAAS,CAACJ,IAAI,CAACX,IAAI,CAAC;MAEzCM,KAAK,CAACC,IAAI,CAAC,IAAIM,QAAQ,KAAKtB,WAAW,kBAAkBuB,MAAM,KAAK,CAAC;IACvE,CAAC,MAAM;MACLR,KAAK,CAACC,IAAI,CACRjB,SAAS,CAACqB,IAAI,CAACK,KAAK,EAAEL,IAAI,CAACF,KAAK,EAAE;QAChCvB,MAAM,EAAE;MACV,CAAC,CACH,CAAC;IACH;IAEAoB,KAAK,CAACC,IAAI,CAAC,OAAO,CAAC;EACrB,CAAC,CAAC;EAEF,MAAMM,QAAQ,GAAGxB,cAAc,CAAC,0BAA0B,CAAC;EAC3DiB,KAAK,CAACC,IAAI,CAAC,IAAIM,QAAQ,KAAKtB,WAAW,kBAAkBM,KAAK,CAACoB,IAAI,KAAK,CAAC;EAEzE,OAAOX,KAAK,CAACY,IAAI,CAAC,IAAI,CAAC;AACzB","ignoreList":[]}
@@ -102,11 +102,19 @@ export class PageController {
102
102
  return def.phaseBanner?.phase;
103
103
  }
104
104
  getHref(path) {
105
- const {
106
- model
107
- } = this;
108
- return path === '/' ? `/${model.basePath}` // Strip trailing slash
109
- : `/${model.basePath}${path}`;
105
+ const basePath = this.model.basePath;
106
+ if (path === '/') {
107
+ return `/${basePath}`;
108
+ }
109
+
110
+ // if ever the path is not prefixed with a slash, add it
111
+ const relativeTargetPath = path.startsWith('/') ? path.substring(1) : path;
112
+ let finalPath = `/${basePath}`;
113
+ if (relativeTargetPath) {
114
+ finalPath += `/${relativeTargetPath}`;
115
+ }
116
+ finalPath = finalPath.replace(/\/{2,}/g, '/');
117
+ return finalPath;
110
118
  }
111
119
  getStartPath() {
112
120
  return getStartPath(this.model);
@@ -1 +1 @@
1
- {"version":3,"file":"PageController.js","names":["ControllerPath","Boom","encodeUrl","getStartPath","normalisePath","PageController","def","name","model","pageDef","title","section","condition","events","collection","viewName","constructor","sections","find","conditions","view","path","href","getHref","keys","getRouteOptions","postRouteOptions","viewModel","showTitle","pageTitle","sectionTitle","hideTitle","page","isStartPage","serviceUrl","feedbackLink","phaseTag","feedback","emailAddress","url","phaseBanner","phase","basePath","getSummaryPath","Summary","valueOf","getStatusPath","Status","makeGetRouteHandler","request","context","h","makePostRouteHandler","badRequest"],"sources":["../../../../../src/server/plugins/engine/pageControllers/PageController.ts"],"sourcesContent":["import {\n ControllerPath,\n type Events,\n type FormDefinition,\n type Page,\n type Section\n} from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport {\n type Lifecycle,\n type ResponseToolkit,\n type RouteOptions\n} from '@hapi/hapi'\n\nimport { type ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'\nimport {\n encodeUrl,\n getStartPath,\n normalisePath\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type ExecutableCondition } from '~/src/server/plugins/engine/models/types.js'\nimport {\n type FormContext,\n type PageViewModelBase\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequest,\n type FormRequestPayload,\n type FormRequestPayloadRefs,\n type FormRequestRefs\n} from '~/src/server/routes/types.js'\n\nexport class PageController {\n /**\n * The base class for all page controllers. Page controllers are responsible for generating the get and post route handlers when a user navigates to `/{id}/{path*}`.\n */\n def: FormDefinition\n name?: string\n model: FormModel\n pageDef: Page\n title: string\n section?: Section\n condition?: ExecutableCondition\n events?: Events\n collection?: ComponentCollection\n viewName = 'index'\n\n constructor(model: FormModel, pageDef: Page) {\n const { def } = model\n\n this.def = def\n this.name = def.name\n this.model = model\n this.pageDef = pageDef\n this.title = pageDef.title\n this.events = pageDef.events\n\n // Resolve section\n this.section = model.sections.find(\n (section) => section.name === pageDef.section\n )\n\n // Resolve condition\n if (pageDef.condition) {\n this.condition = model.conditions[pageDef.condition]\n }\n\n // Override view name\n if (pageDef.view) {\n this.viewName = pageDef.view\n }\n }\n\n get path() {\n return this.pageDef.path\n }\n\n get href() {\n const { path } = this\n return this.getHref(`/${normalisePath(path)}`)\n }\n\n get keys() {\n return this.collection?.keys ?? []\n }\n\n /**\n * {@link https://hapi.dev/api/?v=20.1.2#route-options}\n */\n get getRouteOptions(): RouteOptions<FormRequestRefs> {\n return {}\n }\n\n /**\n * {@link https://hapi.dev/api/?v=20.1.2#route-options}\n */\n get postRouteOptions(): RouteOptions<FormRequestPayloadRefs> {\n return {}\n }\n\n get viewModel(): PageViewModelBase {\n const { name, section, title } = this\n\n const showTitle = true\n const pageTitle = title\n const sectionTitle = section?.hideTitle !== true ? section?.title : ''\n\n return {\n name,\n page: this,\n pageTitle,\n sectionTitle,\n showTitle,\n isStartPage: false,\n serviceUrl: this.getHref('/'),\n feedbackLink: this.feedbackLink,\n phaseTag: this.phaseTag\n }\n }\n\n get feedbackLink() {\n const { def } = this\n\n // setting the feedbackLink to undefined here for feedback forms prevents the feedback link from being shown\n const feedbackLink = def.feedback?.emailAddress\n ? `mailto:${def.feedback.emailAddress}`\n : def.feedback?.url\n\n return encodeUrl(feedbackLink)\n }\n\n get phaseTag() {\n const { def } = this\n return def.phaseBanner?.phase\n }\n\n getHref(path: string) {\n const { model } = this\n\n return path === '/'\n ? `/${model.basePath}` // Strip trailing slash\n : `/${model.basePath}${path}`\n }\n\n getStartPath() {\n return getStartPath(this.model)\n }\n\n getSummaryPath() {\n return ControllerPath.Summary.valueOf()\n }\n\n getStatusPath() {\n return ControllerPath.Status.valueOf()\n }\n\n makeGetRouteHandler(): (\n request: FormRequest,\n context: FormContext,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) => ReturnType<Lifecycle.Method<FormRequestRefs>> {\n return (request, context, h) => {\n const { viewModel, viewName } = this\n return h.view(viewName, viewModel)\n }\n }\n\n makePostRouteHandler(): (\n request: FormRequestPayload,\n context: FormContext,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) => ReturnType<Lifecycle.Method<FormRequestPayloadRefs>> {\n throw Boom.badRequest('Unsupported POST route handler for this page')\n }\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"PageController.js","names":["ControllerPath","Boom","encodeUrl","getStartPath","normalisePath","PageController","def","name","model","pageDef","title","section","condition","events","collection","viewName","constructor","sections","find","conditions","view","path","href","getHref","keys","getRouteOptions","postRouteOptions","viewModel","showTitle","pageTitle","sectionTitle","hideTitle","page","isStartPage","serviceUrl","feedbackLink","phaseTag","feedback","emailAddress","url","phaseBanner","phase","basePath","relativeTargetPath","startsWith","substring","finalPath","replace","getSummaryPath","Summary","valueOf","getStatusPath","Status","makeGetRouteHandler","request","context","h","makePostRouteHandler","badRequest"],"sources":["../../../../../src/server/plugins/engine/pageControllers/PageController.ts"],"sourcesContent":["import {\n ControllerPath,\n type Events,\n type FormDefinition,\n type Page,\n type Section\n} from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport {\n type Lifecycle,\n type ResponseToolkit,\n type RouteOptions\n} from '@hapi/hapi'\n\nimport { type ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'\nimport {\n encodeUrl,\n getStartPath,\n normalisePath\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { type ExecutableCondition } from '~/src/server/plugins/engine/models/types.js'\nimport {\n type FormContext,\n type PageViewModelBase\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequest,\n type FormRequestPayload,\n type FormRequestPayloadRefs,\n type FormRequestRefs\n} from '~/src/server/routes/types.js'\n\nexport class PageController {\n /**\n * The base class for all page controllers. Page controllers are responsible for generating the get and post route handlers when a user navigates to `/{id}/{path*}`.\n */\n def: FormDefinition\n name?: string\n model: FormModel\n pageDef: Page\n title: string\n section?: Section\n condition?: ExecutableCondition\n events?: Events\n collection?: ComponentCollection\n viewName = 'index'\n\n constructor(model: FormModel, pageDef: Page) {\n const { def } = model\n\n this.def = def\n this.name = def.name\n this.model = model\n this.pageDef = pageDef\n this.title = pageDef.title\n this.events = pageDef.events\n\n // Resolve section\n this.section = model.sections.find(\n (section) => section.name === pageDef.section\n )\n\n // Resolve condition\n if (pageDef.condition) {\n this.condition = model.conditions[pageDef.condition]\n }\n\n // Override view name\n if (pageDef.view) {\n this.viewName = pageDef.view\n }\n }\n\n get path() {\n return this.pageDef.path\n }\n\n get href() {\n const { path } = this\n return this.getHref(`/${normalisePath(path)}`)\n }\n\n get keys() {\n return this.collection?.keys ?? []\n }\n\n /**\n * {@link https://hapi.dev/api/?v=20.1.2#route-options}\n */\n get getRouteOptions(): RouteOptions<FormRequestRefs> {\n return {}\n }\n\n /**\n * {@link https://hapi.dev/api/?v=20.1.2#route-options}\n */\n get postRouteOptions(): RouteOptions<FormRequestPayloadRefs> {\n return {}\n }\n\n get viewModel(): PageViewModelBase {\n const { name, section, title } = this\n\n const showTitle = true\n const pageTitle = title\n const sectionTitle = section?.hideTitle !== true ? section?.title : ''\n\n return {\n name,\n page: this,\n pageTitle,\n sectionTitle,\n showTitle,\n isStartPage: false,\n serviceUrl: this.getHref('/'),\n feedbackLink: this.feedbackLink,\n phaseTag: this.phaseTag\n }\n }\n\n get feedbackLink() {\n const { def } = this\n\n // setting the feedbackLink to undefined here for feedback forms prevents the feedback link from being shown\n const feedbackLink = def.feedback?.emailAddress\n ? `mailto:${def.feedback.emailAddress}`\n : def.feedback?.url\n\n return encodeUrl(feedbackLink)\n }\n\n get phaseTag() {\n const { def } = this\n return def.phaseBanner?.phase\n }\n\n getHref(path: string): string {\n const basePath = this.model.basePath\n\n if (path === '/') {\n return `/${basePath}`\n }\n\n // if ever the path is not prefixed with a slash, add it\n const relativeTargetPath = path.startsWith('/') ? path.substring(1) : path\n let finalPath = `/${basePath}`\n if (relativeTargetPath) {\n finalPath += `/${relativeTargetPath}`\n }\n finalPath = finalPath.replace(/\\/{2,}/g, '/')\n\n return finalPath\n }\n\n getStartPath() {\n return getStartPath(this.model)\n }\n\n getSummaryPath() {\n return ControllerPath.Summary.valueOf()\n }\n\n getStatusPath() {\n return ControllerPath.Status.valueOf()\n }\n\n makeGetRouteHandler(): (\n request: FormRequest,\n context: FormContext,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) => ReturnType<Lifecycle.Method<FormRequestRefs>> {\n return (request, context, h) => {\n const { viewModel, viewName } = this\n return h.view(viewName, viewModel)\n }\n }\n\n makePostRouteHandler(): (\n request: FormRequestPayload,\n context: FormContext,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) => ReturnType<Lifecycle.Method<FormRequestPayloadRefs>> {\n throw Boom.badRequest('Unsupported POST route handler for this page')\n }\n}\n"],"mappings":"AAAA,SACEA,cAAc,QAKT,oBAAoB;AAC3B,OAAOC,IAAI,MAAM,YAAY;AAQ7B,SACEC,SAAS,EACTC,YAAY,EACZC,aAAa;AAef,OAAO,MAAMC,cAAc,CAAC;EAC1B;AACF;AACA;EACEC,GAAG;EACHC,IAAI;EACJC,KAAK;EACLC,OAAO;EACPC,KAAK;EACLC,OAAO;EACPC,SAAS;EACTC,MAAM;EACNC,UAAU;EACVC,QAAQ,GAAG,OAAO;EAElBC,WAAWA,CAACR,KAAgB,EAAEC,OAAa,EAAE;IAC3C,MAAM;MAAEH;IAAI,CAAC,GAAGE,KAAK;IAErB,IAAI,CAACF,GAAG,GAAGA,GAAG;IACd,IAAI,CAACC,IAAI,GAAGD,GAAG,CAACC,IAAI;IACpB,IAAI,CAACC,KAAK,GAAGA,KAAK;IAClB,IAAI,CAACC,OAAO,GAAGA,OAAO;IACtB,IAAI,CAACC,KAAK,GAAGD,OAAO,CAACC,KAAK;IAC1B,IAAI,CAACG,MAAM,GAAGJ,OAAO,CAACI,MAAM;;IAE5B;IACA,IAAI,CAACF,OAAO,GAAGH,KAAK,CAACS,QAAQ,CAACC,IAAI,CAC/BP,OAAO,IAAKA,OAAO,CAACJ,IAAI,KAAKE,OAAO,CAACE,OACxC,CAAC;;IAED;IACA,IAAIF,OAAO,CAACG,SAAS,EAAE;MACrB,IAAI,CAACA,SAAS,GAAGJ,KAAK,CAACW,UAAU,CAACV,OAAO,CAACG,SAAS,CAAC;IACtD;;IAEA;IACA,IAAIH,OAAO,CAACW,IAAI,EAAE;MAChB,IAAI,CAACL,QAAQ,GAAGN,OAAO,CAACW,IAAI;IAC9B;EACF;EAEA,IAAIC,IAAIA,CAAA,EAAG;IACT,OAAO,IAAI,CAACZ,OAAO,CAACY,IAAI;EAC1B;EAEA,IAAIC,IAAIA,CAAA,EAAG;IACT,MAAM;MAAED;IAAK,CAAC,GAAG,IAAI;IACrB,OAAO,IAAI,CAACE,OAAO,CAAC,IAAInB,aAAa,CAACiB,IAAI,CAAC,EAAE,CAAC;EAChD;EAEA,IAAIG,IAAIA,CAAA,EAAG;IACT,OAAO,IAAI,CAACV,UAAU,EAAEU,IAAI,IAAI,EAAE;EACpC;;EAEA;AACF;AACA;EACE,IAAIC,eAAeA,CAAA,EAAkC;IACnD,OAAO,CAAC,CAAC;EACX;;EAEA;AACF;AACA;EACE,IAAIC,gBAAgBA,CAAA,EAAyC;IAC3D,OAAO,CAAC,CAAC;EACX;EAEA,IAAIC,SAASA,CAAA,EAAsB;IACjC,MAAM;MAAEpB,IAAI;MAAEI,OAAO;MAAED;IAAM,CAAC,GAAG,IAAI;IAErC,MAAMkB,SAAS,GAAG,IAAI;IACtB,MAAMC,SAAS,GAAGnB,KAAK;IACvB,MAAMoB,YAAY,GAAGnB,OAAO,EAAEoB,SAAS,KAAK,IAAI,GAAGpB,OAAO,EAAED,KAAK,GAAG,EAAE;IAEtE,OAAO;MACLH,IAAI;MACJyB,IAAI,EAAE,IAAI;MACVH,SAAS;MACTC,YAAY;MACZF,SAAS;MACTK,WAAW,EAAE,KAAK;MAClBC,UAAU,EAAE,IAAI,CAACX,OAAO,CAAC,GAAG,CAAC;MAC7BY,YAAY,EAAE,IAAI,CAACA,YAAY;MAC/BC,QAAQ,EAAE,IAAI,CAACA;IACjB,CAAC;EACH;EAEA,IAAID,YAAYA,CAAA,EAAG;IACjB,MAAM;MAAE7B;IAAI,CAAC,GAAG,IAAI;;IAEpB;IACA,MAAM6B,YAAY,GAAG7B,GAAG,CAAC+B,QAAQ,EAAEC,YAAY,GAC3C,UAAUhC,GAAG,CAAC+B,QAAQ,CAACC,YAAY,EAAE,GACrChC,GAAG,CAAC+B,QAAQ,EAAEE,GAAG;IAErB,OAAOrC,SAAS,CAACiC,YAAY,CAAC;EAChC;EAEA,IAAIC,QAAQA,CAAA,EAAG;IACb,MAAM;MAAE9B;IAAI,CAAC,GAAG,IAAI;IACpB,OAAOA,GAAG,CAACkC,WAAW,EAAEC,KAAK;EAC/B;EAEAlB,OAAOA,CAACF,IAAY,EAAU;IAC5B,MAAMqB,QAAQ,GAAG,IAAI,CAAClC,KAAK,CAACkC,QAAQ;IAEpC,IAAIrB,IAAI,KAAK,GAAG,EAAE;MAChB,OAAO,IAAIqB,QAAQ,EAAE;IACvB;;IAEA;IACA,MAAMC,kBAAkB,GAAGtB,IAAI,CAACuB,UAAU,CAAC,GAAG,CAAC,GAAGvB,IAAI,CAACwB,SAAS,CAAC,CAAC,CAAC,GAAGxB,IAAI;IAC1E,IAAIyB,SAAS,GAAG,IAAIJ,QAAQ,EAAE;IAC9B,IAAIC,kBAAkB,EAAE;MACtBG,SAAS,IAAI,IAAIH,kBAAkB,EAAE;IACvC;IACAG,SAAS,GAAGA,SAAS,CAACC,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC;IAE7C,OAAOD,SAAS;EAClB;EAEA3C,YAAYA,CAAA,EAAG;IACb,OAAOA,YAAY,CAAC,IAAI,CAACK,KAAK,CAAC;EACjC;EAEAwC,cAAcA,CAAA,EAAG;IACf,OAAOhD,cAAc,CAACiD,OAAO,CAACC,OAAO,CAAC,CAAC;EACzC;EAEAC,aAAaA,CAAA,EAAG;IACd,OAAOnD,cAAc,CAACoD,MAAM,CAACF,OAAO,CAAC,CAAC;EACxC;EAEAG,mBAAmBA,CAAA,EAIgC;IACjD,OAAO,CAACC,OAAO,EAAEC,OAAO,EAAEC,CAAC,KAAK;MAC9B,MAAM;QAAE7B,SAAS;QAAEZ;MAAS,CAAC,GAAG,IAAI;MACpC,OAAOyC,CAAC,CAACpC,IAAI,CAACL,QAAQ,EAAEY,SAAS,CAAC;IACpC,CAAC;EACH;EAEA8B,oBAAoBA,CAAA,EAIsC;IACxD,MAAMxD,IAAI,CAACyD,UAAU,CAAC,8CAA8C,CAAC;EACvE;AACF","ignoreList":[]}
@@ -317,7 +317,7 @@ export class QuestionPageController extends PageController {
317
317
  evaluationState
318
318
  } = context;
319
319
  const viewModel = this.getViewModel(request, context);
320
- viewModel.errors = collection.getErrors(viewModel.errors);
320
+ viewModel.errors = collection.getViewErrors(viewModel.errors);
321
321
 
322
322
  /**
323
323
  * Content components can be hidden based on a condition. If the condition evaluates to true, it is safe to be kept, otherwise discard it
@@ -419,7 +419,7 @@ export class QuestionPageController extends PageController {
419
419
  */
420
420
  if (context.errors || isForceAccess) {
421
421
  const viewModel = this.getViewModel(request, context);
422
- viewModel.errors = collection.getErrors(viewModel.errors);
422
+ viewModel.errors = collection.getViewErrors(viewModel.errors);
423
423
 
424
424
  // Filter our components based on their conditions using our evaluated state
425
425
  viewModel.components = this.filterConditionalComponents(viewModel, model, evaluationState);
@@ -1 +1 @@
1
- {"version":3,"file":"QuestionPageController.js","names":["ComponentType","ControllerType","Engine","hasComponents","hasNext","hasRepeater","ComponentCollection","optionalText","getCacheService","getErrors","normalisePath","proceed","PageController","actionSchema","crumbSchema","paramsSchema","merge","QuestionPageController","collection","errorSummaryTitle","constructor","model","pageDef","components","page","formSchema","keys","crumb","action","next","def","filter","path","linkPath","pages","some","pagePath","allowContinue","engine","V2","controller","Terminal","length","getItemId","request","itemId","getFormParams","params","getViewModel","context","viewModel","query","payload","errors","pageTitle","showTitle","formComponents","isFormComponent","fieldset","label","isPageHeading","labelOrLegend","legend","size","classes","isOptional","fields","at","options","required","text","backLink","getBackLink","getRelevantPath","paths","startPath","getStartPath","relevantPath","getNextPath","evaluationState","summaryPath","getSummaryPath","statusPath","getStatusPath","defaultPath","undefined","pageIndex","indexOf","nextPage","slice","find","condition","conditionResult","fn","nextLink","link","conditions","getFormDataFromState","state","result","validate","abortEarly","stripUnknown","value","getStateFromValidForm","details","getState","cacheService","server","setState","mergeState","update","updated","filterConditionalComponents","filtered","component","content","type","Details","map","evaluatedComponent","Array","isArray","item","items","makeGetRouteHandler","h","viewName","hasMissingNotificationEmail","view","isForceAccess","formsService","services","getFormMetadata","includes","notificationEmail","slug","returnUrl","href","backPath","endsWith","getHref","makePostRouteHandler","nextPath","nextUrl","getRouteOptions","ext","onPostHandler","method","_request","continue","postRouteOptions","parse","maxBytes","Number","MAX_SAFE_INTEGER","failAction"],"sources":["../../../../../src/server/plugins/engine/pageControllers/QuestionPageController.ts"],"sourcesContent":["import {\n ComponentType,\n ControllerType,\n Engine,\n hasComponents,\n hasNext,\n hasRepeater,\n type Link,\n type Page\n} from '@defra/forms-model'\nimport { type ResponseToolkit, type RouteOptions } from '@hapi/hapi'\nimport { type ValidationErrorItem } from 'joi'\n\nimport { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'\nimport { optionalText } from '~/src/server/plugins/engine/components/constants.js'\nimport { type BackLink } from '~/src/server/plugins/engine/components/types.js'\nimport {\n getCacheService,\n getErrors,\n normalisePath,\n proceed\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport {\n type FormContext,\n type FormContextRequest,\n type FormPageViewModel,\n type FormParams,\n type FormPayload,\n type FormState,\n type FormStateValue,\n type FormSubmissionState\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequest,\n type FormRequestPayload,\n type FormRequestPayloadRefs,\n type FormRequestRefs\n} from '~/src/server/routes/types.js'\nimport {\n actionSchema,\n crumbSchema,\n paramsSchema\n} from '~/src/server/schemas/index.js'\nimport { merge } from '~/src/server/services/cacheService.js'\n\nexport class QuestionPageController extends PageController {\n collection: ComponentCollection\n errorSummaryTitle = 'There is a problem'\n\n constructor(model: FormModel, pageDef: Page) {\n super(model, pageDef)\n\n // Components collection\n this.collection = new ComponentCollection(\n hasComponents(pageDef) ? pageDef.components : [],\n { model, page: this }\n )\n\n this.collection.formSchema = this.collection.formSchema.keys({\n crumb: crumbSchema,\n action: actionSchema\n })\n }\n\n get next(): Link[] {\n const { def, pageDef } = this\n\n if (!hasNext(pageDef)) {\n return []\n }\n\n // Remove stale links\n return pageDef.next.filter(({ path }) => {\n const linkPath = normalisePath(path)\n\n return def.pages.some((page) => {\n const pagePath = normalisePath(page.path)\n return pagePath === linkPath\n })\n })\n }\n\n get allowContinue(): boolean {\n if (this.model.engine === Engine.V2) {\n return this.pageDef.controller !== ControllerType.Terminal\n }\n\n return this.next.length > 0\n }\n\n getItemId(request?: FormContextRequest) {\n const { itemId } = this.getFormParams(request)\n return itemId ?? request?.params.itemId\n }\n\n /**\n * Used for mapping form payloads and errors to govuk-frontend's template api, so a page can be rendered\n * @param request - the hapi request\n * @param context - the form context\n */\n getViewModel(\n request: FormContextRequest,\n context: FormContext\n ): FormPageViewModel {\n const { collection, viewModel } = this\n const { query } = request\n const { payload, errors } = context\n\n let { pageTitle, showTitle } = viewModel\n\n const components = collection.getViewModel(payload, errors, query)\n const formComponents = components.filter(\n ({ isFormComponent }) => isFormComponent\n )\n\n // Single form component? Hide title and customise label or legend instead\n if (formComponents.length === 1) {\n const { model } = formComponents[0]\n const { fieldset, label } = model\n\n // Set as page heading when not following other content\n const isPageHeading = formComponents[0] === components[0]\n\n // Check for legend or label\n const labelOrLegend = fieldset?.legend ?? label\n\n // Use legend or label as page heading\n if (labelOrLegend) {\n const size = isPageHeading ? 'l' : 'm'\n\n labelOrLegend.classes =\n labelOrLegend === label\n ? `govuk-label--${size}`\n : `govuk-fieldset__legend--${size}`\n\n if (isPageHeading) {\n labelOrLegend.isPageHeading = isPageHeading\n\n // Check for optional in label\n const isOptional =\n this.collection.fields.at(0)?.options.required === false\n\n if (pageTitle) {\n labelOrLegend.text = isOptional\n ? `${pageTitle}${optionalText}`\n : pageTitle\n }\n\n pageTitle = pageTitle || labelOrLegend.text\n }\n }\n\n showTitle = !isPageHeading\n } else if (formComponents.length > 1) {\n // When there is more than one form component,\n // adjust the label/legends to give equal prominence\n for (const { model } of formComponents) {\n if (model.fieldset?.legend) {\n model.fieldset.legend.classes = 'govuk-fieldset__legend--m'\n }\n if (model.label) {\n model.label.classes = 'govuk-label--m'\n }\n }\n }\n\n return {\n ...viewModel,\n backLink: this.getBackLink(request, context),\n context,\n showTitle,\n components,\n errors\n }\n }\n\n getRelevantPath(\n request: FormRequest | FormRequestPayload,\n context: FormContext\n ) {\n const { paths } = context\n\n const startPath = this.getStartPath()\n const relevantPath = paths.at(-1) ?? startPath\n\n return !paths.length\n ? startPath // First possible path\n : relevantPath // Last possible path\n }\n\n /**\n * Apply conditions to evaluation state to determine next page path\n */\n getNextPath(context: FormContext) {\n const { model, next, path } = this\n const { evaluationState } = context\n\n const summaryPath = this.getSummaryPath()\n const statusPath = this.getStatusPath()\n\n // Walk from summary page (no next links) to status page\n let defaultPath = path === summaryPath ? statusPath : undefined\n\n if (model.engine === Engine.V2) {\n if (this.pageDef.controller !== ControllerType.Terminal) {\n const { pages } = this.model\n const pageIndex = pages.indexOf(this)\n\n // The \"next\" page is the first found after the current which is\n // either unconditional or has a condition that evaluates to \"true\"\n const nextPage = pages.slice(pageIndex + 1).find((page) => {\n const { condition } = page\n\n if (condition) {\n const conditionResult = condition.fn(evaluationState)\n\n if (!conditionResult) {\n return false\n }\n }\n\n return true\n })\n\n return nextPage?.path ?? defaultPath\n } else {\n return defaultPath\n }\n }\n\n const nextLink = next.find((link) => {\n const { condition } = link\n\n if (condition) {\n return model.conditions[condition]?.fn(evaluationState) ?? false\n }\n\n defaultPath = link.path\n return false\n })\n\n return nextLink?.path ?? defaultPath\n }\n\n /**\n * Gets the form payload (from state) for this page only\n */\n getFormDataFromState(\n request: FormContextRequest | undefined,\n state: FormSubmissionState\n ): FormPayload {\n const { collection } = this\n\n // Form params from request\n const params = this.getFormParams(request)\n\n // Form payload from state\n const payload = collection.getFormDataFromState(state)\n\n return {\n ...params,\n ...payload\n }\n }\n\n /**\n * Gets form params (from payload) for this page only\n */\n getFormParams(request?: FormContextRequest): FormParams {\n const { payload } = request ?? {}\n\n const result = paramsSchema.validate(payload, {\n abortEarly: false,\n stripUnknown: true\n })\n\n return result.value as FormParams\n }\n\n getStateFromValidForm(\n request: FormContextRequest,\n state: FormSubmissionState,\n payload: FormPayload\n ): FormState {\n return this.collection.getStateFromValidForm(payload)\n }\n\n getErrors(details?: ValidationErrorItem[]) {\n return getErrors(details)\n }\n\n async getState(request: FormRequest | FormRequestPayload) {\n const { query } = request\n\n // Skip get for preview URL direct access\n if ('force' in query) {\n return {}\n }\n\n const cacheService = getCacheService(request.server)\n\n return cacheService.getState(request)\n }\n\n async setState(\n request: FormRequest | FormRequestPayload,\n state: FormSubmissionState\n ) {\n const { query } = request\n\n // Skip set for preview URL direct access\n if ('force' in query) {\n return state\n }\n\n const cacheService = getCacheService(request.server)\n\n return cacheService.setState(request, state)\n }\n\n async mergeState(\n request: FormRequest | FormRequestPayload,\n state: FormSubmissionState,\n update: object\n ) {\n const { query } = request\n\n // Merge state before set\n const updated = merge(state, update)\n\n // Skip set for preview URL direct access\n if ('force' in query) {\n return updated\n }\n\n const cacheService = getCacheService(request.server)\n\n return cacheService.setState(request, updated)\n }\n\n filterConditionalComponents(\n viewModel: FormPageViewModel,\n model: FormModel,\n evaluationState: Partial<Record<string, FormStateValue>>\n ) {\n // Filter our components based on their conditions using our evaluated state\n let filtered = viewModel.components.filter((component) => {\n if (\n (!!component.model.content ||\n component.type === ComponentType.Details) &&\n component.model.condition\n ) {\n const condition = model.conditions[component.model.condition]\n return condition?.fn(evaluationState)\n }\n return true\n })\n\n /**\n * For conditional reveal components (which we no longer support until GDS resolves the related accessibility issues {@link https://github.com/alphagov/govuk-frontend/issues/1991}\n */\n filtered = filtered.map((component) => {\n const evaluatedComponent = component\n const content = evaluatedComponent.model.content\n if (Array.isArray(content)) {\n evaluatedComponent.model.content = content.filter((item) =>\n item.condition\n ? model.conditions[item.condition]?.fn(evaluationState)\n : true\n )\n }\n // apply condition to items for radios, checkboxes etc\n const items = evaluatedComponent.model.items\n\n if (Array.isArray(items)) {\n evaluatedComponent.model.items = items.filter((item) =>\n item.condition\n ? model.conditions[item.condition]?.fn(evaluationState)\n : true\n )\n }\n\n return evaluatedComponent\n })\n\n return filtered\n }\n\n makeGetRouteHandler() {\n return async (\n request: FormRequest,\n context: FormContext,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) => {\n const { collection, model, viewName } = this\n const { evaluationState } = context\n\n const viewModel = this.getViewModel(request, context)\n viewModel.errors = collection.getErrors(viewModel.errors)\n\n /**\n * Content components can be hidden based on a condition. If the condition evaluates to true, it is safe to be kept, otherwise discard it\n */\n\n // Filter our components based on their conditions using our evaluated state\n viewModel.components = this.filterConditionalComponents(\n viewModel,\n model,\n evaluationState\n )\n\n viewModel.hasMissingNotificationEmail =\n await this.hasMissingNotificationEmail(request, context)\n\n return h.view(viewName, viewModel)\n }\n }\n\n async hasMissingNotificationEmail(\n request: FormRequest,\n context: FormContext\n ) {\n const { path } = this\n const { params } = request\n const { isForceAccess } = context\n\n const startPath = this.getStartPath()\n const summaryPath = this.getSummaryPath()\n const { formsService } = this.model.services\n const { getFormMetadata } = formsService\n\n // Warn the user if the form has no notification email set only on start page and summary page\n if ([startPath, summaryPath].includes(path) && !isForceAccess) {\n const { notificationEmail } = await getFormMetadata(params.slug)\n return !notificationEmail\n }\n\n return false\n }\n\n /**\n * Get the back link for a given progress.\n */\n protected getBackLink(\n request: FormContextRequest,\n context: FormContext\n ): BackLink | undefined {\n const { pageDef } = this\n const { path, query } = request\n const { returnUrl } = query\n const { paths } = context\n\n const itemId = this.getItemId(request)\n\n // Check answers back link\n if (returnUrl) {\n return {\n text:\n hasRepeater(pageDef) && itemId\n ? 'Go back to add another'\n : 'Go back to check answers',\n href: returnUrl\n }\n }\n\n // Item delete pages etc\n const backPath =\n itemId && !path.endsWith(itemId)\n ? paths.at(-1) // Back to main page\n : paths.at(-2) // Back to previous page\n\n // No back link\n if (!backPath) {\n return\n }\n\n // Default back link\n return {\n text: 'Back',\n href: this.getHref(backPath)\n }\n }\n\n makePostRouteHandler() {\n return async (\n request: FormRequestPayload,\n context: FormContext,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) => {\n const { collection, viewName, model } = this\n const { isForceAccess, state, evaluationState } = context\n\n /**\n * If there are any errors, render the page with the parsed errors\n * @todo Refactor to match POST REDIRECT GET pattern\n */\n if (context.errors || isForceAccess) {\n const viewModel = this.getViewModel(request, context)\n viewModel.errors = collection.getErrors(viewModel.errors)\n\n // Filter our components based on their conditions using our evaluated state\n viewModel.components = this.filterConditionalComponents(\n viewModel,\n model,\n evaluationState\n )\n\n return h.view(viewName, viewModel)\n }\n\n // Save and proceed\n await this.setState(request, state)\n return this.proceed(request, h, this.getNextPath(context))\n }\n }\n\n proceed(\n request: FormContextRequest,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>,\n nextPath?: string\n ) {\n const nextUrl = nextPath\n ? this.getHref(nextPath) // Redirect to next page\n : this.href // Redirect to current page (refresh)\n\n return proceed(request, h, nextUrl)\n }\n\n /**\n * {@link https://hapi.dev/api/?v=20.1.2#route-options}\n */\n get getRouteOptions(): RouteOptions<FormRequestRefs> {\n return {\n ext: {\n onPostHandler: {\n method(_request, h) {\n return h.continue\n }\n }\n }\n }\n }\n\n /**\n * {@link https://hapi.dev/api/?v=20.1.2#route-options}\n */\n get postRouteOptions(): RouteOptions<FormRequestPayloadRefs> {\n return {\n payload: {\n parse: true,\n maxBytes: Number.MAX_SAFE_INTEGER,\n failAction: 'ignore'\n },\n ext: {\n onPostHandler: {\n method(_request, h) {\n return h.continue\n }\n }\n }\n }\n }\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"QuestionPageController.js","names":["ComponentType","ControllerType","Engine","hasComponents","hasNext","hasRepeater","ComponentCollection","optionalText","getCacheService","getErrors","normalisePath","proceed","PageController","actionSchema","crumbSchema","paramsSchema","merge","QuestionPageController","collection","errorSummaryTitle","constructor","model","pageDef","components","page","formSchema","keys","crumb","action","next","def","filter","path","linkPath","pages","some","pagePath","allowContinue","engine","V2","controller","Terminal","length","getItemId","request","itemId","getFormParams","params","getViewModel","context","viewModel","query","payload","errors","pageTitle","showTitle","formComponents","isFormComponent","fieldset","label","isPageHeading","labelOrLegend","legend","size","classes","isOptional","fields","at","options","required","text","backLink","getBackLink","getRelevantPath","paths","startPath","getStartPath","relevantPath","getNextPath","evaluationState","summaryPath","getSummaryPath","statusPath","getStatusPath","defaultPath","undefined","pageIndex","indexOf","nextPage","slice","find","condition","conditionResult","fn","nextLink","link","conditions","getFormDataFromState","state","result","validate","abortEarly","stripUnknown","value","getStateFromValidForm","details","getState","cacheService","server","setState","mergeState","update","updated","filterConditionalComponents","filtered","component","content","type","Details","map","evaluatedComponent","Array","isArray","item","items","makeGetRouteHandler","h","viewName","getViewErrors","hasMissingNotificationEmail","view","isForceAccess","formsService","services","getFormMetadata","includes","notificationEmail","slug","returnUrl","href","backPath","endsWith","getHref","makePostRouteHandler","nextPath","nextUrl","getRouteOptions","ext","onPostHandler","method","_request","continue","postRouteOptions","parse","maxBytes","Number","MAX_SAFE_INTEGER","failAction"],"sources":["../../../../../src/server/plugins/engine/pageControllers/QuestionPageController.ts"],"sourcesContent":["import {\n ComponentType,\n ControllerType,\n Engine,\n hasComponents,\n hasNext,\n hasRepeater,\n type Link,\n type Page\n} from '@defra/forms-model'\nimport { type ResponseToolkit, type RouteOptions } from '@hapi/hapi'\nimport { type ValidationErrorItem } from 'joi'\n\nimport { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'\nimport { optionalText } from '~/src/server/plugins/engine/components/constants.js'\nimport { type BackLink } from '~/src/server/plugins/engine/components/types.js'\nimport {\n getCacheService,\n getErrors,\n normalisePath,\n proceed\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport {\n type FormContext,\n type FormContextRequest,\n type FormPageViewModel,\n type FormParams,\n type FormPayload,\n type FormState,\n type FormStateValue,\n type FormSubmissionState\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequest,\n type FormRequestPayload,\n type FormRequestPayloadRefs,\n type FormRequestRefs\n} from '~/src/server/routes/types.js'\nimport {\n actionSchema,\n crumbSchema,\n paramsSchema\n} from '~/src/server/schemas/index.js'\nimport { merge } from '~/src/server/services/cacheService.js'\n\nexport class QuestionPageController extends PageController {\n collection: ComponentCollection\n errorSummaryTitle = 'There is a problem'\n\n constructor(model: FormModel, pageDef: Page) {\n super(model, pageDef)\n\n // Components collection\n this.collection = new ComponentCollection(\n hasComponents(pageDef) ? pageDef.components : [],\n { model, page: this }\n )\n\n this.collection.formSchema = this.collection.formSchema.keys({\n crumb: crumbSchema,\n action: actionSchema\n })\n }\n\n get next(): Link[] {\n const { def, pageDef } = this\n\n if (!hasNext(pageDef)) {\n return []\n }\n\n // Remove stale links\n return pageDef.next.filter(({ path }) => {\n const linkPath = normalisePath(path)\n\n return def.pages.some((page) => {\n const pagePath = normalisePath(page.path)\n return pagePath === linkPath\n })\n })\n }\n\n get allowContinue(): boolean {\n if (this.model.engine === Engine.V2) {\n return this.pageDef.controller !== ControllerType.Terminal\n }\n\n return this.next.length > 0\n }\n\n getItemId(request?: FormContextRequest) {\n const { itemId } = this.getFormParams(request)\n return itemId ?? request?.params.itemId\n }\n\n /**\n * Used for mapping form payloads and errors to govuk-frontend's template api, so a page can be rendered\n * @param request - the hapi request\n * @param context - the form context\n */\n getViewModel(\n request: FormContextRequest,\n context: FormContext\n ): FormPageViewModel {\n const { collection, viewModel } = this\n const { query } = request\n const { payload, errors } = context\n\n let { pageTitle, showTitle } = viewModel\n\n const components = collection.getViewModel(payload, errors, query)\n const formComponents = components.filter(\n ({ isFormComponent }) => isFormComponent\n )\n\n // Single form component? Hide title and customise label or legend instead\n if (formComponents.length === 1) {\n const { model } = formComponents[0]\n const { fieldset, label } = model\n\n // Set as page heading when not following other content\n const isPageHeading = formComponents[0] === components[0]\n\n // Check for legend or label\n const labelOrLegend = fieldset?.legend ?? label\n\n // Use legend or label as page heading\n if (labelOrLegend) {\n const size = isPageHeading ? 'l' : 'm'\n\n labelOrLegend.classes =\n labelOrLegend === label\n ? `govuk-label--${size}`\n : `govuk-fieldset__legend--${size}`\n\n if (isPageHeading) {\n labelOrLegend.isPageHeading = isPageHeading\n\n // Check for optional in label\n const isOptional =\n this.collection.fields.at(0)?.options.required === false\n\n if (pageTitle) {\n labelOrLegend.text = isOptional\n ? `${pageTitle}${optionalText}`\n : pageTitle\n }\n\n pageTitle = pageTitle || labelOrLegend.text\n }\n }\n\n showTitle = !isPageHeading\n } else if (formComponents.length > 1) {\n // When there is more than one form component,\n // adjust the label/legends to give equal prominence\n for (const { model } of formComponents) {\n if (model.fieldset?.legend) {\n model.fieldset.legend.classes = 'govuk-fieldset__legend--m'\n }\n if (model.label) {\n model.label.classes = 'govuk-label--m'\n }\n }\n }\n\n return {\n ...viewModel,\n backLink: this.getBackLink(request, context),\n context,\n showTitle,\n components,\n errors\n }\n }\n\n getRelevantPath(\n request: FormRequest | FormRequestPayload,\n context: FormContext\n ) {\n const { paths } = context\n\n const startPath = this.getStartPath()\n const relevantPath = paths.at(-1) ?? startPath\n\n return !paths.length\n ? startPath // First possible path\n : relevantPath // Last possible path\n }\n\n /**\n * Apply conditions to evaluation state to determine next page path\n */\n getNextPath(context: FormContext) {\n const { model, next, path } = this\n const { evaluationState } = context\n\n const summaryPath = this.getSummaryPath()\n const statusPath = this.getStatusPath()\n\n // Walk from summary page (no next links) to status page\n let defaultPath = path === summaryPath ? statusPath : undefined\n\n if (model.engine === Engine.V2) {\n if (this.pageDef.controller !== ControllerType.Terminal) {\n const { pages } = this.model\n const pageIndex = pages.indexOf(this)\n\n // The \"next\" page is the first found after the current which is\n // either unconditional or has a condition that evaluates to \"true\"\n const nextPage = pages.slice(pageIndex + 1).find((page) => {\n const { condition } = page\n\n if (condition) {\n const conditionResult = condition.fn(evaluationState)\n\n if (!conditionResult) {\n return false\n }\n }\n\n return true\n })\n\n return nextPage?.path ?? defaultPath\n } else {\n return defaultPath\n }\n }\n\n const nextLink = next.find((link) => {\n const { condition } = link\n\n if (condition) {\n return model.conditions[condition]?.fn(evaluationState) ?? false\n }\n\n defaultPath = link.path\n return false\n })\n\n return nextLink?.path ?? defaultPath\n }\n\n /**\n * Gets the form payload (from state) for this page only\n */\n getFormDataFromState(\n request: FormContextRequest | undefined,\n state: FormSubmissionState\n ): FormPayload {\n const { collection } = this\n\n // Form params from request\n const params = this.getFormParams(request)\n\n // Form payload from state\n const payload = collection.getFormDataFromState(state)\n\n return {\n ...params,\n ...payload\n }\n }\n\n /**\n * Gets form params (from payload) for this page only\n */\n getFormParams(request?: FormContextRequest): FormParams {\n const { payload } = request ?? {}\n\n const result = paramsSchema.validate(payload, {\n abortEarly: false,\n stripUnknown: true\n })\n\n return result.value as FormParams\n }\n\n getStateFromValidForm(\n request: FormContextRequest,\n state: FormSubmissionState,\n payload: FormPayload\n ): FormState {\n return this.collection.getStateFromValidForm(payload)\n }\n\n getErrors(details?: ValidationErrorItem[]) {\n return getErrors(details)\n }\n\n async getState(request: FormRequest | FormRequestPayload) {\n const { query } = request\n\n // Skip get for preview URL direct access\n if ('force' in query) {\n return {}\n }\n\n const cacheService = getCacheService(request.server)\n\n return cacheService.getState(request)\n }\n\n async setState(\n request: FormRequest | FormRequestPayload,\n state: FormSubmissionState\n ) {\n const { query } = request\n\n // Skip set for preview URL direct access\n if ('force' in query) {\n return state\n }\n\n const cacheService = getCacheService(request.server)\n\n return cacheService.setState(request, state)\n }\n\n async mergeState(\n request: FormRequest | FormRequestPayload,\n state: FormSubmissionState,\n update: object\n ) {\n const { query } = request\n\n // Merge state before set\n const updated = merge(state, update)\n\n // Skip set for preview URL direct access\n if ('force' in query) {\n return updated\n }\n\n const cacheService = getCacheService(request.server)\n\n return cacheService.setState(request, updated)\n }\n\n filterConditionalComponents(\n viewModel: FormPageViewModel,\n model: FormModel,\n evaluationState: Partial<Record<string, FormStateValue>>\n ) {\n // Filter our components based on their conditions using our evaluated state\n let filtered = viewModel.components.filter((component) => {\n if (\n (!!component.model.content ||\n component.type === ComponentType.Details) &&\n component.model.condition\n ) {\n const condition = model.conditions[component.model.condition]\n return condition?.fn(evaluationState)\n }\n return true\n })\n\n /**\n * For conditional reveal components (which we no longer support until GDS resolves the related accessibility issues {@link https://github.com/alphagov/govuk-frontend/issues/1991}\n */\n filtered = filtered.map((component) => {\n const evaluatedComponent = component\n const content = evaluatedComponent.model.content\n if (Array.isArray(content)) {\n evaluatedComponent.model.content = content.filter((item) =>\n item.condition\n ? model.conditions[item.condition]?.fn(evaluationState)\n : true\n )\n }\n // apply condition to items for radios, checkboxes etc\n const items = evaluatedComponent.model.items\n\n if (Array.isArray(items)) {\n evaluatedComponent.model.items = items.filter((item) =>\n item.condition\n ? model.conditions[item.condition]?.fn(evaluationState)\n : true\n )\n }\n\n return evaluatedComponent\n })\n\n return filtered\n }\n\n makeGetRouteHandler() {\n return async (\n request: FormRequest,\n context: FormContext,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) => {\n const { collection, model, viewName } = this\n const { evaluationState } = context\n\n const viewModel = this.getViewModel(request, context)\n viewModel.errors = collection.getViewErrors(viewModel.errors)\n\n /**\n * Content components can be hidden based on a condition. If the condition evaluates to true, it is safe to be kept, otherwise discard it\n */\n\n // Filter our components based on their conditions using our evaluated state\n viewModel.components = this.filterConditionalComponents(\n viewModel,\n model,\n evaluationState\n )\n\n viewModel.hasMissingNotificationEmail =\n await this.hasMissingNotificationEmail(request, context)\n\n return h.view(viewName, viewModel)\n }\n }\n\n async hasMissingNotificationEmail(\n request: FormRequest,\n context: FormContext\n ) {\n const { path } = this\n const { params } = request\n const { isForceAccess } = context\n\n const startPath = this.getStartPath()\n const summaryPath = this.getSummaryPath()\n const { formsService } = this.model.services\n const { getFormMetadata } = formsService\n\n // Warn the user if the form has no notification email set only on start page and summary page\n if ([startPath, summaryPath].includes(path) && !isForceAccess) {\n const { notificationEmail } = await getFormMetadata(params.slug)\n return !notificationEmail\n }\n\n return false\n }\n\n /**\n * Get the back link for a given progress.\n */\n protected getBackLink(\n request: FormContextRequest,\n context: FormContext\n ): BackLink | undefined {\n const { pageDef } = this\n const { path, query } = request\n const { returnUrl } = query\n const { paths } = context\n\n const itemId = this.getItemId(request)\n\n // Check answers back link\n if (returnUrl) {\n return {\n text:\n hasRepeater(pageDef) && itemId\n ? 'Go back to add another'\n : 'Go back to check answers',\n href: returnUrl\n }\n }\n\n // Item delete pages etc\n const backPath =\n itemId && !path.endsWith(itemId)\n ? paths.at(-1) // Back to main page\n : paths.at(-2) // Back to previous page\n\n // No back link\n if (!backPath) {\n return\n }\n\n // Default back link\n return {\n text: 'Back',\n href: this.getHref(backPath)\n }\n }\n\n makePostRouteHandler() {\n return async (\n request: FormRequestPayload,\n context: FormContext,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>\n ) => {\n const { collection, viewName, model } = this\n const { isForceAccess, state, evaluationState } = context\n\n /**\n * If there are any errors, render the page with the parsed errors\n * @todo Refactor to match POST REDIRECT GET pattern\n */\n if (context.errors || isForceAccess) {\n const viewModel = this.getViewModel(request, context)\n viewModel.errors = collection.getViewErrors(viewModel.errors)\n\n // Filter our components based on their conditions using our evaluated state\n viewModel.components = this.filterConditionalComponents(\n viewModel,\n model,\n evaluationState\n )\n\n return h.view(viewName, viewModel)\n }\n\n // Save and proceed\n await this.setState(request, state)\n return this.proceed(request, h, this.getNextPath(context))\n }\n }\n\n proceed(\n request: FormContextRequest,\n h: Pick<ResponseToolkit, 'redirect' | 'view'>,\n nextPath?: string\n ) {\n const nextUrl = nextPath\n ? this.getHref(nextPath) // Redirect to next page\n : this.href // Redirect to current page (refresh)\n\n return proceed(request, h, nextUrl)\n }\n\n /**\n * {@link https://hapi.dev/api/?v=20.1.2#route-options}\n */\n get getRouteOptions(): RouteOptions<FormRequestRefs> {\n return {\n ext: {\n onPostHandler: {\n method(_request, h) {\n return h.continue\n }\n }\n }\n }\n }\n\n /**\n * {@link https://hapi.dev/api/?v=20.1.2#route-options}\n */\n get postRouteOptions(): RouteOptions<FormRequestPayloadRefs> {\n return {\n payload: {\n parse: true,\n maxBytes: Number.MAX_SAFE_INTEGER,\n failAction: 'ignore'\n },\n ext: {\n onPostHandler: {\n method(_request, h) {\n return h.continue\n }\n }\n }\n }\n }\n}\n"],"mappings":"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","ignoreList":[]}
@@ -1,4 +1,6 @@
1
+ import { hasComponentsEvenIfNoNext } from '@defra/forms-model';
1
2
  import Boom from '@hapi/boom';
3
+ import { ComponentCollection } from "../components/ComponentCollection.js";
2
4
  import { FileUploadField } from "../components/FileUploadField.js";
3
5
  import { getAnswer } from "../components/helpers.js";
4
6
  import { checkEmailAddressForLiveFormSubmission, checkFormStatus, getCacheService } from "../helpers.js";
@@ -12,15 +14,30 @@ export class SummaryPageController extends QuestionPageController {
12
14
  constructor(model, pageDef) {
13
15
  super(model, pageDef);
14
16
  this.viewName = 'summary';
17
+
18
+ // Components collection
19
+ this.collection = new ComponentCollection(hasComponentsEvenIfNoNext(pageDef) ? pageDef.components : [], {
20
+ model,
21
+ page: this
22
+ });
15
23
  }
16
24
  getSummaryViewModel(request, context) {
17
25
  const viewModel = new SummaryViewModel(request, this, context);
26
+ const {
27
+ query
28
+ } = request;
29
+ const {
30
+ payload,
31
+ errors
32
+ } = context;
33
+ const components = this.collection.getViewModel(payload, errors, query);
18
34
 
19
35
  // We already figure these out in the base page controller. Take them and apply them to our page-specific model.
20
36
  // This is a stop-gap until we can add proper inheritance in place.
21
37
  viewModel.backLink = this.getBackLink(request, context);
22
38
  viewModel.feedbackLink = this.feedbackLink;
23
39
  viewModel.phaseTag = this.phaseTag;
40
+ viewModel.components = components;
24
41
  return viewModel;
25
42
  }
26
43
 
@@ -67,7 +84,7 @@ export class SummaryPageController extends QuestionPageController {
67
84
  } = await getFormMetadata(params.slug);
68
85
  const {
69
86
  isPreview
70
- } = checkFormStatus(request.path);
87
+ } = checkFormStatus(request.params);
71
88
  const emailAddress = notificationEmail ?? this.model.def.outputEmail;
72
89
  checkEmailAddressForLiveFormSubmission(emailAddress, isPreview);
73
90
 
@@ -99,10 +116,7 @@ export class SummaryPageController extends QuestionPageController {
99
116
  }
100
117
  async function submitForm(request, summaryViewModel, model, state, emailAddress) {
101
118
  await extendFileRetention(model, state, emailAddress);
102
- const {
103
- path
104
- } = request;
105
- const formStatus = checkFormStatus(path);
119
+ const formStatus = checkFormStatus(request.params);
106
120
  const logTags = ['submit', 'submissionApi'];
107
121
  request.logger.info(logTags, 'Preparing email', formStatus);
108
122