File: D:/HostingSpaces/SBogers10/carrot.komma.pro/resources/js/kms/entity/confirmationController.js
/**
* The confirmation controller receives a navigatable element in its constructor.
* This is an element, that when clicked on, navigates the browser to some other page. Usually an anchor tag <a>.
*
* When the user then clicks on that element, he must confirm the click, because a modal will pop up, asking for confirmation.
* When the user presses the confirm button, the button will be really clicked.
* Or otherwise, when a confirmCallback is specified with the setConfirmCallback method, that callback will be triggered instead.
*
* You can disable confirmation by passing a callback to the setOnlyConfirmIfTrueCallback method. When this callback returns false,
* not confirmation modal is shown when the users presses the navigatable element.
*
* You can set the translations for the model using the methods setHeaderText, setMessage, setConfirmText and setCancelText.
* You can also set the translations automatically up construction when you set the following data attributes on the navigatable element:
* data-confirm-header, data-confirm-message, data-confirm-confirm-text, data-confirm-cancel-text.
*/
class ConfirmationController {
/**
* ConfirmationController constructor
*
* @param navigatableElement {HTMLElement}
*/
constructor(navigatableElement) {
if (this._validateElement(navigatableElement) === false) {
console.error('The navigatableElement isn\'t valid. Did not enable the confirmation functionality for that navigatableElement. You passed a "'+navigatableElement.tagName+'"');
return;
}
//Set translations form dataset or an empty string if not present
this._headerText = navigatableElement.dataset.confirmHeader ? navigatableElement.dataset.confirmHeader : '';
this._message = navigatableElement.dataset.confirmMessage ? navigatableElement.dataset.confirmMessage : '';
this._confirmText = navigatableElement.dataset.confirmConfirmText ? navigatableElement.dataset.confirmConfirmText : '';
this._cancelText = navigatableElement.dataset.confirmCancelText ? navigatableElement.dataset.confirmCancelText : '';
this._navigatableElement = navigatableElement;
this._onlyConfirmIfTrueCallback = null; //An optional callback that must return true or false to determine if the confirmation controller must show the prompt or not for the navigatableElement
this._confirmCallback = null; //An optional callback that is triggered when the user confirmed the action
this._clickEventHandler = this._clickEventHandler.bind(this);
this._confirmClicked = this._confirmClicked.bind(this);
this._cancelClicked = this._cancelClicked.bind(this);
this.enableListeners();
}
/**
* Enables listeners so that clicking on the navigatableElement can work.
*/
enableListeners() {
this.disableListeners();
// this._navigatableElement.addEventListener('click', this._clickEventHandler.bind(this));
this._navigatableElement.addEventListener('click', this._clickEventHandler);
}
/**
* Disables listeners so that clicking on the navigatableElement will never trigger the confirmation modal anymore via this controller
*/
disableListeners() {
// this._navigatableElement.removeEventListener('click', this._clickEventHandler.bind(this));
this._navigatableElement.removeEventListener('click', this._clickEventHandler);
}
/**
* The click event handler that handles clicks on the navigatable that was passed in via the constructor.
*
* @var {MouseEvent} clickEvent
*/
_clickEventHandler(clickEvent)
{
let confirm = false;
if((this._onlyConfirmIfTrueCallback && this._onlyConfirmIfTrueCallback.call() === true) || !this._onlyConfirmIfTrueCallback) confirm = true;
if(confirm) {
clickEvent.preventDefault();
this._showConfirmationPrompt(true, this._navigatableElement);
}
}
/**
* Sets a callback that wil be used to check if we actually need to confirm the action on the navigatableElement or just allow it.
* When the callback returns true or if you don't specify the callback, confirmation wil be done. When false. it won't be done.
*
* @param onlyConfirmIfTrueCallback
*/
setOnlyConfirmIfTrueCallback(onlyConfirmIfTrueCallback) {
this._onlyConfirmIfTrueCallback = onlyConfirmIfTrueCallback;
}
/**
* Add or remove listeners to / from the modal buttons elements.
* Usually the yes no button elements.
*
* @private
*/
_addListenersToModalButtons(add = true, originalNavigatableElement) {
if (!this.promptElement) console.error('First create the modal with the createPrompt method');
let confirmButton = this.promptElement.querySelector('button.confirm');
let cancelButton = this.promptElement.querySelector('button.cancel');
let shader = this.promptElement.querySelector('div.shader');
confirmButton.removeEventListener('click', this._confirmClicked);
cancelButton.removeEventListener('click', this._cancelClicked);
shader.removeEventListener('click', this._cancelClicked);
if(add) {
confirmButton.addEventListener('click', this._confirmClicked);
cancelButton.addEventListener('click', this._cancelClicked);
shader.addEventListener('click', this._cancelClicked);
}
}
/**
* Sets an optional callback that is triggered when the user confirmed the action.
* If you don't specify it, the nearest form that is found by traversing up into the dom is submitted
*
* @param callback
*/
setConfirmCallback(callback)
{
this._confirmCallback = callback;
return this;
}
/**
* Triggered when the user clicks the confirm button
*
* @param mouseEvent
* @private
*/
_confirmClicked(mouseEvent) {
mouseEvent.preventDefault();
if(this._confirmCallback) {
this._confirmCallback.call();
} else {
//Find the nearest form by traversing up into the dom
this._addNavigatableElementValueToForm();
this._submitNearestForm(false);
}
this._showConfirmationPrompt(false);
}
/**
* If the navigatable element contains a value,
* we add a hidden input to the form with the same name and value
* as the navigatable. Making sure that its value is passed as expected.
*
* @private
*/
_addNavigatableElementValueToForm()
{
if(this._navigatableElement.hasAttribute('value') && this._navigatableElement.hasAttribute('name')) {
let form = this._findForm(this._navigatableElement);
if(!form) return;
let input = document.createElement('INPUT');
input.setAttribute('type', 'hidden');
input.setAttribute('name', this._navigatableElement.getAttribute('name'));
input.setAttribute('value', this._navigatableElement.getAttribute('value'));
form.appendChild(input);
}
}
/**
* Submit the nearest form the navigatableElement is in,
* by traversing up into the dome as long as no form is found
*
* @private
*/
_submitNearestForm()
{
let form = this._findForm(this._navigatableElement);
if (form) {
form.submit();
return true;
} else {
console.error('Could not submit the nearest form this navigatableElement should be in, because it isn\'t in a form.');
return false;
}
}
/**
* Triggered when the user clicks the cancel button
*
* @param mouseEvent
* @private
*/
_cancelClicked(mouseEvent) {
mouseEvent.preventDefault();
this._showConfirmationPrompt(false);
}
/**
* Retrieves an HTML element and starts traversing up into the dom to find the first form the element is in.
* Returns the form or false if it cannot be found
* @param element
* @private
*/
_findForm(element) {
if (!element.parentNode) return false;
if (element.parentNode.tagName === 'FORM') return element.parentNode;
return this._findForm(element.parentNode);
}
/**
* Create a div that represents a prompt
*
* @returns {HTMLDivElement}
* @private
*/
_createPrompt() {
let html = '<div class="modal">' +
'<div class="header"><h4>' + this._headerText + '</h4></div>' +
'<div class="body">' +
'<p class="message">' + this._message + '</p>' +
'<div class="navigatableElements buttons">' +
'<button class="confirm" dusk="confirmation_confirm">' + this._confirmText + '</button>' +
'<button class="cancel" dusk="confirmation_cancel">' + this._cancelText + '</button>' +
'</div>' +
'</div>' +
'</div>' +
'</div>' +
'<div class="shader"></div>';
let promptElement = document.createElement("div");
promptElement.setAttribute('id', 'confirmBox');
promptElement.setAttribute('dusk', 'confirmBox');
promptElement.innerHTML = html;
return promptElement;
}
/**
* Show or hides the confirmation prompt by adding a hidden class to it.
*
* @private
*/
_showConfirmationPrompt(show, originalNavigatableElement) {
console.log(show ? 'show confirmation prompt' : 'hide confirmation prompt');
if(!this.promptElement) {
this.promptElement = this._createPrompt();
this._addListenersToModalButtons(true, originalNavigatableElement);
}
if(show) {
this.promptElement.classList.add('show');
document.body.appendChild(this.promptElement);
} else {
this.promptElement.classList.remove('show');
if(this.promptElement.parentElement) {
this.promptElement.parentElement.removeChild(this.promptElement);
}
this.promptElement = null;
}
}
/**
* Returns true when the navigatableElement is a navigatableElement tag or an input tag with type navigatableElement.
* false if not
*
* @param navigatableElement {HTMLElement}
* @returns {boolean}
*
* @private
*/
_validateElement(navigatableElement) {
if (!navigatableElement) return false;
if (navigatableElement.tagName !== 'BUTTON' && navigatableElement.tagName !== 'INPUT' && navigatableElement.tagName !== 'A') return false;
if (navigatableElement.tagName === "INPUT") {
if (navigatableElement.getAttribute('type').toLowerCase() !== 'navigatableElement' && navigatableElement.getAttribute('type').toLowerCase() !== 'submit') return false;
}
return true;
}
/**
* @param text
* @returns {ConfirmationController}
*/
setHeaderText(text) {
this._headerText = text;
return this;
}
/**
* @param text
* @returns {ConfirmationController}
*/
setMessage(text) {
this._message = text;
return this;
}
/**
* @param text
* @returns {ConfirmationController}
*/
setConfirmText(text) {
this._confirmText= text;
return this;
}
/**
* @param text
* @returns {ConfirmationController}
*/
setCancelText(text) {
this._cancelText= text;
return this;
}
}