Skip to content

Instantly share code, notes, and snippets.

@wolph
Last active April 30, 2025 11:20
Show Gist options
  • Save wolph/b3e94d7549919cca4ee3c4513c24efd1 to your computer and use it in GitHub Desktop.
Save wolph/b3e94d7549919cca4ee3c4513c24efd1 to your computer and use it in GitHub Desktop.
Enhanced Toastr Notification Library

Enhanced Toastr Notification Library

Version: 2.1.4
Built for modern web applications with extended notification API functionality.


Overview

The Enhanced Toastr Notification Library is a lightweight, flexible, and developer-friendly solution for displaying notifications in your web applications. Building on the legacy of Toastr.js, this modified version returns a rich notification API object with methods for dynamic updates and programmatic control. The library adheres to modern JavaScript best practices, supporting asynchronous operations, streamlined debugging, and modular configuration.


Key Features

  • Simple API: Create notifications with a single method call (success, info, warning, or error).
  • Dynamic Updates: Easily modify notification content (title, message, styling) in real time.
  • Programmatic Dismissal: Close notifications via API methods without requiring user interaction.
  • Asynchronous Support: Integrate with async/await patterns to provide smoother user experiences.
  • Debugging Enabled: Toggle debug mode in configuration to get detailed logging (ideal for development).
  • Duplicate Prevention: Use tagging to avoid duplicate notifications and manage update behavior.
  • Event Subscription: Listen to toast lifecycle events (creation, replacement, dismissal) to integrate with other parts of your application.

Installation

Prerequisites

Ensure that jQuery is loaded in your application before including the library:

<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>

Including the Library

  1. Download the latest version of the enhanced library.
  2. Add the script to your HTML document (make sure it comes after jQuery):
<script src="path/to/enhanced-toastr.js"></script>

Configuration

Customize global options via the toastr.options object. This allows you to tailor the notifications to your application's needs.

toastr.options = {
    closeButton: true,
    progressBar: true,
    timeOut: 5000,
    debug: true, // Enable extensive debug logs for development
    positionClass: 'toast-top-right',
    // Additional options as needed...
};

Usage Examples

1. Creating Notifications

Simply call one of the notification methods to display a toast. Each method returns a notification API object for further manipulation.

// Display a success notification.
const successNotification = toastr.success('Operation completed successfully!', 'Success');

2. Updating Notifications

Modify the content or styling of an existing notification using the API methods on the returned object.

// Create an info notification.
const updateNotification = toastr.info('Initial info message', 'Initial Info');

// After 2 seconds, update the notification's title and message.
setTimeout(() => {
    updateNotification.setTitle('Updated Info');
    updateNotification.setMessage('The content has been updated successfully!');
    // Optionally, update additional options such as the icon styling.
    updateNotification.update({ iconClass: 'toast-info' });
}, 2000);

3. Dismissing Notifications

Programmatically close a notification using the close() method.

// Create a warning notification.
const dismissNotification = toastr.warning('This notification will close in 4 seconds.', 'Warning');

// Automatically dismiss the notification after 4 seconds.
setTimeout(() => {
    dismissNotification.close();
}, 4000);

4. Managing Duplicates with Tags

Prevent duplicate notifications by specifying a tag in the options. When a notification is issued with an existing tag, the library will update or remove the prior notification as configured.

// Create a notification with a unique tag.
toastr.success('Initial tagged notification', 'Tagged', { tag: 'uniqueNotification1' });

// Create another notification with the same tag,
// which will update or replace the previous one.
toastr.success('Updated content for tagged notification', 'Tagged Update', { tag: 'uniqueNotification1' });

5. Asynchronous Usage and Debug Logging

Leverage modern JavaScript patterns and see detailed logs when debug mode is enabled.

(async () => {
    try {
        const asyncNotif = await new Promise((resolve) => {
            resolve(toastr.success('Async notification created!', 'Async Success'));
        });
        if (toastr.options.debug) {
            console.log("Notification created:", asyncNotif);
        }
    } catch (error) {
        if (toastr.options.debug) {
            console.error("Error creating notification:", error);
        }
    }
})();

API Documentation

Toastr Methods

  • toastr.success(message, title?, optionsOverride?)
    Displays a success notification and returns a notification API object.

  • toastr.info(message, title?, optionsOverride?)
    Displays an informational notification and returns a notification API object.

  • toastr.warning(message, title?, optionsOverride?)
    Displays a warning notification and returns a notification API object.

  • toastr.error(message, title?, optionsOverride?)
    Displays an error notification and returns a notification API object.

