{"version":3,"file":"form-helper-DWLFLO6I.js","sources":["../src/webcomponents/back-button.js","../src/webcomponents/form-helper.js"],"sourcesContent":["import BootstrapModal from \"bootstrap/js/dist/modal\";\r\n\r\nimport { UnshadowedElement, html, render } from \"../lib/unshadowed.js\";\r\n\r\n/** Back button, with prevention modal if form is dirty */\r\nexport class BackButton extends UnshadowedElement {\r\n static elementName() { return \"back-button\" }\r\n\r\n static properties = {\r\n href: { type: String },\r\n text: { type: String },\r\n /** Don't prompt the user if the form is dirty */\r\n preventDirtyWatch: { type: Boolean },\r\n /** Form id to watch for changes */\r\n dirtyWatch: { type: String },\r\n buttonClass: { type: String },\r\n\r\n dirty: { state: true, type: Boolean },\r\n }\r\n\r\n connectedCallback() {\r\n super.connectedCallback();\r\n // If there's not already a confirm modal available in the document, add one\r\n if (!this.preventDirtyWatch && !document.getElementById(\"modalBackConfirm\")) {\r\n const modal = html`\r\n
\r\n
\r\n
\r\n
\r\n

Unsaved Changes

\r\n \r\n
\r\n
\r\n

Do you want to leave this page? Changes you made will not be saved.

\r\n
\r\n Leave\r\n \r\n
\r\n
\r\n
\r\n
\r\n
\r\n `;\r\n // Manually render to body. Kludgey, but the modal needs to be outside of the button so we don't get messed up with position: sticky\r\n render(modal, document.body);\r\n }\r\n\r\n if (this.preventDirtyWatch) return;\r\n\r\n // Watch for form changes\r\n let form;\r\n if (this.dirtyWatch) {\r\n form = document.getElementById(this.dirtyWatch);\r\n } else {\r\n form = this.closest(\"form\");\r\n }\r\n if (form) {\r\n form.addEventListener(\"change\", () => { this.dirty = true; });\r\n form.addEventListener(\"pristine\", () => { this.dirty = false; });\r\n }\r\n }\r\n\r\n Click(e) {\r\n if (this.dirty) {\r\n e.preventDefault();\r\n const modalDiv = document.getElementById(\"modalBackConfirm\");\r\n if (modalDiv) {\r\n const anchor = modalDiv.querySelector(\"a\");\r\n if (anchor) anchor.href = this.href;\r\n const modal = BootstrapModal.getOrCreateInstance(modalDiv);\r\n modal.show();\r\n }\r\n }\r\n }\r\n\r\n render() {\r\n let content;\r\n if (this.text) {\r\n content = this.text;\r\n } else {\r\n content = html`\r\n \r\n \r\n Back\r\n `;\r\n }\r\n return html`\r\n ${content}\r\n `;\r\n }\r\n}\r\n","import { UnshadowedElement, html } from \"../lib/unshadowed.js\";\r\nimport { PostJSON } from \"../lib/fetchers.js\";\r\nimport { FormToObject } from \"../lib/form-utilities.js\";\r\n\r\nimport { BackButton } from \"./back-button.js\";\r\nimport { SubmitStatus } from \"./submit-status.js\";\r\n\r\nBackButton.Register();\r\nSubmitStatus.Register();\r\n\r\n/**\r\n * Form wrapper with helper functions\r\n * Creates a standardized form, with a sticky-button-bar on the bottom\r\n * Handles form submission, button loading state, status message, dirty and validated states\r\n * Makes any inputs tagged with enterkeyhint=\"next\" intercept the enter key and set focus to next input\r\n */\r\nexport class FormHelper extends UnshadowedElement {\r\n static elementName() { return \"form-helper\" }\r\n\r\n static properties = {\r\n /** URL to post the data to */\r\n postUrl: { type: String },\r\n /** Alternately, a function to run on submit. Preempts postUrl */\r\n submitFunction: { type: Function },\r\n /** Message to show on success */\r\n successMsg: { type: String },\r\n /** Message to show in submit button while posting */\r\n loadingMsg: { type: String },\r\n /** Back button can be hidden */\r\n backButtonHidden: { type: Boolean },\r\n /** Don't prompt the user if the form is dirty */\r\n preventDirtyWatch: { type: Boolean },\r\n /** Optional text to override back button text */\r\n backButtonText: { type: String },\r\n /** URL for back button destination */\r\n backButtonUrl: { type: String },\r\n /** Submit button text */\r\n submitButtonText: { type: String },\r\n /** Optional class for the form content and sticky buttons. \"center\" if not provided */\r\n wrapperClass: { type: String },\r\n /** Transformer function for formdata. Set this via JS after load. Should take in formdata, and modify it in place */\r\n transformer: { type: Function },\r\n /** SuccessProcessor function for successful post results. Set this via JS after load. Should take in post result object for processing */\r\n successProcessor: { type: Function },\r\n /** Submit button can be hidden */\r\n submitButtonHidden: { type: Boolean },\r\n\r\n content: { state: true, type: Array },\r\n dirty: { state: true, type: Array },\r\n validated: { state: true, type: Boolean },\r\n }\r\n\r\n constructor() {\r\n super();\r\n // Defaults\r\n this.wrapperClass = \"center\";\r\n }\r\n\r\n connectedCallback() {\r\n super.connectedCallback();\r\n // Intercept \"enter\" to behave like tab, if source input has enterkeyhint=\"next\"\r\n try {\r\n /** @type {HTMLInputElement[]} all inputs in the form that should intercept enter */\r\n const nextSources = this.querySelectorAll(\"[enterkeyhint=next]\");\r\n for (const nextSource of nextSources) {\r\n nextSource.addEventListener(\"keydown\", e => {\r\n if (e.key === \"Enter\") {\r\n /** @type {HTMLInputElement[]} all inputs in the form that can be tabbed into */\r\n const destinations = Array.from(this.querySelectorAll(\"input:not([type=hidden]):not([disabled]):not([type=button]):not([type=submit]), textarea\"));\r\n // Find the input in the big array since we don't want to only tab to \"enterkeyhint\" inputs\r\n const index = destinations.indexOf(nextSource);\r\n if (index > -1 && index < destinations.length - 1) {\r\n destinations[index + 1].focus();\r\n e.preventDefault();\r\n }\r\n }\r\n });\r\n }\r\n } catch (err) {\r\n console.error(\"Unable to intercept Enter key\");\r\n console.error(err);\r\n }\r\n\r\n // Scroll-margin doesn't always work in chromium browsers (for textarea), so we need to scroll focused inputs above the sticky bar manually\r\n try {\r\n const focusables = this.querySelectorAll(\"input, textarea\");\r\n for (const element of focusables) {\r\n element.addEventListener(\"focus\", () => {\r\n const elementBottom = element.getBoundingClientRect().bottom;\r\n const viewportHeight = window.innerHeight;\r\n const stickyFooter = document.querySelector(\".sticky-button-bar\");\r\n\r\n if (stickyFooter && elementBottom + stickyFooter.offsetHeight > viewportHeight) {\r\n window.scrollBy(0, 200);\r\n }\r\n });\r\n }\r\n } catch (err) {\r\n console.error(\"Unable to intercept input focus\");\r\n console.error(err);\r\n }\r\n\r\n // Slots don't work without shadow DOM, so we have to grab the contents ourselves\r\n this.content = Array.from(this.childNodes);\r\n }\r\n\r\n async PerformSubmit() {\r\n const submitButton = this.querySelector(\"[data-formhelper-submit-button]\");\r\n SubmitStatus.Hide(\"form-helper-status\");\r\n // Gather up all form fields as an object\r\n const form = this.querySelector(\"form\");\r\n let data = FormToObject(form);\r\n // If a transformer is provided, let it process the data\r\n if (this.transformer) {\r\n this.transformer(data);\r\n }\r\n try {\r\n if (this.loadingMsg && submitButton) {\r\n // Set loading status on submit button\r\n submitButton.classList.add(\"button-loading\");\r\n submitButton.setAttribute(\"disabled\", \"disabled\");\r\n submitButton.innerText = this.loadingMsg;\r\n }\r\n if (this.submitFunction) {\r\n await this.submitFunction(data);\r\n // Return submit button to normal\r\n if (this.loadingMsg && submitButton) {\r\n submitButton.classList.remove(\"button-loading\");\r\n submitButton.removeAttribute(\"disabled\");\r\n submitButton.innerHTML = this.submitButtonText;\r\n }\r\n } else {\r\n const result = await PostJSON(this.postUrl, data);\r\n if (result.returnUrl) {\r\n location.href = result.returnUrl;\r\n // Leave the submit button be and don't show success, since we're redirecting\r\n } else {\r\n SubmitStatus.Set(\"form-helper-status\", result.message || this.successMsg);\r\n form.dispatchEvent(new Event(\"pristine\"));\r\n if (this.successProcessor) {\r\n this.successProcessor(result.obj);\r\n }\r\n // Return submit button to normal\r\n if (this.loadingMsg && submitButton) {\r\n submitButton.classList.remove(\"button-loading\");\r\n submitButton.removeAttribute(\"disabled\");\r\n submitButton.innerHTML = this.submitButtonText;\r\n }\r\n }\r\n }\r\n } catch (err) {\r\n SubmitStatus.Set(\"form-helper-status\", err.message || \"The action did not complete. Please try again.\", true);\r\n // Return submit button to normal\r\n if (this.loadingMsg && submitButton) {\r\n submitButton.classList.remove(\"button-loading\");\r\n submitButton.removeAttribute(\"disabled\");\r\n submitButton.innerHTML = this.submitButtonText;\r\n }\r\n }\r\n }\r\n\r\n render() {\r\n let buttonContent = [];\r\n if (!this.backButtonHidden) {\r\n buttonContent.push(html``);\r\n }\r\n if (!this.submitButtonHidden) {\r\n buttonContent.push(html``);\r\n }\r\n\r\n return html`\r\n this.dirty = true}\r\n class=\"space-children:stack:space-child-default ${this.validated ? \"was-validated\" : null}\"\r\n action=\"javascript:void(0);\"\r\n autocomplete=\"off\"\r\n >\r\n
\r\n ${this.content}\r\n
\r\n
\r\n
\r\n \r\n ${buttonContent}\r\n
\r\n
\r\n \r\n `;\r\n }\r\n\r\n observer = null;\r\n updated() {\r\n // Create an intersection observer for the sticky bar to toggle the shadow\r\n if (!this.observer) {\r\n const el = document.querySelector(\".sticky-button-bar\")\r\n if (el) {\r\n this.observer = new IntersectionObserver(\r\n ([e]) => e.target.classList.toggle(\"threshold-reached\", e.intersectionRatio < 1),\r\n { threshold: [1] }\r\n );\r\n this.observer.observe(el);\r\n }\r\n }\r\n }\r\n}\r\n"],"names":["BackButton","_UnshadowedElement","_classCallCheck","_callSuper","this","arguments","_inherits","UnshadowedElement","_createClass","key","value","form","_this","_superPropGet","preventDirtyWatch","document","getElementById","modal","html","_templateObject","_taggedTemplateLiteral","render","body","dirtyWatch","closest","addEventListener","dirty","e","preventDefault","modalDiv","anchor","querySelector","href","BootstrapModal","getOrCreateInstance","show","content","text","_templateObject2","_templateObject3","Click","buttonClass","_defineProperty","type","String","Boolean","state","Register","SubmitStatus","FormHelper","wrapperClass","_this2","_step","nextSources","querySelectorAll","_iterator","_createForOfIteratorHelper","_loop","nextSource","destinations","Array","from","index","indexOf","length","focus","s","n","done","err","f","console","error","_step2","focusables","_iterator2","_loop2","element","elementBottom","getBoundingClientRect","bottom","viewportHeight","window","innerHeight","stickyFooter","offsetHeight","scrollBy","childNodes","_PerformSubmit","_asyncToGenerator","_regeneratorRuntime","mark","_callee","submitButton","data","result","wrap","_context","prev","next","Hide","FormToObject","transformer","loadingMsg","classList","add","setAttribute","innerText","submitFunction","remove","removeAttribute","innerHTML","submitButtonText","PostJSON","postUrl","sent","returnUrl","location","Set","message","successMsg","dispatchEvent","Event","successProcessor","obj","t0","stop","apply","_this3","buttonContent","backButtonHidden","push","backButtonUrl","backButtonText","submitButtonHidden","validated","PerformSubmit","observer","el","IntersectionObserver","_ref","_slicedToArray","target","toggle","intersectionRatio","threshold","observe","Function"],"mappings":"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"}