UNPKG

jolt-ui

Version:

A web components based SPA framework

637 lines (588 loc) 23.4 kB
import App from "../app.js"; import { html } from "../baseCore.js"; class CDfMessenger extends HTMLElement{ static tagName = "c-df-messenger"; constructor(){ super(); } /** * Generates message id with random number * @param {number} max * @param {number} min * @returns {string} */ _randomMsgId = (max = 1000, min = 1) => { const id = Math.floor(Math.random() * (max - min + 1)) + min; return `message-${id}`; } markup = ({ message, status, msgId }) => { return html` <div class="msg-container row ${status} ${msgId} bg-${status} border border-dark rounded py-4 px-1 shadow" id="${msgId}"> <div class="col-auto"> <button class="btn btn-sm close-${msgId}" type="button"><i class="fa-solid fa-xmark"></i></button> </div> <div class="col text-center m-1"> ${message} </div> </div>` } /** * Sets message * @param {Object} configs * @param {string} configs.message - message to display * @param {string} configs.status - status of the message (bootstrap class) * @param {Number} configs.timeout - timeout for message in seconds */ _setMessage = ({ message, status, timeout }) => { const msgId = this._randomMsgId(); const msgMarkup = this.markup({ message, status, msgId}); this.insertAdjacentHTML("afterbegin", msgMarkup); setTimeout(() => this.querySelector("#"+msgId)?.classList.add("shown"), 20); const intervalId = this._activateMsg(msgId, timeout); this._setTimeout(msgId, timeout, intervalId); } /** * Sets timeout for shown message * @param {string} msgId * @param {number} timeout * @param {number} intervalId */ _setTimeout = (msgId, timeout, intervalId) => { setTimeout(() => { const el = this.querySelector(`.${msgId}`); if(el){ el.remove() } }, timeout*1000); } /** * Adds event listener to msg button for removing message * and activates animation * @param {string} msgId * @param {number} timeout */ _activateMsg = (msgId, timeout) => { const btn = this.querySelector(`.close-${msgId}`); btn?.addEventListener("click", (event) => { this._removeMsg(event); }); } /** * Removes msg containing the pressed button - event listener method * @param {Event} event */ _removeMsg = (event) => { const el = event.target.closest(".msg-container"); if(!el) return; el.classList.remove("shown"); el.ontransitionend = (event) => { el.remove(); }; } /** * Returns simple html markup for this HTML element with some styling * @returns {string} */ static _generate(appId){ if(!appId){ throw new Error("Line messenger element need a valid appId from their parent application.") } return `<${this.tagName} app-id="${appId}" style="position: fixed; z-index: 3000; width: 95%; max-width:330px; right:20px; top:65px"></${this.tagName}>` } } class CDfModalMessenger extends HTMLElement{ static tagName = "c-df-modal-messenger"; constructor(){ super(); } /** * Generates message id with random number * @param {number} max * @param {number} min * @returns {string} */ _modalId = (max = 1000, min = 1) => { const id = Math.floor(Math.random() * (max - min + 1)) + min; return `modal-${id}`; } _confirmModalMarkup = ({ title, message, closeTxt, modalSize, confirmTxt, modalId }) => { const zIndex = this._getCurrentZindex(); return html` <div class="modal fade modal-animate anim-blur ${modalId}" tabindex="-1" id="${modalId}" style="z-index: ${zIndex};" data-bs-backdrop="static" data-bs-keyboard="false"> <div class="modal-dialog ${modalSize}"> <div class="modal-content"> <div class="modal-body swal2-icon-warning"> <div class="swal2-icon swal2-warning swal2-icon-show d-flex"> <div class="swal2-icon-content">!</div> </div> <h2 class="swal2-title text-center" id="confirm-title">${title}</h2> <div class="swal2-html-container text-center" id="swal2-html-container">${message}</div> </div> <div class="modal-footer justify-content-center"> <button type="button" class="btn btn-success confirm confirm-${modalId}"> ${confirmTxt} </button> <button type="button" class="btn btn-light-secondary close close-${modalId}"> ${closeTxt} </button> </div> </div> </div> </div>` } _createConfirmModal = ({ title, message, closeTxt, confirmTxt, modalSize, callbackFunction }) => { const modalId = this._modalId(); const markup = this._confirmModalMarkup({ title: title, message: message, closeTxt: closeTxt, confirmTxt: confirmTxt, modalSize: modalSize, modalId: modalId }); this._hideActiveModals(); this.insertAdjacentHTML("afterbegin", markup); const modal = this._activateConfirmModal(modalId, callbackFunction); return modal; } _activateConfirmModal = (modalId, callbackFunction) => { const modalContainer = this.querySelector(`.${modalId}`) const modal = new bootstrap.Modal(modalContainer); const closeBtn = modalContainer.querySelectorAll(`.close-${modalId}`); this._activateCloseBtns(closeBtn, modal, modalContainer); const confirmBtn = modalContainer.querySelector(`.confirm-${modalId}`); this._activateConfirmBtn(confirmBtn, modal, modalContainer, callbackFunction); modal.show(); return modal; } _errorModalMarkup = ({ title, message, closeTxt, modalSize, modalId }) => { const zIndex = this._getCurrentZindex(); return html` <div class="modal fade modal-animate anim-blur ${modalId}" tabindex="-1" id="${modalId}" style="z-index: ${zIndex};" data-bs-backdrop="static" data-bs-keyboard="false"> <div class="modal-dialog ${modalSize}"> <div class="modal-content"> <div class="modal-body"> <div class="swal2-icon swal2-error swal2-icon-show" style="display: flex;"> <span class="swal2-x-mark"> <span class="swal2-x-mark-line-left"></span> <span class="swal2-x-mark-line-right"></span> </span> </div> <h2 class="swal2-title text-center" id="confirm-title">${title}</h2> <div class="swal2-html-container text-center" id="swal2-html-container">${message}</div> </div> <div class="modal-footer justify-content-center"> <button type="button" class="btn btn-light-secondary close close-${modalId}"> ${closeTxt}</button> </div> </div> </div> </div>` } _createErrorModal = ({ title, message, modalSize, closeTxt, callbackFunction }) => { const modalId = this._modalId(); const markup = this._errorModalMarkup({ title: title, message: message, closeTxt: closeTxt, modalSize: modalSize, modalId: modalId }); this._hideActiveModals(); this.insertAdjacentHTML("afterbegin", markup); const modal = this._activateErrorModal(modalId, callbackFunction); return modal; } _activateErrorModal = (modalId, callbackFunction) => { const modalContainer = this.querySelector(`.${modalId}`); const modal = new bootstrap.Modal(modalContainer); const closeBtn = modalContainer.querySelectorAll(`.close-${modalId}`); this._activateCloseBtns(closeBtn, modal, modalContainer, callbackFunction); modal.show(); return modal; } /** * * @param {Object} configs * @param {string} configs.title - modal title * @param {string} configs.content * @param {string} [configs.modalSize] - size of the modal (valid bootstrap modal size classes) * @param {string} configs.closeTxt * @param {string} configs.modalId * @param {string} configs.confirmTxt * @returns {string} */ _blankModalMarkup = ({ title, content, modalSize, closeTxt, confirmTxt, modalId }) => { const zIndex = this._getCurrentZindex(); return html` <div id="${modalId}" class="modal fade modal-animate anim-blur ${modalId}" tabindex="-1" style="z-index: ${zIndex};" data-bs-backdrop="static" data-bs-keyboard="false"> <div class="modal-dialog ${modalSize}"> <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title">${title}</h5> <button type="button" class="btn-close close-${modalId}" aria-label="Close"></button> </div> <div class="modal-body"> ${content} </div> <div class="modal-footer"> <button type="button" class="btn btn-primary confirm confirm-${modalId}" style="display: ${confirmTxt ? 'inline-block' : 'none'};"> ${confirmTxt} </button> <button type="button" class="btn btn-secondary close close-${modalId}"> ${closeTxt} </button> </div> </div> </div> </div>` } /** * * @param {Object} configs * @param {string} configs.title - modal title * @param {string} configs.content * @param {string} [configs.modalSize] - size of the modal (valid bootstrap modal size classes) * @param {string} configs.closeTxt * @param {string} configs.modalId * @returns {string} */ _infoModalMarkup = ({ title, content, modalSize = "", closeTxt, modalId }) => { const zIndex = this._getCurrentZindex(); return html` <div id="${modalId}" class="modal fade modal-animate anim-blur ${modalId}" tabindex="-1" style="z-index: ${zIndex};" data-bs-backdrop="static" data-bs-keyboard="false"> <div class="modal-dialog ${modalSize}"> <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title">${title}</h5> <button type="button" class="btn-close close-${modalId}" aria-label="Close"></button> </div> <div class="modal-body"> ${content} </div> <div class="modal-footer"> <button type="button" class="btn btn-secondary close close-${modalId}"> ${closeTxt} </button> </div> </div> </div> </div>` } /** * * @param {Object} configs - blank modal configurations * @param {string} configs.title - blank modal title * @param {string} configs.content - blank modal content (HTML) * @param {string} [configs.closeTxt] - blank modal close btn text * @param {string} [configs.confirmTxt] - blank modal confirm btn text * @param {string} [configs.modalSize] - size of the modal (valid bootstrap modal size classes) * @param {CallableFunction} configs.callbackFunction - callback function for blank modal * @returns {bootstrap.Modal} */ _createBlankModal = ({title, content, closeTxt, confirmTxt, modalSize, callbackFunction}) => { const modalId = this._modalId(); const markup = this._blankModalMarkup({ title: title, content: content, closeTxt: closeTxt, confirmTxt: confirmTxt, modalSize: modalSize, modalId: modalId }); this._hideActiveModals(); this.insertAdjacentHTML("afterbegin", markup); const modal = this._activateBlankModal(modalId, callbackFunction); return modal; } _activateBlankModal = (modalId, callbackFunction) => { const modalContainer = this.querySelector(`.${modalId}`) const modal = new bootstrap.Modal(modalContainer); const closeBtn = modalContainer.querySelectorAll(`.close-${modalId}`); this._activateCloseBtns(closeBtn, modal, modalContainer); const confirmBtn = modalContainer.querySelector(`.confirm-${modalId}`); this._activateConfirmBtn(confirmBtn, modal, modalContainer, callbackFunction); modal.show(); return modal; } /** * * @param {Object} configs - blank modal configurations * @param {string} configs.title - blank modal title * @param {string} configs.content - blank modal content (HTML) * @param {string} [configs.closeTxt] - blank modal close btn text * @param {string} [configs.modalSize] - size of the modal (valid bootstrap modal size classes) * @returns {bootstrap.Modal} */ _createInfoModal = ({title, content, closeTxt, modalSize}) => { const modalId = this._modalId(); const markup = this._infoModalMarkup({ title: title, content: content, closeTxt: closeTxt, modalSize: modalSize, modalId: modalId }); this._hideActiveModals(); this.insertAdjacentHTML("afterbegin", markup); const modal = this._activateInfoModal(modalId); return modal; } _activateInfoModal = (modalId) => { const modalContainer = this.querySelector(`.${modalId}`) const modal = new bootstrap.Modal(modalContainer); const closeBtn = modalContainer.querySelectorAll(`.close-${modalId}`); this._activateCloseBtns(closeBtn, modal, modalContainer); modal.show(); return modal; } _activateConfirmBtn = (btn, modal, modalContainer, callbackFunction) => { btn.addEventListener("click", async (event) => { await callbackFunction(modalContainer); modal.hide(); modalContainer.remove(); this._showActiveModal(); }) } _activateCloseBtns = (btns, modal, modalContainer, callbackFunction = () => {}) => { btns.forEach((btn) => { btn.addEventListener("click", (event) => { callbackFunction(); modal.hide(); modalContainer.remove(); this._showActiveModal(); }) }) } _showActiveModal = () => { let highestZIndex = 0; let modalToShow = null; document.querySelectorAll(".modal").forEach((modal) => { const modalZIndex = parseInt(modal.style.zIndex) || 0; if(highestZIndex < modalZIndex){ highestZIndex = modalZIndex; modalToShow = modal; } }); if(modalToShow){ const modal = bootstrap.Modal.getInstance(modalToShow); modal.show(); } } _hideActiveModals = () => { document.querySelectorAll(".modal").forEach((modal) => { const activeModal = bootstrap.Modal.getInstance(modal); activeModal.hide(); }) } _getCurrentZindex = () => { const modals = document.querySelectorAll(".modal"); const zIndexOffset = modals.length*100; return 1100 + zIndexOffset; } /** * Returns simple html markup for this HTML element with some styling * @returns {string} */ static _generate(appId){ if(!appId){ throw new Error("Modal messenger element need a valid appId from their parent application.") } return `<${this.tagName} app-id="${appId}"></${this.tagName}>` } } /** * Enum for message status * @typedef msgStatus * @property {string} SUCCESS * @property {string} DANGER * @property {string} INFO * @property {string} WARNING */ /** * @type {msgStatus} */ let MSGSTATUSTYPE; /** * @type {msgStatus} */ const msgStatus = { "SUCCESS": "success", "DANGER": "danger", "INFO": "info", "WARNING": "warning" } class Messenger{ /** * @type {Object<string, string>} * @property {string} SUCCESS * @property {string} DANGER * @property {string} INFO * @property {string} WARNING */ msgStatus = { "SUCCESS": "success", "DANGER": "danger", "INFO": "info", "WARNING": "warning" } /** * @type {CDfModalMessenger} */ modalMessenger; /** * * @param {Object} configs * @param {string} configs.defaultStatus - default status for messages * @param {Number} configs.defaultTimeout - default timeout for messages * @param {App} configs.app - application instance */ constructor({ defaultStatus, defaultTimeout, app }){ if(!defaultStatus || !defaultTimeout || !app){ throw new Error("Missing defaultStatus, defaultTimeout or app instance in Messenger constructor.") } this.app = app; this.defaultStatus = defaultStatus this.defaultTimeout = defaultTimeout; //defines custom message elements (row and modal) customElements.define(CDfMessenger.tagName, CDfMessenger); customElements.define(CDfModalMessenger.tagName, CDfModalMessenger); const appId = this.app.container.getAttribute("app-id") this.app.container.insertAdjacentHTML("beforebegin", CDfMessenger._generate(appId)); this.app.container.insertAdjacentHTML("afterend", CDfModalMessenger._generate(appId)); this.messenger = document.querySelector(CDfMessenger.tagName); this.messenger.app = app; this.modalMessenger = document.querySelector(CDfModalMessenger.tagName); this.modalMessenger.app = app; } /** * Sets message * @param {Object} configs * @param {string} configs.message - message to display * @param {string} [configs.status] - status of the message (bootstrap class) * @param {Number} [configs.timeout] - timeout for message (seconds) */ setMessage({ message, status, timeout }){ if(!message){ throw new Error("Missing message in setMessage.") } let useStatus = status; if(!useStatus){ useStatus = this.defaultStatus; } let useTimeout = timeout; if(!useTimeout){ useTimeout = this.defaultTimeout; } this.messenger._setMessage({ message: message, status: useStatus, timeout: useTimeout }); window.scrollTo(0,0); } /** * Creates confirm modal * @param {configs} configs * @param {string|undefined} [configs.title] - title of the modal * @param {string} configs.message - message of the modal * @param {string|undefined} [configs.closeTxt] - close btn text * @param {string|undefined} [configs.comfirmTxt] - confirm btn text * @param {string} [configs.modalSize] - size of the modal (bootstrap modal size classes) * @param {callbackFunction} configs.callbackFunction - callback function bound to the confirm button * @returns {bootstrap.Modal} */ confirmModal = ({ title = "Confirm action", message, closeTxt = "Close", confirmTxt = "Confirm", modalSize = "", callbackFunction }) => { if(!callbackFunction || !message){ throw new Error("Missing callback function or message for confirm modal."); } return this.modalMessenger._createConfirmModal({ title: title, message: message, closeTxt: closeTxt, confirmTxt: confirmTxt, modalSize: modalSize, callbackFunction: callbackFunction }) } /** * Creates error modal * @param {Object} configs * @param {string|undefined} [configs.title] - title of the modal * @param {string} configs.message - message of the modal * @param {string} [configs.modalSize] - size of the modal (bootstrap modal size classes) * @param {string|undefined} [configs.closeTxt] - close btn text, * @param {CallableFunction|undefined} [configs.callbackFunction] - function that should be executed upon modal close (optional) * @returns {bootstrap.Modal} */ errorModal = ({ title = "Title", message, modalSize = "", closeTxt = "Close", callbackFunction = () => {} }) => { if(!message){ throw new Error("Missing error modal message"); } return this.modalMessenger._createErrorModal({ title: title, message: message, modalSize: modalSize, closeTxt: closeTxt, callbackFunction: callbackFunction }) } /** * * @param {Object} configs * @param {string} [configs.title] - optional title of modal * @param {string} configs.content - content of modal (HTML) * @param {string} [configs.closeTxt] - text for close btn * @param {string} [configs.confirmTxt] - text for confirm btn * @param {string} [configs.modalSize] - size of the modal (bootstrap modal size classes) * @param {CallableFunction} configs.callbackFunction - callback function of modal * @returns {bootstrap.Modal} */ blankModal = ({title = "", content, closeTxt = "Close", confirmTxt = "Confirm", modalSize = "", callbackFunction}) => { if(!content){ throw new Error("Missing content for blank modal"); } return this.modalMessenger._createBlankModal({ title, content, closeTxt, confirmTxt, modalSize: modalSize, callbackFunction }); } /** * * @param {Object} configs * @param {string} [configs.title] - optional title of modal * @param {string} configs.content - content of modal (HTML) * @param {string} [configs.closeTxt] - text for close btn * @param {string} [configs.modalSize] - size of the modal (bootstrap modal size classes) * @returns {bootstrap.Modal} */ infoModal = ({title = "", content, closeTxt = "Close", modalSize = ""}) => { if(!content){ throw new Error("Missing content for blank modal"); } return this.modalMessenger._createInfoModal({ title, content, closeTxt, modalSize, }); } } export default Messenger; export { msgStatus, MSGSTATUSTYPE }