mini-toastr.js
export function fadeOut (element, cb) {
if (element.style.opacity && element.style.opacity > 0.05) {
element.style.opacity = element.style.opacity - 0.05
} else if (element.style.opacity && element.style.opacity <= 0.1) {
if (element.parentNode) {
element.parentNode.removeChild(element)
if (cb) cb()
}
} else {
element.style.opacity = 0.9
}
setTimeout(() => fadeOut.apply(this, [element, cb]), 1000 / 30
)
}
export const LIB_NAME = 'mini-toastr'
export const ERROR = 'error'
export const WARN = 'warn'
export const SUCCESS = 'success'
export const INFO = 'info'
export const CONTAINER_CLASS = LIB_NAME
export const NOTIFICATION_CLASS = `${LIB_NAME}__notification`
export const TITLE_CLASS = `${LIB_NAME}-notification__title`
export const ICON_CLASS = `${LIB_NAME}-notification__icon`
export const MESSAGE_CLASS = `${LIB_NAME}-notification__message`
export const ERROR_CLASS = `-${ERROR}`
export const WARN_CLASS = `-${WARN}`
export const SUCCESS_CLASS = `-${SUCCESS}`
export const INFO_CLASS = `-${INFO}`
export const DEFAULT_TIMEOUT = 3000
const EMPTY_STRING = ''
export function flatten (obj, into, prefix) {
into = into || {}
prefix = prefix || EMPTY_STRING
for (const k in obj) {
if (obj.hasOwnProperty(k)) {
const prop = obj[k]
if (prop && typeof prop === 'object' && !(prop instanceof Date || prop instanceof RegExp)) {
flatten(prop, into, prefix + k + ' ')
} else {
if (into[prefix] && typeof into[prefix] === 'object') {
into[prefix][k] = prop
} else {
into[prefix] = {}
into[prefix][k] = prop
}
}
}
}
return into
}
export function makeCss (obj) {
const flat = flatten(obj)
let str = JSON.stringify(flat, null, 2)
str = str.replace(/"([^"]*)": {/g, '$1 {')
.replace(/"([^"]*)"/g, '$1')
.replace(/(\w*-?\w*): ([\w\d .#]*),?/g, '$1: $2;')
.replace(/},/g, '}\n')
.replace(/ &([.:])/g, '$1')
str = str.substr(1, str.lastIndexOf('}') - 1)
return str
}
export function appendStyles (css) {
let head = document.head || document.getElementsByTagName('head')[0]
let styleElem = makeNode('style')
styleElem.id = `${LIB_NAME}-styles`
styleElem.type = 'text/css'
if (styleElem.styleSheet) {
styleElem.styleSheet.cssText = css
} else {
styleElem.appendChild(document.createTextNode(css))
}
head.appendChild(styleElem)
}
export const config = {
types: {ERROR, WARN, SUCCESS, INFO},
animation: fadeOut,
timeout: DEFAULT_TIMEOUT,
icons: {},
appendTarget: document.body,
node: makeNode(),
allowHtml: false,
style: {
[`.${CONTAINER_CLASS}`]: {
position: 'fixed',
'z-index': 99999,
right: '12px',
top: '12px'
},
[`.${NOTIFICATION_CLASS}`]: {
cursor: 'pointer',
padding: '12px 18px',
margin: '0 0 6px 0',
'background-color': '#000',
opacity: 0.8,
color: '#fff',
'border-radius': '3px',
'box-shadow': '#3c3b3b 0 0 12px',
width: '300px',
[`&.${ERROR_CLASS}`]: {
'background-color': '#D5122B'
},
[`&.${WARN_CLASS}`]: {
'background-color': '#F5AA1E'
},
[`&.${SUCCESS_CLASS}`]: {
'background-color': '#7AC13E'
},
[`&.${INFO_CLASS}`]: {
'background-color': '#4196E1'
},
'&:hover': {
opacity: 1,
'box-shadow': '#000 0 0 12px'
}
},
[`.${TITLE_CLASS}`]: {
'font-weight': '500'
},
[`.${MESSAGE_CLASS}`]: {
display: 'inline-block',
'vertical-align': 'middle',
width: '240px',
padding: '0 12px'
}
}
}
export function makeNode (type = 'div') {
return document.createElement(type)
}
export function createIcon (node, type, config) {
const iconNode = makeNode(config.icons[type].nodeType)
const attrs = config.icons[type].attrs
for (const k in attrs) {
if (attrs.hasOwnProperty(k)) {
iconNode.setAttribute(k, attrs[k])
}
}
node.appendChild(iconNode)
}
export function addElem (node, text, className, config) {
const elem = makeNode()
elem.className = className
if (config.allowHtml) {
elem.innerHTML = text
} else {
elem.appendChild(document.createTextNode(text))
}
node.appendChild(elem)
}
export function getTypeClass (type) {
if (type === SUCCESS) return SUCCESS_CLASS
if (type === WARN) return WARN_CLASS
if (type === ERROR) return ERROR_CLASS
if (type === INFO) return INFO_CLASS
return EMPTY_STRING
}
const miniToastr = {
config,
isInitialised: false,
showMessage (message, title, type, timeout, cb, overrideConf) {
const config = {}
Object.assign(config, this.config)
Object.assign(config, overrideConf)
const notificationElem = makeNode()
notificationElem.className = `${NOTIFICATION_CLASS} ${getTypeClass(type)}`
notificationElem.onclick = function () {
config.animation(notificationElem, null)
}
if (title) addElem(notificationElem, title, TITLE_CLASS, config)
if (config.icons[type]) createIcon(notificationElem, type, config)
if (message) addElem(notificationElem, message, MESSAGE_CLASS, config)
config.node.insertBefore(notificationElem, config.node.firstChild)
setTimeout(() => config.animation(notificationElem, cb), timeout || config.timeout
)
if (cb) cb()
return this
},
init (aConfig) {
const newConfig = {}
Object.assign(newConfig, config)
Object.assign(newConfig, aConfig)
this.config = newConfig
const cssStr = makeCss(newConfig.style)
appendStyles(cssStr)
newConfig.node.id = CONTAINER_CLASS
newConfig.node.className = CONTAINER_CLASS
newConfig.appendTarget.appendChild(newConfig.node)
Object.keys(newConfig.types).forEach(v => {
this[newConfig.types[v]] = function (message, title, timeout, cb, config) {
this.showMessage(message, title, newConfig.types[v], timeout, cb, config)
return this
}.bind(this)
}
)
this.isInitialised = true
return this
},
setIcon (type, nodeType = 'i', attrs = []) {
attrs.class = attrs.class ? attrs.class + ' ' + ICON_CLASS : ICON_CLASS
this.config.icons[type] = {nodeType, attrs}
}
}
export default miniToastr