import { Utility } from '../../core/utility'; import { AUTO_SUBMIT_BUTTON_UTIL_SELECTOR } from './auto-submit-button'; import { AUTO_SUBMIT_INPUT_UTIL_SELECTOR } from './auto-submit-input'; /** * Key generator from an arbitrary number of FormData objects. * @param {...any} formDatas FormData objects */ function* generatorFromFormDatas(...formDatas) { for (let formData of formDatas) { yield* formData.keys(); } } const NAVIGATE_AWAY_PROMPT_INITIALIZED_CLASS = 'navigate-away-prompt--initialized'; const NAVIGATE_AWAY_PROMPT_UTIL_OPTOUT = '[uw-no-navigate-away-prompt]'; @Utility({ selector: 'form', }) export class NavigateAwayPrompt { _element; _initFormData; _unloadDueToSubmit = false; constructor(element) { if (!element) { throw new Error('Navigate Away Prompt utility needs to be passed an element!'); } this._element = element; this._initFormData = new FormData(this._element); if (this._element.classList.contains(NAVIGATE_AWAY_PROMPT_INITIALIZED_CLASS)) { return false; } // ignore forms that get submitted automatically if (this._element.querySelector(AUTO_SUBMIT_BUTTON_UTIL_SELECTOR) || this._element.querySelector(AUTO_SUBMIT_INPUT_UTIL_SELECTOR)) { return false; } if (this._element.matches(NAVIGATE_AWAY_PROMPT_UTIL_OPTOUT)) { return false; } window.addEventListener('beforeunload', this._beforeUnloadHandler); this._element.addEventListener('submit', () => { this._unloadDueToSubmit = true; }); // mark initialized this._element.classList.add(NAVIGATE_AWAY_PROMPT_INITIALIZED_CLASS); } destroy() { window.removeEventListener('beforeunload', this._beforeUnloadHandler); } _beforeUnloadHandler = (event) => { // compare every value of the current FormData with every corresponding value of the initial FormData and set formDataHasChanged to true if there is at least one change const currentFormData = new FormData(this._element); var formDataHasChanged = false; for (let key of generatorFromFormDatas(this._initFormData, currentFormData)) { if (currentFormData.get(key) !== this._initFormData.get(key)) { formDataHasChanged = true; break; } } // allow the event to happen if the form was not touched by the // user (i.e. if the current FormData is equal to the initial FormData) // or the unload event was initiated by a form submit if (!formDataHasChanged || this._unloadDueToSubmit) { return false; } // cancel the unload event. This is the standard to force the prompt to appear. event.preventDefault(); // chrome event.returnValue = true; // for all non standard compliant browsers we return a truthy value to activate the prompt. return true; } }