ModalDialog Web Component

PHOTO EMBED

Sun Feb 06 2022 18:56:33 GMT+0000 (Coordinated Universal Time)

Saved by @daemondevin #javascript #modal #alert #prompt #confirm

/**
 * ## ModalDialog Web Component
 * Custom dialog components using a Promise-based modal for user interaction.
 *
 * ## Example:
 * ```
 * // Replace the native javascript functions
 * const alert   = modal.alert,
 *       prompt  = modal.prompt,
 *       confirm = modal.confirm;
 *
 * let reset = async () => {
 *     let result = await confirm("Are you sure you want to reset the application? <br /><br /> This will erase all your saved data and cannot be undone.", "Reset Application?");
 *     if (result === true) {
 *         let done = await resetFunction();
 *         if (done) {
 *             alert("This application has been successfully reset!", "Finished!");
 *         } else {
 *             alert("Something went wrong! You're settings remain unchanged.", "Unexpected Error!");
 *         }
 *     } else if (result === false) {
 *         alert("This application was not reset! You're settings remain unchanged", "Reset Canceled!");
 *     }
 * }
 * ```
 */
class ModalDialogElement extends HTMLElement {

    constructor() {
        super();
        this.build();
    }

    /**
     * Assigns and builds the dialog
     */
    build() {
        this.attachShadow({ mode: "open" });
        const modalDialog = document.createElement("div");
        modalDialog.classList.add("modal-dialog-window");

        const dialogElement = document.createElement("div");
        const dialogHeader = document.createElement("div");
        const dialogBody = document.createElement("div");
        const dialogFooter = document.createElement("div");

        dialogElement.classList.add("modal-dialog");
        dialogHeader.classList.add("modal-dialog-header");
        dialogBody.classList.add("modal-dialog-body");
        dialogFooter.classList.add("modal-dialog-footer");
        dialogBody.append(document.createElement("p"));
        dialogElement.append(dialogHeader, dialogBody, dialogFooter);
        modalDialog.append(dialogElement);

        const style = document.createElement("style");
        style.textContent = this.setStyle();
        this.shadowRoot.append(style, modalDialog);
    }

    /**
     * Returns the CSS styles for the modal dialogs.
     * @param {string} cssPadding the amount of padding for the modal
     */
    setStyle(cssPadding= '1em') {
        const padding = cssPadding;
        return `
      .modal-dialog-window {
        user-select: none;
        font-family: inherit;
        font-size: inherit;
        z-index: 999;
        width: 100%;
        height: 100%;
        display: flex;
        justify-content: center;
        align-items: center;
        overflow: auto;
        position: fixed;
        top: 0;
        left: 0;
      }
      
      .modal-dialog {
        width: calc(100% - 2em);
        max-width: 400px;
        overflow: hidden;
        box-sizing: border-box;
        box-shadow: 0 0.5em 1em rgba(0, 0, 0, 0.5);
        border-radius: 0.3em;
        animation: modal-dialog-show 265ms cubic-bezier(0.18, 0.89, 0.32, 1.28)
      }
      
      .modal-dialog.modal-dialog-hide {
        opacity: 0;
        animation: modal-dialog-hide 265ms ease-in;
      }
      
      @keyframes modal-dialog-show {
        0% {
          opacity: 0;
          transform: translateY(-100%);
        }
        100% {
          opacity: 1;
          transform: translateX(0);
        }
      }
      
      @keyframes modal-dialog-hide {
        0% {
          opacity: 1;
          transform: translateX(0);
        }
        100% {
          opacity: 0;
          transform: translateY(-50%);
        }
      }
      
      .modal-dialog-header {
        font-family: inherit;
        font-size: 1.25em;
        color: inherit;
        background-color: rgba(0, 0, 0, 0.05);
        padding: 1em;
        border-bottom: solid 1px rgba(0, 0, 0, 0.15);
      }
      
      .modal-dialog-body {
        color: inherit;
        padding: ${padding};
      }
      
      .modal-dialog-body > p {
        color: inherit;
        padding: 0;
        margin: 0;
      }
            
      .modal-dialog .error {
        color: #db2828;
      }

      .modal-dialog .success {
        color: #21ba45;
      }

      .modal-dialog .info {
        color: #31ccec;
      }
                  
      .modal-dialog .warning {
        color: #f2c037;
      }
      
      .modal-dialog-footer {
        color: inherit;
        background-color: rgba(0, 0, 0, 0.5);
        display: flex;
        justify-content: stretch;
      }
      
      .modal-dialog-button {
        color: inherit;
        font-family: inherit;
        font-size: inherit;
        background-color: rgba(0, 0, 0, 0);
        width: 100%;
        padding: 1em;
        border: none;
        border-top: solid 1px rgba(0, 0, 0, 0.15);
        outline: 0;
        border-radius: 0px;
        transition: background-color 225ms ease-out;
      }
      
      .modal-dialog-button:focus {
        background-color: rgba(0, 0, 0, 0.05);
      }
      
      .modal-dialog-button:active {
        background-color: rgba(0, 0, 0, 0.15);
      }
      
      .modal-dialog-input {
        color: inherit;
        font-family: inherit;
        font-size: inherit;
        width: 100%;
        padding: 0.5em;
        border: solid 1px rgba(0, 0, 0, 0.15);
        margin-top: ${padding};
        outline: 0;
        box-sizing: border-box;
        border-radius: 0;
        box-shadow: 0 0 0 0 rgba(13, 134, 255, 0.5);
        transition: box-shadow 125ms ease-out, border 125ms ease-out;
      }
      
      .modal-dialog-input:focus {
        border: solid 1px rgba(13, 134, 255, 0.8);
        box-shadow: 0 0 0.1em 0.2em rgba(13, 134, 255, 0.5);
      }
      
      @media (prefers-color-scheme: dark) {
        .modal-dialog-window {
          background-color: rgba(31, 31, 31, 0.5);
        }
        
        .modal-dialog {
          color: #f2f2f2;
          background-color: #464646;
        }
        
        .modal-dialog-input {
          background-color: #2f2f2f;
        }
      }
      
      @media (prefers-color-scheme: light) {
        .modal-dialog-window {
          background-color: rgba(221, 221, 221, 0.5);
        }
        
        .modal-dialog {
          color: #101010;
          background-color: #ffffff;
        }
      }
    `;
    }

