src/toastr.js
(function() {
'use strict';
angular.module('toastr', [])
.factory('toastr', toastr);
toastr.$inject = ['$animate', '$injector', '$rootScope', '$sce', 'toastrConfig', '$q'];
function toastr($animate, $injector, $rootScope, $sce, toastrConfig, $q) {
var container;
var index = 0;
var toasts = [];
var previousToastMessage = '';
var openToasts = {};
var containerDefer = $q.defer();
var toast = {
active: active,
clear: clear,
error: error,
info: info,
remove: remove,
success: success,
warning: warning,
refreshTimer: refreshTimer
};
return toast;
/* Public API */
function active() {
return toasts.length;
}
function clear(toast) {
// Bit of a hack, I will remove this soon with a BC
if (arguments.length === 1 && !toast) { return; }
if (toast) {
remove(toast.toastId);
} else {
for (var i = 0; i < toasts.length; i++) {
remove(toasts[i].toastId);
}
}
}
function error(message, title, optionsOverride) {
var type = _getOptions().iconClasses.error;
return _buildNotification(type, message, title, optionsOverride);
}
function info(message, title, optionsOverride) {
var type = _getOptions().iconClasses.info;
return _buildNotification(type, message, title, optionsOverride);
}
function success(message, title, optionsOverride) {
var type = _getOptions().iconClasses.success;
return _buildNotification(type, message, title, optionsOverride);
}
function warning(message, title, optionsOverride) {
var type = _getOptions().iconClasses.warning;
return _buildNotification(type, message, title, optionsOverride);
}
function refreshTimer(toast, newTime) {
if (toast && toast.isOpened && toasts.indexOf(toast) >= 0) {
toast.scope.refreshTimer(newTime);
}
}
function remove(toastId, wasClicked) {
var toast = findToast(toastId);
if (toast && ! toast.deleting) { // Avoid clicking when fading out
toast.deleting = true;
toast.isOpened = false;
$animate.leave(toast.el).then(function() {
if (toast.scope.options.onHidden) {
toast.scope.options.onHidden(!!wasClicked, toast);
}
toast.scope.$destroy();
var index = toasts.indexOf(toast);
delete openToasts[toast.scope.message];
toasts.splice(index, 1);
var maxOpened = toastrConfig.maxOpened;
if (maxOpened && toasts.length >= maxOpened) {
toasts[maxOpened - 1].open.resolve();
}
if (lastToast()) {
container.remove();
container = null;
containerDefer = $q.defer();
}
});
}
function findToast(toastId) {
for (var i = 0; i < toasts.length; i++) {
if (toasts[i].toastId === toastId) {
return toasts[i];
}
}
}
function lastToast() {
return !toasts.length;
}
}
/* Internal functions */
function _buildNotification(type, message, title, optionsOverride) {
if (angular.isObject(title)) {
optionsOverride = title;
title = null;
}
return _notify({
iconClass: type,
message: message,
optionsOverride: optionsOverride,
title: title
});
}
function _getOptions() {
return angular.extend({}, toastrConfig);
}
function _createOrGetContainer(options) {
if(container) { return containerDefer.promise; }
container = angular.element('<div></div>');
container.attr('id', options.containerId);
container.addClass(options.positionClass);
container.css({'pointer-events': 'auto'});
var target = angular.element(document.querySelector(options.target));
if ( ! target || ! target.length) {
throw 'Target for toasts doesn\'t exist';
}
$animate.enter(container, target).then(function() {
containerDefer.resolve();
});
return containerDefer.promise;
}
function _notify(map) {
var options = _getOptions();
if (shouldExit()) { return; }
var newToast = createToast();
toasts.push(newToast);
if (ifMaxOpenedAndAutoDismiss()) {
var oldToasts = toasts.slice(0, (toasts.length - options.maxOpened));
for (var i = 0, len = oldToasts.length; i < len; i++) {
remove(oldToasts[i].toastId);
}
}
if (maxOpenedNotReached()) {
newToast.open.resolve();
}
newToast.open.promise.then(function() {
_createOrGetContainer(options).then(function() {
newToast.isOpened = true;
if (options.newestOnTop) {
$animate.enter(newToast.el, container).then(function() {
newToast.scope.init();
});
} else {
var sibling = container[0].lastChild ? angular.element(container[0].lastChild) : null;
$animate.enter(newToast.el, container, sibling).then(function() {
newToast.scope.init();
});
}
});
});
return newToast;
function ifMaxOpenedAndAutoDismiss() {
return options.autoDismiss && options.maxOpened && toasts.length > options.maxOpened;
}
function createScope(toast, map, options) {
if (options.allowHtml) {
toast.scope.allowHtml = true;
toast.scope.title = $sce.trustAsHtml(map.title);
toast.scope.message = $sce.trustAsHtml(map.message);
} else {
toast.scope.title = map.title;
toast.scope.message = map.message;
}
toast.scope.toastType = toast.iconClass;
toast.scope.toastId = toast.toastId;
toast.scope.extraData = options.extraData;
toast.scope.options = {
extendedTimeOut: options.extendedTimeOut,
messageClass: options.messageClass,
onHidden: options.onHidden,
onShown: generateEvent('onShown'),
onTap: generateEvent('onTap'),
progressBar: options.progressBar,
tapToDismiss: options.tapToDismiss,
timeOut: options.timeOut,
titleClass: options.titleClass,
toastClass: options.toastClass
};
if (options.closeButton) {
toast.scope.options.closeHtml = options.closeHtml;
}
function generateEvent(event) {
if (options[event]) {
return function() {
options[event](toast);
};
}
}
}
function createToast() {
var newToast = {
toastId: index++,
isOpened: false,
scope: $rootScope.$new(),
open: $q.defer()
};
newToast.iconClass = map.iconClass;
if (map.optionsOverride) {
angular.extend(options, cleanOptionsOverride(map.optionsOverride));
newToast.iconClass = map.optionsOverride.iconClass || newToast.iconClass;
}
createScope(newToast, map, options);
newToast.el = createToastEl(newToast.scope);
return newToast;
function cleanOptionsOverride(options) {
var badOptions = ['containerId', 'iconClasses', 'maxOpened', 'newestOnTop',
'positionClass', 'preventDuplicates', 'preventOpenDuplicates', 'templates'];
for (var i = 0, l = badOptions.length; i < l; i++) {
delete options[badOptions[i]];
}
return options;
}
}
function createToastEl(scope) {
var angularDomEl = angular.element('<div toast></div>'),
$compile = $injector.get('$compile');
return $compile(angularDomEl)(scope);
}
function maxOpenedNotReached() {
return options.maxOpened && toasts.length <= options.maxOpened || !options.maxOpened;
}
function shouldExit() {
var isDuplicateOfLast = options.preventDuplicates && map.message === previousToastMessage;
var isDuplicateOpen = options.preventOpenDuplicates && openToasts[map.message];
if (isDuplicateOfLast || isDuplicateOpen) {
return true;
}
previousToastMessage = map.message;
openToasts[map.message] = true;
return false;
}
}
}
}());