/* * Notification js * https://amaterasusan.github.io/notification * @license MIT licensed * * Copyright (C) 2024 Helen Nikitina */ function Notification(options = {}) { let opts = {}; let timeouts = {}; const defNumberOpened = 5; const defMaxNumberOpened = 10; const defDuration = 5000; const allowedPosition = ['top-right', 'bottom-right', 'top-left', 'bottom-left', 'center']; const defaultOpts = { position: 'top-right', duration: defDuration, isHidePrev: false, isHideTitle: false, maxOpened: defNumberOpened, }; const setProperty = (obj = {}) => { const defOpts = Object.keys(opts).length ? opts : defaultOpts; opts = !!obj && obj.constructor.name === 'Object' ? Object.assign({}, defOpts, obj) : defOpts; // check position if (!allowedPosition.includes(opts.position)) { opts.position = allowedPosition[0]; } // check duration opts.duration = parseInt(opts.duration); if (isNaN(opts.duration) || (opts.duration < 1000 && opts.duration !== 0)) { opts.duration = defDuration; } // check maxOpened opts.maxOpened = parseInt(opts.maxOpened); if (isNaN(opts.maxOpened) || opts.maxOpened < 1 || opts.maxOpened > defMaxNumberOpened) { opts.maxOpened = defNumberOpened; } }; setProperty(options); // selectors const classContainer = 'notification-container'; const classPopup = 'notification'; const animationInClass = 'animation-slide-in'; const animationOutClass = 'animation-slide-out'; const animationFadeInClass = 'animation-fade-in'; const animationFadeOutClass = 'animation-fade-out'; const titleTextSel = '.notification-title .title'; const descSel = '.notification-desc'; const closeSel = '.notification-close'; const actionButSel = '.notification-action'; const cancelButSel = '.notification-cancel'; const overlayClass = 'overlay'; // class, defaultTitle and defaultMessage const dataByType = { dialog: { classType: 'notification-dialog', defaultTitle: 'Confirm', defaultMessage: 'Default Confirm message', }, info: { classType: 'notification-info', defaultTitle: 'Info', defaultMessage: 'default Info', }, success: { classType: 'notification-success', defaultTitle: 'Success', defaultMessage: 'default Success', }, warning: { classType: 'notification-warning', defaultTitle: 'Warning', defaultMessage: 'default Warning', }, error: { classType: 'notification-error', defaultTitle: 'Error', defaultMessage: 'An error has occurred', }, }; const dialogButtons = () => { return `
`; }; const createOverlay = () => { if (!document.querySelector(`.${overlayClass}`)) { const overlayEl = document.createElement('div'); overlayEl.classList.add(overlayClass); document.body.appendChild(overlayEl); } document.querySelector(`.${overlayClass}`).classList.add('active'); }; const tempatePopup = (type) => { const closeEl = ``; const titleBlock = `
${closeEl}
`; return `${opts.isHideTitle ? '' : titleBlock}
${opts.isHideTitle && opts.duration === 0 && type !== 'dialog' ? closeEl : ''}
${type === 'dialog' ? dialogButtons() : ''}`; }; const createMainContainer = () => { let container = document.querySelector(`.${classContainer}.${opts.position}`); if (!container) { container = document.createElement('div'); container.classList = `${classContainer} ${opts.position}`; document.body.appendChild(container); } return container; }; const createPopup = (type) => { const container = createMainContainer(); if (container.childElementCount >= opts.maxOpened) { if (opts.position.includes('bottom')) { hidePopUp(container.lastChild); } else { hidePopUp(container.firstChild); } } const elPopup = document.createElement('div'); elPopup.classList.add( classPopup, opts.position === 'center' ? animationFadeInClass : animationInClass, dataByType[type].classType ); elPopup.dataset.type = type; elPopup.dataset.position = opts.position; elPopup.dataset.id = new Date().getTime(); // insert template in element elPopup.insertAdjacentHTML('beforeend', tempatePopup(type)); // create overlay if (type === 'dialog') { createOverlay(); } // add element to container in the required sequence if (opts.position.includes('bottom')) { container.prepend(elPopup); } else { container.appendChild(elPopup); } return elPopup; }; const showPopup = ({ type, title, message, callback = null, validFunc = null } = {}) => { if (opts.isHidePrev) { hide(); } const elPopup = createPopup(type); // set title and message const elTitle = elPopup.querySelector(titleTextSel); const elText = elPopup.querySelector(descSel); if (elTitle) { elTitle.innerHTML = title || dataByType[type].defaultTitle; } elText.innerHTML = message || dataByType[type].defaultMessage; if (type === 'dialog') { // set buttons click event setButtonsEvent(elPopup, callback, validFunc); } else if (opts.duration) { // store new timeout to timeouts obj if type is not dialog const timeout = setTimeout(() => hidePopUp(elPopup), opts.duration); timeouts[elPopup.dataset.id] = timeout; } // add click event to close element setCloseEvent(elPopup); }; const setButtonsEvent = (elPopup, callback = null, validFunc = null) => { const elAction = elPopup.querySelector(actionButSel); elAction?.addEventListener( 'click', function handlerAction(event) { event.stopPropagation(); event.preventDefault(); let valid = true; if (validFunc) { valid = validFunc(); } if (!valid) { return false; } hidePopUp(elPopup); elAction.removeEventListener('click', handlerAction, false); if (callback) { return callback('ok'); } return false; }, false ); const elCancel = elPopup.querySelector(cancelButSel); elCancel?.addEventListener( 'click', function handlerCancel(event) { event.stopPropagation(); event.preventDefault(); hidePopUp(elPopup); elCancel.removeEventListener('click', handlerCancel, false); if (callback) { return callback('cancel'); } return false; }, false ); }; const setCloseEvent = (elPopup) => { const elClose = elPopup.querySelector(closeSel); elClose?.addEventListener( 'click', function handlerClose() { hidePopUp(elPopup); elClose.removeEventListener('click', handlerClose, false); }, false ); }; const hidePopUp = (elPopup) => { const container = document.querySelector(`.${classContainer}.${elPopup.dataset.position}`); clearTimeout(timeouts[elPopup.dataset.id]); delete timeouts[elPopup.dataset.id]; // change animation class elPopup.classList.remove(elPopup.dataset.position === 'center' ? animationFadeInClass : animationInClass); elPopup.classList.add(elPopup.dataset.position === 'center' ? animationFadeOutClass : animationOutClass); if (elPopup.dataset.type === 'dialog') { document.querySelector(`.${overlayClass}`)?.classList.remove('active'); } setTimeout(function () { if (elPopup.parentNode === container) { container.removeChild(elPopup); } // Remove container if it empty if (!container?.hasChildNodes() && container?.parentElement === document.body) { document.body.removeChild(container); } }, 400); }; const hide = () => { const containers = document.querySelectorAll(`.${classContainer}`); document.querySelector(`.${overlayClass}`)?.classList.remove('active'); for (const key in timeouts) { clearTimeout(timeouts[key]); } timeouts = {}; containers.forEach((container) => { if (container && container.parentElement === document.body) { document.body.removeChild(container); } }); }; const dialog = ({ title, message, callback = null, validFunc = null }) => showPopup({ type: 'dialog', title, message, callback, validFunc }); const info = ({ title, message }) => showPopup({ type: 'info', title, message }); const success = ({ title, message }) => showPopup({ type: 'success', title, message }); const warning = ({ title, message }) => showPopup({ type: 'warning', title, message }); const error = ({ title, message }) => showPopup({ type: 'error', title, message }); return { dialog, info, success, warning, error, setProperty, hide }; }