    /**
     * Creates a dialog with the specified content and optional title header.
     * @param {string} content the message to alert the client
     * @param {string} title optional title for the dialog modal
     */
    createDialog(content, title= undefined) {
        const dialogHeader = this.shadowRoot.querySelector(".modal-dialog-header");
        const dialogBody = this.shadowRoot.querySelector(".modal-dialog-body > p");
        dialogBody.innerHTML = content;
        if (title === undefined) {
            dialogHeader.remove();
        }
        else {
            dialogHeader.innerHTML = title;
        }
    }

    /**
     * Removes a dialog after listening for the 'animationend' event to trigger
     */
    disposeDialog() {
        const self = this;
        const dialogElement = self.shadowRoot.querySelector(".modal-dialog");
        dialogElement.classList.add("modal-dialog-hide");
        dialogElement.addEventListener("animationend", function dialogElementAnimationEnd(event) {
            if (event.animationName === "modal-dialog-hide") {
                dialogElement.removeEventListener("animationend", dialogElementAnimationEnd);
                self.remove();
            }
        });
    }
}

/**
 * Promised-based alert dialog
 */
class AlertModalDialog extends ModalDialogElement {

    constructor() {
        super();
        this.setDefault()
    }

    /**
     * Sets the default values for the modal dialog
     */
    setDefault() {
        let content = this.dataset.content;
        let title = this.dataset.title;
        if (typeof content === "undefined") {
            return;
        }
        if (typeof title === "undefined") {
            title = null;
        }
        this.setAlert(content, title);
    }

    /**
     * Creates an alert dialog modal that waits for user interaction.
     * @param {string} content the message to alert the client
     * @param {string} title optional title for the dialog modal
     * @returns {Promise<boolean>}
     */
    setAlert(content, title) {
        const self = this;
        self.createDialog(content, title);
        const dialogFooterElm = self.shadowRoot.querySelector(".modal-dialog-footer");
        const dialogConfirmBtn = document.createElement("button");
        dialogConfirmBtn.classList.add("modal-dialog-button");
        dialogConfirmBtn.innerText = "OK";
        dialogFooterElm.append(dialogConfirmBtn);
        dialogConfirmBtn.focus();
        return new Promise(function (resolve) {
            dialogConfirmBtn.addEventListener("click", function dialogConfirmBtnClick() {
                dialogConfirmBtn.removeEventListener("click", dialogConfirmBtnClick);
                self.disposeDialog();
                resolve(true);
            });
        });
    }
}

// Declare the custom alert modal dialog element as a web component
customElements.define("modal-alert", AlertModalDialog);

class ConfirmModalDialog extends ModalDialogElement {

    constructor() {
        super();
        this.setDefault();
    }

    /**
     * Sets the default values for the modal dialog
     */
    setDefault() {
        let content = this.dataset.content;
        let title = this.dataset.title;
        if (typeof content === "undefined") {
            return;
        }
        if (typeof title === "undefined") {
            title = null;
        }
        this.setConfirm(content, title);
    }

