File: D:/HostingSpaces/SBogers10/vangogh.komma.pro/resources/js/kms/entity/preventNavigationController.js
/**
* Tracks all inputs, selects and textareas in the given wrapper element for changes.
*
* If the user clicked an anchor the user first must confirm that he's going to lose changes before the anchor is followed.
*/
class PreventNavigationController
{
/**
* inputs in the wrapper element will be tracked for changes
*
* @param WrapperElement {Element}
*/
constructor(WrapperElement) {
//Create properties
this._inputs = []; //Inputs, textareas, selects
this._wrapperElement = null;
this._translation = {
headerText: '',
message: '',
confirmText: '',
cancelText: '',
};
this._anchorsAndConfirmationControllers = []; //Contains objects containing properties element and controller to keep track of all anchors and their confirmation controllers.
this._changed = false; //Automatically set to true when one of the inputs was changed
//validate stuff
if(!WrapperElement) {
console.error('PreventNavigationController: No wrapper element given. Not preventing navigation.');
return;
}
if(!WrapperElement.dataset.translation) {
console.error('PreventNavigationController: No translation present. Not preventing navigation when not all changes have been saved.');
return;
}
this._wrapperElement = WrapperElement;
//Make sure event handlers have the correct this
this._inputChanged = this._inputChanged.bind(this);
//Delegate control to specialist parts of the class
this._loadTranslation();
this.refresh();
}
/**
* Returns true if the given input element is an HTMLElement of tag Input.
*
* @param InputElement {HTMLElement}
* @returns {boolean}
* @private
*/
_isTrackableElement(InputElement) {
return (InputElement.tagName === "INPUT" || InputElement.tagName === "SELECT" || InputElement.tagName === "TEXTAREA")
}
/**
* Add / remove listeners on inputs
*
* @param inputs {array}
* @param add {boolean} true when you want to add the listeners. false if not.
* @private
*/
_setListenersOnInputs(inputs, add = true)
{
let nInputs = inputs.length;
for(let currentInputNumber = 0; currentInputNumber < nInputs; currentInputNumber++)
{
if(add) {
inputs[currentInputNumber].addEventListener('change', this._inputChanged);
} else {
inputs[currentInputNumber].removeEventListener('change', this._inputChanged);
}
}
}
/**
* Triggered in response to a changed input
*
* @param event
* @private
*/
_inputChanged(event) {
// console.log('Form changed. Source: ', event.target);
this._changed = true;
}
/**
* Loads the translations. They are defined on the wrapper element
* @private
*/
_loadTranslation()
{
this._translation = JSON.parse(this._wrapperElement.dataset.translation);
}
/**
* Scans the documents for inputs to monitor changes on. These inputs will be stored as a reference.
* We then intercept clicks on all elements that cause navigations (<a> tags).
* And finally we set listeners on the found inputs to detect changes. This ultimately will cause
* a model to pop up when navigating. If the page is changed somehow, you may also call this method to
* make the controller aware of changes.
*/
refresh()
{
this._inputs = this._findAllInputsIn(this._wrapperElement);
this._interceptClicksOnAnchors();
this._setListenersOnInputs(this._inputs);
}
/**
* Scans the document for anchor tags. Intercepts click events on them when one of the inputs
* has changed. When a click is intercepted, a modal is shown asking for confirmation.
* When the action gets confirmed, a new click on the anchor tag is done, without interception.
*
* @private
*/
_interceptClicksOnAnchors()
{
this._clearAnchorsAndControllers();
let anchors = document.getElementsByTagName('A');
let nAnchors = anchors.length;
for(let linkNumber = 0; linkNumber < nAnchors; linkNumber++)
{
let currentAnchor = anchors[linkNumber];
let anchorHref = currentAnchor.getAttribute('href');
if(anchorHref && anchorHref.substr(0,1) !== "#") { //The anchor has an href value and that is not a hash.
let controller = new ConfirmationController(currentAnchor);
controller.setHeaderText(this._translation.headerText)
.setMessage(this._translation.message)
.setConfirmText(this._translation.confirmText)
.setCancelText(this._translation.cancelText)
.setConfirmCallback(function() {
window.location = anchorHref;
})
.setOnlyConfirmIfTrueCallback((function (controller) {
return function()
{
return controller._hasChanged();
}
})(this));
this._anchorsAndConfirmationControllers.push({
'anchor': currentAnchor,
'controller': controller
})
}
}
}
/**
* Clears all anchors and controllers.
* Also disables event listeners in those controllers.
*
* @private
*/
_clearAnchorsAndControllers() {
let anchorControllerObject;
while(anchorControllerObject = this._anchorsAndConfirmationControllers.pop()) {
/** @var {ConfirmationController} controller **/
// console.log('Clearing old confirmation controller: ', anchorControllerObject);
let controller = anchorControllerObject.controller;
controller.disableListeners();
controller = null;
}
}
/**
* Returns true if one of the input has changed
* @returns {boolean}
* @private
*/
_hasChanged()
{
// console.log('Something changed! Preventing navigation');
return this._changed;
}
/**
* Finds all html elements witch match elements specified in the isTrackableElement method
*
* @param WrapperElement {Element}
* @param inputs
* @returns {Array}
* @private
*/
_findAllInputsIn(WrapperElement, inputs = []) {
let nChildren = WrapperElement.children.length;
if(nChildren === 0) return inputs;
for(let index = 0; index < nChildren; index++)
{
let currentChild = WrapperElement.children[index];
if(this._isTrackableElement(currentChild)) {
inputs.push(currentChild);
}
if(currentChild.children.length > 0) {
let childInputs = this._findAllInputsIn(currentChild, inputs);
inputs.concat(childInputs);
}
}
return inputs;
}
}