This repository has been archived on 2024-10-24. You can view files and clone it, but cannot push or open issues or pull requests.
fradrive-old/frontend/src/utils/form/navigate-away-prompt.js
Sarah Vaupel 293ab6dc62 feat(navigate-away-prompt): prompt on actual value change only
Store the initial FormData of a form and only prompt if any current value is actually different than the corresponding initial value (i.e. do not trigger prompt just by change events)
2019-08-27 17:55:21 +02:00

90 lines
2.7 KiB
JavaScript

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;
}
}