@defra/forms-engine-plugin 0.1.11 → 0.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.public/javascripts/file-upload.min.js +1 -1
- package/.public/javascripts/file-upload.min.js.map +1 -1
- package/.server/client/javascripts/file-upload.js +45 -4
- package/.server/client/javascripts/file-upload.js.map +1 -1
- package/.server/server/constants.js +2 -0
- package/.server/server/constants.js.map +1 -1
- package/.server/server/index.js +1 -1
- package/.server/server/index.js.map +1 -1
- package/.server/server/plugins/engine/components/AutocompleteField.js +2 -0
- package/.server/server/plugins/engine/components/AutocompleteField.js.map +1 -1
- package/.server/server/plugins/engine/components/CheckboxesField.js +3 -4
- package/.server/server/plugins/engine/components/CheckboxesField.js.map +1 -1
- package/.server/server/plugins/engine/components/ComponentCollection.js +37 -16
- package/.server/server/plugins/engine/components/ComponentCollection.js.map +1 -1
- package/.server/server/plugins/engine/components/DatePartsField.js +36 -2
- package/.server/server/plugins/engine/components/DatePartsField.js.map +1 -1
- package/.server/server/plugins/engine/components/EmailAddressField.js +19 -3
- package/.server/server/plugins/engine/components/EmailAddressField.js.map +1 -1
- package/.server/server/plugins/engine/components/FileUploadField.js +44 -4
- package/.server/server/plugins/engine/components/FileUploadField.js.map +1 -1
- package/.server/server/plugins/engine/components/FormComponent.js +14 -2
- package/.server/server/plugins/engine/components/FormComponent.js.map +1 -1
- package/.server/server/plugins/engine/components/ListFormComponent.js +16 -3
- package/.server/server/plugins/engine/components/ListFormComponent.js.map +1 -1
- package/.server/server/plugins/engine/components/Markdown.js +24 -0
- package/.server/server/plugins/engine/components/Markdown.js.map +1 -0
- package/.server/server/plugins/engine/components/MonthYearField.js +30 -2
- package/.server/server/plugins/engine/components/MonthYearField.js.map +1 -1
- package/.server/server/plugins/engine/components/MultilineTextField.js +32 -3
- package/.server/server/plugins/engine/components/MultilineTextField.js.map +1 -1
- package/.server/server/plugins/engine/components/NumberField.js +28 -3
- package/.server/server/plugins/engine/components/NumberField.js.map +1 -1
- package/.server/server/plugins/engine/components/SelectionControlField.js +14 -0
- package/.server/server/plugins/engine/components/SelectionControlField.js.map +1 -1
- package/.server/server/plugins/engine/components/TelephoneNumberField.js +19 -3
- package/.server/server/plugins/engine/components/TelephoneNumberField.js.map +1 -1
- package/.server/server/plugins/engine/components/TextField.js +22 -3
- package/.server/server/plugins/engine/components/TextField.js.map +1 -1
- package/.server/server/plugins/engine/components/UkAddressField.js +29 -0
- package/.server/server/plugins/engine/components/UkAddressField.js.map +1 -1
- package/.server/server/plugins/engine/components/YesNoField.js +18 -0
- package/.server/server/plugins/engine/components/YesNoField.js.map +1 -1
- package/.server/server/plugins/engine/components/helpers.js +16 -0
- package/.server/server/plugins/engine/components/helpers.js.map +1 -1
- package/.server/server/plugins/engine/components/index.js +1 -0
- package/.server/server/plugins/engine/components/index.js.map +1 -1
- package/.server/server/plugins/engine/configureEnginePlugin.js +3 -1
- package/.server/server/plugins/engine/configureEnginePlugin.js.map +1 -1
- package/.server/server/plugins/engine/helpers.js +38 -18
- package/.server/server/plugins/engine/helpers.js.map +1 -1
- package/.server/server/plugins/engine/models/FormModel.js +60 -2
- package/.server/server/plugins/engine/models/FormModel.js.map +1 -1
- package/.server/server/plugins/engine/models/SummaryViewModel.js +3 -2
- package/.server/server/plugins/engine/models/SummaryViewModel.js.map +1 -1
- package/.server/server/plugins/engine/outputFormatters/human/v1.js +1 -1
- package/.server/server/plugins/engine/outputFormatters/human/v1.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/PageController.js +13 -5
- package/.server/server/plugins/engine/pageControllers/PageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/QuestionPageController.js +2 -2
- package/.server/server/plugins/engine/pageControllers/QuestionPageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/SummaryPageController.js +19 -5
- package/.server/server/plugins/engine/pageControllers/SummaryPageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/validationOptions.js +6 -11
- package/.server/server/plugins/engine/pageControllers/validationOptions.js.map +1 -1
- package/.server/server/plugins/engine/plugin.js +5 -4
- package/.server/server/plugins/engine/plugin.js.map +1 -1
- package/.server/server/plugins/engine/services/notifyService.js +1 -4
- package/.server/server/plugins/engine/services/notifyService.js.map +1 -1
- package/.server/server/plugins/engine/services/uploadService.js +5 -3
- package/.server/server/plugins/engine/services/uploadService.js.map +1 -1
- package/.server/server/plugins/engine/types.js.map +1 -1
- package/.server/server/plugins/engine/views/components/html.html +1 -1
- package/.server/server/plugins/engine/views/components/markdown.html +5 -0
- package/.server/server/plugins/engine/views/summary.html +7 -1
- package/.server/server/plugins/nunjucks/context.js +7 -6
- package/.server/server/plugins/nunjucks/context.js.map +1 -1
- package/.server/server/plugins/nunjucks/enviroment.test.js +6 -3
- package/.server/server/plugins/nunjucks/enviroment.test.js.map +1 -1
- package/.server/server/utils/type-utils.js +8 -0
- package/.server/server/utils/type-utils.js.map +1 -0
- package/.server/typings/joi/index.d.js.map +1 -1
- package/package.json +3 -3
- package/src/client/javascripts/file-upload.js +60 -4
- package/src/server/constants.js +2 -0
- package/src/server/index.test.ts +34 -29
- package/src/server/index.ts +2 -1
- package/src/server/plugins/engine/components/AutocompleteField.test.ts +71 -3
- package/src/server/plugins/engine/components/AutocompleteField.ts +6 -2
- package/src/server/plugins/engine/components/CheckboxesField.test.ts +40 -8
- package/src/server/plugins/engine/components/CheckboxesField.ts +7 -3
- package/src/server/plugins/engine/components/ComponentCollection.ts +45 -18
- package/src/server/plugins/engine/components/DatePartsField.test.ts +13 -4
- package/src/server/plugins/engine/components/DatePartsField.ts +29 -8
- package/src/server/plugins/engine/components/EmailAddressField.test.ts +51 -1
- package/src/server/plugins/engine/components/EmailAddressField.ts +17 -2
- package/src/server/plugins/engine/components/FileUploadField.test.ts +53 -0
- package/src/server/plugins/engine/components/FileUploadField.ts +52 -3
- package/src/server/plugins/engine/components/FormComponent.ts +24 -2
- package/src/server/plugins/engine/components/ListFormComponent.ts +16 -2
- package/src/server/plugins/engine/components/Markdown.test.ts +48 -0
- package/src/server/plugins/engine/components/Markdown.ts +29 -0
- package/src/server/plugins/engine/components/MonthYearField.test.ts +35 -0
- package/src/server/plugins/engine/components/MonthYearField.ts +34 -9
- package/src/server/plugins/engine/components/MultilineTextField.test.ts +83 -5
- package/src/server/plugins/engine/components/MultilineTextField.ts +37 -2
- package/src/server/plugins/engine/components/NumberField.test.ts +24 -2
- package/src/server/plugins/engine/components/NumberField.ts +23 -3
- package/src/server/plugins/engine/components/RadiosField.test.ts +10 -1
- package/src/server/plugins/engine/components/SelectField.test.ts +2 -1
- package/src/server/plugins/engine/components/SelectionControlField.ts +14 -0
- package/src/server/plugins/engine/components/TelephoneNumberField.test.ts +30 -2
- package/src/server/plugins/engine/components/TelephoneNumberField.ts +17 -2
- package/src/server/plugins/engine/components/TextField.test.ts +33 -1
- package/src/server/plugins/engine/components/TextField.ts +17 -2
- package/src/server/plugins/engine/components/UkAddressField.test.ts +46 -3
- package/src/server/plugins/engine/components/UkAddressField.ts +28 -0
- package/src/server/plugins/engine/components/YesNoField.test.ts +9 -1
- package/src/server/plugins/engine/components/YesNoField.ts +24 -0
- package/src/server/plugins/engine/components/helpers.test.ts +24 -0
- package/src/server/plugins/engine/components/helpers.ts +39 -0
- package/src/server/plugins/engine/components/index.ts +1 -0
- package/src/server/plugins/engine/configureEnginePlugin.ts +13 -3
- package/src/server/plugins/engine/helpers.test.ts +71 -20
- package/src/server/plugins/engine/helpers.ts +46 -19
- package/src/server/plugins/engine/models/FormModel.test.ts +91 -1
- package/src/server/plugins/engine/models/FormModel.ts +86 -3
- package/src/server/plugins/engine/models/SummaryViewModel.test.ts +46 -7
- package/src/server/plugins/engine/models/SummaryViewModel.ts +7 -3
- package/src/server/plugins/engine/outputFormatters/human/v1.test.ts +1 -2
- package/src/server/plugins/engine/outputFormatters/human/v1.ts +1 -1
- package/src/server/plugins/engine/pageControllers/FileUploadPageController.test.ts +1 -0
- package/src/server/plugins/engine/pageControllers/PageController.test.ts +9 -6
- package/src/server/plugins/engine/pageControllers/PageController.ts +15 -5
- package/src/server/plugins/engine/pageControllers/QuestionPageController.ts +2 -2
- package/src/server/plugins/engine/pageControllers/SummaryPageController.ts +21 -6
- package/src/server/plugins/engine/pageControllers/validationOptions.ts +31 -17
- package/src/server/plugins/engine/plugin.ts +9 -5
- package/src/server/plugins/engine/services/notifyService.ts +1 -2
- package/src/server/plugins/engine/services/uploadService.js +10 -6
- package/src/server/plugins/engine/types.ts +10 -1
- package/src/server/plugins/engine/views/components/html.html +1 -1
- package/src/server/plugins/engine/views/components/markdown.html +5 -0
- package/src/server/plugins/engine/views/summary.html +7 -1
- package/src/server/plugins/nunjucks/context.js +5 -5
- package/src/server/plugins/nunjucks/enviroment.test.js +9 -3
- package/src/server/utils/type-utils.ts +15 -0
- package/src/typings/joi/index.d.ts +8 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var e={d:(t,n)=>{for(var r in n)e.o(n,r)&&!e.o(t,r)&&Object.defineProperty(t,r,{enumerable:!0,get:n[r]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)},t={};e.d(t,{
|
|
1
|
+
var e={d:(t,n)=>{for(var r in n)e.o(n,r)&&!e.o(t,r)&&Object.defineProperty(t,r,{enumerable:!0,get:n[r]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)},t={};e.d(t,{Ct:()=>n,n1:()=>u,u8:()=>i});const n=300,r="aria-describedby",o="error-summary-title";function l(e,t,n){if(document.querySelector(".govuk-error-summary"))return void(document.getElementById(o)?n.setAttribute(r,o):n.removeAttribute(r));t&&(t.innerHTML=`\n <div class="govuk-error-summary" data-module="govuk-error-summary">\n <div role="alert">\n <h2 class="govuk-error-summary__title" id="${o}">\n There is a problem\n </h2>\n <div class="govuk-error-summary__body">\n <ul class="govuk-list govuk-error-summary__list">\n <li>\n <a href="#file-upload">${e}</a>\n </li>\n </ul>\n </div>\n </div>\n </div>\n `,n.setAttribute(r,o));const l=n.closest(".govuk-form-group");if(l){l.classList.add("govuk-form-group--error"),n.classList.add("govuk-file-upload--error");const t=n.id;let o=document.getElementById(`${t}-error`);o||(o=document.createElement("p"),o.id=`${t}-error`,o.className="govuk-error-message",o.innerHTML=`<span class="govuk-visually-hidden">Error:</span> ${e}`,l.insertBefore(o,n)),n.setAttribute(r,`error-summary-title ${t}-error`)}}function a(){window.history.replaceState(null,"",window.location.href),window.location.href=window.location.pathname}function u(e,t){const n=e.split("/").filter((e=>e));return`${n.length>0?`/${n[0]}`:""}/upload-status/${t}`}function i(){var e;const t=document.querySelector('form:has(input[type="file"])'),o=t?t.querySelector('input[type="file"]'):null,i=t?t.querySelector(".upload-file-button"):null,s=null!=(e=Array.from(document.querySelectorAll("button.govuk-button")).find((e=>{var t;return"Continue"===(null==(t=e.textContent)?void 0:t.trim())})))?e:null,d=document.querySelector(".govuk-error-summary-container");if(!t||!o||!i)return;const c=t;let m=null,v=!1;const f=c.dataset.uploadId;o.addEventListener("change",(()=>{d&&(d.innerHTML=""),o.files&&o.files.length>0&&(m=o.files[0])})),i.addEventListener("click",(e=>{if(!m)return e.preventDefault(),void l("Select a file",d,o);v?e.preventDefault():(v=!0,function(e,t,n,o,l){(function(e,t,n){var o;const l=document.getElementById("uploadedFilesContainer"),a=l?l.closest("form"):null;if(!(a&&a instanceof HTMLFormElement))return;const u=a.querySelector("p.govuk-body");if(!u)return;const i=function(e,t){let n=null==e?void 0:e.querySelector("#statusInformation");if(!n){n=document.createElement("div"),n.id="statusInformation",n.className="govuk-visually-hidden",n.setAttribute("aria-live","polite");try{!function(e,t,n){var r;null!=t&&t.nextSibling&&t.parentNode===e?e.insertBefore(n,t.nextSibling):(null!=(r=null==t?void 0:t.parentNode)?r:e).appendChild(n)}(e,t,n)}catch(r){try{null==e||e.appendChild(n)}catch(o){document.body.appendChild(n)}}}return n}(a,u),s=n.querySelector('input[type="file"]');s&&s.setAttribute(r,"statusInformation");const d=function(e,t){let n=e.querySelector("dl.govuk-summary-list");if(!n){n=document.createElement("dl"),n.className="govuk-summary-list govuk-summary-list--long-key";const r=e.querySelector(".govuk-button");r?e.insertBefore(n,r):e.insertBefore(n,t.nextSibling)}return n}(a,u),c=document.querySelector(`[data-filename="${null==e?void 0:e.name}"]`);c&&c.remove();const m=function(e,t){var n,r;const o=document.createElement("div");return o.className="govuk-summary-list__row",o.setAttribute("data-filename",null!=(n=null==e?void 0:e.name)?n:""),o.innerHTML=`\n <dt class="govuk-summary-list__key">\n ${null!=(r=null==e?void 0:e.name)?r:""}\n </dt>\n <dd class="govuk-summary-list__value">\n <strong class="govuk-tag govuk-tag--yellow">${t}</strong>\n </dd>\n <dd class="govuk-summary-list__actions">\n </dd>\n `,o}(e,t);d.insertBefore(m,d.firstChild),i.textContent=`${null!=(o=null==e?void 0:e.name)?o:""} ${t}`})(l,"Uploading…",e),t.focus(),setTimeout((()=>{t.disabled=!0,n.disabled=!0,o.disabled=!0}),100)}(c,o,i,s,m),function(e,t,r,o,i,s){var d;if(!t.action||!s)return!1;e.preventDefault();const c=new FormData(t),m=!!t.dataset.proxyUrl,v=null!=(d=t.dataset.proxyUrl)?d:t.action,f={method:"POST",body:c,redirect:m?"follow":"manual"};m&&(f.mode="no-cors"),fetch(v,f).then((()=>{!function(e){let t=0;const r=setInterval((()=>{if(t++,t>=n)return clearInterval(r),void a();const o=u(window.location.pathname,e);fetch(o,{headers:{Accept:"application/json"}}).then((e=>{if(!e.ok)throw new Error("Network response was not ok");return e.json()})).then((e=>{"ready"===e.uploadStatus&&(clearInterval(r),a())})).catch((()=>{clearInterval(r),a()}))}),1e3)}(s)})).catch((()=>(r.disabled=!1,o.disabled=!1,l("There was a problem uploading the file",i,r),null)))}(e,c,o,i,d,f))}))}var s=t.Ct,d=t.n1,c=t.u8;export{s as MAX_POLLING_DURATION,d as buildUploadStatusUrl,c as initFileUpload};
|
|
2
2
|
//# sourceMappingURL=file-upload.min.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"javascripts/file-upload.min.js","mappings":"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","sources":["webpack:///webpack/bootstrap","webpack:///webpack/runtime/define property getters","webpack:///webpack/runtime/hasOwnProperty shorthand","webpack:///./javascripts/file-upload.js"],"sourcesContent":["// The require scope\nvar __webpack_require__ = {};\n\n","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","export const MAX_POLLING_DURATION = 300 // 5 minutes\n\n/**\n * Creates or updates status announcer for screen readers\n * @param {HTMLElement | null} form - The form element\n * @param {HTMLElement | null} fileCountP - The file count paragraph element\n * @returns {HTMLElement} The status announcer element\n */\nfunction createOrUpdateStatusAnnouncer(form, fileCountP) {\n let statusAnnouncer = form?.querySelector('#statusInformation')\n\n if (!statusAnnouncer) {\n statusAnnouncer = document.createElement('div')\n statusAnnouncer.id = 'statusInformation'\n statusAnnouncer.className = 'govuk-visually-hidden'\n statusAnnouncer.setAttribute('aria-live', 'polite')\n\n // multiple fallbacks to ensure the status announcer is always added to the DOM\n // this helps with cross-browser compatibility and unexpected DOM structures encountered during QA\n try {\n addStatusAnnouncerToDOM(\n asHTMLElement(form),\n asHTMLElement(fileCountP),\n asHTMLElement(statusAnnouncer)\n )\n } catch {\n try {\n form?.appendChild(statusAnnouncer)\n } catch {\n document.body.appendChild(statusAnnouncer)\n }\n }\n }\n\n return /** @type {HTMLElement} */ (statusAnnouncer)\n}\n\n/**\n * Helper function to add the status announcer to the DOM\n * @param {HTMLElement} form - The form element\n * @param {HTMLElement | null} fileCountP - The file count paragraph element\n * @param {HTMLElement} statusAnnouncer - The status announcer element to add\n */\nfunction addStatusAnnouncerToDOM(form, fileCountP, statusAnnouncer) {\n if (fileCountP?.nextSibling && fileCountP.parentNode === form) {\n form.insertBefore(statusAnnouncer, fileCountP.nextSibling)\n return\n }\n\n const parentElement = fileCountP?.parentNode ?? form\n parentElement.appendChild(statusAnnouncer)\n}\n\n/**\n * Finds an existing summary list or creates a new one\n * @param {HTMLFormElement} form - The form element\n * @param {HTMLElement} fileCountP - The file count paragraph element\n * @returns {HTMLElement} The summary list element\n */\nfunction findOrCreateSummaryList(form, fileCountP) {\n let summaryList = form.querySelector('dl.govuk-summary-list')\n\n if (!summaryList) {\n summaryList = document.createElement('dl')\n summaryList.className = 'govuk-summary-list govuk-summary-list--long-key'\n\n const continueButton = form.querySelector('.govuk-button')\n\n if (continueButton) {\n form.insertBefore(summaryList, continueButton)\n } else {\n form.insertBefore(summaryList, fileCountP.nextSibling)\n }\n }\n\n return /** @type {HTMLElement} */ (summaryList)\n}\n\n/**\n * Creates a file row element for the summary list\n * @param {File | null} selectedFile - The selected file\n * @param {string} statusText - The status to display\n * @returns {HTMLElement} The created row element\n */\nfunction createFileRow(selectedFile, statusText) {\n const row = document.createElement('div')\n row.className = 'govuk-summary-list__row'\n row.setAttribute('data-filename', selectedFile?.name ?? '')\n row.innerHTML = `\n <dt class=\"govuk-summary-list__key\">\n ${selectedFile?.name ?? ''}\n </dt>\n <dd class=\"govuk-summary-list__value\">\n <strong class=\"govuk-tag govuk-tag--yellow\">${statusText}</strong>\n </dd>\n <dd class=\"govuk-summary-list__actions\">\n </dd>\n `\n return row\n}\n\n/**\n * Renders or updates the file summary box for the selected file\n * @param {File | null} selectedFile - The selected file\n * @param {string} statusText - The status to display\n * @param {HTMLElement} form - The form element\n */\nfunction renderSummary(selectedFile, statusText, form) {\n const container = document.getElementById('uploadedFilesContainer')\n const uploadForm = container ? container.closest('form') : null\n\n if (!uploadForm || !(uploadForm instanceof HTMLFormElement)) {\n return\n }\n\n const fileCountP = uploadForm.querySelector('p.govuk-body')\n\n if (!fileCountP) {\n return\n }\n\n const statusAnnouncer = createOrUpdateStatusAnnouncer(\n /** @type {HTMLElement} */ (uploadForm),\n /** @type {HTMLElement | null} */ (fileCountP)\n )\n\n const fileInput = form.querySelector('input[type=\"file\"]')\n\n if (fileInput) {\n fileInput.setAttribute('aria-describedby', 'statusInformation')\n }\n\n const summaryList = findOrCreateSummaryList(\n /** @type {HTMLFormElement} */ (uploadForm),\n /** @type {HTMLElement} */ (fileCountP)\n )\n\n const existingRow = document.querySelector(\n `[data-filename=\"${selectedFile?.name}\"]`\n )\n\n if (existingRow) {\n existingRow.remove()\n }\n\n const row = createFileRow(selectedFile, statusText)\n summaryList.insertBefore(row, summaryList.firstChild)\n statusAnnouncer.textContent = `${selectedFile?.name ?? ''} ${statusText}`\n}\n\n/**\n * Shows an error message using the GOV.UK error summary component\n * @param {string} message - The error message to display\n * @param {HTMLElement | null} errorSummary - The error summary container\n * @param {HTMLInputElement} fileInput - The file input element\n * @returns {void}\n */\nfunction showError(message, errorSummary, fileInput) {\n if (errorSummary) {\n errorSummary.innerHTML = `\n <div class=\"govuk-error-summary\" data-module=\"govuk-error-summary\">\n <div role=\"alert\">\n <h2 class=\"govuk-error-summary__title\" id=\"error-summary-title\">\n There is a problem\n </h2>\n <div class=\"govuk-error-summary__body\">\n <ul class=\"govuk-list govuk-error-summary__list\">\n <li>\n <a href=\"#file-upload\">${message}</a>\n </li>\n </ul>\n </div>\n </div>\n </div>\n `\n fileInput.setAttribute('aria-describedby', 'error-summary-title')\n }\n}\n\n/**\n * Helper to safely convert an Element to HTMLElement\n * @param {Element | null} element - The element to convert\n */\nfunction asHTMLElement(element) {\n return /** @type {HTMLElement} */ (element)\n}\n\nfunction reloadPage() {\n window.history.replaceState(null, '', window.location.href)\n window.location.href = window.location.pathname\n}\n\n/**\n * Polls the upload status endpoint until the file is ready or timeout occurs\n * @param {string} uploadId - The upload ID to check\n */\nfunction pollUploadStatus(uploadId) {\n let attempts = 0\n const interval = setInterval(() => {\n attempts++\n\n if (attempts >= MAX_POLLING_DURATION) {\n clearInterval(interval)\n reloadPage()\n return\n }\n\n fetch(`/upload-status/${uploadId}`, {\n headers: {\n Accept: 'application/json'\n }\n })\n .then((response) => {\n if (!response.ok) {\n throw new Error('Network response was not ok')\n }\n return response.json()\n })\n .then((data) => {\n if (data.uploadStatus === 'ready') {\n clearInterval(interval)\n reloadPage()\n }\n })\n .catch(() => {\n clearInterval(interval)\n reloadPage()\n })\n }, 1000)\n}\n\n/**\n * Handle standard form submission for file upload\n * @param {HTMLFormElement} formElement - The form element\n * @param {HTMLInputElement} fileInput - The file input element\n * @param {HTMLButtonElement} uploadButton - The upload button\n * @param {HTMLButtonElement} continueButton - The continue button\n * @param {File | null} selectedFile - The selected file\n */\nfunction handleStandardFormSubmission(\n formElement,\n fileInput,\n uploadButton,\n continueButton,\n selectedFile\n) {\n renderSummary(selectedFile, 'Uploading…', formElement)\n\n fileInput.focus()\n\n setTimeout(() => {\n fileInput.disabled = true\n uploadButton.disabled = true\n continueButton.disabled = true\n }, 100)\n}\n\n/**\n * Handle AJAX form submission with upload ID\n * @param {Event} event - The click event\n * @param {HTMLFormElement} formElement - The form element\n * @param {HTMLInputElement} fileInput - The file input element\n * @param {HTMLButtonElement} uploadButton - The upload button\n * @param {HTMLElement | null} errorSummary - The error summary container\n * @param {string | undefined} uploadId - The upload ID\n * @returns {boolean} Whether the event was handled\n */\nfunction handleAjaxFormSubmission(\n event,\n formElement,\n fileInput,\n uploadButton,\n errorSummary,\n uploadId\n) {\n if (!formElement.action || !uploadId) {\n return false\n }\n\n event.preventDefault()\n\n const formData = new FormData(formElement)\n const isLocalDev = !!formElement.dataset.proxyUrl\n const uploadUrl = formElement.dataset.proxyUrl ?? formElement.action\n\n const fetchOptions = /** @type {RequestInit} */ ({\n method: 'POST',\n body: formData,\n redirect: isLocalDev ? 'follow' : 'manual' // follow mode if local development with the proxy\n })\n\n // no-cors mode if needed local development with the proxy\n if (isLocalDev) {\n fetchOptions.mode = 'no-cors'\n }\n\n fetch(uploadUrl, fetchOptions)\n .then(() => {\n pollUploadStatus(uploadId)\n })\n .catch(() => {\n fileInput.disabled = false\n uploadButton.disabled = false\n\n showError(\n 'There was a problem uploading the file',\n errorSummary,\n fileInput\n )\n\n return null\n })\n\n return true\n}\n\nexport function initFileUpload() {\n const form = document.querySelector('form:has(input[type=\"file\"])')\n /** @type {HTMLInputElement | null} */\n const fileInput = form ? form.querySelector('input[type=\"file\"]') : null\n /** @type {HTMLButtonElement | null} */\n const uploadButton = form ? form.querySelector('.upload-file-button') : null\n const continueButton =\n /** @type {HTMLButtonElement} */ (\n Array.from(document.querySelectorAll('button.govuk-button')).find(\n (button) => button.textContent?.trim() === 'Continue'\n )\n ) ?? null\n\n const errorSummary = document.querySelector('.govuk-error-summary-container')\n\n if (!form || !fileInput || !uploadButton) {\n return\n }\n\n const formElement = /** @type {HTMLFormElement} */ (form)\n /** @type {File | null} */\n let selectedFile = null\n let isSubmitting = false\n const uploadId = formElement.dataset.uploadId\n\n fileInput.addEventListener('change', () => {\n if (errorSummary) {\n errorSummary.innerHTML = ''\n }\n if (fileInput.files && fileInput.files.length > 0) {\n selectedFile = fileInput.files[0]\n }\n })\n\n uploadButton.addEventListener('click', (event) => {\n if (!selectedFile) {\n event.preventDefault()\n showError(\n 'Select a file',\n /** @type {HTMLElement | null} */ (errorSummary),\n fileInput\n )\n return\n }\n\n if (isSubmitting) {\n event.preventDefault()\n return\n }\n\n isSubmitting = true\n\n handleStandardFormSubmission(\n formElement,\n fileInput,\n uploadButton,\n continueButton,\n selectedFile\n )\n\n handleAjaxFormSubmission(\n event,\n formElement,\n fileInput,\n uploadButton,\n /** @type {HTMLElement | null} */ (errorSummary),\n uploadId\n )\n })\n}\n"],"names":["__webpack_require__","exports","definition","key","o","Object","defineProperty","enumerable","get","obj","prop","prototype","hasOwnProperty","call","MAX_POLLING_DURATION","showError","message","errorSummary","fileInput","innerHTML","setAttribute","reloadPage","window","history","replaceState","location","href","pathname","initFileUpload","_Array$from$find","form","document","querySelector","uploadButton","continueButton","Array","from","querySelectorAll","find","button","_button$textContent","textContent","trim","formElement","selectedFile","isSubmitting","uploadId","dataset","addEventListener","files","length","event","preventDefault","statusText","_selectedFile$name3","container","getElementById","uploadForm","closest","HTMLFormElement","fileCountP","statusAnnouncer","createElement","id","className","_fileCountP$parentNod","nextSibling","parentNode","insertBefore","appendChild","addStatusAnnouncerToDOM","_unused","_unused2","body","createOrUpdateStatusAnnouncer","summaryList","findOrCreateSummaryList","existingRow","name","remove","row","_selectedFile$name","_selectedFile$name2","createFileRow","firstChild","renderSummary","focus","setTimeout","disabled","handleStandardFormSubmission","_formElement$dataset$","action","formData","FormData","isLocalDev","proxyUrl","uploadUrl","fetchOptions","method","redirect","mode","fetch","then","attempts","interval","setInterval","clearInterval","headers","Accept","response","ok","Error","json","data","uploadStatus","catch","pollUploadStatus","handleAjaxFormSubmission"],"sourceRoot":""}
|
|
1
|
+
{"version":3,"file":"javascripts/file-upload.min.js","mappings":"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","sources":["webpack:///webpack/bootstrap","webpack:///webpack/runtime/define property getters","webpack:///webpack/runtime/hasOwnProperty shorthand","webpack:///./javascripts/file-upload.js"],"sourcesContent":["// The require scope\nvar __webpack_require__ = {};\n\n","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","export const MAX_POLLING_DURATION = 300 // 5 minutes\nconst ARIA_DESCRIBEDBY = 'aria-describedby'\nconst ERROR_SUMMARY_TITLE_ID = 'error-summary-title'\n\n/**\n * Creates or updates status announcer for screen readers\n * @param {HTMLElement | null} form - The form element\n * @param {HTMLElement | null} fileCountP - The file count paragraph element\n * @returns {HTMLElement} The status announcer element\n */\nfunction createOrUpdateStatusAnnouncer(form, fileCountP) {\n let statusAnnouncer = form?.querySelector('#statusInformation')\n\n if (!statusAnnouncer) {\n statusAnnouncer = document.createElement('div')\n statusAnnouncer.id = 'statusInformation'\n statusAnnouncer.className = 'govuk-visually-hidden'\n statusAnnouncer.setAttribute('aria-live', 'polite')\n\n // multiple fallbacks to ensure the status announcer is always added to the DOM\n // this helps with cross-browser compatibility and unexpected DOM structures encountered during QA\n try {\n addStatusAnnouncerToDOM(\n asHTMLElement(form),\n asHTMLElement(fileCountP),\n asHTMLElement(statusAnnouncer)\n )\n } catch {\n try {\n form?.appendChild(statusAnnouncer)\n } catch {\n document.body.appendChild(statusAnnouncer)\n }\n }\n }\n\n return /** @type {HTMLElement} */ (statusAnnouncer)\n}\n\n/**\n * Helper function to add the status announcer to the DOM\n * @param {HTMLElement} form - The form element\n * @param {HTMLElement | null} fileCountP - The file count paragraph element\n * @param {HTMLElement} statusAnnouncer - The status announcer element to add\n */\nfunction addStatusAnnouncerToDOM(form, fileCountP, statusAnnouncer) {\n if (fileCountP?.nextSibling && fileCountP.parentNode === form) {\n form.insertBefore(statusAnnouncer, fileCountP.nextSibling)\n return\n }\n\n const parentElement = fileCountP?.parentNode ?? form\n parentElement.appendChild(statusAnnouncer)\n}\n\n/**\n * Finds an existing summary list or creates a new one\n * @param {HTMLFormElement} form - The form element\n * @param {HTMLElement} fileCountP - The file count paragraph element\n * @returns {HTMLElement} The summary list element\n */\nfunction findOrCreateSummaryList(form, fileCountP) {\n let summaryList = form.querySelector('dl.govuk-summary-list')\n\n if (!summaryList) {\n summaryList = document.createElement('dl')\n summaryList.className = 'govuk-summary-list govuk-summary-list--long-key'\n\n const continueButton = form.querySelector('.govuk-button')\n\n if (continueButton) {\n form.insertBefore(summaryList, continueButton)\n } else {\n form.insertBefore(summaryList, fileCountP.nextSibling)\n }\n }\n\n return /** @type {HTMLElement} */ (summaryList)\n}\n\n/**\n * Creates a file row element for the summary list\n * @param {File | null} selectedFile - The selected file\n * @param {string} statusText - The status to display\n * @returns {HTMLElement} The created row element\n */\nfunction createFileRow(selectedFile, statusText) {\n const row = document.createElement('div')\n row.className = 'govuk-summary-list__row'\n row.setAttribute('data-filename', selectedFile?.name ?? '')\n row.innerHTML = `\n <dt class=\"govuk-summary-list__key\">\n ${selectedFile?.name ?? ''}\n </dt>\n <dd class=\"govuk-summary-list__value\">\n <strong class=\"govuk-tag govuk-tag--yellow\">${statusText}</strong>\n </dd>\n <dd class=\"govuk-summary-list__actions\">\n </dd>\n `\n return row\n}\n\n/**\n * Renders or updates the file summary box for the selected file\n * @param {File | null} selectedFile - The selected file\n * @param {string} statusText - The status to display\n * @param {HTMLElement} form - The form element\n */\nfunction renderSummary(selectedFile, statusText, form) {\n const container = document.getElementById('uploadedFilesContainer')\n const uploadForm = container ? container.closest('form') : null\n\n if (!uploadForm || !(uploadForm instanceof HTMLFormElement)) {\n return\n }\n\n const fileCountP = uploadForm.querySelector('p.govuk-body')\n\n if (!fileCountP) {\n return\n }\n\n const statusAnnouncer = createOrUpdateStatusAnnouncer(\n /** @type {HTMLElement} */ (uploadForm),\n /** @type {HTMLElement | null} */ (fileCountP)\n )\n\n const fileInput = form.querySelector('input[type=\"file\"]')\n\n if (fileInput) {\n fileInput.setAttribute(ARIA_DESCRIBEDBY, 'statusInformation')\n }\n\n const summaryList = findOrCreateSummaryList(\n /** @type {HTMLFormElement} */ (uploadForm),\n /** @type {HTMLElement} */ (fileCountP)\n )\n\n const existingRow = document.querySelector(\n `[data-filename=\"${selectedFile?.name}\"]`\n )\n\n if (existingRow) {\n existingRow.remove()\n }\n\n const row = createFileRow(selectedFile, statusText)\n summaryList.insertBefore(row, summaryList.firstChild)\n statusAnnouncer.textContent = `${selectedFile?.name ?? ''} ${statusText}`\n}\n\n/**\n * Shows an error message using the GOV.UK error summary component\n * and adds inline error styling to the file input\n * @param {string} message - The error message to display\n * @param {HTMLElement | null} errorSummary - The error summary container\n * @param {HTMLInputElement} fileInput - The file input element\n * @returns {void}\n */\nfunction showError(message, errorSummary, fileInput) {\n const topErrorSummary = document.querySelector('.govuk-error-summary')\n\n if (topErrorSummary) {\n const titleElement = document.getElementById(ERROR_SUMMARY_TITLE_ID)\n if (titleElement) {\n fileInput.setAttribute(ARIA_DESCRIBEDBY, ERROR_SUMMARY_TITLE_ID)\n } else {\n fileInput.removeAttribute(ARIA_DESCRIBEDBY)\n }\n return\n }\n\n if (errorSummary) {\n errorSummary.innerHTML = `\n <div class=\"govuk-error-summary\" data-module=\"govuk-error-summary\">\n <div role=\"alert\">\n <h2 class=\"govuk-error-summary__title\" id=\"${ERROR_SUMMARY_TITLE_ID}\">\n There is a problem\n </h2>\n <div class=\"govuk-error-summary__body\">\n <ul class=\"govuk-list govuk-error-summary__list\">\n <li>\n <a href=\"#file-upload\">${message}</a>\n </li>\n </ul>\n </div>\n </div>\n </div>\n `\n\n fileInput.setAttribute(ARIA_DESCRIBEDBY, ERROR_SUMMARY_TITLE_ID)\n }\n\n const formGroup = fileInput.closest('.govuk-form-group')\n if (formGroup) {\n formGroup.classList.add('govuk-form-group--error')\n fileInput.classList.add('govuk-file-upload--error')\n\n const inputId = fileInput.id\n let errorMessage = document.getElementById(`${inputId}-error`)\n\n if (!errorMessage) {\n errorMessage = document.createElement('p')\n errorMessage.id = `${inputId}-error`\n errorMessage.className = 'govuk-error-message'\n errorMessage.innerHTML = `<span class=\"govuk-visually-hidden\">Error:</span> ${message}`\n formGroup.insertBefore(errorMessage, fileInput)\n }\n\n fileInput.setAttribute(\n ARIA_DESCRIBEDBY,\n `error-summary-title ${inputId}-error`\n )\n }\n}\n\n/**\n * Helper to safely convert an Element to HTMLElement\n * @param {Element | null} element - The element to convert\n */\nfunction asHTMLElement(element) {\n return /** @type {HTMLElement} */ (element)\n}\n\nfunction reloadPage() {\n window.history.replaceState(null, '', window.location.href)\n window.location.href = window.location.pathname\n}\n\n/**\n * Build the upload status URL given the current pathname and the upload ID.\n * @param {string} pathname – e.g. window.location.pathname\n * @param {string} uploadId\n * @returns {string} e.g. \"/form/upload-status/abc123\"\n */\nexport function buildUploadStatusUrl(pathname, uploadId) {\n const pathSegments = pathname.split('/').filter((segment) => segment)\n const prefix = pathSegments.length > 0 ? `/${pathSegments[0]}` : ''\n return `${prefix}/upload-status/${uploadId}`\n}\n\n/**\n * Polls the upload status endpoint until the file is ready or timeout occurs\n * @param {string} uploadId - The upload ID to check\n */\nfunction pollUploadStatus(uploadId) {\n let attempts = 0\n const interval = setInterval(() => {\n attempts++\n\n if (attempts >= MAX_POLLING_DURATION) {\n clearInterval(interval)\n reloadPage()\n return\n }\n\n const uploadStatusUrl = buildUploadStatusUrl(\n window.location.pathname,\n uploadId\n )\n\n fetch(uploadStatusUrl, {\n headers: {\n Accept: 'application/json'\n }\n })\n .then((response) => {\n if (!response.ok) {\n throw new Error('Network response was not ok')\n }\n return response.json()\n })\n .then((data) => {\n if (data.uploadStatus === 'ready') {\n clearInterval(interval)\n reloadPage()\n }\n })\n .catch(() => {\n clearInterval(interval)\n reloadPage()\n })\n }, 1000)\n}\n\n/**\n * Handle standard form submission for file upload\n * @param {HTMLFormElement} formElement - The form element\n * @param {HTMLInputElement} fileInput - The file input element\n * @param {HTMLButtonElement} uploadButton - The upload button\n * @param {HTMLButtonElement} continueButton - The continue button\n * @param {File | null} selectedFile - The selected file\n */\nfunction handleStandardFormSubmission(\n formElement,\n fileInput,\n uploadButton,\n continueButton,\n selectedFile\n) {\n renderSummary(selectedFile, 'Uploading…', formElement)\n\n fileInput.focus()\n\n setTimeout(() => {\n fileInput.disabled = true\n uploadButton.disabled = true\n continueButton.disabled = true\n }, 100)\n}\n\n/**\n * Handle AJAX form submission with upload ID\n * @param {Event} event - The click event\n * @param {HTMLFormElement} formElement - The form element\n * @param {HTMLInputElement} fileInput - The file input element\n * @param {HTMLButtonElement} uploadButton - The upload button\n * @param {HTMLElement | null} errorSummary - The error summary container\n * @param {string | undefined} uploadId - The upload ID\n * @returns {boolean} Whether the event was handled\n */\nfunction handleAjaxFormSubmission(\n event,\n formElement,\n fileInput,\n uploadButton,\n errorSummary,\n uploadId\n) {\n if (!formElement.action || !uploadId) {\n return false\n }\n\n event.preventDefault()\n\n const formData = new FormData(formElement)\n const isLocalDev = !!formElement.dataset.proxyUrl\n const uploadUrl = formElement.dataset.proxyUrl ?? formElement.action\n\n const fetchOptions = /** @type {RequestInit} */ ({\n method: 'POST',\n body: formData,\n redirect: isLocalDev ? 'follow' : 'manual' // follow mode if local development with the proxy\n })\n\n // no-cors mode if needed local development with the proxy\n if (isLocalDev) {\n fetchOptions.mode = 'no-cors'\n }\n\n fetch(uploadUrl, fetchOptions)\n .then(() => {\n pollUploadStatus(uploadId)\n })\n .catch(() => {\n fileInput.disabled = false\n uploadButton.disabled = false\n\n showError(\n 'There was a problem uploading the file',\n errorSummary,\n fileInput\n )\n\n return null\n })\n\n return true\n}\n\nexport function initFileUpload() {\n const form = document.querySelector('form:has(input[type=\"file\"])')\n /** @type {HTMLInputElement | null} */\n const fileInput = form ? form.querySelector('input[type=\"file\"]') : null\n /** @type {HTMLButtonElement | null} */\n const uploadButton = form ? form.querySelector('.upload-file-button') : null\n const continueButton =\n /** @type {HTMLButtonElement} */ (\n Array.from(document.querySelectorAll('button.govuk-button')).find(\n (button) => button.textContent?.trim() === 'Continue'\n )\n ) ?? null\n\n const errorSummary = document.querySelector('.govuk-error-summary-container')\n\n if (!form || !fileInput || !uploadButton) {\n return\n }\n\n const formElement = /** @type {HTMLFormElement} */ (form)\n /** @type {File | null} */\n let selectedFile = null\n let isSubmitting = false\n const uploadId = formElement.dataset.uploadId\n\n fileInput.addEventListener('change', () => {\n if (errorSummary) {\n errorSummary.innerHTML = ''\n }\n\n if (fileInput.files && fileInput.files.length > 0) {\n selectedFile = fileInput.files[0]\n }\n })\n\n uploadButton.addEventListener('click', (event) => {\n if (!selectedFile) {\n event.preventDefault()\n showError(\n 'Select a file',\n /** @type {HTMLElement | null} */ (errorSummary),\n fileInput\n )\n return\n }\n\n if (isSubmitting) {\n event.preventDefault()\n return\n }\n\n isSubmitting = true\n\n handleStandardFormSubmission(\n formElement,\n fileInput,\n uploadButton,\n continueButton,\n selectedFile\n )\n\n handleAjaxFormSubmission(\n event,\n formElement,\n fileInput,\n uploadButton,\n /** @type {HTMLElement | null} */ (errorSummary),\n uploadId\n )\n })\n}\n"],"names":["__webpack_require__","exports","definition","key","o","Object","defineProperty","enumerable","get","obj","prop","prototype","hasOwnProperty","call","MAX_POLLING_DURATION","ARIA_DESCRIBEDBY","ERROR_SUMMARY_TITLE_ID","showError","message","errorSummary","fileInput","document","querySelector","getElementById","setAttribute","removeAttribute","innerHTML","formGroup","closest","classList","add","inputId","id","errorMessage","createElement","className","insertBefore","reloadPage","window","history","replaceState","location","href","pathname","buildUploadStatusUrl","uploadId","pathSegments","split","filter","segment","length","initFileUpload","_Array$from$find","form","uploadButton","continueButton","Array","from","querySelectorAll","find","button","_button$textContent","textContent","trim","formElement","selectedFile","isSubmitting","dataset","addEventListener","files","event","preventDefault","statusText","_selectedFile$name3","container","uploadForm","HTMLFormElement","fileCountP","statusAnnouncer","_fileCountP$parentNod","nextSibling","parentNode","appendChild","addStatusAnnouncerToDOM","_unused","_unused2","body","createOrUpdateStatusAnnouncer","summaryList","findOrCreateSummaryList","existingRow","name","remove","row","_selectedFile$name","_selectedFile$name2","createFileRow","firstChild","renderSummary","focus","setTimeout","disabled","handleStandardFormSubmission","_formElement$dataset$","action","formData","FormData","isLocalDev","proxyUrl","uploadUrl","fetchOptions","method","redirect","mode","fetch","then","attempts","interval","setInterval","clearInterval","uploadStatusUrl","headers","Accept","response","ok","Error","json","data","uploadStatus","catch","pollUploadStatus","handleAjaxFormSubmission"],"sourceRoot":""}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export const MAX_POLLING_DURATION = 300; // 5 minutes
|
|
2
|
+
const ARIA_DESCRIBEDBY = 'aria-describedby';
|
|
3
|
+
const ERROR_SUMMARY_TITLE_ID = 'error-summary-title';
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
6
|
* Creates or updates status announcer for screen readers
|
|
@@ -107,7 +109,7 @@ function renderSummary(selectedFile, statusText, form) {
|
|
|
107
109
|
const statusAnnouncer = createOrUpdateStatusAnnouncer(/** @type {HTMLElement} */uploadForm, /** @type {HTMLElement | null} */fileCountP);
|
|
108
110
|
const fileInput = form.querySelector('input[type="file"]');
|
|
109
111
|
if (fileInput) {
|
|
110
|
-
fileInput.setAttribute(
|
|
112
|
+
fileInput.setAttribute(ARIA_DESCRIBEDBY, 'statusInformation');
|
|
111
113
|
}
|
|
112
114
|
const summaryList = findOrCreateSummaryList(/** @type {HTMLFormElement} */uploadForm, /** @type {HTMLElement} */fileCountP);
|
|
113
115
|
const existingRow = document.querySelector(`[data-filename="${selectedFile?.name}"]`);
|
|
@@ -121,17 +123,28 @@ function renderSummary(selectedFile, statusText, form) {
|
|
|
121
123
|
|
|
122
124
|
/**
|
|
123
125
|
* Shows an error message using the GOV.UK error summary component
|
|
126
|
+
* and adds inline error styling to the file input
|
|
124
127
|
* @param {string} message - The error message to display
|
|
125
128
|
* @param {HTMLElement | null} errorSummary - The error summary container
|
|
126
129
|
* @param {HTMLInputElement} fileInput - The file input element
|
|
127
130
|
* @returns {void}
|
|
128
131
|
*/
|
|
129
132
|
function showError(message, errorSummary, fileInput) {
|
|
133
|
+
const topErrorSummary = document.querySelector('.govuk-error-summary');
|
|
134
|
+
if (topErrorSummary) {
|
|
135
|
+
const titleElement = document.getElementById(ERROR_SUMMARY_TITLE_ID);
|
|
136
|
+
if (titleElement) {
|
|
137
|
+
fileInput.setAttribute(ARIA_DESCRIBEDBY, ERROR_SUMMARY_TITLE_ID);
|
|
138
|
+
} else {
|
|
139
|
+
fileInput.removeAttribute(ARIA_DESCRIBEDBY);
|
|
140
|
+
}
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
130
143
|
if (errorSummary) {
|
|
131
144
|
errorSummary.innerHTML = `
|
|
132
145
|
<div class="govuk-error-summary" data-module="govuk-error-summary">
|
|
133
146
|
<div role="alert">
|
|
134
|
-
<h2 class="govuk-error-summary__title" id="
|
|
147
|
+
<h2 class="govuk-error-summary__title" id="${ERROR_SUMMARY_TITLE_ID}">
|
|
135
148
|
There is a problem
|
|
136
149
|
</h2>
|
|
137
150
|
<div class="govuk-error-summary__body">
|
|
@@ -144,7 +157,22 @@ function showError(message, errorSummary, fileInput) {
|
|
|
144
157
|
</div>
|
|
145
158
|
</div>
|
|
146
159
|
`;
|
|
147
|
-
fileInput.setAttribute(
|
|
160
|
+
fileInput.setAttribute(ARIA_DESCRIBEDBY, ERROR_SUMMARY_TITLE_ID);
|
|
161
|
+
}
|
|
162
|
+
const formGroup = fileInput.closest('.govuk-form-group');
|
|
163
|
+
if (formGroup) {
|
|
164
|
+
formGroup.classList.add('govuk-form-group--error');
|
|
165
|
+
fileInput.classList.add('govuk-file-upload--error');
|
|
166
|
+
const inputId = fileInput.id;
|
|
167
|
+
let errorMessage = document.getElementById(`${inputId}-error`);
|
|
168
|
+
if (!errorMessage) {
|
|
169
|
+
errorMessage = document.createElement('p');
|
|
170
|
+
errorMessage.id = `${inputId}-error`;
|
|
171
|
+
errorMessage.className = 'govuk-error-message';
|
|
172
|
+
errorMessage.innerHTML = `<span class="govuk-visually-hidden">Error:</span> ${message}`;
|
|
173
|
+
formGroup.insertBefore(errorMessage, fileInput);
|
|
174
|
+
}
|
|
175
|
+
fileInput.setAttribute(ARIA_DESCRIBEDBY, `error-summary-title ${inputId}-error`);
|
|
148
176
|
}
|
|
149
177
|
}
|
|
150
178
|
|
|
@@ -160,6 +188,18 @@ function reloadPage() {
|
|
|
160
188
|
window.location.href = window.location.pathname;
|
|
161
189
|
}
|
|
162
190
|
|
|
191
|
+
/**
|
|
192
|
+
* Build the upload status URL given the current pathname and the upload ID.
|
|
193
|
+
* @param {string} pathname – e.g. window.location.pathname
|
|
194
|
+
* @param {string} uploadId
|
|
195
|
+
* @returns {string} e.g. "/form/upload-status/abc123"
|
|
196
|
+
*/
|
|
197
|
+
export function buildUploadStatusUrl(pathname, uploadId) {
|
|
198
|
+
const pathSegments = pathname.split('/').filter(segment => segment);
|
|
199
|
+
const prefix = pathSegments.length > 0 ? `/${pathSegments[0]}` : '';
|
|
200
|
+
return `${prefix}/upload-status/${uploadId}`;
|
|
201
|
+
}
|
|
202
|
+
|
|
163
203
|
/**
|
|
164
204
|
* Polls the upload status endpoint until the file is ready or timeout occurs
|
|
165
205
|
* @param {string} uploadId - The upload ID to check
|
|
@@ -173,7 +213,8 @@ function pollUploadStatus(uploadId) {
|
|
|
173
213
|
reloadPage();
|
|
174
214
|
return;
|
|
175
215
|
}
|
|
176
|
-
|
|
216
|
+
const uploadStatusUrl = buildUploadStatusUrl(window.location.pathname, uploadId);
|
|
217
|
+
fetch(uploadStatusUrl, {
|
|
177
218
|
headers: {
|
|
178
219
|
Accept: 'application/json'
|
|
179
220
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-upload.js","names":["MAX_POLLING_DURATION","createOrUpdateStatusAnnouncer","form","fileCountP","statusAnnouncer","querySelector","document","createElement","id","className","setAttribute","addStatusAnnouncerToDOM","asHTMLElement","appendChild","body","nextSibling","parentNode","insertBefore","parentElement","findOrCreateSummaryList","summaryList","continueButton","createFileRow","selectedFile","statusText","row","name","innerHTML","renderSummary","container","getElementById","uploadForm","closest","HTMLFormElement","fileInput","existingRow","remove","firstChild","textContent","showError","message","errorSummary","element","reloadPage","window","history","replaceState","location","href","pathname","pollUploadStatus","uploadId","attempts","interval","setInterval","clearInterval","fetch","headers","Accept","then","response","ok","Error","json","data","uploadStatus","catch","handleStandardFormSubmission","formElement","uploadButton","focus","setTimeout","disabled","handleAjaxFormSubmission","event","action","preventDefault","formData","FormData","isLocalDev","dataset","proxyUrl","uploadUrl","fetchOptions","method","redirect","mode","initFileUpload","Array","from","querySelectorAll","find","button","trim","isSubmitting","addEventListener","files","length"],"sources":["../../../src/client/javascripts/file-upload.js"],"sourcesContent":["export const MAX_POLLING_DURATION = 300 // 5 minutes\n\n/**\n * Creates or updates status announcer for screen readers\n * @param {HTMLElement | null} form - The form element\n * @param {HTMLElement | null} fileCountP - The file count paragraph element\n * @returns {HTMLElement} The status announcer element\n */\nfunction createOrUpdateStatusAnnouncer(form, fileCountP) {\n let statusAnnouncer = form?.querySelector('#statusInformation')\n\n if (!statusAnnouncer) {\n statusAnnouncer = document.createElement('div')\n statusAnnouncer.id = 'statusInformation'\n statusAnnouncer.className = 'govuk-visually-hidden'\n statusAnnouncer.setAttribute('aria-live', 'polite')\n\n // multiple fallbacks to ensure the status announcer is always added to the DOM\n // this helps with cross-browser compatibility and unexpected DOM structures encountered during QA\n try {\n addStatusAnnouncerToDOM(\n asHTMLElement(form),\n asHTMLElement(fileCountP),\n asHTMLElement(statusAnnouncer)\n )\n } catch {\n try {\n form?.appendChild(statusAnnouncer)\n } catch {\n document.body.appendChild(statusAnnouncer)\n }\n }\n }\n\n return /** @type {HTMLElement} */ (statusAnnouncer)\n}\n\n/**\n * Helper function to add the status announcer to the DOM\n * @param {HTMLElement} form - The form element\n * @param {HTMLElement | null} fileCountP - The file count paragraph element\n * @param {HTMLElement} statusAnnouncer - The status announcer element to add\n */\nfunction addStatusAnnouncerToDOM(form, fileCountP, statusAnnouncer) {\n if (fileCountP?.nextSibling && fileCountP.parentNode === form) {\n form.insertBefore(statusAnnouncer, fileCountP.nextSibling)\n return\n }\n\n const parentElement = fileCountP?.parentNode ?? form\n parentElement.appendChild(statusAnnouncer)\n}\n\n/**\n * Finds an existing summary list or creates a new one\n * @param {HTMLFormElement} form - The form element\n * @param {HTMLElement} fileCountP - The file count paragraph element\n * @returns {HTMLElement} The summary list element\n */\nfunction findOrCreateSummaryList(form, fileCountP) {\n let summaryList = form.querySelector('dl.govuk-summary-list')\n\n if (!summaryList) {\n summaryList = document.createElement('dl')\n summaryList.className = 'govuk-summary-list govuk-summary-list--long-key'\n\n const continueButton = form.querySelector('.govuk-button')\n\n if (continueButton) {\n form.insertBefore(summaryList, continueButton)\n } else {\n form.insertBefore(summaryList, fileCountP.nextSibling)\n }\n }\n\n return /** @type {HTMLElement} */ (summaryList)\n}\n\n/**\n * Creates a file row element for the summary list\n * @param {File | null} selectedFile - The selected file\n * @param {string} statusText - The status to display\n * @returns {HTMLElement} The created row element\n */\nfunction createFileRow(selectedFile, statusText) {\n const row = document.createElement('div')\n row.className = 'govuk-summary-list__row'\n row.setAttribute('data-filename', selectedFile?.name ?? '')\n row.innerHTML = `\n <dt class=\"govuk-summary-list__key\">\n ${selectedFile?.name ?? ''}\n </dt>\n <dd class=\"govuk-summary-list__value\">\n <strong class=\"govuk-tag govuk-tag--yellow\">${statusText}</strong>\n </dd>\n <dd class=\"govuk-summary-list__actions\">\n </dd>\n `\n return row\n}\n\n/**\n * Renders or updates the file summary box for the selected file\n * @param {File | null} selectedFile - The selected file\n * @param {string} statusText - The status to display\n * @param {HTMLElement} form - The form element\n */\nfunction renderSummary(selectedFile, statusText, form) {\n const container = document.getElementById('uploadedFilesContainer')\n const uploadForm = container ? container.closest('form') : null\n\n if (!uploadForm || !(uploadForm instanceof HTMLFormElement)) {\n return\n }\n\n const fileCountP = uploadForm.querySelector('p.govuk-body')\n\n if (!fileCountP) {\n return\n }\n\n const statusAnnouncer = createOrUpdateStatusAnnouncer(\n /** @type {HTMLElement} */ (uploadForm),\n /** @type {HTMLElement | null} */ (fileCountP)\n )\n\n const fileInput = form.querySelector('input[type=\"file\"]')\n\n if (fileInput) {\n fileInput.setAttribute('aria-describedby', 'statusInformation')\n }\n\n const summaryList = findOrCreateSummaryList(\n /** @type {HTMLFormElement} */ (uploadForm),\n /** @type {HTMLElement} */ (fileCountP)\n )\n\n const existingRow = document.querySelector(\n `[data-filename=\"${selectedFile?.name}\"]`\n )\n\n if (existingRow) {\n existingRow.remove()\n }\n\n const row = createFileRow(selectedFile, statusText)\n summaryList.insertBefore(row, summaryList.firstChild)\n statusAnnouncer.textContent = `${selectedFile?.name ?? ''} ${statusText}`\n}\n\n/**\n * Shows an error message using the GOV.UK error summary component\n * @param {string} message - The error message to display\n * @param {HTMLElement | null} errorSummary - The error summary container\n * @param {HTMLInputElement} fileInput - The file input element\n * @returns {void}\n */\nfunction showError(message, errorSummary, fileInput) {\n if (errorSummary) {\n errorSummary.innerHTML = `\n <div class=\"govuk-error-summary\" data-module=\"govuk-error-summary\">\n <div role=\"alert\">\n <h2 class=\"govuk-error-summary__title\" id=\"error-summary-title\">\n There is a problem\n </h2>\n <div class=\"govuk-error-summary__body\">\n <ul class=\"govuk-list govuk-error-summary__list\">\n <li>\n <a href=\"#file-upload\">${message}</a>\n </li>\n </ul>\n </div>\n </div>\n </div>\n `\n fileInput.setAttribute('aria-describedby', 'error-summary-title')\n }\n}\n\n/**\n * Helper to safely convert an Element to HTMLElement\n * @param {Element | null} element - The element to convert\n */\nfunction asHTMLElement(element) {\n return /** @type {HTMLElement} */ (element)\n}\n\nfunction reloadPage() {\n window.history.replaceState(null, '', window.location.href)\n window.location.href = window.location.pathname\n}\n\n/**\n * Polls the upload status endpoint until the file is ready or timeout occurs\n * @param {string} uploadId - The upload ID to check\n */\nfunction pollUploadStatus(uploadId) {\n let attempts = 0\n const interval = setInterval(() => {\n attempts++\n\n if (attempts >= MAX_POLLING_DURATION) {\n clearInterval(interval)\n reloadPage()\n return\n }\n\n fetch(`/upload-status/${uploadId}`, {\n headers: {\n Accept: 'application/json'\n }\n })\n .then((response) => {\n if (!response.ok) {\n throw new Error('Network response was not ok')\n }\n return response.json()\n })\n .then((data) => {\n if (data.uploadStatus === 'ready') {\n clearInterval(interval)\n reloadPage()\n }\n })\n .catch(() => {\n clearInterval(interval)\n reloadPage()\n })\n }, 1000)\n}\n\n/**\n * Handle standard form submission for file upload\n * @param {HTMLFormElement} formElement - The form element\n * @param {HTMLInputElement} fileInput - The file input element\n * @param {HTMLButtonElement} uploadButton - The upload button\n * @param {HTMLButtonElement} continueButton - The continue button\n * @param {File | null} selectedFile - The selected file\n */\nfunction handleStandardFormSubmission(\n formElement,\n fileInput,\n uploadButton,\n continueButton,\n selectedFile\n) {\n renderSummary(selectedFile, 'Uploading…', formElement)\n\n fileInput.focus()\n\n setTimeout(() => {\n fileInput.disabled = true\n uploadButton.disabled = true\n continueButton.disabled = true\n }, 100)\n}\n\n/**\n * Handle AJAX form submission with upload ID\n * @param {Event} event - The click event\n * @param {HTMLFormElement} formElement - The form element\n * @param {HTMLInputElement} fileInput - The file input element\n * @param {HTMLButtonElement} uploadButton - The upload button\n * @param {HTMLElement | null} errorSummary - The error summary container\n * @param {string | undefined} uploadId - The upload ID\n * @returns {boolean} Whether the event was handled\n */\nfunction handleAjaxFormSubmission(\n event,\n formElement,\n fileInput,\n uploadButton,\n errorSummary,\n uploadId\n) {\n if (!formElement.action || !uploadId) {\n return false\n }\n\n event.preventDefault()\n\n const formData = new FormData(formElement)\n const isLocalDev = !!formElement.dataset.proxyUrl\n const uploadUrl = formElement.dataset.proxyUrl ?? formElement.action\n\n const fetchOptions = /** @type {RequestInit} */ ({\n method: 'POST',\n body: formData,\n redirect: isLocalDev ? 'follow' : 'manual' // follow mode if local development with the proxy\n })\n\n // no-cors mode if needed local development with the proxy\n if (isLocalDev) {\n fetchOptions.mode = 'no-cors'\n }\n\n fetch(uploadUrl, fetchOptions)\n .then(() => {\n pollUploadStatus(uploadId)\n })\n .catch(() => {\n fileInput.disabled = false\n uploadButton.disabled = false\n\n showError(\n 'There was a problem uploading the file',\n errorSummary,\n fileInput\n )\n\n return null\n })\n\n return true\n}\n\nexport function initFileUpload() {\n const form = document.querySelector('form:has(input[type=\"file\"])')\n /** @type {HTMLInputElement | null} */\n const fileInput = form ? form.querySelector('input[type=\"file\"]') : null\n /** @type {HTMLButtonElement | null} */\n const uploadButton = form ? form.querySelector('.upload-file-button') : null\n const continueButton =\n /** @type {HTMLButtonElement} */ (\n Array.from(document.querySelectorAll('button.govuk-button')).find(\n (button) => button.textContent?.trim() === 'Continue'\n )\n ) ?? null\n\n const errorSummary = document.querySelector('.govuk-error-summary-container')\n\n if (!form || !fileInput || !uploadButton) {\n return\n }\n\n const formElement = /** @type {HTMLFormElement} */ (form)\n /** @type {File | null} */\n let selectedFile = null\n let isSubmitting = false\n const uploadId = formElement.dataset.uploadId\n\n fileInput.addEventListener('change', () => {\n if (errorSummary) {\n errorSummary.innerHTML = ''\n }\n if (fileInput.files && fileInput.files.length > 0) {\n selectedFile = fileInput.files[0]\n }\n })\n\n uploadButton.addEventListener('click', (event) => {\n if (!selectedFile) {\n event.preventDefault()\n showError(\n 'Select a file',\n /** @type {HTMLElement | null} */ (errorSummary),\n fileInput\n )\n return\n }\n\n if (isSubmitting) {\n event.preventDefault()\n return\n }\n\n isSubmitting = true\n\n handleStandardFormSubmission(\n formElement,\n fileInput,\n uploadButton,\n continueButton,\n selectedFile\n )\n\n handleAjaxFormSubmission(\n event,\n formElement,\n fileInput,\n uploadButton,\n /** @type {HTMLElement | null} */ (errorSummary),\n uploadId\n )\n })\n}\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"file-upload.js","names":["MAX_POLLING_DURATION","ARIA_DESCRIBEDBY","ERROR_SUMMARY_TITLE_ID","createOrUpdateStatusAnnouncer","form","fileCountP","statusAnnouncer","querySelector","document","createElement","id","className","setAttribute","addStatusAnnouncerToDOM","asHTMLElement","appendChild","body","nextSibling","parentNode","insertBefore","parentElement","findOrCreateSummaryList","summaryList","continueButton","createFileRow","selectedFile","statusText","row","name","innerHTML","renderSummary","container","getElementById","uploadForm","closest","HTMLFormElement","fileInput","existingRow","remove","firstChild","textContent","showError","message","errorSummary","topErrorSummary","titleElement","removeAttribute","formGroup","classList","add","inputId","errorMessage","element","reloadPage","window","history","replaceState","location","href","pathname","buildUploadStatusUrl","uploadId","pathSegments","split","filter","segment","prefix","length","pollUploadStatus","attempts","interval","setInterval","clearInterval","uploadStatusUrl","fetch","headers","Accept","then","response","ok","Error","json","data","uploadStatus","catch","handleStandardFormSubmission","formElement","uploadButton","focus","setTimeout","disabled","handleAjaxFormSubmission","event","action","preventDefault","formData","FormData","isLocalDev","dataset","proxyUrl","uploadUrl","fetchOptions","method","redirect","mode","initFileUpload","Array","from","querySelectorAll","find","button","trim","isSubmitting","addEventListener","files"],"sources":["../../../src/client/javascripts/file-upload.js"],"sourcesContent":["export const MAX_POLLING_DURATION = 300 // 5 minutes\nconst ARIA_DESCRIBEDBY = 'aria-describedby'\nconst ERROR_SUMMARY_TITLE_ID = 'error-summary-title'\n\n/**\n * Creates or updates status announcer for screen readers\n * @param {HTMLElement | null} form - The form element\n * @param {HTMLElement | null} fileCountP - The file count paragraph element\n * @returns {HTMLElement} The status announcer element\n */\nfunction createOrUpdateStatusAnnouncer(form, fileCountP) {\n let statusAnnouncer = form?.querySelector('#statusInformation')\n\n if (!statusAnnouncer) {\n statusAnnouncer = document.createElement('div')\n statusAnnouncer.id = 'statusInformation'\n statusAnnouncer.className = 'govuk-visually-hidden'\n statusAnnouncer.setAttribute('aria-live', 'polite')\n\n // multiple fallbacks to ensure the status announcer is always added to the DOM\n // this helps with cross-browser compatibility and unexpected DOM structures encountered during QA\n try {\n addStatusAnnouncerToDOM(\n asHTMLElement(form),\n asHTMLElement(fileCountP),\n asHTMLElement(statusAnnouncer)\n )\n } catch {\n try {\n form?.appendChild(statusAnnouncer)\n } catch {\n document.body.appendChild(statusAnnouncer)\n }\n }\n }\n\n return /** @type {HTMLElement} */ (statusAnnouncer)\n}\n\n/**\n * Helper function to add the status announcer to the DOM\n * @param {HTMLElement} form - The form element\n * @param {HTMLElement | null} fileCountP - The file count paragraph element\n * @param {HTMLElement} statusAnnouncer - The status announcer element to add\n */\nfunction addStatusAnnouncerToDOM(form, fileCountP, statusAnnouncer) {\n if (fileCountP?.nextSibling && fileCountP.parentNode === form) {\n form.insertBefore(statusAnnouncer, fileCountP.nextSibling)\n return\n }\n\n const parentElement = fileCountP?.parentNode ?? form\n parentElement.appendChild(statusAnnouncer)\n}\n\n/**\n * Finds an existing summary list or creates a new one\n * @param {HTMLFormElement} form - The form element\n * @param {HTMLElement} fileCountP - The file count paragraph element\n * @returns {HTMLElement} The summary list element\n */\nfunction findOrCreateSummaryList(form, fileCountP) {\n let summaryList = form.querySelector('dl.govuk-summary-list')\n\n if (!summaryList) {\n summaryList = document.createElement('dl')\n summaryList.className = 'govuk-summary-list govuk-summary-list--long-key'\n\n const continueButton = form.querySelector('.govuk-button')\n\n if (continueButton) {\n form.insertBefore(summaryList, continueButton)\n } else {\n form.insertBefore(summaryList, fileCountP.nextSibling)\n }\n }\n\n return /** @type {HTMLElement} */ (summaryList)\n}\n\n/**\n * Creates a file row element for the summary list\n * @param {File | null} selectedFile - The selected file\n * @param {string} statusText - The status to display\n * @returns {HTMLElement} The created row element\n */\nfunction createFileRow(selectedFile, statusText) {\n const row = document.createElement('div')\n row.className = 'govuk-summary-list__row'\n row.setAttribute('data-filename', selectedFile?.name ?? '')\n row.innerHTML = `\n <dt class=\"govuk-summary-list__key\">\n ${selectedFile?.name ?? ''}\n </dt>\n <dd class=\"govuk-summary-list__value\">\n <strong class=\"govuk-tag govuk-tag--yellow\">${statusText}</strong>\n </dd>\n <dd class=\"govuk-summary-list__actions\">\n </dd>\n `\n return row\n}\n\n/**\n * Renders or updates the file summary box for the selected file\n * @param {File | null} selectedFile - The selected file\n * @param {string} statusText - The status to display\n * @param {HTMLElement} form - The form element\n */\nfunction renderSummary(selectedFile, statusText, form) {\n const container = document.getElementById('uploadedFilesContainer')\n const uploadForm = container ? container.closest('form') : null\n\n if (!uploadForm || !(uploadForm instanceof HTMLFormElement)) {\n return\n }\n\n const fileCountP = uploadForm.querySelector('p.govuk-body')\n\n if (!fileCountP) {\n return\n }\n\n const statusAnnouncer = createOrUpdateStatusAnnouncer(\n /** @type {HTMLElement} */ (uploadForm),\n /** @type {HTMLElement | null} */ (fileCountP)\n )\n\n const fileInput = form.querySelector('input[type=\"file\"]')\n\n if (fileInput) {\n fileInput.setAttribute(ARIA_DESCRIBEDBY, 'statusInformation')\n }\n\n const summaryList = findOrCreateSummaryList(\n /** @type {HTMLFormElement} */ (uploadForm),\n /** @type {HTMLElement} */ (fileCountP)\n )\n\n const existingRow = document.querySelector(\n `[data-filename=\"${selectedFile?.name}\"]`\n )\n\n if (existingRow) {\n existingRow.remove()\n }\n\n const row = createFileRow(selectedFile, statusText)\n summaryList.insertBefore(row, summaryList.firstChild)\n statusAnnouncer.textContent = `${selectedFile?.name ?? ''} ${statusText}`\n}\n\n/**\n * Shows an error message using the GOV.UK error summary component\n * and adds inline error styling to the file input\n * @param {string} message - The error message to display\n * @param {HTMLElement | null} errorSummary - The error summary container\n * @param {HTMLInputElement} fileInput - The file input element\n * @returns {void}\n */\nfunction showError(message, errorSummary, fileInput) {\n const topErrorSummary = document.querySelector('.govuk-error-summary')\n\n if (topErrorSummary) {\n const titleElement = document.getElementById(ERROR_SUMMARY_TITLE_ID)\n if (titleElement) {\n fileInput.setAttribute(ARIA_DESCRIBEDBY, ERROR_SUMMARY_TITLE_ID)\n } else {\n fileInput.removeAttribute(ARIA_DESCRIBEDBY)\n }\n return\n }\n\n if (errorSummary) {\n errorSummary.innerHTML = `\n <div class=\"govuk-error-summary\" data-module=\"govuk-error-summary\">\n <div role=\"alert\">\n <h2 class=\"govuk-error-summary__title\" id=\"${ERROR_SUMMARY_TITLE_ID}\">\n There is a problem\n </h2>\n <div class=\"govuk-error-summary__body\">\n <ul class=\"govuk-list govuk-error-summary__list\">\n <li>\n <a href=\"#file-upload\">${message}</a>\n </li>\n </ul>\n </div>\n </div>\n </div>\n `\n\n fileInput.setAttribute(ARIA_DESCRIBEDBY, ERROR_SUMMARY_TITLE_ID)\n }\n\n const formGroup = fileInput.closest('.govuk-form-group')\n if (formGroup) {\n formGroup.classList.add('govuk-form-group--error')\n fileInput.classList.add('govuk-file-upload--error')\n\n const inputId = fileInput.id\n let errorMessage = document.getElementById(`${inputId}-error`)\n\n if (!errorMessage) {\n errorMessage = document.createElement('p')\n errorMessage.id = `${inputId}-error`\n errorMessage.className = 'govuk-error-message'\n errorMessage.innerHTML = `<span class=\"govuk-visually-hidden\">Error:</span> ${message}`\n formGroup.insertBefore(errorMessage, fileInput)\n }\n\n fileInput.setAttribute(\n ARIA_DESCRIBEDBY,\n `error-summary-title ${inputId}-error`\n )\n }\n}\n\n/**\n * Helper to safely convert an Element to HTMLElement\n * @param {Element | null} element - The element to convert\n */\nfunction asHTMLElement(element) {\n return /** @type {HTMLElement} */ (element)\n}\n\nfunction reloadPage() {\n window.history.replaceState(null, '', window.location.href)\n window.location.href = window.location.pathname\n}\n\n/**\n * Build the upload status URL given the current pathname and the upload ID.\n * @param {string} pathname – e.g. window.location.pathname\n * @param {string} uploadId\n * @returns {string} e.g. \"/form/upload-status/abc123\"\n */\nexport function buildUploadStatusUrl(pathname, uploadId) {\n const pathSegments = pathname.split('/').filter((segment) => segment)\n const prefix = pathSegments.length > 0 ? `/${pathSegments[0]}` : ''\n return `${prefix}/upload-status/${uploadId}`\n}\n\n/**\n * Polls the upload status endpoint until the file is ready or timeout occurs\n * @param {string} uploadId - The upload ID to check\n */\nfunction pollUploadStatus(uploadId) {\n let attempts = 0\n const interval = setInterval(() => {\n attempts++\n\n if (attempts >= MAX_POLLING_DURATION) {\n clearInterval(interval)\n reloadPage()\n return\n }\n\n const uploadStatusUrl = buildUploadStatusUrl(\n window.location.pathname,\n uploadId\n )\n\n fetch(uploadStatusUrl, {\n headers: {\n Accept: 'application/json'\n }\n })\n .then((response) => {\n if (!response.ok) {\n throw new Error('Network response was not ok')\n }\n return response.json()\n })\n .then((data) => {\n if (data.uploadStatus === 'ready') {\n clearInterval(interval)\n reloadPage()\n }\n })\n .catch(() => {\n clearInterval(interval)\n reloadPage()\n })\n }, 1000)\n}\n\n/**\n * Handle standard form submission for file upload\n * @param {HTMLFormElement} formElement - The form element\n * @param {HTMLInputElement} fileInput - The file input element\n * @param {HTMLButtonElement} uploadButton - The upload button\n * @param {HTMLButtonElement} continueButton - The continue button\n * @param {File | null} selectedFile - The selected file\n */\nfunction handleStandardFormSubmission(\n formElement,\n fileInput,\n uploadButton,\n continueButton,\n selectedFile\n) {\n renderSummary(selectedFile, 'Uploading…', formElement)\n\n fileInput.focus()\n\n setTimeout(() => {\n fileInput.disabled = true\n uploadButton.disabled = true\n continueButton.disabled = true\n }, 100)\n}\n\n/**\n * Handle AJAX form submission with upload ID\n * @param {Event} event - The click event\n * @param {HTMLFormElement} formElement - The form element\n * @param {HTMLInputElement} fileInput - The file input element\n * @param {HTMLButtonElement} uploadButton - The upload button\n * @param {HTMLElement | null} errorSummary - The error summary container\n * @param {string | undefined} uploadId - The upload ID\n * @returns {boolean} Whether the event was handled\n */\nfunction handleAjaxFormSubmission(\n event,\n formElement,\n fileInput,\n uploadButton,\n errorSummary,\n uploadId\n) {\n if (!formElement.action || !uploadId) {\n return false\n }\n\n event.preventDefault()\n\n const formData = new FormData(formElement)\n const isLocalDev = !!formElement.dataset.proxyUrl\n const uploadUrl = formElement.dataset.proxyUrl ?? formElement.action\n\n const fetchOptions = /** @type {RequestInit} */ ({\n method: 'POST',\n body: formData,\n redirect: isLocalDev ? 'follow' : 'manual' // follow mode if local development with the proxy\n })\n\n // no-cors mode if needed local development with the proxy\n if (isLocalDev) {\n fetchOptions.mode = 'no-cors'\n }\n\n fetch(uploadUrl, fetchOptions)\n .then(() => {\n pollUploadStatus(uploadId)\n })\n .catch(() => {\n fileInput.disabled = false\n uploadButton.disabled = false\n\n showError(\n 'There was a problem uploading the file',\n errorSummary,\n fileInput\n )\n\n return null\n })\n\n return true\n}\n\nexport function initFileUpload() {\n const form = document.querySelector('form:has(input[type=\"file\"])')\n /** @type {HTMLInputElement | null} */\n const fileInput = form ? form.querySelector('input[type=\"file\"]') : null\n /** @type {HTMLButtonElement | null} */\n const uploadButton = form ? form.querySelector('.upload-file-button') : null\n const continueButton =\n /** @type {HTMLButtonElement} */ (\n Array.from(document.querySelectorAll('button.govuk-button')).find(\n (button) => button.textContent?.trim() === 'Continue'\n )\n ) ?? null\n\n const errorSummary = document.querySelector('.govuk-error-summary-container')\n\n if (!form || !fileInput || !uploadButton) {\n return\n }\n\n const formElement = /** @type {HTMLFormElement} */ (form)\n /** @type {File | null} */\n let selectedFile = null\n let isSubmitting = false\n const uploadId = formElement.dataset.uploadId\n\n fileInput.addEventListener('change', () => {\n if (errorSummary) {\n errorSummary.innerHTML = ''\n }\n\n if (fileInput.files && fileInput.files.length > 0) {\n selectedFile = fileInput.files[0]\n }\n })\n\n uploadButton.addEventListener('click', (event) => {\n if (!selectedFile) {\n event.preventDefault()\n showError(\n 'Select a file',\n /** @type {HTMLElement | null} */ (errorSummary),\n fileInput\n )\n return\n }\n\n if (isSubmitting) {\n event.preventDefault()\n return\n }\n\n isSubmitting = true\n\n handleStandardFormSubmission(\n formElement,\n fileInput,\n uploadButton,\n continueButton,\n selectedFile\n )\n\n handleAjaxFormSubmission(\n event,\n formElement,\n fileInput,\n uploadButton,\n /** @type {HTMLElement | null} */ (errorSummary),\n uploadId\n )\n })\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.js","names":["PREVIEW_PATH_PREFIX"],"sources":["../../src/server/constants.js"],"sourcesContent":["export const PREVIEW_PATH_PREFIX = '/preview'\n"],"mappings":"AAAA,OAAO,MAAMA,mBAAmB,GAAG,UAAU","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"constants.js","names":["PREVIEW_PATH_PREFIX","ERROR_PREVIEW_PATH_PREFIX","FORM_PREFIX"],"sources":["../../src/server/constants.js"],"sourcesContent":["export const PREVIEW_PATH_PREFIX = '/preview'\nexport const ERROR_PREVIEW_PATH_PREFIX = '/error-preview'\nexport const FORM_PREFIX = ''\n"],"mappings":"AAAA,OAAO,MAAMA,mBAAmB,GAAG,UAAU;AAC7C,OAAO,MAAMC,yBAAyB,GAAG,gBAAgB;AACzD,OAAO,MAAMC,WAAW,GAAG,EAAE","ignoreList":[]}
|
package/.server/server/index.js
CHANGED
|
@@ -72,6 +72,7 @@ export async function createServer(routeConfig) {
|
|
|
72
72
|
await server.register(inert);
|
|
73
73
|
await server.register(Scooter);
|
|
74
74
|
await server.register(pluginCrumb);
|
|
75
|
+
await server.register(pluginEngine);
|
|
75
76
|
server.ext('onPreResponse', (request, h) => {
|
|
76
77
|
const {
|
|
77
78
|
response
|
|
@@ -90,7 +91,6 @@ export async function createServer(routeConfig) {
|
|
|
90
91
|
return h.continue;
|
|
91
92
|
});
|
|
92
93
|
await server.register(pluginViews);
|
|
93
|
-
await server.register(pluginEngine);
|
|
94
94
|
await server.register({
|
|
95
95
|
plugin: {
|
|
96
96
|
name: 'router',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["Engine","CatboxMemory","CatboxRedis","hapi","inert","Scooter","Wreck","blipp","ProxyAgent","config","requestLogger","requestTracing","buildRedisClient","configureCrumbPlugin","configureEnginePlugin","pluginErrorPages","plugin","pluginViews","pluginPulse","pluginSession","publicRoutes","prepareSecureContext","proxyAgent","agents","https","http","httpsAllowUnauthorized","serverOptions","debug","request","get","port","router","stripTrailingSlash","routes","validate","options","abortEarly","security","hsts","maxAge","includeSubDomains","preload","xss","noSniff","xframe","cache","name","engine","client","createServer","routeConfig","server","register","pluginCrumb","pluginEngine","ext","h","response","continue","header","path","startsWith","route"],"sources":["../../src/server/index.ts"],"sourcesContent":["import { Engine as CatboxMemory } from '@hapi/catbox-memory'\nimport { Engine as CatboxRedis } from '@hapi/catbox-redis'\nimport hapi, {\n type Request,\n type ResponseToolkit,\n type ServerOptions\n} from '@hapi/hapi'\nimport inert from '@hapi/inert'\nimport Scooter from '@hapi/scooter'\nimport Wreck from '@hapi/wreck'\nimport blipp from 'blipp'\nimport { ProxyAgent } from 'proxy-agent'\n\nimport { config } from '~/src/config/index.js'\nimport { requestLogger } from '~/src/server/common/helpers/logging/request-logger.js'\nimport { requestTracing } from '~/src/server/common/helpers/logging/request-tracing.js'\nimport { buildRedisClient } from '~/src/server/common/helpers/redis-client.js'\nimport { configureCrumbPlugin } from '~/src/server/plugins/crumb.js'\nimport { configureEnginePlugin } from '~/src/server/plugins/engine/configureEnginePlugin.js'\nimport pluginErrorPages from '~/src/server/plugins/errorPages.js'\nimport { plugin as pluginViews } from '~/src/server/plugins/nunjucks/index.js'\nimport pluginPulse from '~/src/server/plugins/pulse.js'\nimport pluginSession from '~/src/server/plugins/session.js'\nimport { publicRoutes } from '~/src/server/routes/index.js'\nimport { prepareSecureContext } from '~/src/server/secure-context.js'\nimport { type RouteConfig } from '~/src/server/types.js'\n\nconst proxyAgent = new ProxyAgent()\n\nWreck.agents = {\n https: proxyAgent,\n http: proxyAgent,\n httpsAllowUnauthorized: proxyAgent\n}\n\nconst serverOptions = (): ServerOptions => {\n const serverOptions: ServerOptions = {\n debug: { request: [`${config.get('isDevelopment')}`] },\n port: config.get('port'),\n router: {\n stripTrailingSlash: true\n },\n routes: {\n validate: {\n options: {\n abortEarly: false\n }\n },\n security: {\n hsts: {\n maxAge: 31536000,\n includeSubDomains: true,\n preload: false\n },\n xss: 'enabled',\n noSniff: true,\n xframe: true\n }\n },\n cache: [\n {\n name: 'session',\n engine: config.get('isTest')\n ? new CatboxMemory()\n : new CatboxRedis({\n client: buildRedisClient()\n })\n }\n ]\n }\n\n return serverOptions\n}\n\nexport async function createServer(routeConfig?: RouteConfig) {\n const server = hapi.server(serverOptions())\n\n await server.register(requestLogger)\n\n if (config.get('isProduction')) {\n prepareSecureContext(server)\n }\n\n const pluginCrumb = configureCrumbPlugin(routeConfig)\n const pluginEngine = await configureEnginePlugin(routeConfig)\n\n await server.register(pluginSession)\n await server.register(pluginPulse)\n await server.register(inert)\n await server.register(Scooter)\n await server.register(pluginCrumb)\n\n server.ext('onPreResponse', (request: Request, h: ResponseToolkit) => {\n const { response } = request\n\n if ('isBoom' in response) {\n return h.continue\n }\n\n // Prevent search engine indexing\n response.header('x-robots-tag', 'noindex, nofollow')\n\n // Disable cache to ensure back/foward navigation updates progress\n if (\n !request.path.startsWith('/javascripts/') &&\n !request.path.startsWith('/stylesheets/') &&\n !request.path.startsWith('/assets/')\n ) {\n response.header('cache-control', 'no-store')\n }\n\n return h.continue\n })\n\n await server.register(pluginViews)\n
|
|
1
|
+
{"version":3,"file":"index.js","names":["Engine","CatboxMemory","CatboxRedis","hapi","inert","Scooter","Wreck","blipp","ProxyAgent","config","requestLogger","requestTracing","buildRedisClient","configureCrumbPlugin","configureEnginePlugin","pluginErrorPages","plugin","pluginViews","pluginPulse","pluginSession","publicRoutes","prepareSecureContext","proxyAgent","agents","https","http","httpsAllowUnauthorized","serverOptions","debug","request","get","port","router","stripTrailingSlash","routes","validate","options","abortEarly","security","hsts","maxAge","includeSubDomains","preload","xss","noSniff","xframe","cache","name","engine","client","createServer","routeConfig","server","register","pluginCrumb","pluginEngine","ext","h","response","continue","header","path","startsWith","route"],"sources":["../../src/server/index.ts"],"sourcesContent":["import { Engine as CatboxMemory } from '@hapi/catbox-memory'\nimport { Engine as CatboxRedis } from '@hapi/catbox-redis'\nimport hapi, {\n type Request,\n type ResponseToolkit,\n type ServerOptions\n} from '@hapi/hapi'\nimport inert from '@hapi/inert'\nimport Scooter from '@hapi/scooter'\nimport Wreck from '@hapi/wreck'\nimport blipp from 'blipp'\nimport { ProxyAgent } from 'proxy-agent'\n\nimport { config } from '~/src/config/index.js'\nimport { requestLogger } from '~/src/server/common/helpers/logging/request-logger.js'\nimport { requestTracing } from '~/src/server/common/helpers/logging/request-tracing.js'\nimport { buildRedisClient } from '~/src/server/common/helpers/redis-client.js'\nimport { configureCrumbPlugin } from '~/src/server/plugins/crumb.js'\nimport { configureEnginePlugin } from '~/src/server/plugins/engine/configureEnginePlugin.js'\nimport pluginErrorPages from '~/src/server/plugins/errorPages.js'\nimport { plugin as pluginViews } from '~/src/server/plugins/nunjucks/index.js'\nimport pluginPulse from '~/src/server/plugins/pulse.js'\nimport pluginSession from '~/src/server/plugins/session.js'\nimport { publicRoutes } from '~/src/server/routes/index.js'\nimport { prepareSecureContext } from '~/src/server/secure-context.js'\nimport { type RouteConfig } from '~/src/server/types.js'\n\nconst proxyAgent = new ProxyAgent()\n\nWreck.agents = {\n https: proxyAgent,\n http: proxyAgent,\n httpsAllowUnauthorized: proxyAgent\n}\n\nconst serverOptions = (): ServerOptions => {\n const serverOptions: ServerOptions = {\n debug: { request: [`${config.get('isDevelopment')}`] },\n port: config.get('port'),\n router: {\n stripTrailingSlash: true\n },\n routes: {\n validate: {\n options: {\n abortEarly: false\n }\n },\n security: {\n hsts: {\n maxAge: 31536000,\n includeSubDomains: true,\n preload: false\n },\n xss: 'enabled',\n noSniff: true,\n xframe: true\n }\n },\n cache: [\n {\n name: 'session',\n engine: config.get('isTest')\n ? new CatboxMemory()\n : new CatboxRedis({\n client: buildRedisClient()\n })\n }\n ]\n }\n\n return serverOptions\n}\n\nexport async function createServer(routeConfig?: RouteConfig) {\n const server = hapi.server(serverOptions())\n\n await server.register(requestLogger)\n\n if (config.get('isProduction')) {\n prepareSecureContext(server)\n }\n\n const pluginCrumb = configureCrumbPlugin(routeConfig)\n const pluginEngine = await configureEnginePlugin(routeConfig)\n\n await server.register(pluginSession)\n await server.register(pluginPulse)\n await server.register(inert)\n await server.register(Scooter)\n await server.register(pluginCrumb)\n\n await server.register(pluginEngine)\n\n server.ext('onPreResponse', (request: Request, h: ResponseToolkit) => {\n const { response } = request\n\n if ('isBoom' in response) {\n return h.continue\n }\n\n // Prevent search engine indexing\n response.header('x-robots-tag', 'noindex, nofollow')\n\n // Disable cache to ensure back/foward navigation updates progress\n if (\n !request.path.startsWith('/javascripts/') &&\n !request.path.startsWith('/stylesheets/') &&\n !request.path.startsWith('/assets/')\n ) {\n response.header('cache-control', 'no-store')\n }\n\n return h.continue\n })\n\n await server.register(pluginViews)\n\n await server.register({\n plugin: {\n name: 'router',\n register: (server) => {\n server.route(publicRoutes)\n }\n }\n })\n\n await server.register(pluginErrorPages)\n await server.register(blipp)\n await server.register(requestTracing)\n\n return server\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -12,7 +12,9 @@ export class AutocompleteField extends SelectField {
|
|
|
12
12
|
if (options.required !== false) {
|
|
13
13
|
const messages = options.customValidationMessages;
|
|
14
14
|
formSchema = formSchema.messages({
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
15
16
|
'any.only': messages?.['any.only'] ?? messageTemplate.required,
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
16
18
|
'any.required': messages?.['any.required'] ?? messageTemplate.required
|
|
17
19
|
});
|
|
18
20
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AutocompleteField.js","names":["SelectField","messageTemplate","AutocompleteField","constructor","def","props","options","formSchema","required","messages","customValidationMessages","getViewModel","payload","errors","viewModel","formGroup","attributes"],"sources":["../../../../../src/server/plugins/engine/components/AutocompleteField.ts"],"sourcesContent":["import { type AutocompleteFieldComponent } from '@defra/forms-model'\n\nimport { SelectField } from '~/src/server/plugins/engine/components/SelectField.js'\nimport { messageTemplate } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'\nimport {\n type FormPayload,\n type FormSubmissionError\n} from '~/src/server/plugins/engine/types.js'\n\nexport class AutocompleteField extends SelectField {\n declare options: AutocompleteFieldComponent['options']\n\n constructor(\n def: AutocompleteFieldComponent,\n props: ConstructorParameters<typeof SelectField>[1]\n ) {\n super(def, props)\n\n const { options } = def\n let { formSchema } = this\n\n if (options.required !== false) {\n const messages = options.customValidationMessages\n\n formSchema = formSchema.messages({\n 'any.only'
|
|
1
|
+
{"version":3,"file":"AutocompleteField.js","names":["SelectField","messageTemplate","AutocompleteField","constructor","def","props","options","formSchema","required","messages","customValidationMessages","getViewModel","payload","errors","viewModel","formGroup","attributes"],"sources":["../../../../../src/server/plugins/engine/components/AutocompleteField.ts"],"sourcesContent":["import { type AutocompleteFieldComponent } from '@defra/forms-model'\n\nimport { SelectField } from '~/src/server/plugins/engine/components/SelectField.js'\nimport { messageTemplate } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'\nimport {\n type FormPayload,\n type FormSubmissionError\n} from '~/src/server/plugins/engine/types.js'\n\nexport class AutocompleteField extends SelectField {\n declare options: AutocompleteFieldComponent['options']\n\n constructor(\n def: AutocompleteFieldComponent,\n props: ConstructorParameters<typeof SelectField>[1]\n ) {\n super(def, props)\n\n const { options } = def\n let { formSchema } = this\n\n if (options.required !== false) {\n const messages = options.customValidationMessages\n\n formSchema = formSchema.messages({\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n 'any.only':\n messages?.['any.only'] ?? (messageTemplate.required as string),\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n 'any.required':\n messages?.['any.required'] ?? (messageTemplate.required as string)\n })\n }\n\n this.options = options\n this.formSchema = formSchema\n }\n\n getViewModel(payload: FormPayload, errors?: FormSubmissionError[]) {\n const viewModel = super.getViewModel(payload, errors)\n let { formGroup } = viewModel\n\n formGroup ??= {}\n formGroup.attributes = {\n 'data-module': 'govuk-accessible-autocomplete'\n }\n\n return {\n ...viewModel,\n formGroup\n }\n }\n}\n"],"mappings":"AAEA,SAASA,WAAW;AACpB,SAASC,eAAe;AAMxB,OAAO,MAAMC,iBAAiB,SAASF,WAAW,CAAC;EAGjDG,WAAWA,CACTC,GAA+B,EAC/BC,KAAmD,EACnD;IACA,KAAK,CAACD,GAAG,EAAEC,KAAK,CAAC;IAEjB,MAAM;MAAEC;IAAQ,CAAC,GAAGF,GAAG;IACvB,IAAI;MAAEG;IAAW,CAAC,GAAG,IAAI;IAEzB,IAAID,OAAO,CAACE,QAAQ,KAAK,KAAK,EAAE;MAC9B,MAAMC,QAAQ,GAAGH,OAAO,CAACI,wBAAwB;MAEjDH,UAAU,GAAGA,UAAU,CAACE,QAAQ,CAAC;QAC/B;QACA,UAAU,EACRA,QAAQ,GAAG,UAAU,CAAC,IAAKR,eAAe,CAACO,QAAmB;QAChE;QACA,cAAc,EACZC,QAAQ,GAAG,cAAc,CAAC,IAAKR,eAAe,CAACO;MACnD,CAAC,CAAC;IACJ;IAEA,IAAI,CAACF,OAAO,GAAGA,OAAO;IACtB,IAAI,CAACC,UAAU,GAAGA,UAAU;EAC9B;EAEAI,YAAYA,CAACC,OAAoB,EAAEC,MAA8B,EAAE;IACjE,MAAMC,SAAS,GAAG,KAAK,CAACH,YAAY,CAACC,OAAO,EAAEC,MAAM,CAAC;IACrD,IAAI;MAAEE;IAAU,CAAC,GAAGD,SAAS;IAE7BC,SAAS,KAAK,CAAC,CAAC;IAChBA,SAAS,CAACC,UAAU,GAAG;MACrB,aAAa,EAAE;IACjB,CAAC;IAED,OAAO;MACL,GAAGF,SAAS;MACZC;IACF,CAAC;EACH;AACF","ignoreList":[]}
|
|
@@ -8,12 +8,11 @@ export class CheckboxesField extends SelectionControlField {
|
|
|
8
8
|
listType: type
|
|
9
9
|
} = this;
|
|
10
10
|
const {
|
|
11
|
-
options
|
|
12
|
-
title
|
|
11
|
+
options
|
|
13
12
|
} = def;
|
|
14
13
|
let formSchema = type === 'string' ? joi.array() : joi.array();
|
|
15
|
-
const itemsSchema = joi[type]().valid(...this.values).label(
|
|
16
|
-
formSchema = formSchema.items(itemsSchema).single().label(
|
|
14
|
+
const itemsSchema = joi[type]().valid(...this.values).label(this.label);
|
|
15
|
+
formSchema = formSchema.items(itemsSchema).single().label(this.label).required();
|
|
17
16
|
if (options.required === false) {
|
|
18
17
|
formSchema = formSchema.optional();
|
|
19
18
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CheckboxesField.js","names":["joi","isFormValue","SelectionControlField","CheckboxesField","constructor","def","props","listType","type","options","
|
|
1
|
+
{"version":3,"file":"CheckboxesField.js","names":["joi","isFormValue","SelectionControlField","CheckboxesField","constructor","def","props","listType","type","options","formSchema","array","itemsSchema","valid","values","label","items","single","required","optional","default","stateSchema","allow","getFormValueFromState","state","name","getFormValue","selected","filter","item","includes","value","map","length","undefined","isValue","getDisplayStringFromState","text","join","getContextValueFromState","Array","isArray","every"],"sources":["../../../../../src/server/plugins/engine/components/CheckboxesField.ts"],"sourcesContent":["import { type CheckboxesFieldComponent, type Item } from '@defra/forms-model'\nimport joi, { type ArraySchema } from 'joi'\n\nimport { isFormValue } from '~/src/server/plugins/engine/components/FormComponent.js'\nimport { SelectionControlField } from '~/src/server/plugins/engine/components/SelectionControlField.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/FormModel.js'\nimport { type QuestionPageController } from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js'\nimport {\n type FormState,\n type FormStateValue,\n type FormSubmissionState\n} from '~/src/server/plugins/engine/types.js'\n\nexport class CheckboxesField extends SelectionControlField {\n declare options: CheckboxesFieldComponent['options']\n declare formSchema: ArraySchema<string> | ArraySchema<number>\n declare stateSchema: ArraySchema<string> | ArraySchema<number>\n\n constructor(\n def: CheckboxesFieldComponent,\n props: ConstructorParameters<typeof SelectionControlField>[1]\n ) {\n super(def, props)\n\n const { listType: type } = this\n const { options } = def\n\n let formSchema =\n type === 'string' ? joi.array<string>() : joi.array<number>()\n\n const itemsSchema = joi[type]()\n .valid(...this.values)\n .label(this.label)\n\n formSchema = formSchema\n .items(itemsSchema)\n .single()\n .label(this.label)\n .required()\n\n if (options.required === false) {\n formSchema = formSchema.optional()\n }\n\n this.formSchema = formSchema.default([])\n this.stateSchema = formSchema.default(null).allow(null)\n this.options = options\n }\n\n getFormValueFromState(state: FormSubmissionState) {\n const { items, name } = this\n\n // State checkbox values\n const values = this.getFormValue(state[name]) ?? []\n\n // Map (or discard) state values to item values\n const selected = items\n .filter((item) => values.includes(item.value))\n .map((item) => item.value)\n\n return selected.length ? selected : undefined\n }\n\n getFormValue(value?: FormStateValue | FormState) {\n return this.isValue(value) ? value : undefined\n }\n\n getDisplayStringFromState(state: FormSubmissionState) {\n const { items } = this\n\n // Selected checkbox values\n const selected = this.getFormValueFromState(state) ?? []\n\n // Map selected values to text\n return items\n .filter((item) => selected.includes(item.value))\n .map((item) => item.text)\n .join(', ')\n }\n\n getContextValueFromState(state: FormSubmissionState) {\n const values = this.getFormValueFromState(state)\n\n /**\n * For evaluation context purposes, optional {@link CheckboxesField}\n * with an undefined value (i.e. nothing selected) should default to [].\n * This way conditions are not evaluated against `undefined` which throws errors.\n * Currently these errors are caught and the evaluation returns default `false`.\n * @see {@link QuestionPageController.getNextPath} for `undefined` return value\n * @see {@link FormModel.makeCondition} for try/catch block with default `false`\n * For negative conditions this is a problem because E.g.\n * The condition: 'selectedchecks' does not contain 'someval'\n * should return true IF 'selectedchecks' is undefined, not throw and return false.\n */\n return values ?? []\n }\n\n isValue(value?: FormStateValue | FormState): value is Item['value'][] {\n if (!Array.isArray(value)) {\n return false\n }\n\n // Skip checks when empty\n if (!value.length) {\n return true\n }\n\n return value.every(isFormValue)\n }\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -68,7 +68,7 @@ export class ComponentCollection {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
// Update error with parent title
|
|
71
|
-
error.local.title ??= parent?.
|
|
71
|
+
error.local.title ??= parent?.label;
|
|
72
72
|
return error;
|
|
73
73
|
});
|
|
74
74
|
});
|
|
@@ -139,23 +139,21 @@ export class ComponentCollection {
|
|
|
139
139
|
}
|
|
140
140
|
return context;
|
|
141
141
|
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Get all errors for all fields in this collection
|
|
145
|
+
*/
|
|
142
146
|
getErrors(errors) {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
} = this;
|
|
146
|
-
const list = [];
|
|
147
|
+
return this.getFieldErrors(field => field.getErrors(errors), errors);
|
|
148
|
+
}
|
|
147
149
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
if (!list.length) {
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
return list;
|
|
150
|
+
/**
|
|
151
|
+
* Get view errors for all fields in this collection.
|
|
152
|
+
* For most fields this means filtering to the first error in the list.
|
|
153
|
+
* Composite fields like UKAddress can choose to return more than one error.
|
|
154
|
+
*/
|
|
155
|
+
getViewErrors(errors) {
|
|
156
|
+
return this.getFieldErrors(field => field.getViewErrors(errors), errors);
|
|
159
157
|
}
|
|
160
158
|
getViewModel(payload, errors, query = {}) {
|
|
161
159
|
const {
|
|
@@ -187,6 +185,29 @@ export class ComponentCollection {
|
|
|
187
185
|
errors: this.page?.getErrors(details) ?? getErrors(details)
|
|
188
186
|
};
|
|
189
187
|
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Helper to get errors from all fields
|
|
191
|
+
*/
|
|
192
|
+
getFieldErrors(callback, errors) {
|
|
193
|
+
const {
|
|
194
|
+
fields
|
|
195
|
+
} = this;
|
|
196
|
+
if (!errors?.length) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
const list = [];
|
|
200
|
+
for (const field of fields) {
|
|
201
|
+
const fieldErrors = callback(field);
|
|
202
|
+
if (fieldErrors?.length) {
|
|
203
|
+
list.push(...fieldErrors);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
if (!list.length) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
return list;
|
|
210
|
+
}
|
|
190
211
|
}
|
|
191
212
|
|
|
192
213
|
/**
|