Notification API Object Methods

Each notification API object returned by a toastr method includes:

  • setTitle(newTitle)
    Update or set the notification's title.

  • setMessage(newMessage)
    Update or set the notification's message content.

  • update(newOptions)
    Merge a new options object to update parts of the notification. Supported keys include:

    • title
    • message
    • iconClass
  • replace(newMessage, newTitle?, newOptions?)
    Fully replace the toast’s content and optionally update additional options.

  • close()
    Programmatically dismiss the notification. This method may return a Promise if asynchronous behavior is leveraged.


Event Subscription

Subscribe to toast lifecycle events to get insights into creation, replacement, and removal.

toastr.subscribe(function(event) {
    console.log("Toast event:", event);
});

The event object includes details such as the toast ID, creation time, and current state.


Debugging

Activate debug mode to receive detailed logging for development and troubleshooting:

toastr.options.debug = true;

In debug mode, key events such as toast creation, updates, replacements, and removals are logged to the browser console with context and timestamps.


Troubleshooting

  • No Notifications Displaying

    • Verify that jQuery is loaded before the toastr script.
    • Ensure the library script is properly included in your HTML after jQuery.
  • Styling or Positioning Issues

    • Include the required CSS styles. You may start with the original Toastr CSS from the Toastr GitHub repository and adjust as needed.
  • API Methods Not Working

    • Confirm that you’re interacting with the correct notification API object returned by a toastr method.
    • Check that your toastr.options configuration is not conflicting with your setup.
  • Duplicate Notifications

    • Use the tag property to manage duplicates and ensure that notifications are updated or replaced appropriately.

Contributing

Contributions, issues, and feature requests are welcome! Please fork the repository, implement your changes on a feature branch, and submit a pull request. Ensure your contributions follow the library's coding guidelines and include proper documentation.


License

This project is licensed under the MIT License. See the LICENSE file for more details.


This documentation provides clear, concise, and up-to-date guidance for integrating and using the Enhanced Toastr Notification Library in your projects. Enjoy a robust, flexible, and modern approach to handling notifications in your web applications!

Happy coding!

