Skip to content

Instantly share code, notes, and snippets.

@SigurdMW
Created April 26, 2018 06:52
Show Gist options
  • Save SigurdMW/c36d8a136d750b8858421cf81c99006b to your computer and use it in GitHub Desktop.
Save SigurdMW/c36d8a136d750b8858421cf81c99006b to your computer and use it in GitHub Desktop.
light weight, pure js, no dependencies js toast
function createHtmlEl (type, { classes, text, attributes }) {
const el = document.createElement(type);
if (classes) {
classes.map(classToAdd => {
el.classList.add(classToAdd);
});
}
if (attributes) {
attributes.map(attr => {
el.setAttribute(attr[0], attr[1]);
});
}
if (text) el.innerText = text;
return el;
}
export class ToastMessage {
constructor ({ text, icon = "", showClose = true, closeText = "Close", closeOnClick = false, hideAfter = 6000, onInit, animationDelay = 200, onRemove }) {
this.text = text;
this.icon = icon;
this.showClose = showClose;
this.closeText = closeText;
this.hideAfter = hideAfter;
this.onInit = onInit;
this.animationDelay = animationDelay;
this.closeOnClick = closeOnClick;
this.onRemove = onRemove;
// state
this.mouseOver = false;
this.timeouts = {};
this.el = null;
this.closeBtn = null;
this.init();
}
init () {
if (!this.text) {
throw new Error("Text is required.");
} else {
if (this.onInit) {
this.onInit();
}
this.create();
}
}
create () {
const toastParent = createHtmlEl("div", { classes: ["toast"], attributes: [["role", "alert"], ["aria-live", "assertive"]] });
if (this.icon) {
const icon = createHtmlEl("span", { classes: ["icon", this.icon], attributes: [["aria-hidden", "true"]] });
toastParent.appendChild(icon);
}
const text = createHtmlEl("p", { classes: ["toast__text"], text: this.text });
toastParent.appendChild(text);
if (this.showClose) {
this.closeBtn = createHtmlEl("button", { classes: ["toast__close"], text: this.closeText });
toastParent.appendChild(this.closeBtn);
}
document.body.appendChild(toastParent);
this.el = toastParent;
this.el.addEventListener("click", (e) => this.handleClick(e));
// TODO: do not remove if mouse over
this.el.addEventListener("mouseover", (e) => {
this.mouseOver = true;
});
this.el.addEventListener("mouseout", (e) => {
this.mouseOver = false;
});
if (this.animationDelay > 0) {
this.el.classList.add("toast--animate-in");
setTimeout(() => {
this.el.classList.remove("toast--animate-in");
}, this.animationDelay);
}
this.timeouts = {
animationOut: this.animationOutTimeout(),
customTimeout: this.customTimeout()
};
}
handleClick (e) {
if (e.target === this.closeBtn) {
this.animateOutAndRemove();
}
if (this.closeOnClick && e.target === this.el) {
this.animateOutAndRemove();
}
}
animationOutTimeout () {
return setTimeout(() => {
this.el.classList.add("toast--animate-out");
}, this.hideAfter - this.animationDelay);
}
customTimeout () {
return setTimeout(() => {
this.remove();
}, this.hideAfter);
}
animateOutAndRemove () {
this.el.classList.add("toast--animate-out");
setTimeout(() => this.remove(), this.animationDelay);
}
remove () {
this.el.removeEventListener("click", this.handleClick);
this.el.remove();
if (this.timeouts.animationOut || this.timeouts.customTimeout) {
clearTimeout(this.timeouts.animationOut);
clearTimeout(this.timeouts.customTimeout);
}
if (this.onRemove) {
this.onRemove();
}
}
}
export class Toast {
constructor () {
this.toasts = [];
}
show (opt) {
const toast = new ToastMessage(opt);
toast.onRemove = function () {
this.remove(toast);
}.bind(this);
this.add(toast);
}
add (toast) {
this.toasts.push(toast);
this.update();
}
remove (inst) {
this.toasts = this.toasts.filter(toast => {
return toast !== inst;
});
this.update();
}
update () {
let prevHeight = 32;
this.toasts.map((toast, index) => {
if (index === 0) {
toast.el.style.top = prevHeight + "px";
} else {
prevHeight = prevHeight + 16;
toast.el.style.top = prevHeight + "px";
}
prevHeight = prevHeight + toast.el.offsetHeight;
});
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment