HEX
Server: Microsoft-IIS/8.5
System: Windows NT YDAWBH120 6.3 build 9600 (Windows Server 2012 R2 Standard Edition) AMD64
User: tentjecom_web (0)
PHP: 7.4.14
Disabled: NONE
Upload Files
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;
    }
}