/*
* Toastr
* Copyright 2025
* Authors: John Papa, Hans Fjällemark, and Tim Ferrell
* All Rights Reserved.
* Use, reproduction, distribution, and modification of this code is subject to the terms and
* conditions of the MIT license, available at http://www.opensource.org/licenses/mit-license.php
*
* ARIA Support: Greta Krafsig
*
* Updates and improvements: Rick van Hattem.
*
* Original project: https://github.com/CodeSeven/toastr
* Current homepage: https://gist.github.com/wolph/b3e94d7549919cca4ee3c4513c24efd1/
*/
/* global define */
(function (define) {
define(['jquery'], function ($) {
return (function () {
var $container;
var listener;
var toastId = 0;
var toastType = {
error: 'error',
info: 'info',
success: 'success',
warning: 'warning'
};
/**
* Global mapping to hold toast notifications by tag.
*/
var taggedToasts = {};
var toastr = {
clear: clear,
remove: remove,
error: error,
getContainer: getContainer,
info: info,
options: {},
subscribe: subscribe,
success: success,
version: '2.1.4',
warning: warning
};
var previousToast;
// Helper to prevent duplicate toasts if configured.
function shouldExit(options, map) {
if (options.preventDuplicates) {
if (map.message === previousToast) {
return true;
} else {
previousToast = map.message;
}
}
return false;
}
return toastr;
/////////////////////////////////////////////////////////////////////////
// Public API calls
function error(message, title, optionsOverride) {
return notify({
type: toastType.error,
iconClass: getOptions().iconClasses.error,
message: message,
optionsOverride: optionsOverride,
title: title
});
}
function info(message, title, optionsOverride) {
return notify({
type: toastType.info,
iconClass: getOptions().iconClasses.info,
message: message,
optionsOverride: optionsOverride,
title: title
});
}
function success(message, title, optionsOverride) {
return notify({
type: toastType.success,
iconClass: getOptions().iconClasses.success,
message: message,
optionsOverride: optionsOverride,
title: title
});
}
function warning(message, title, optionsOverride) {
return notify({
type: toastType.warning,
iconClass: getOptions().iconClasses.warning,
message: message,
optionsOverride: optionsOverride,
title: title
});
}
function subscribe(callback) {
listener = callback;
}
/////////////////////////////////////////////////////////////////////////
// Main notify function with tag support and full replacement logic
/**
* Creates and displays a toast notification.
* If options.tag is provided and an existing notification with that tag exists:
* - If the new toast is the same type (based on iconClass), update it in place.
* - Otherwise, immediately remove the old toast before creating the new one.
*
* @param {Object} map - Object containing type, title, message, iconClass, and optionsOverride.
* @returns {Object} The notification API object for the toast.
*/
function notify(map) {
var options = getOptions();
if (typeof map.optionsOverride !== 'undefined') {
options = $.extend(options, map.optionsOverride);
}
// Tag handling:
if (options.tag) {
var existingToast = taggedToasts[options.tag];
if (existingToast) {
if (existingToast.map.iconClass === map.iconClass) {
// Update in place.
existingToast.replace(map.message, map.title, options);
if (options.debug && console) {
console.log("[DEBUG] Replaced existing toast in place with tag:", options.tag);
}
return existingToast;
} else {
// Immediately remove the existing toast (no animation).
existingToast.immediateClose();
if (options.debug && console) {
console.log("[DEBUG] Immediately removed toast with tag:", options.tag);
}
delete taggedToasts[options.tag];
}
}
}
toastId++;
$container = getContainer(options, true);
var intervalId = null;
var $toastElement = $('<div/>');
var $titleElement = $('<div/>');
var $messageElement = $('<div/>');
var $progressElement = $('<div/>');
var $closeElement = $(options.closeHtml);
var progressBar = {
intervalId: null,
hideEta: null,
maxHideTime: null
};
var response = {
toastId: toastId,
state: 'visible',
startTime: new Date(),
options: options,
map: map
};
// Create the notification API object.
var notificationAPI = {
element: $toastElement,
map: map, // expose underlying data
setTitle: function (newTitle) {
if ($titleElement.children().length === 0) {
$titleElement.addClass(options.titleClass);
$toastElement.prepend($titleElement);
}
$titleElement.html(newTitle);
},
setMessage: function (newMessage) {
if ($messageElement.children().length === 0) {
$messageElement.addClass(options.messageClass);
$toastElement.append($messageElement);
}
$messageElement.html(newMessage);
},
update: function (newOptions) {
options = $.extend(options, newOptions);
if (newOptions.title !== undefined) {
this.setTitle(newOptions.title);
}
if (newOptions.message !== undefined) {
this.setMessage(newOptions.message);
}
if (newOptions.iconClass !== undefined) {
$toastElement.removeClass(map.iconClass).addClass(newOptions.iconClass);
map.iconClass = newOptions.iconClass;
}
},
/**
* Fully replaces the current toast's DOM content.
* Clears old event handlers, empties the DOM, updates underlying data,
* and rebuilds the toast's content with new event bindings.
*
* @param {string} newMessage - The updated message.
* @param {string} newTitle - The updated title.
* @param {Object} newOptions - Updated options.
*/
replace: function (newMessage, newTitle, newOptions) {
// Clear timers and unbind events.
clearTimeout(intervalId);
if (progressBar.intervalId) clearInterval(progressBar.intervalId);
$toastElement.off();
// Update underlying data.
if (newTitle !== undefined) {
map.title = newTitle;
}
if (newMessage !== undefined) {
map.message = newMessage;
}
if (newOptions !== undefined) {
options = $.extend(options, newOptions);
}
// Clear old content and rebuild completely.
$toastElement.empty();
$toastElement.removeClass().addClass(options.toastClass).addClass(map.iconClass);
// Build title.
if (map.title) {
var titleText = options.escapeHtml ? escapeHtml(map.title) : map.title;
$titleElement = $('<div/>').addClass(options.titleClass).html(titleText);
$toastElement.append($titleElement);
}
// Build message.
if (map.message) {
var messageText = options.escapeHtml ? escapeHtml(map.message) : map.message;
$messageElement = $('<div/>').addClass(options.messageClass).html(messageText);
$toastElement.append($messageElement);
}
// Build close button if needed.
if (options.closeButton) {
$closeElement = $(options.closeHtml).addClass(options.closeClass).attr('role', 'button');
$toastElement.prepend($closeElement);
}
// Build progress bar if enabled.
if (options.progressBar) {
$progressElement = $('<div/>').addClass(options.progressClass);
$toastElement.prepend($progressElement);
}
setRTL();
setAria();
handleEvents();
resetTimeout();
},
/**
* Closes the toast normally.
* @returns {*} - Result of the hide animation.
*/
close: function () {
return hideToast(true);
},
/**
* Immediately closes the toast without any animation.
* This ensures that no ghost notifications remain.
*/
immediateClose: function () {
clearTimeout(intervalId);
if (progressBar.intervalId) clearInterval(progressBar.intervalId);
$toastElement.stop(true, true).hide(0);
removeToast($toastElement);
}
};
// Helper to reset auto-hide time and progress.
function resetTimeout() {
clearTimeout(intervalId);
if (progressBar.intervalId) clearInterval(progressBar.intervalId);
if (options.timeout > 0) {
intervalId = setTimeout(hideToast, options.timeout);
progressBar.maxHideTime = parseFloat(options.timeout);
progressBar.hideEta = new Date().getTime() + progressBar.maxHideTime;
if (options.progressBar) {
progressBar.intervalId = setInterval(updateProgress, 10);
}
}
}
// Helper: escape HTML if enabled.
function escapeHtml(source) {
if (source == null) {
source = '';
}
return source
.replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
}
// Build the toast structure.
function personalizeToast() {
setIcon();
setTitle();
setMessage();
setCloseButton();
setProgressBar();
setRTL();
setSequence();
setAria();
}
function setIcon() {
if (map.iconClass) {
$toastElement.addClass(options.toastClass).addClass(map.iconClass);
}
}
function setSequence() {
if (options.newestOnTop) {
$container.prepend($toastElement);
} else {
$container.append($toastElement);
}
}
function setTitle() {
if (map.title) {
var titleText = options.escapeHtml ? escapeHtml(map.title) : map.title;
$titleElement.html(titleText).addClass(options.titleClass);
$toastElement.append($titleElement);
}
}
function setMessage() {
if (map.message) {
var messageText = options.escapeHtml ? escapeHtml(map.message) : map.message;
$messageElement.html(messageText).addClass(options.messageClass);
$toastElement.append($messageElement);
}
}
function setCloseButton() {
if (options.closeButton) {
$closeElement.addClass(options.closeClass).attr('role', 'button');
$toastElement.prepend($closeElement);
}
}
function setProgressBar() {
if (options.progressBar) {
$progressElement.addClass(options.progressClass);
$toastElement.prepend($progressElement);
}
}
function setRTL() {
if (options.rtl) {
$toastElement.addClass('rtl');
}
}
function setAria() {
var ariaValue = '';
switch (map.iconClass) {
case 'toast-success':
case 'toast-info':
ariaValue = 'polite';
break;
default:
ariaValue = 'assertive';
}
$toastElement.attr('aria-live', ariaValue);
}
function displayToast() {
$toastElement.hide();
if (options.oncreate) {
options.oncreate.call(notificationAPI);
}
$toastElement[options.showMethod]({
duration: options.showDuration,
easing: options.showEasing,
complete: function () {
if (options.onShown) {
options.onShown.call(notificationAPI);
}
}
});
if (options.timeout > 0) {
intervalId = setTimeout(hideToast, options.timeout);
progressBar.maxHideTime = parseFloat(options.timeout);
progressBar.hideEta = new Date().getTime() + progressBar.maxHideTime;
if (options.progressBar) {
progressBar.intervalId = setInterval(updateProgress, 10);
}
}
}
function handleEvents() {
if (options.closeOnhover) {
$toastElement.on("mouseenter", stickAround).on("mouseleave", delayedHideToast);
}
if (options.onclick) {
$toastElement.on("click", function (event) {
options.onclick.call(notificationAPI, event);
if (options.tapToDismiss) {
hideToast();
}
});
} else if (options.tapToDismiss) {
$toastElement.on("click", hideToast);
}
if (options.closeButton && $closeElement) {
$closeElement.on("click", function (event) {
if (event.stopPropagation) {
event.stopPropagation();
} else if (event.cancelBubble !== undefined && event.cancelBubble !== true) {
event.cancelBubble = true;
}
if (options.closeOnclick) {
options.closeOnclick.call(notificationAPI, event);
}
hideToast(true);
});
}
}
function hideToast(override) {
var method = override && options.closeMethod !== false ? options.closeMethod : options.hideMethod;
var duration = override && options.closeDuration !== false ? options.closeDuration : options.hideDuration;
var easing = override && options.closeEasing !== false ? options.closeEasing : options.hideEasing;
if ($(':focus', $toastElement).length && !override) {
return;
}
clearTimeout(progressBar.intervalId);
return $toastElement[method]({
duration: duration,
easing: easing,
complete: function () {
removeToast($toastElement);
clearTimeout(intervalId);
if (options.tag) {
delete taggedToasts[options.tag];
}
if (options.onhidden && response.state !== 'hidden') {
options.onhidden.call(notificationAPI);
}
response.state = 'hidden';
response.endTime = new Date();
publish(response);
}
});
}
function delayedHideToast() {
if (options.timeout > 0 || options.extendedTimeout > 0) {
intervalId = setTimeout(hideToast, options.extendedTimeout);
progressBar.maxHideTime = parseFloat(options.extendedTimeout);
progressBar.hideEta = new Date().getTime() + progressBar.maxHideTime;
}
}
function stickAround() {
clearTimeout(intervalId);
progressBar.hideEta = 0;
$toastElement.stop(true, true)[options.showMethod]({
duration: options.showDuration,
easing: options.showEasing
});
}
function updateProgress() {
var percentage = ((progressBar.hideEta - (new Date().getTime())) / progressBar.maxHideTime) * 100;
$progressElement.width(percentage + '%');
}
personalizeToast();
displayToast();
handleEvents();
publish(response);
if (options.debug && console) {
console.log("[DEBUG] Toast response:", response);
}
if (options.tag) {
taggedToasts[options.tag] = notificationAPI;
}
return notificationAPI;
}
/////////////////////////////////////////////////////////////////////////
// Default options and helper functions
function getOptions() {
return $.extend({}, getDefaults(), toastr.options);
}
function getDefaults() {
return {
tapToDismiss: true,
toastClass: 'toast',
containerId: 'toast-container',
debug: false,
oncreate: undefined,
showMethod: 'fadeIn',
showDuration: 300,
showEasing: 'swing',
onShown: undefined,
hideMethod: 'fadeOut',
hideDuration: 1000,
hideEasing: 'swing',
onhidden: undefined,
closeMethod: false,
closeDuration: false,
closeEasing: false,
closeOnhover: true,
closeOnclick: undefined,
extendedTimeout: 1000,
iconClasses: {
error: 'toast-error',
info: 'toast-info',
success: 'toast-success',
warning: 'toast-warning'
},
iconClass: 'toast-info',
positionClass: 'toast-top-right',
timeout: 5000,
titleClass: 'toast-title',
messageClass: 'toast-message',
escapeHtml: false,
target: 'body',
closeHtml: '<button type="button">&times;</button>',
closeClass: 'toast-close-button',
newestOnTop: true,
preventDuplicates: false,
progressBar: false,
progressClass: 'toast-progress',
rtl: false,
onclick: undefined,
tag: undefined
};
}
function removeToast($toastElement) {
if (!$container) {
$container = getContainer();
}
if ($toastElement.is(':visible')) {
return;
}
$toastElement.remove();
$toastElement = null;
if ($container.children().length === 0) {
$container.remove();
previousToast = undefined;
}
}
function clear($toastElement, clearOptions) {
var options = getOptions();
if (!$container) {
getContainer(options);
}
if (!clearToast($toastElement, options, clearOptions)) {
clearContainer(options);
}
}
function remove($toastElement) {
var options = getOptions();
if (!$container) {
getContainer(options);
}
if ($toastElement && $(':focus', $toastElement).length === 0) {
removeToast($toastElement);
return;
}
if ($container.children().length) {
$container.remove();
}
}
function clearContainer(options) {
var toastsToClear = $container.children();
for (var i = toastsToClear.length - 1; i >= 0; i--) {
clearToast($(toastsToClear[i]), options);
}
}
function clearToast($toastElement, options, clearOptions) {
var force = clearOptions && clearOptions.force ? clearOptions.force : false;
if ($toastElement && (force || $(':focus', $toastElement).length === 0)) {
$toastElement[options.hideMethod]({
duration: options.hideDuration,
easing: options.hideEasing,
complete: function () {
removeToast($toastElement);
}
});
return true;
}
return false;
}
function publish(args) {
if (!listener) {
return;
}
listener(args);
}
function getContainer(options, create) {
if (!options) {
options = getOptions();
}
$container = $('#' + options.containerId + '[position=' + options.positionClass + ']');
if ($container.length) {
return $container;
}
if (create) {
$container = createContainer(options);
}
return $container;
}
function createContainer(options) {
$container = $('<div/>')
.attr('id', options.containerId)
.attr('position', options.positionClass)
.addClass(options.positionClass);
$container.appendTo($(options.target));
return $container;
}
return toastr;
})();
});
}(typeof define === 'function' && define.amd ? define : function (deps, factory) {
if (typeof module !== 'undefined' && module.exports) {
module.exports = factory(require('jquery'));
} else {
window.toastr = factory(window.jQuery);
}
}));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment