diff --git a/README.md b/README.md index 0b1deb3..4ff944c 100644 --- a/README.md +++ b/README.md @@ -1,84 +1,153 @@ -# notification -**Pop-up notifications** is a Javascript library for pop-up messages on a web page. Pure javascript and css, any dependencies. +# Notification +![Built with JavaScript](https://img.shields.io/badge/Built%20with-JavaScript-green?logo=javascript) +**Popup notifications** is a lightweight Javascript library for popup messages on a web page. Pure javascript and css, any dependencies. You can use it as toast messages or a single notification. -## Installation -Download files, then: -1. Link to notification.css `` +## Live Demo +[Click here](https://amaterasusan.github.io/notification/) -2. Link to notification.js `` +## Getting Started +### Just include files in the HTML: +``\ +`` + +### Or use it as an ES module in your project: +```javascript +import Notification from 'path/to/notification-es.js'; +``` + +## options +All options are optional +* **position**\ + top-right (default value)\ + bottom-right\ + top-left\ + bottom-left\ + center +* **duration**\ + default 5000\ + time in milliseconds the notification will be displayed\ + if duration is 0 - popup notification will be displayed all the time +* **isHidePrev**\ + default false\ + hide previous popup(s) or not +* **isHideTitle**\ + default false\ + hide title block or not\ + if it is set to true and the duration is 0,\ + an X close button will appear on the right side of the notification body to allow the popup to close. +* **maxOpened**\ + default 5, the maximum value can be set to 10 ## Usage -### options -``` -position - can have such values : - top-right //default value - bottom-right - top-left - bottom-left - center -duration time in milliseconds the notification will be displayed - default 4000 - -if duration is 0 - popup notification will be displayed all the time -``` ``` const popup = Notification({ position: 'top-left', - duration: 7000 + duration: 4000, + isHidePrev: false, + isHideTitle: false, + maxOpened: 3, }); -``` -### notification type -``` - info - success - warning - error - dialog +// or +const popup = Notification(); // options will be set by default + +// then later you can set any options like +popup.setProperty({ + duration: 5000, + isHidePrev: true, +}); ``` -### Info notification +### the following popup methods are available: +* error +* warning +* info +* success +* dialog +* setProperty +* hide + ``` +// error +popup.error({ + title: 'Oops', + message: `An error has occurred"`, +}); + +// or even insert HTML +popup.error({ + title: `
Oops
`, + message: `
+
+
An error has occurred
`, +}); + +// info popup.info({ title: 'Info', message: 'Info message' }); -``` -### Success notification -``` -popup.success({ - title: 'Success', - message: 'Success message' -}); -``` - -### Warning notification -``` +// warning popup.warning({ title: 'Warning', message: 'Warning message' }); -``` -### Error notification -``` -popup.error({ - title: 'Error', - message: 'Error message' +// success +popup.success({ + title: 'Success', + message: 'Success message' }); ``` -### Confirmation Dialog -If use "Confirmation dialog" two buttons are available [Ok] and [Cancel]. -The display time of the notification will not matter here, even if it has been set. -The third parameter is a callback function that is called when any of the buttons is pressed. +### Dialog +If use "Confirmation dialog" two buttons are available [Ok] and [Cancel].\ +The popup display time here will not matter even if it has been set,\ +callback function is called when any of the buttons is pressed.\ +You can also insert HTML. ``` popup.dialog({ title: 'Confirm', message: 'Confirm message', - (result) => { + callback: (result) => { console.log('result = ', result) } }); -``` \ No newline at end of file + +/* + Example with HTML + you can pass a validation function to be able to check the filled fields in the form and + not to close the popup immediately after clicking [Ok] +*/ +const validTextarea = () => { + let valid = true; + const textarea = document.querySelector('textarea[name="your_mess"]'); + if (textarea.value.trim() === '') { + valid = false; + textarea.focus(); + } + return valid; +}; + +popup.dialog({ + title:
Send
', + message: `
Your message*:
+ `, + callback:(result) => { + console.log('result = ', result) + }, + validFunc: validTextarea, +}); +``` + +## Authors + +👤 **Helen Nikitina** + +- Twitter: [@twitterhandle](https://twitter.com/@HelenNikit1ina ) + +## License +[![GitHub license](https://img.shields.io/github/license/Naereen/StrapDown.js.svg)](https://github.com/amaterasusan/notification/blob/master/LICENSE) + +[!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/amaterasu.san) diff --git a/demo.css b/demo.css index e5ce3b3..551ec2d 100644 --- a/demo.css +++ b/demo.css @@ -1,41 +1,41 @@ -html, -body { - border: 0; +* { + box-sizing: border-box; margin: 0; + padding: 0; } body { font-family: 'Roboto', sans-serif; - width: 100%; - height: 100%; + font-size: 1rem; background-color: #1d1d22; + letter-spacing: 1px; } .container-form { position: relative; overflow: hidden; - height: 100%; - width: 70%; - margin: 25px auto; + height: auto; + width: 98vw; + max-width: 800px; + margin: 10px auto; display: flex; - background-color: #0d1117; - border: 1px solid #c6c6c6; + background-color: #0b101a; + border: 1px solid rgb(108, 117, 125); border-radius: 5px; - box-shadow: 0 0 8px#999999; + box-shadow: 0 0 20px rgba(255, 255, 255, 0.4); } form { - width: 50%; + width: 100%; min-width: 200px; - padding: 15px 35px; + padding: 15px 20px; color: #fdfeff; - box-sizing: border-box; justify-content: center; flex-grow: 1; } form h1 { - font-size: 3em; + font-size: 2em; font-weight: 300; text-align: center; color: #2196f3; @@ -43,89 +43,117 @@ form h1 { margin-top: 0; } -.code { - position: relative; - overflow: auto; - width: 50%; - color: #f8f8f2; - font-size: 1.2em; - border-left: 1px solid #c6c6c6; - box-shadow: 0 0 8px#999999; - justify-content: left; - flex-grow: 1; - display: flex; - align-items: left; - padding: 0 !important; - margin: 0 !important; -} - -pre { - margin: 0 !important; - padding: 0 !important; - white-space: pre; -} - input, -select { +select, +textarea { border: 1px solid #c6c6c6; border-radius: 5px; text-align: left; width: 100%; - box-sizing: border-box; } .group { position: relative; - margin: 50px 0; - box-sizing: border-box; + margin: 35px 15px; } -.group input, +.group input:not([type="checkbox"]), .group select { - background: none; + background-color: transparent; color: #c6c6c6; - font-size: 18px; - padding: 10px 10px 10px 5px; + font-size: 1em; + padding: 10px; + padding-left: 5px; display: block; border: none; + border: 0; + outline: 0; border-radius: 0; - border-bottom: 1px solid #c6c6c6; + border-bottom: 1px solid rgba(108, 117, 125, 0.4); +} + +/* Select only webkit browsers */ +@media screen and (-webkit-min-device-pixel-ratio:0) { + select { + appearance: none; + padding-right: 15px; + background-image: url('data:image/svg+xml;utf8,'); + background-repeat: no-repeat; + background-position: center right; + } } .group select option { - color: #ededee; + color: #c5e4f9; background-color: #22232a; } .group input:focus, .group select:focus { - outline: none; + outline: none !important; } -.group input:focus ~ label, -.group select:focus ~ label, -.group input:valid ~ label, -.group select:valid ~ label { - top: -14px; - font-size: 12px; - color: #2196f3; - /*color: #04c484;*/ -} - -.group input:focus ~ .bar:before, -.group select:focus ~ .bar:before { +.group input:focus~.bar:before, +.group select:focus~.bar:before { width: 100%; } .group label { - color: #c6c6c6; - font-size: 16px; - font-weight: normal; position: absolute; - pointer-events: none; + top: -14px; left: 5px; - top: 10px; - transition: 300ms ease all; + font-size: 12px; + color: #2196f3; + font-weight: normal; + pointer-events: none; +} + +.group.inline { + margin: 15px; + display: flex; + justify-content: flex-start; + align-items: center; + border-bottom: 1px solid rgba(108, 117, 125, 0.4); + padding: 10px 0; +} + +.group.inline label { + position: relative; + top: 0; + font-size: 14px; + margin-left: 8px; + pointer-events: all; +} + +.wrapper-checkbox { + width: 30px !important; +} + +.custom-checkbox { + position: relative; + font-size: 1em; + height: 30px; + width: 30px; + padding: 0; + appearance: none; + border: 1px solid rgba(70, 96, 120, 0.9) !important; + border-radius: 6px !important; + background: transparent; +} + +.custom-checkbox:checked:before, +.custom-checkbox:checked:after { + content: "\2714"; + position: absolute; + display: flex; + justify-content: center; + align-items: center; + top: 0; + left: 0; + font-size: 1.4em; + height: 100%; + width: 100%; + color: #2196f3; } .group .bar { @@ -140,32 +168,32 @@ select { bottom: 0px; position: absolute; background-color: #2196f3; - /*background-color: #04c484;*/ transition: 300ms ease all; left: 0%; } .btn-box { text-align: center; - margin: 20px 10px; + margin: 10px; } .btn { cursor: pointer; - background-color: #2196f3; + background-color: #0875ce; color: #deeffd; border: 0; - padding: 10px 20px; + padding: 18px 30px; font-size: 1.1em; border-radius: 3px; letter-spacing: 0.06em; text-decoration: none; outline: none; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24); + user-select: none; } .btn:hover { - background: #0875ce; + background-color: #2196f3; color: #fff; } @@ -197,3 +225,151 @@ select { opacity: 0.3; transition: 0s; } + + +/* examples insert html*/ +.wrapper-notification { + display: flex; + justify-content: flex-start; + align-items: center; + gap: 10px; + height: 100%; + letter-spacing: 2px; +} + +.icons { + display: flex; + justify-content: center; + align-items: center; + width: 70px; + height: 70px; + background-repeat: no-repeat; + background-position: center; + background-size: contain; +} + +.icons.icon-info { + background-image: url('demo_img/info.png'); +} + +.icons.icon-warning { + background-image: url('demo_img/warning.png'); +} + +.icons.icon-success { + background-image: url('demo_img/success.png'); +} + +.icons.icon-error { + background-image: url('demo_img/404-error.png'); + width: 100px; +} + +.icons.small { + width: 24px; + height: 24px; +} + +.icons.medium { + width: 38px; + height: 38px; +} + +.icons.small.icon-send { + background-image: url('demo_img/send.png'); +} + +.wrapper-notification .title-cust { + color: #fff; + font-size: 1.1em; + font-weight: 600; +} + +.wrapper-notification .title-error { + -webkit-text-stroke: 1px red; + -webkit-text-fill-color: transparent; +} + +.wrapper-notification .title-info { + -webkit-text-stroke: 1px #084981; + -webkit-text-fill-color: transparent; +} + +.wrapper-notification .title-warning { + -webkit-text-stroke: 1px #ff4500; + -webkit-text-fill-color: transparent; +} + +.wrapper-notification .title-success { + -webkit-text-stroke: 1px #037a4a; + -webkit-text-fill-color: transparent; +} + +.wrapper-notification .title-dialog { + color: #02659b; + font-size: 1em; + font-weight: 600; +} + +.wrapper-notification .message { + flex: 1; +} + +.wrapper-notification .message-text-error { + color: rgb(66, 67, 67); + font-weight: 500; +} + +.label-message { + margin-bottom: 5px; + color: #033e5e; +} + +.asterisk { + color: #fa6868; + margin-left: 4px; +} + +textarea.popup-textarea { + font-weight: 500; + font-size: 0.9em; + color: #777; + outline: none; + border: 1px solid rgba(3, 62, 94, 0.15); + background: #f4f4f4; + border-radius: 5px; + padding: 0.6em; + width: 100%; + max-height: 150px; + resize: vertical; +} + +.popup-textarea::placeholder { + color: rgb(136, 136, 136, 0.7); + font-size: 0.88em; +} + +.popup-textarea.invalid { + animation: pulse-border 400ms 1; +} + +.popup-textarea.invalid::placeholder { + color: rgb(255, 0, 0); +} + +@keyframes pulse-border { + 0% { + border-color: gba(3, 62, 94, 0.15); + background: #f4f4f4; + } + + 50% { + border-color: rgba(255, 0, 0, 0.7); + background-color: rgb(255, 240, 240, 0.7); + } + + 100% { + border-color: gba(3, 62, 94, 0.15); + background: #f4f4f4; + } +} \ No newline at end of file diff --git a/demo.js b/demo.js new file mode 100644 index 0000000..9737fec --- /dev/null +++ b/demo.js @@ -0,0 +1,129 @@ +const defaultText = { + info: { + defaultTitle: 'Info', + defaultMessage: 'Default Info Message', + htmlTitle: '
Info
', + html: '
Please read the description carefully
', + }, + success: { + defaultTitle: 'Success', + defaultMessage: 'Default Success Message', + htmlTitle: '
OK
', + html: '
Your message has been sent successfully
', + }, + warning: { + defaultTitle: 'Warning', + defaultMessage: 'Default Warning Message', + htmlTitle: '
Warning
', + html: `
Don't forget to save your data, otherwise it may be lost
`, + }, + error: { + defaultTitle: 'Error', + defaultMessage: 'An error has occurred', + htmlTitle: '
Oops
', + html: `
The Page you're looking for isn't here
`, + }, + dialog: { + defaultTitle: 'Confirm', + defaultMessage: 'Default Confirm message', + htmlTitle: + '
Send
', + html: `
Your message*:
`, + }, +}; + +function sleep(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +function validTextarea() { + let valid = true; + const textarea = document.querySelector('textarea[name="your_mess"]'); + if (!textarea) { + return valid; + } + if (textarea.value.trim() === '') { + valid = false; + textarea.focus(); + textarea.classList.add('invalid'); + textarea.placeholder = 'This field cannot be empty!'; + setTimeout(() => { + textarea.classList.remove('invalid'); + }, 400); + } + return valid; +} + +window.addEventListener('DOMContentLoaded', function () { + const form = document.querySelector('form'); + const titleEl = form.querySelector('#title'); + const messageEl = form.querySelector('#message'); + const positionEl = form.querySelector('#position'); + const durationEl = form.querySelector('#duration'); + const typeEl = form.querySelector('#type'); + const textOrHtmlEl = form.querySelector('#use-html'); + const hidePrevEl = form.querySelector('#hide-prev'); + const hideTitleEl = form.querySelector('#hide-title'); + const btn = form.querySelector('#show-notification'); + + // create popup with default option, now we can set it later + const popup = Notification(); + + typeEl.addEventListener('change', () => { + titleEl.value = defaultText[typeEl.value].defaultTitle; + messageEl.value = defaultText[typeEl.value].defaultMessage; + }); + + // show popup + btn.addEventListener('click', function (e) { + e.preventDefault(); + btn.disabled = true; + sleep(500).then(() => (btn.disabled = false)); + + // Form values + let title = titleEl.value; + let message = messageEl.value; + const type = typeEl.value; + + // set need property + popup.setProperty({ + position: positionEl.value, + duration: durationEl.value, + isHidePrev: hidePrevEl.checked, + isHideTitle: hideTitleEl.checked, + // maxOpened: 3, + }); + + let callback = null; + let validFunc = null; + if (textOrHtmlEl.checked) { + title = defaultText[type].htmlTitle || title; + message = defaultText[type].html || message; + if (type === 'dialog') { + validFunc = validTextarea; + } + } + + if (type === 'dialog') { + callback = (result) => { + console.log('result = ', result); + }; + } + + if (!popup[type]) { + popup.error({ + title: 'Error', + message: `Notification has no such method "${type}"`, + }); + return; + } + + popup[type]({ + title: title, + message: message, + callback: callback, + validFunc: validFunc, + }); + return false; + }); +}); diff --git a/demo_img/404-error.png b/demo_img/404-error.png new file mode 100644 index 0000000..2f15196 Binary files /dev/null and b/demo_img/404-error.png differ diff --git a/demo_img/info.png b/demo_img/info.png new file mode 100644 index 0000000..ed86ae7 Binary files /dev/null and b/demo_img/info.png differ diff --git a/demo_img/send.png b/demo_img/send.png new file mode 100644 index 0000000..86d1231 Binary files /dev/null and b/demo_img/send.png differ diff --git a/demo_img/success.png b/demo_img/success.png new file mode 100644 index 0000000..2e27450 Binary files /dev/null and b/demo_img/success.png differ diff --git a/demo_img/warning.png b/demo_img/warning.png new file mode 100644 index 0000000..5012cdc Binary files /dev/null and b/demo_img/warning.png differ diff --git a/index.html b/index.html index a589f48..46d4900 100644 --- a/index.html +++ b/index.html @@ -8,13 +8,24 @@ Notifications -

Notifications

+
+
+ +
+
+
+ +
+
+
+ +
@@ -26,17 +37,17 @@
- +
@@ -45,138 +56,22 @@
+
- +
-
-
-            
-/*
-  position may be:
-    top-right //default value
-    bottom-right
-    top-left
-    bottom-left
-    center
-  default duration value: 4000(ms)
-*/
-// Example of using
-const popup = Notification({
-  position: 'top-left',
-  duration: 3500
-});
-
-popup.error({
-  title: 'Error',
-  message: 'An error has occurred'
-});
-/*
-  Available methods:
-    error
-    warning
-    success
-    info
-    dialog 
-    
-  If you use dialog - 
-    the third parameter is the callback function
-*/  
-        
-      
-
-
+ - - - + \ No newline at end of file diff --git a/notification-es.js b/notification-es.js new file mode 100644 index 0000000..ce2ef7b --- /dev/null +++ b/notification-es.js @@ -0,0 +1,299 @@ +/* + * Notification js + * https://amaterasusan.github.io/notification + * @license MIT licensed + * + * Copyright (C) 2024 Helen Nikitina + */ + +import './notification.css'; +export default 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 = 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 }; +} diff --git a/notification.css b/notification.css index 65600c9..6991bbe 100644 --- a/notification.css +++ b/notification.css @@ -1,99 +1,101 @@ /* Notification */ -:root { - --notification-border-radius: 6px; - --notification-border-width: 1px; - --notification-border-left-width: 6px; - --notification-box-shadow: rgba(36, 30, 30, 0.3); - --notification-hover-shadow: rgba(36, 30, 30, 0.5); - --notification-close-x-color-light: #ffffff; - --notification-close-x-color-dark: #bd362f; - --overlay-bg: rgba(0, 0, 0, 0.1); - /* default */ - --notification-default-bg: rgba(255, 255, 255, 0.99); - --notification-default-color: rgba(0, 0, 0, 0.7); - --notification-default-lborder-color: #519798; - --notification-default-border-color: #8e9d9e; - --notification-default-title-color: #427071; - --notification-default-desc-color: #427071; - --notification-default-icon-color: #dea90b; - --notification-default-close-x-color: #519798; +.notification-container { + --popup-min-width: 200px; + --popup-max-height: 400px; + --min-header-height: 42px; + --popup-border-radius: 8px; + --popup-border-width: 1px; + --popup-border-left-width: 6px; + --popup-border-color: transparent; + --popup-box-shadow: 0 0 20px rgba(0, 0, 0, 0.6); + --popup-dividing-border: 1px solid rgba(0, 0, 0, 0.05); + /* dialog */ + --popup-dialog-bg: rgba(255, 255, 255, 0.93); + --popup-dialog-border-width: 2px; + --popup-dialog-border-left-width: 8px; + --popup-dialog-lborder-color: #0b8dad; + --popup-dialog-border-color: rgba(0, 0, 0, 0.2); + --popup-dialog-title-color: #222; + --popup-dialog-desc-color: #444; + --popup-dialog-close-x-color: #a61818; + --popup-dialog-box-shadow: -1px -1px 10px rgba(255, 255, 255, 0.4), + 5px 5px 15px rgba(0, 0, 0, 0.8); /* dialog buttons */ - --notification-buttons-tborder-color: #e6e2e2; - --notification-cancel-rborder-color: #d7d4d4; - --notification-button-bg: transparent; - --notification-button-color: #000000; - --notification-button-hover-bg: #ffffff; - --notification-action-color: #008060; - --notification-action-hover-color: #04adad; - --notification-cancel-color: #8b0000; - --notification-cancel-hover-color: #ff0000; + --popup-btns-dividing-border: none; + --popup-btn-border-width: 2px; + --popup-btn-min-height: 38px; + --popup-btn-min-width: 80px; + --popup-btn-radus: 8px; + --popup-btn-text-shadow: 1px 1px rgba(0, 0, 0, 0.6); + --popup-action-border: #12ba82; + --popup-action-color: #12ba82; + --popup-cancel-border: #bbb; + --popup-cancel-color: #a61818; + --popup-cancel-hover-color: #ff0000; + --popup-action-hover-color: #14d796; /* error */ - --notification-error-bg: #fff0f0; - --notification-error-color: #9f3a38; - --notification-error-border-color: #e0b4b4; - --notification-error-lborder-color: #ca0e0e; - --notification-error-desc-color: #994140; - --notification-error-icon-color: #951e1e; - --notification-error-close-x-color: #ca0e0e; + --popup-error-bg: #fff0f0; + --popup-error-color: #9f3a38; + --popup-error-border-color: #e0b4b4; + --popup-error-lborder-color: #ca0e0e; + --popup-error-title-color: #f01111; + --popup-error-close-x-color: #ca0e0e; /* warning */ - --notification-warning-bg: #fffaf3; - --notification-warning-color: #997240; - --notification-warning-border-color: #c9ba9b; - --notification-warning-lborder-color: #f7a307; - --notification-warning-desc-color: #997240; - --notification-warning-icon-color: #f7a307; - --notification-warning-close-x-color: #997240; + --popup-warning-bg: #fffaf3; + --popup-warning-color: #997240; + --popup-warning-border-color: #c9ba9b; + --popup-warning-lborder-color: #f7a307; + --popup-warning-title-color: #f07911; + --popup-warning-close-x-color: #997240; /* success */ - --notification-success-bg: #fcfff5; - --notification-success-color: #1a531b; - --notification-success-border-color: #6da16d; - --notification-success-lborder-color: #1e9520; - --notification-success-desc-color: #627961; - --notification-success-icon-color: #1e9520; - --notification-success-close-x-color: #6a9469; + --popup-success-bg: #fcfff5; + --popup-success-color: #1a531b; + --popup-success-border-color: #6da16d; + --popup-success-lborder-color: #1e9520; + --popup-success-title-color: #12ba82; + --popup-success-close-x-color: #6a9469; /* info */ - --notification-info-bg: #f8ffff; - --notification-info-color: #0e566c; - --notification-info-border-color: #558798; - --notification-info-lborder-color: #468498; - --notification-info-desc-color: #4c7786; - --notification-info-icon-color: #3492b1; - --notification-info-close-x-color: #659aaa; + --popup-info-bg: #f8ffff; + --popup-info-color: #064b84; + --popup-info-border-color: #295b83; + --popup-info-lborder-color: #0b86ea; + --popup-info-title-color: #2984c4; + --popup-info-close-x-color: #5a7184; } .notification-container { position: fixed; - font-family: 'Roboto', sans-serif; - font-size: 1.1em; - box-sizing: border-box; - z-index: 1000; - transition: all 0.5s linear; + overflow: hidden; + font-size: 1em; + z-index: 1001; + transition: all 0.4s linear; } .notification-container.center { - top: 50%; + top: 30%; left: 50%; - transform: translate(-50%, -50%); + transform: translate(-50%, -30%); + height: fit-content; } .notification-container.top-left { - left: 10px; - top: 10px; + left: 1vw; + top: 2vh; } .notification-container.bottom-left { - left: 10px; - bottom: 10px; + left: 1vw; + bottom: 2vh; } .notification-container.top-right { - right: 10px; - top: 10px; + right: 1vw; + top: 2vh; } .notification-container.bottom-right { - right: 10px; - bottom: 10px; + right: 1vw; + bottom: 2vh; } .notification-container.top-right .notification, @@ -104,90 +106,125 @@ .notification { position: relative; overflow: hidden; - max-height: 500px; + width: 350px; + min-width: var(--popup-min-width); + max-width: 98vw; + max-height: var(--popup-max-height); + margin-bottom: 6px; + border: var(--popup-border-width) solid var(--popup-border-color); + border-left-width: var(--popup-border-left-width); + border-radius: var(--popup-border-radius); + box-shadow: var(--popup-box-shadow); transition-property: all; transition-duration: 0.5s; transition-timing-function: cubic-bezier(0, 1, 0.5, 1); - margin: 0 0 6px; - opacity: 0.9; - border-width: var(--notification-border-width); - border-left-width: var(--notification-border-left-width); - border-style: solid; - border-color: transparent; - border-radius: var(--notification-border-radius); - box-shadow: 0 0 30px var(--notification-box-shadow); - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; user-select: none; - z-index: 100; + z-index: 1001; } -.notification:hover { - box-shadow: 0 0 30px var(--notification-hover-shadow); - opacity: 1; - cursor: pointer; +.notification-title { + display: flex; + justify-content: space-between; + align-items: center; + min-height: var(--min-header-height); + line-height: var(--min-header-height); + border-bottom: var(--popup-dividing-border); } -.overlay { - position: fixed; - top: 0; - left: 0; - height: 100%; - width: 100%; - background: var(--overlay-bg); - z-index: 10; - display: none; +.notification-title .title { + padding-left: 12px; + flex: 1; + font-size: 1.2em; + font-weight: 500; } -.overlay.active { - display: block; +.notification-body { + position: relative; + display: flex; + justify-content: flex-start; + align-items: center; + min-height: 56px; + padding: 0; + word-break: break-word !important; } -.notification:not(.notification-default) { - width: 310px; +.notification-body .notification-close { + position: absolute; + top: 4px; + right: 4px; } -.notification-default { - background: var(--notification-default-bg); - color: var(--notification-default-color); - border-color: var(--notification-default-border-color); - border-left-color: var(--notification-default-lborder-color); - width: 350px; +.notification-desc { + position: relative; + overflow: hidden; + flex: 1; + font-weight: 500; + line-height: 2em; + padding: 16px 12px; + max-height: calc(var(--popup-max-height) - var(--min-header-height)); +} + +/* spec */ +.notification-dialog { + position: relative; + background-color: var(--popup-dialog-bg); + color: var(--popup-dialog-desc-color); + border-color: var(--popup-dialog-border-color); + border-width: var(--popup-dialog-border-width); + border-left-width: var(--popup-dialog-border-left-width); + border-left-color: var(--popup-dialog-lborder-color); + box-shadow: var(--popup-dialog-box-shadow); +} + +.notification-dialog .notification-title { + color: var(--popup-dialog-title-color); } .notification-error { - background: var(--notification-error-bg); - color: var(--notification-error-color); - border-color: var(--notification-error-border-color); - border-left-color: var(--notification-error-lborder-color); + background-color: var(--popup-error-bg); + color: var(--popup-error-color); + border-color: var(--popup-error-border-color); + border-left-color: var(--popup-error-lborder-color); +} + +.notification-error .notification-title { + color: var(--popup-error-title-color); } .notification-success { - background: var(--notification-success-bg); - color: var(--notification-success-color); - border-color: var(--notification-success-border-color); - border-left-color: var(--notification-success-lborder-color); + background-color: var(--popup-success-bg); + color: var(--popup-success-color); + border-color: var(--popup-success-border-color); + border-left-color: var(--popup-success-lborder-color); +} + +.notification-success .notification-title { + color: var(--popup-success-title-color); } .notification-warning { - background: var(--notification-warning-bg); - color: var(--notification-warning-color); - border-color: var(--notification-warning-border-color); - border-left-color: var(--notification-warning-lborder-color); + background: var(--popup-warning-bg); + color: var(--popup-warning-color); + border-color: var(--popup-warning-border-color); + border-left-color: var(--popup-warning-lborder-color); +} + +.notification-warning .notification-title { + color: var(--popup-warning-title-color); } .notification-info { - background: var(--notification-info-bg); - color: var(--notification-info-color); - border-color: var(--notification-info-border-color); - border-left-color: var(--notification-info-lborder-color); + background: var(--popup-info-bg); + color: var(--popup-info-color); + border-color: var(--popup-info-border-color); + border-left-color: var(--popup-info-lborder-color); +} + +.notification-info .notification-title { + color: var(--popup-info-title-color); } .notification-close { - position: absolute; - right: 8px; - top: 8px; display: block; height: 24px; width: 24px; @@ -200,193 +237,92 @@ } .notification-close .close-x { - stroke: var(--notification-close-x-color-dark); fill: transparent; stroke-linecap: round; stroke-width: 5; } -.notification-default .close-x { - stroke: var(--notification-default-close-x-color); +.notification-dialog .close-x { + stroke: var(--popup-dialog-close-x-color); } .notification-error .close-x { - stroke: var(--notification-error-close-x-color); + stroke: var(--popup-error-close-x-color); } .notification-warning .close-x { - stroke: var(--notification-warning-close-x-color); + stroke: var(--popup-warning-close-x-color); } .notification-success .close-x { - stroke: var(--notification-success-close-x-color); + stroke: var(--popup-success-close-x-color); } .notification-info .close-x { - stroke: var(--notification-info-close-x-color); -} - -.notification-body { - align-items: center; - display: flex; - min-height: 56px; - /* width: 290px; */ - padding: 10px; - letter-spacing: 1px; - word-break: break-word !important; -} - -.notification-icon { - font-size: 3rem; - line-height: 3rem; - text-align: center; - padding: 0; - margin: 0; - line-height: 1; - vertical-align: middle; - text-align: left; - opacity: 0.8; -} - -.notification-error .notification-icon { - color: var(--notification-error-icon-color); -} - -.notification-error .notification-icon::before { - content: '\26A0'; -} - -.notification-default .notification-icon { - color: var(--notification-default-icon-color); -} - -.notification-default .notification-icon::before { - content: '\1F514'; -} - -.notification-warning .notification-icon { - color: var(--notification-warning-icon-color); -} - -.notification-warning .notification-icon::before { - content: '\26A0'; -} - -.notification-success .notification-icon { - color: var(--notification-success-icon-color); -} - -.notification-success .notification-icon::before { - content: '\2714'; -} - -.notification-info .notification-icon { - color: var(--notification-info-icon-color); -} - -.notification-info .notification-icon::before { - content: '\24D8'; -} - -.notification-body > div { - padding: 4px; - height: 100%; -} - -.notification .notification-title { - font-size: 18px; - font-weight: 600; - padding: 4px; - margin-top: -2px; -} - -.notification-default .notification-title { - color: var(--notification-default-title-color); -} - -.notification-desc { - padding: 4px; - font-size: smaller; - line-height: 1.6em; -} - -.notification-error .notification-desc { - color: var(--notification-error-desc-color); -} - -.notification-default .notification-desc { - color: var(--notification-default-desc-color); -} - -.notification-warning .notification-desc { - color: var(--notification-warning-desc-color); -} - -.notification-success .notification-desc { - color: var(--notification-success-desc-color); -} - -.notification-info .notification-desc { - color: var(--notification-info-desc-color); + stroke: var(--popup-info-close-x-color); } .bottom-right .notification.animation-slide-in, .top-right .notification.animation-slide-in { - animation: right-slide-in 0.5s forwards, bounceIn 0.7s forwards; + animation: slide-in 0.4s forwards, bounceIn 0.7s forwards; transform: translateX(100%); } .bottom-right .notification.animation-slide-out, .top-right .notification.animation-slide-out { - animation: right-slide-out 0.5s forwards; + animation: right-slide-out 0.4s forwards; } .top-left .notification.animation-slide-in, .bottom-left .notification.animation-slide-in { - animation: left-slide-in 0.5s forwards, bounceIn 0.7s forwards; + animation: slide-in 0.4s forwards, bounceIn 0.7s forwards; transform: translateX(-100%); } .top-left .notification.animation-slide-out, .bottom-left .notification.animation-slide-out { - animation: left-slide-out 0.5s forwards; + animation: left-slide-out 0.4s forwards; } .notification.animation-fade-in { - animation: fade-in 0.5s forwards, bounceIn 0.7s forwards; - opacity: 0; + animation: fade-in 0.4s forwards; } .notification.animation-fade-out { - animation: fade-out 0.5s forwards; + animation: fade-out 0.4s forwards; } .notification-buttons { display: flex; - justify-content: center; -} - -.notification-default .notification-buttons { - border-top: 1px solid var(--notification-buttons-tborder-color); + justify-content: space-around; + padding: 4px 0; + border-top: var(--popup-btns-dividing-border); } .notification-button { - background: var(--notification-button-bg); - align-items: center; - color: var(--notification-button-color); - display: flex; - justify-content: center; - flex-grow: 1; - min-height: 40px; - font-size: 24px; + position: relative; + display: inline-block; + cursor: pointer; + min-height: var(--popup-btn-min-height); + min-width: var(--popup-btn-min-width); font-weight: 600; - cursor: pointer; - cursor: pointer; + text-align: center; + vertical-align: middle; + border: var(--popup-btn-border-width) solid transparent; + border-radius: var(--popup-btn-radus); + text-shadow: var(--popup-btn-text-shadow); } -.notification-button:hover { - background: var(--notification-button-hover-bg); - text-decoration: none; +.notification-button::before { + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + font-size: 1.32em; } .notification-cancel:before { @@ -394,12 +330,12 @@ } .notification-cancel { - color: var(--notification-cancel-color); - border-right: 1px solid var(--notification-cancel-rborder-color); + color: var(--popup-cancel-color); + border-color: var(--popup-cancel-border); } .notification-cancel:hover { - color: var(--notification-cancel-hover-color); + color: var(--popup-cancel-hover-color); } .notification-action:before { @@ -407,14 +343,56 @@ } .notification-action { - color: var(--notification-action-color); + color: var(--popup-action-color); + border-color: var(--popup-action-border); } .notification-action:hover { - color: var(--notification-action-hover-color); + color: var(--popup-action-hover-color); } -@keyframes left-slide-in { +.overlay { + position: fixed; + top: 0; + left: 0; + height: 100%; + width: 100%; + background-color: rgba(0, 0, 0, 0.2); + z-index: 1000; + display: none; +} + +.overlay.active { + display: block; +} + +@keyframes fade-in { + 0% { + transform: scale(0); + visibility: hidden; + opacity: 0; + max-height: 0; + } + + 100% { + transform: scale(1); + visibility: visible; + opacity: 1; + max-height: var(--popup-max-height); + } +} + +@keyframes fade-out { + to { + transform: scale(0); + max-height: 0; + opacity: 0; + visibility: hidden; + } +} + + +@keyframes slide-in { to { transform: translateX(0); } @@ -422,29 +400,17 @@ @keyframes left-slide-out { to { - transform: translateX(-100%); - margin-top: 0; - margin-bottom: 0; - padding-top: 0; - padding-bottom: 0; + transform: translateX(-120%); + margin: 0; max-height: 0; opacity: 0; } } -@keyframes right-slide-in { - to { - transform: translateX(0); - } -} - @keyframes right-slide-out { to { - transform: translateX(100%); - margin-top: 0; - margin-bottom: 0; - padding-top: 0; - padding-bottom: 0; + transform: translateX(120%); + margin: 0; max-height: 0; opacity: 0; } @@ -455,29 +421,14 @@ transform: scale(0.84); opacity: 0.5; } + 75% { transform: scale(0.99); opacity: 0.9; } + 100% { transform: scale(1); opacity: 1; } -} - -@keyframes fade-in { - to { - opacity: 1; - } -} - -@keyframes fade-out { - to { - margin-top: 0; - margin-bottom: 0; - padding-top: 0; - padding-bottom: 0; - max-height: 0; - opacity: 0; - } -} +} \ No newline at end of file diff --git a/notification.js b/notification.js index b8f932b..1bb75ca 100644 --- a/notification.js +++ b/notification.js @@ -1,32 +1,64 @@ -function Notification(opts) { +/* + * 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: 4000, + duration: defDuration, + isHidePrev: false, + isHideTitle: false, + maxOpened: defNumberOpened, }; - opts = Object.assign({}, defaultOpts, opts); - opts.duration = parseInt(opts.duration) || 0; - const timeouts = []; + 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 = 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 classMainSelector = 'notification-container'; + 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 titleSelector = '.notification-title'; - const descSelector = '.notification-desc'; - const closeSelector = '.notification-close'; - const actionButSelector = '.notification-action'; - const cancelButSelector = '.notification-cancel'; + const titleTextSel = '.notification-title .title'; + const descSel = '.notification-desc'; + const closeSel = '.notification-close'; + const actionButSel = '.notification-action'; + const cancelButSel = '.notification-cancel'; const overlayClass = 'overlay'; - const overlaySelector = `.${overlayClass}`; // class, defaultTitle and defaultMessage const dataByType = { dialog: { - classType: 'notification-default', + classType: 'notification-dialog', defaultTitle: 'Confirm', defaultMessage: 'Default Confirm message', }, @@ -52,26 +84,6 @@ function Notification(opts) { }, }; - const setPosition = (newPosition) => { - opts.position = newPosition; - }; - - const tempatePopup = () => { - return ` - - - - - -
-
-
-
-
-
-
`; - }; - const dialogButtons = () => { return `
@@ -79,12 +91,33 @@ function Notification(opts) {
`; }; - const createMainContainer = (position) => { - let container = document.querySelector(`.${classMainSelector}.${position}`); + 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 = classMainSelector + ' ' + position; + container.classList = `${classContainer} ${opts.position}`; document.body.appendChild(container); } @@ -92,28 +125,31 @@ function Notification(opts) { }; const createPopup = (type) => { - const container = createMainContainer(opts.position); + 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'); - - // add classes - elPopup.classList.add(classPopup); - elPopup.classList.add(opts.position === 'center' ? animationFadeInClass : animationInClass); - elPopup.classList.add(dataByType[type].classType); + 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()); + elPopup.insertAdjacentHTML('beforeend', tempatePopup(type)); - // add buttons if confirm dialog + // create overlay if (type === 'dialog') { - elPopup.insertAdjacentHTML('beforeend', dialogButtons()); - if (!document.querySelector(overlaySelector)) { - const overlayEl = document.createElement('div'); - overlayEl.classList.add(overlayClass); - document.body.appendChild(overlayEl); - } - - document.querySelector(overlaySelector).classList.add('active'); + createOverlay(); } // add element to container in the required sequence @@ -126,13 +162,48 @@ function Notification(opts) { return elPopup; }; - const setButtonsEvent = (elPopup, callback = null) => { - const elAction = elPopup.querySelector(actionButSelector); - elAction.addEventListener( + 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); @@ -144,8 +215,8 @@ function Notification(opts) { false ); - const elCancel = elPopup.querySelector(cancelButSelector); - elCancel.addEventListener( + const elCancel = elPopup.querySelector(cancelButSel); + elCancel?.addEventListener( 'click', function handlerCancel(event) { event.stopPropagation(); @@ -162,62 +233,11 @@ function Notification(opts) { ); }; - const hidePopUp = (elPopup) => { - const container = document.querySelector(`.${classMainSelector}.${opts.position}`); - - const firstTimeout = timeouts.shift(); - clearTimeout(firstTimeout); - - // change animation class - elPopup.classList.remove(opts.position === 'center' ? animationFadeInClass : animationInClass); - - elPopup.classList.add(opts.position === 'center' ? animationFadeOutClass : animationOutClass); - - setTimeout(function () { - if (elPopup.parentNode == container) { - container.removeChild(elPopup); - - if (opts.type === 'dialog') { - document.querySelector(overlaySelector).classList.remove('active'); - } - } - - // Remove container if it empty - if (!container.hasChildNodes()) { - document.body.removeChild(container); - } - }, 500); - }; - - const showPopup = ({ type, title, message, callback = null } = {}) => { - opts.type = type; - const elPopup = createPopup(type); - - // set title and message to created element - const elTitle = elPopup.querySelector(titleSelector); - const elText = elPopup.querySelector(descSelector); - - const titlePopup = title || dataByType[type].defaultTitle; - const messagePopup = message || dataByType[type].defaultMessage; - - elTitle.innerText = titlePopup; - elText.innerText = messagePopup; - - // click event - if (type === 'dialog') { - // set buttons click event - setButtonsEvent(elPopup, callback); - } else if (opts.duration) { - // push new timeout to timeouts array if type is not dialog - const timeout = setTimeout(() => hidePopUp(elPopup), opts.duration); - timeouts.push(timeout); - } - - // add click event to close element - const elClose = elPopup.querySelector(closeSelector); - elClose.addEventListener( + const setCloseEvent = (elPopup) => { + const elClose = elPopup.querySelector(closeSel); + elClose?.addEventListener( 'click', - function handlerClose(event) { + function handlerClose() { hidePopUp(elPopup); elClose.removeEventListener('click', handlerClose, false); }, @@ -225,11 +245,53 @@ function Notification(opts) { ); }; - const dialog = ({ title, message, callback = null }) => - showPopup({ type: 'dialog', title, message, callback }); + 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, setPosition }; + return { dialog, info, success, warning, error, setProperty, hide }; }