noty
Version:
Noty is a dependency-free notification library that makes it easy to create alert - success - error - warning - information - confirmation messages as an alternative the standard alert dialog. Each notification is added to a queue. (Optional)
420 lines (360 loc) • 8.57 kB
JavaScript
import * as Utils from 'utils'
export let PageHidden = false
export let DocModalCount = 0
const DocTitleProps = {
originalTitle: null,
count: 0,
changed: false,
timer: -1
}
export const docTitle = {
increment: () => {
DocTitleProps.count++
docTitle._update()
},
decrement: () => {
DocTitleProps.count--
if (DocTitleProps.count <= 0) {
docTitle._clear()
return
}
docTitle._update()
},
_update: () => {
let title = document.title
if (!DocTitleProps.changed) {
DocTitleProps.originalTitle = title
document.title = `(${DocTitleProps.count}) ${title}`
DocTitleProps.changed = true
} else {
document.title = `(${DocTitleProps.count}) ${DocTitleProps.originalTitle}`
}
},
_clear: () => {
if (DocTitleProps.changed) {
DocTitleProps.count = 0
document.title = DocTitleProps.originalTitle
DocTitleProps.changed = false
}
}
}
export const DefaultMaxVisible = 5
export const Queues = {
global: {
maxVisible: DefaultMaxVisible,
queue: []
}
}
export const Store = {}
export let Defaults = {
type: 'alert',
layout: 'topRight',
theme: 'mint',
text: '',
timeout: false,
progressBar: true,
closeWith: ['click'],
animation: {
open: 'noty_effects_open',
close: 'noty_effects_close'
},
id: false,
force: false,
killer: false,
queue: 'global',
container: false,
buttons: [],
callbacks: {
beforeShow: null,
onShow: null,
afterShow: null,
onClose: null,
afterClose: null,
onClick: null,
onHover: null,
onTemplate: null
},
sounds: {
sources: [],
volume: 1,
conditions: []
},
titleCount: {
conditions: []
},
modal: false,
visibilityControl: false
}
/**
* @param {string} queueName
* @return {object}
*/
export function getQueueCounts (queueName = 'global') {
let count = 0
let max = DefaultMaxVisible
if (Queues.hasOwnProperty(queueName)) {
max = Queues[queueName].maxVisible
Object.keys(Store).forEach(i => {
if (Store[i].options.queue === queueName && !Store[i].closed) count++
})
}
return {
current: count,
maxVisible: max
}
}
/**
* @param {Noty} ref
* @return {void}
*/
export function addToQueue (ref) {
if (!Queues.hasOwnProperty(ref.options.queue)) {
Queues[ref.options.queue] = {maxVisible: DefaultMaxVisible, queue: []}
}
Queues[ref.options.queue].queue.push(ref)
}
/**
* @param {Noty} ref
* @return {void}
*/
export function removeFromQueue (ref) {
if (Queues.hasOwnProperty(ref.options.queue)) {
const queue = []
Object.keys(Queues[ref.options.queue].queue).forEach(i => {
if (Queues[ref.options.queue].queue[i].id !== ref.id) {
queue.push(Queues[ref.options.queue].queue[i])
}
})
Queues[ref.options.queue].queue = queue
}
}
/**
* @param {string} queueName
* @return {void}
*/
export function queueRender (queueName = 'global') {
if (Queues.hasOwnProperty(queueName)) {
const noty = Queues[queueName].queue.shift()
if (noty) noty.show()
}
}
/**
* @return {void}
*/
export function queueRenderAll () {
Object.keys(Queues).forEach(queueName => {
queueRender(queueName)
})
}
/**
* @param {Noty} ref
* @return {void}
*/
export function ghostFix (ref) {
const ghostID = Utils.generateID('ghost')
let ghost = document.createElement('div')
ghost.setAttribute('id', ghostID)
Utils.css(ghost, {
height: Utils.outerHeight(ref.barDom) + 'px'
})
ref.barDom.insertAdjacentHTML('afterend', ghost.outerHTML)
Utils.remove(ref.barDom)
ghost = document.getElementById(ghostID)
Utils.addClass(ghost, 'noty_fix_effects_height')
Utils.addListener(ghost, Utils.animationEndEvents, () => {
Utils.remove(ghost)
})
}
/**
* @param {Noty} ref
* @return {void}
*/
export function build (ref) {
findOrCreateContainer(ref)
const markup = `<div class="noty_body">${ref.options.text}</div>${buildButtons(ref)}<div class="noty_progressbar"></div>`
ref.barDom = document.createElement('div')
ref.barDom.setAttribute('id', ref.id)
Utils.addClass(
ref.barDom,
`noty_bar noty_type__${ref.options.type} noty_theme__${ref.options.theme}`
)
ref.barDom.innerHTML = markup
fire(ref, 'onTemplate')
}
/**
* @param {Noty} ref
* @return {boolean}
*/
export function hasButtons (ref) {
return !!(ref.options.buttons && Object.keys(ref.options.buttons).length)
}
/**
* @param {Noty} ref
* @return {string}
*/
function buildButtons (ref) {
if (hasButtons(ref)) {
let buttons = document.createElement('div')
Utils.addClass(buttons, 'noty_buttons')
Object.keys(ref.options.buttons).forEach(key => {
buttons.appendChild(ref.options.buttons[key].dom)
})
ref.options.buttons.forEach(btn => {
buttons.appendChild(btn.dom)
})
return buttons.outerHTML
}
return ''
}
/**
* @param {Noty} ref
* @return {void}
*/
export function handleModal (ref) {
if (ref.options.modal) {
if (DocModalCount === 0) {
createModal(ref)
}
DocModalCount++
}
}
/**
* @param {Noty} ref
* @return {void}
*/
export function handleModalClose (ref) {
if (ref.options.modal && DocModalCount > 0) {
DocModalCount--
if (DocModalCount <= 0) {
const modal = document.querySelector('.noty_modal')
if (modal) {
Utils.removeClass(modal, 'noty_modal_open')
Utils.addClass(modal, 'noty_modal_close')
Utils.addListener(modal, Utils.animationEndEvents, () => {
Utils.remove(modal)
})
}
}
}
}
/**
* @return {void}
*/
function createModal () {
const body = document.querySelector('body')
const modal = document.createElement('div')
Utils.addClass(modal, 'noty_modal')
body.insertBefore(modal, body.firstChild)
Utils.addClass(modal, 'noty_modal_open')
Utils.addListener(modal, Utils.animationEndEvents, () => {
Utils.removeClass(modal, 'noty_modal_open')
})
}
/**
* @param {Noty} ref
* @return {void}
*/
function findOrCreateContainer (ref) {
if (ref.options.container) {
ref.layoutDom = document.querySelector(ref.options.container)
return
}
const layoutID = `noty_layout__${ref.options.layout}`
ref.layoutDom = document.querySelector(`div#${layoutID}`)
if (!ref.layoutDom) {
ref.layoutDom = document.createElement('div')
ref.layoutDom.setAttribute('id', layoutID)
ref.layoutDom.setAttribute('role', 'alert')
ref.layoutDom.setAttribute('aria-live', 'polite')
Utils.addClass(ref.layoutDom, 'noty_layout')
document.querySelector('body').appendChild(ref.layoutDom)
}
}
/**
* @param {Noty} ref
* @return {void}
*/
export function queueClose (ref) {
if (ref.options.timeout) {
if (ref.options.progressBar && ref.progressDom) {
Utils.css(ref.progressDom, {
transition: `width ${ref.options.timeout}ms linear`,
width: '0%'
})
}
clearTimeout(ref.closeTimer)
ref.closeTimer = setTimeout(
() => {
ref.close()
},
ref.options.timeout
)
}
}
/**
* @param {Noty} ref
* @return {void}
*/
export function dequeueClose (ref) {
if (ref.options.timeout && ref.closeTimer) {
clearTimeout(ref.closeTimer)
ref.closeTimer = -1
if (ref.options.progressBar && ref.progressDom) {
Utils.css(ref.progressDom, {
transition: 'width 0ms linear',
width: '100%'
})
}
}
}
/**
* @param {Noty} ref
* @param {string} eventName
* @return {void}
*/
export function fire (ref, eventName) {
if (ref.listeners.hasOwnProperty(eventName)) {
ref.listeners[eventName].forEach(cb => {
if (typeof cb === 'function') {
cb.apply(ref)
}
})
}
}
/**
* @param {Noty} ref
* @return {void}
*/
export function openFlow (ref) {
fire(ref, 'afterShow')
queueClose(ref)
Utils.addListener(ref.barDom, 'mouseenter', () => {
dequeueClose(ref)
})
Utils.addListener(ref.barDom, 'mouseleave', () => {
queueClose(ref)
})
}
/**
* @param {Noty} ref
* @return {void}
*/
export function closeFlow (ref) {
delete Store[ref.id]
ref.closing = false
fire(ref, 'afterClose')
Utils.remove(ref.barDom)
if (
ref.layoutDom.querySelectorAll('.noty_bar').length === 0 &&
!ref.options.container
) {
Utils.remove(ref.layoutDom)
}
if (
Utils.inArray('docVisible', ref.options.titleCount.conditions) ||
Utils.inArray('docHidden', ref.options.titleCount.conditions)
) {
docTitle.decrement()
}
queueRender(ref.options.queue)
}