    /**
     * Creates a confirm dialog modal that waits for user interaction.
     * @param {string} condition the message inquiry for the client 
     * @param {string} title optional title for the dialog modal
     * @returns {Promise<boolean>}
     */
    setConfirm(condition, title) {
        const self = this;
        self.createDialog(condition, title);
        const dialogFooter = self.shadowRoot.querySelector(".modal-dialog-footer");
        const dialogBtnCancel = document.createElement("button");
        const dialogBtnConfirm = document.createElement("button");
        dialogBtnCancel.classList.add("modal-dialog-button");
        dialogBtnCancel.innerText = "Cancel";
        dialogBtnConfirm.classList.add("modal-dialog-button");
        dialogBtnConfirm.innerText = "OK";
        dialogFooter.append(dialogBtnCancel, dialogBtnConfirm);
        dialogBtnCancel.focus();
        return new Promise(function (resolve) {
            dialogBtnCancel.addEventListener("click", function dialogBtnCancelClick() {
                this.removeEventListener("click", dialogBtnCancelClick);
                self.disposeDialog();
                resolve(false);
            });
            dialogBtnConfirm.addEventListener("click", function dialogBtnConfirmClick() {
                this.removeEventListener("click", dialogBtnConfirmClick);
                self.disposeDialog();
                resolve(true);
            });
        });
    }
}

// Declare the custom confirm modal dialog element as a web component
customElements.define("modal-confirm", ConfirmModalDialog);

class PromptModalDialog extends ModalDialogElement {

    constructor() {
        super();
        this.setDefault();
    }

    /**
     * Sets the default values for the modal dialog
     */
    setDefault() {
        let content = this.dataset.content;
        let title = this.dataset.title;
        if (typeof content === "undefined") {
            return;
        }
        if (typeof title === "undefined") {
            title = null;
        }
        this.setPrompt(content, title);
    }

    /**
     * Creates a prompt dialog modal that waits for user interaction.
     * @param {string} content the message to prompt the client
     * @param {string} title optional title for the dialog modal
     * @returns {Promise<value>}
     */
    setPrompt(content, title) {
        const self = this;
        self.createDialog(content, title);
        const dialogBody = self.shadowRoot.querySelector(".modal-dialog-body");
        const dialogInputWrapper = document.createElement("p");
        const dialogInput = document.createElement("input");
        dialogInput.classList.add("modal-dialog-textbox");
        dialogInput.type = "text";
        dialogInputWrapper.append(dialogInput);
        dialogBody.append(dialogInputWrapper);
        const dialogFooter = self.shadowRoot.querySelector(".modal-dialog-footer");
        const dialogBtnCancel = document.createElement("button");
        const dialogBtnConfirm = document.createElement("button");
        dialogBtnCancel.classList.add("modal-dialog-button");
        dialogBtnCancel.innerText = "Cancel";
        dialogBtnConfirm.classList.add("modal-dialog-button");
        dialogBtnConfirm.innerText = "OK";
        dialogFooter.append(dialogBtnCancel, dialogBtnConfirm);
        dialogInput.focus();

        /**
         * Listen for the <code>Enter</code> key press event.
         * @param {event} e handler for the key press event
         */
        function dialogInputKeyPress(e) {
            if (e.key === "Enter") {
                dialogBtnConfirm.click();
            }
        }
        dialogInput.addEventListener("keypress", dialogInputKeyPress);
        return new Promise(function (resolve) {
            let userInput = null;
            dialogBtnCancel.addEventListener("click", function dialogBtnCancelClick() {
                this.removeEventListener("click", dialogBtnCancelClick);
                dialogInput.removeEventListener("keypress", dialogInputKeyPress);
                self.disposeDialog();
                resolve(userInput);
            });
            dialogBtnConfirm.addEventListener("click", function dialogBtnConfirmClick() {
                this.removeEventListener("click", dialogBtnConfirmClick);
                dialogInput.removeEventListener("keypress", dialogInputKeyPress);
                self.disposeDialog();
                userInput = dialogInput.value;
                resolve(userInput);
            });
        });
    }
}

// Declare the custom prompt modal dialog element as a web component
customElements.define("modal-prompt", PromptModalDialog);

/**
 * Class to handle the rendering of the custom ModalDialog elements.
 */
class modal {
    static async alert(content, title) {
        const alertDialog = document.createElement("modal-alert");
        document.body.appendChild(alertDialog);
        return await alertDialog.setAlert(content, title);
    }
    static async confirm(content, title = null) {
        const confirmDialog = document.createElement("modal-confirm");
        document.body.appendChild(confirmDialog);
        return await confirmDialog.setConfirm(content, title);
    }
    static async prompt(content, title = null) {
        const promptDialog = document.createElement("modal-prompt");
        document.body.appendChild(promptDialog);
        return await promptDialog.setPrompt(content, title);
    }
}
 Save
content_copyCOPY

https://github.com/daemondevin/cdn/blob/main/ModalDialog.js