diff --git a/README.md b/README.md
index 4169003..ee77ab7 100644
--- a/README.md
+++ b/README.md
@@ -4,9 +4,9 @@ AngularJS-Toaster
**AngularJS Toaster** is an AngularJS port of the **toastr** non-blocking notification jQuery library. It requires AngularJS v1.2.6 or higher and angular-animate for the CSS3 transformations.
[](https://travis-ci.org/jirikavi/AngularJS-Toaster)
-[](https://coveralls.io/github/jirikavi/AngularJS-Toaster?branch=master)
+[](https://coveralls.io/github/jirikavi/AngularJS-Toaster?branch=master)
-### Current Version 1.2.1
+### Current Version 2.0.0
## Angular Compatibility
AngularJS-Toaster requires AngularJS v1.2.6 or higher and specifically targets AngularJS, not Angular 2, although it could be used via ngUpgrade.
diff --git a/package.json b/package.json
index 61e15d1..6d6328c 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "angularjs-toaster",
- "version": "1.2.1",
+ "version": "2.0.0",
"description": "AngularJS Toaster is a customized version of toastr non-blocking notification javascript library",
"author": "Jiri Kavulak",
"license": "MIT",
diff --git a/test/toasterContainerSpec.js b/test/toasterContainerSpec.js
index 40353a5..c345844 100644
--- a/test/toasterContainerSpec.js
+++ b/test/toasterContainerSpec.js
@@ -78,7 +78,7 @@ describe('toasterContainer', function () {
expect(scope.toasters.length).toBe(2);
});
- it('should not allow subsequent duplicates if prevent-duplicates is true without toastId param', function () {
+ it('should not allow subsequent duplicates if prevent-duplicates is true and body matches', function () {
var container = angular.element(
'');
@@ -92,12 +92,27 @@ describe('toasterContainer', function () {
toaster.pop({ type: 'info', title: 'title', body: 'body' });
toaster.pop({ type: 'info', title: 'title', body: 'body' });
+ expect(scope.toasters.length).toBe(1);
+ });
+
+ it('should not allow subsequent duplicates if prevent-duplicates is true and id matches with unique bodies', function () {
+ var container = angular.element(
+ '');
+
+ $compile(container)(rootScope);
rootScope.$digest();
+
+ var scope = container.scope();
+
+ expect(scope.toasters.length).toBe(0);
+
+ var toastWrapper = toaster.pop({ type: 'info', title: 'title', body: 'body' });
+ toaster.pop({ type: 'info', title: 'title', body: 'body2', toastId: toastWrapper.toastId });
expect(scope.toasters.length).toBe(1);
});
-
- it('should allow subsequent duplicates if prevent-duplicates is true with unique toastId params', function () {
+
+ it('should allow subsequent duplicates if prevent-duplicates is true with unique toastId and body params', function () {
var container = angular.element(
'');
@@ -109,7 +124,7 @@ describe('toasterContainer', function () {
expect(scope.toasters.length).toBe(0);
toaster.pop({ type: 'info', title: 'title', body: 'body', toastId: 1 });
- toaster.pop({ type: 'info', title: 'title', body: 'body', toastId: 2 });
+ toaster.pop({ type: 'info', title: 'title', body: 'body2', toastId: 2 });
rootScope.$digest();
@@ -416,18 +431,18 @@ describe('toasterContainer', function () {
describe('removeToast', function () {
- it('should not remove toast if id does not match a toast id', function() {
+ it('should not remove toast if toastId does not match a toastId', function() {
var container = compileContainer();
var scope = container.scope();
- toaster.pop({ type: 'info', body: 'toast 1' });
- toaster.pop({ type: 'info', body: 'toast 2' });
+ var toast1 = toaster.pop({ type: 'info', body: 'toast 1' });
+ var toast2 = toaster.pop({ type: 'info', body: 'toast 2' });
rootScope.$digest();
expect(scope.toasters.length).toBe(2);
- expect(scope.toasters[0].id).toBe(2)
- expect(scope.toasters[1].id).toBe(1)
+ expect(scope.toasters[1].toastId).toBe(toast1.toastId)
+ expect(scope.toasters[0].toastId).toBe(toast2.toastId)
scope.removeToast(3);
@@ -446,10 +461,10 @@ describe('toasterContainer', function () {
spyOn(mock, 'callback');
- toaster.pop({ type: 'info', body: 'toast 1', onHideCallback: mock.callback });
+ var toast = toaster.pop({ type: 'info', body: 'toast 1', onHideCallback: mock.callback });
rootScope.$digest();
- scope.removeToast(1);
+ scope.removeToast(toast.toastId);
rootScope.$digest();
expect(mock.callback).toHaveBeenCalled();
@@ -619,16 +634,16 @@ describe('toasterContainer', function () {
// removeAllToasts explicitly looks for toast.uid, which is only set
// if toastId is passed as a parameter
- toaster.pop({ type: 'info', body: 'toast 1', toasterId: 1, toastId: 1 });
- toaster.pop({ type: 'info', body: 'toast 2', toasterId: 2, toastId: 1 });
- toaster.pop({ type: 'info', body: 'toast 3', toasterId: 2, toastId: 2 });
+ var toast1 = toaster.pop({ type: 'info', body: 'toast 1', toasterId: 1, toastId: 1 });
+ var toast2 = toaster.pop({ type: 'info', body: 'toast 2', toasterId: 2, toastId: 1 });
+ var toast3 = toaster.pop({ type: 'info', body: 'toast 3', toasterId: 2, toastId: 2 });
rootScope.$digest();
expect(scope1.toasters.length).toBe(1);
expect(scope2.toasters.length).toBe(2);
- toaster.clear(2, 1);
+ toaster.clear(2, toast2.toastId);
rootScope.$digest();
diff --git a/test/toasterServiceSpec.js b/test/toasterServiceSpec.js
index 77de3dd..6d45fdb 100644
--- a/test/toasterServiceSpec.js
+++ b/test/toasterServiceSpec.js
@@ -39,7 +39,7 @@ describe('toasterService', function () {
expect(scope.toasters[0].type).toBe('toast-error');
});
- it('should create an error method from info icon class', function () {
+ it('should create an info method from info icon class', function () {
var container = angular.element('');
$compile(container)(rootScope);
@@ -58,7 +58,7 @@ describe('toasterService', function () {
expect(scope.toasters[0].type).toBe('toast-info');
});
- it('should create an error method from wait icon class', function () {
+ it('should create an wait method from wait icon class', function () {
var container = angular.element('');
$compile(container)(rootScope);
@@ -77,7 +77,7 @@ describe('toasterService', function () {
expect(scope.toasters[0].type).toBe('toast-wait');
});
- it('should create an error method from success icon class', function () {
+ it('should create an success method from success icon class', function () {
var container = angular.element('');
$compile(container)(rootScope);
@@ -96,7 +96,7 @@ describe('toasterService', function () {
expect(scope.toasters[0].type).toBe('toast-success');
});
- it('should create an error method from warning icon class', function () {
+ it('should create an warning method from warning icon class', function () {
var container = angular.element('');
$compile(container)(rootScope);
@@ -115,7 +115,7 @@ describe('toasterService', function () {
expect(scope.toasters[0].type).toBe('toast-warning');
});
- it('should create a method from the icon class that takes an object', function () {
+ it('should create a method from the icon class that takes an object', function () {
var container = angular.element('');
$compile(container)(rootScope);
@@ -133,4 +133,62 @@ describe('toasterService', function () {
expect(scope.toasters.length).toBe(1)
expect(scope.toasters[0].type).toBe('toast-error');
});
+
+ it('should return a toast wrapper instance from pop', function () {
+ var container = angular.element('');
+
+ $compile(container)(rootScope);
+ rootScope.$digest();
+
+ var toast = toaster.pop('success', 'title', 'body');
+ expect(toast).toBeDefined();
+ expect(angular.isObject(toast)).toBe(true);
+ expect(angular.isUndefined(toast.toasterId)).toBe(true);
+ expect(toast.toastId).toBe(container.scope().toasters[0].toastId);
+ });
+
+ it('should return a toast wrapper instance from each helper function', function () {
+ var container = angular.element('');
+
+ $compile(container)(rootScope);
+ rootScope.$digest();
+
+ var errorToast = toaster.error('title', 'body');
+ var infoToast = toaster.info('title', 'body');
+ var waitToast = toaster.wait('title', 'body');
+ var successToast = toaster.success('title', 'body');
+ var warningToast = toaster.warning('title', 'body');
+
+ expect(errorToast).toBeDefined();
+ expect(infoToast).toBeDefined();
+ expect(waitToast).toBeDefined();
+ expect(successToast).toBeDefined();
+ expect(warningToast).toBeDefined();
+ });
+
+ it('clear should take toast wrapper returned from pop', function () {
+ var container = angular.element('');
+
+ $compile(container)(rootScope);
+ rootScope.$digest();
+ var scope = container.scope();
+
+ var toast = toaster.pop('success', 'title', 'body');
+ expect(scope.toasters.length).toBe(1);
+ toaster.clear(toast);
+ expect(scope.toasters.length).toBe(0);
+ });
+
+ it('clear should take individual arguments from toast wrapper returned from pop', function () {
+ var container = angular.element('');
+
+ $compile(container)(rootScope);
+ rootScope.$digest();
+ var scope = container.scope();
+
+ var toast = toaster.pop('success', 'title', 'body');
+ expect(scope.toasters.length).toBe(1);
+ toaster.clear(toast.toasterId, toast.toastId);
+ expect(scope.toasters.length).toBe(0);
+ });
});
\ No newline at end of file
diff --git a/toaster.js b/toaster.js
index d8edf09..666069e 100644
--- a/toaster.js
+++ b/toaster.js
@@ -1,10 +1,10 @@
/* global angular */
-(function (window, document) {
+(function(window, document) {
'use strict';
/*
* AngularJS Toaster
- * Version: 1.2.1
+ * Version: 2.0.0
*
* Copyright 2013-2016 Jiri Kavulak.
* All Rights Reserved.
@@ -42,10 +42,22 @@
'prevent-duplicates': false,
'mouseover-timer-stop': true // stop timeout on mouseover and restart timer on mouseout
}
- ).service(
+ ).service(
'toaster', [
- '$rootScope', 'toasterConfig', function ($rootScope, toasterConfig) {
- this.pop = function (type, title, body, timeout, bodyOutputType, clickHandler, toasterId, showCloseButton, toastId, onHideCallback) {
+ '$rootScope', 'toasterConfig', function($rootScope, toasterConfig) {
+ // http://stackoverflow.com/questions/26501688/a-typescript-guid-class
+ var Guid = (function() {
+ var Guid = {};
+ Guid.newGuid = function() {
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
+ var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
+ return v.toString(16);
+ });
+ };
+ return Guid;
+ }());
+
+ this.pop = function(type, title, body, timeout, bodyOutputType, clickHandler, toasterId, showCloseButton, toastId, onHideCallback) {
if (angular.isObject(type)) {
var params = type; // Enable named parameters as pop argument
this.toast = {
@@ -57,12 +69,11 @@
clickHandler: params.clickHandler,
showCloseButton: params.showCloseButton,
closeHtml: params.closeHtml,
- uid: params.toastId,
+ toastId: params.toastId,
onShowCallback: params.onShowCallback,
onHideCallback: params.onHideCallback,
directiveData: params.directiveData
};
- toastId = params.toastId;
toasterId = params.toasterId;
} else {
this.toast = {
@@ -73,15 +84,29 @@
bodyOutputType: bodyOutputType,
clickHandler: clickHandler,
showCloseButton: showCloseButton,
- uid: toastId,
+ toastId: toastId,
onHideCallback: onHideCallback
};
}
- $rootScope.$emit('toaster-newToast', toasterId, toastId);
+
+ if (!this.toast.toastId || !this.toast.toastId.length) {
+ this.toast.toastId = Guid.newGuid();
+ }
+
+ $rootScope.$emit('toaster-newToast', toasterId, this.toast.toastId);
+
+ return {
+ toasterId: toasterId,
+ toastId: this.toast.toastId
+ };
};
- this.clear = function (toasterId, toastId) {
- $rootScope.$emit('toaster-clearToasts', toasterId, toastId);
+ this.clear = function(toasterId, toastId) {
+ if (angular.isObject(toasterId)) {
+ $rootScope.$emit('toaster-clearToasts', toasterId.toasterId, toasterId.toastId);
+ } else {
+ $rootScope.$emit('toaster-clearToasts', toasterId, toastId);
+ }
};
// Create one method per icon class, to allow to call toaster.info() and similar
@@ -90,9 +115,9 @@
}
function createTypeMethod(toasterType) {
- return function (title, body, timeout, bodyOutputType, clickHandler, toasterId, showCloseButton, toastId, onHideCallback) {
+ return function(title, body, timeout, bodyOutputType, clickHandler, toasterId, showCloseButton, toastId, onHideCallback) {
if (angular.isString(title)) {
- this.pop(
+ return this.pop(
toasterType,
title,
body,
@@ -104,21 +129,21 @@
toastId,
onHideCallback);
} else { // 'title' is actually an object with options
- this.pop(angular.extend(title, { type: toasterType }));
+ return this.pop(angular.extend(title, { type: toasterType }));
}
};
}
}]
).factory(
'toasterEventRegistry', [
- '$rootScope', function ($rootScope) {
+ '$rootScope', function($rootScope) {
var deregisterNewToast = null, deregisterClearToasts = null, newToastEventSubscribers = [], clearToastsEventSubscribers = [], toasterFactory;
toasterFactory = {
- setup: function () {
+ setup: function() {
if (!deregisterNewToast) {
deregisterNewToast = $rootScope.$on(
- 'toaster-newToast', function (event, toasterId, toastId) {
+ 'toaster-newToast', function(event, toasterId, toastId) {
for (var i = 0, len = newToastEventSubscribers.length; i < len; i++) {
newToastEventSubscribers[i](event, toasterId, toastId);
}
@@ -127,7 +152,7 @@
if (!deregisterClearToasts) {
deregisterClearToasts = $rootScope.$on(
- 'toaster-clearToasts', function (event, toasterId, toastId) {
+ 'toaster-clearToasts', function(event, toasterId, toastId) {
for (var i = 0, len = clearToastsEventSubscribers.length; i < len; i++) {
clearToastsEventSubscribers[i](event, toasterId, toastId);
}
@@ -135,13 +160,13 @@
}
},
- subscribeToNewToastEvent: function (onNewToast) {
+ subscribeToNewToastEvent: function(onNewToast) {
newToastEventSubscribers.push(onNewToast);
},
- subscribeToClearToastsEvent: function (onClearToasts) {
+ subscribeToClearToastsEvent: function(onClearToasts) {
clearToastsEventSubscribers.push(onClearToasts);
},
- unsubscribeToNewToastEvent: function (onNewToast) {
+ unsubscribeToNewToastEvent: function(onNewToast) {
var index = newToastEventSubscribers.indexOf(onNewToast);
if (index >= 0) {
newToastEventSubscribers.splice(index, 1);
@@ -152,7 +177,7 @@
deregisterNewToast = null;
}
},
- unsubscribeToClearToastsEvent: function (onClearToasts) {
+ unsubscribeToClearToastsEvent: function(onClearToasts) {
var index = clearToastsEventSubscribers.indexOf(onClearToasts);
if (index >= 0) {
clearToastsEventSubscribers.splice(index, 1);
@@ -180,40 +205,40 @@
directiveName: '@directiveName',
directiveData: '@directiveData'
},
- replace: true,
- link: function (scope, elm, attrs) {
- scope.$watch('directiveName', function (directiveName) {
+ replace: true,
+ link: function(scope, elm, attrs) {
+ scope.$watch('directiveName', function(directiveName) {
if (angular.isUndefined(directiveName) || directiveName.length <= 0)
throw new Error('A valid directive name must be provided via the toast body argument when using bodyOutputType: directive');
-
+
var directive;
-
+
try {
directive = $injector.get(attrs.$normalize(directiveName) + 'Directive');
- } catch(e) {
+ } catch (e) {
throw new Error(directiveName + ' could not be found. ' +
- 'The name should appear as it exists in the markup, not camelCased as it would appear in the directive declaration,' +
- ' e.g. directive-name not directiveName.');
+ 'The name should appear as it exists in the markup, not camelCased as it would appear in the directive declaration,' +
+ ' e.g. directive-name not directiveName.');
}
-
-
+
+
var directiveDetails = directive[0];
-
+
if (directiveDetails.scope !== true && directiveDetails.scope) {
- throw new Error('Cannot use a directive with an isolated scope. ' +
- 'The scope must be either true or falsy (e.g. false/null/undefined). ' +
+ throw new Error('Cannot use a directive with an isolated scope. ' +
+ 'The scope must be either true or falsy (e.g. false/null/undefined). ' +
'Occurred for directive ' + directiveName + '.');
}
-
+
if (directiveDetails.restrict.indexOf('A') < 0) {
throw new Error('Directives must be usable as attributes. ' +
- 'Add "A" to the restrict option (or remove the option entirely). Occurred for directive ' +
- directiveName + '.');
+ 'Add "A" to the restrict option (or remove the option entirely). Occurred for directive ' +
+ directiveName + '.');
}
-
+
if (scope.directiveData)
scope.directiveData = angular.fromJson(scope.directiveData);
-
+
var template = $compile('
')(scope);
elm.append(template);
@@ -224,13 +249,13 @@
.directive(
'toasterContainer', [
'$parse', '$rootScope', '$interval', '$sce', 'toasterConfig', 'toaster', 'toasterEventRegistry',
- function ($parse, $rootScope, $interval, $sce, toasterConfig, toaster, toasterEventRegistry) {
+ function($parse, $rootScope, $interval, $sce, toasterConfig, toaster, toasterEventRegistry) {
return {
replace: true,
restrict: 'EA',
scope: true, // creates an internal scope for this directive (one per directive instance)
- link: function (scope, elm, attrs) {
- var id = 0, mergedConfig;
+ link: function(scope, elm, attrs) {
+ var mergedConfig;
// Merges configuration set in directive with default one
mergedConfig = angular.extend({}, toasterConfig, scope.$eval(attrs.toasterOptions));
@@ -248,21 +273,21 @@
};
scope.$on(
- "$destroy", function () {
+ "$destroy", function() {
toasterEventRegistry.unsubscribeToNewToastEvent(scope._onNewToast);
toasterEventRegistry.unsubscribeToClearToastsEvent(scope._onClearToasts);
}
- );
+ );
function setTimeout(toast, time) {
toast.timeoutPromise = $interval(
- function () {
- scope.removeToast(toast.id);
+ function() {
+ scope.removeToast(toast.toastId);
}, time, 1
- );
+ );
}
- scope.configureTimer = function (toast) {
+ scope.configureTimer = function(toast) {
var timeout = angular.isNumber(toast.timeout) ? toast.timeout : mergedConfig['time-out'];
if (typeof timeout === "object") timeout = timeout[toast.type];
if (timeout > 0) {
@@ -276,30 +301,22 @@
toast.type = mergedConfig['icon-class'];
}
- if (mergedConfig['prevent-duplicates'] === true) {
- // Prevent adding duplicate toasts if it's set
- if (isUndefinedOrNull(toastId)) {
- if (scope.toasters.length > 0 && scope.toasters[scope.toasters.length - 1].body === toast.body) {
- return;
- }
+ if (mergedConfig['prevent-duplicates'] === true && scope.toasters.length) {
+ if (scope.toasters[scope.toasters.length - 1].body === toast.body) {
+ return;
} else {
- var i, len;
+ var i, len, dupFound = false;
for (i = 0, len = scope.toasters.length; i < len; i++) {
- if (scope.toasters[i].uid === toastId) {
- removeToast(i);
- // update loop
- i--;
- len = scope.toasters.length;
+ if (scope.toasters[i].toastId === toastId) {
+ dupFound = true;
+ break;
}
}
+
+ if (dupFound) return;
}
}
- toast.id = ++id;
- // Sure uid defined
- if (!isUndefinedOrNull(toastId)) {
- toast.uid = toastId;
- }
// set the showCloseButton property on the toast so that
// each template can bind directly to the property to show/hide
@@ -322,11 +339,11 @@
// if an option was not set, default to false.
toast.showCloseButton = false;
}
-
+
if (toast.showCloseButton) {
toast.closeHtml = $sce.trustAsHtml(toast.closeHtml || scope.config.closeHtml);
}
-
+
// Set the toast.bodyOutputType to the default if it isn't set
toast.bodyOutputType = toast.bodyOutputType || mergedConfig['body-output-type'];
switch (toast.bodyOutputType) {
@@ -348,7 +365,7 @@
}
scope.configureTimer(toast);
-
+
if (mergedConfig['newest-on-top'] === true) {
scope.toasters.unshift(toast);
if (mergedConfig['limit'] > 0 && scope.toasters.length > mergedConfig['limit']) {
@@ -360,16 +377,16 @@
scope.toasters.shift();
}
}
-
+
if (angular.isFunction(toast.onShowCallback)) {
toast.onShowCallback();
}
}
- scope.removeToast = function (id) {
+ scope.removeToast = function(toastId) {
var i, len;
for (i = 0, len = scope.toasters.length; i < len; i++) {
- if (scope.toasters[i].id === id) {
+ if (scope.toasters[i].toastId === toastId) {
removeToast(i);
break;
}
@@ -378,7 +395,7 @@
function removeToast(toastIndex) {
var toast = scope.toasters[toastIndex];
-
+
// toast is always defined since the index always has a match
if (toast.timeoutPromise) {
$interval.cancel(toast.timeoutPromise);
@@ -395,7 +412,7 @@
if (isUndefinedOrNull(toastId)) {
removeToast(i);
} else {
- if (scope.toasters[i].uid == toastId) {
+ if (scope.toasters[i].toastId == toastId) {
removeToast(i);
}
}
@@ -408,7 +425,7 @@
return angular.isUndefined(val) || val === null;
}
- scope._onNewToast = function (event, toasterId, toastId) {
+ scope._onNewToast = function(event, toasterId, toastId) {
// Compatibility: if toaster has no toasterId defined, and if call to display
// hasn't either, then the request is for us
@@ -416,7 +433,7 @@
addToast(toaster.toast, toastId);
}
};
- scope._onClearToasts = function (event, toasterId, toastId) {
+ scope._onClearToasts = function(event, toasterId, toastId) {
// Compatibility: if toaster has no toasterId defined, and if call to display
// hasn't either, then the request is for us
if (toasterId == '*' || (isUndefinedOrNull(scope.config.toasterId) && isUndefinedOrNull(toasterId)) || (!isUndefinedOrNull(scope.config.toasterId) && !isUndefinedOrNull(toasterId) && scope.config.toasterId == toasterId)) {
@@ -430,9 +447,9 @@
toasterEventRegistry.subscribeToClearToastsEvent(scope._onClearToasts);
},
controller: [
- '$scope', '$element', '$attrs', function ($scope, $element, $attrs) {
+ '$scope', '$element', '$attrs', function($scope, $element, $attrs) {
// Called on mouseover
- $scope.stopTimer = function (toast) {
+ $scope.stopTimer = function(toast) {
if ($scope.config.mouseoverTimer === true) {
if (toast.timeoutPromise) {
$interval.cancel(toast.timeoutPromise);
@@ -442,17 +459,17 @@
};
// Called on mouseout
- $scope.restartTimer = function (toast) {
+ $scope.restartTimer = function(toast) {
if ($scope.config.mouseoverTimer === true) {
if (!toast.timeoutPromise) {
$scope.configureTimer(toast);
}
} else if (toast.timeoutPromise === null) {
- $scope.removeToast(toast.id);
+ $scope.removeToast(toast.toastId);
}
};
- $scope.click = function (toast, isCloseButton) {
+ $scope.click = function(toast, isCloseButton) {
if ($scope.config.tap === true || (toast.showCloseButton === true && isCloseButton === true)) {
var removeToast = true;
if (toast.clickHandler) {
@@ -465,25 +482,25 @@
}
}
if (removeToast) {
- $scope.removeToast(toast.id);
+ $scope.removeToast(toast.toastId);
}
}
};
}],
- template:
- '
' +
- '
' +
- '' +
- '
{{toaster.title}}
' +
- '
' +
- '' +
- '
' +
- '
' +
- '
' +
- '
{{toaster.body}}
' +
- '
' +
- '
' +
- '
'
+ template:
+ '
' +
+ '
' +
+ '' +
+ '
{{toaster.title}}
' +
+ '
' +
+ '' +
+ '
' +
+ '
' +
+ '
' +
+ '
{{toaster.body}}
' +
+ '
' +
+ '
' +
+ '
'
};
}]
);
diff --git a/toaster.min.js b/toaster.min.js
index 792387b..9caf5e6 100644
--- a/toaster.min.js
+++ b/toaster.min.js
@@ -1,6 +1,6 @@
/*
* AngularJS Toaster
- * Version: 1.2.1
+ * Version: 2.0.0
*
* Copyright 2013-2016 Jiri Kavulak.
* All Rights Reserved.
@@ -10,4 +10,4 @@
* Author: Jiri Kavulak
* Related to project of John Papa, Hans Fjällemark and Nguyễn Thiện Hùng (thienhung1989)
*/
-!function(t,e){"use strict";angular.module("toaster",[]).constant("toasterConfig",{limit:0,"tap-to-dismiss":!0,"close-button":!1,"close-html":'',"newest-on-top":!0,"time-out":5e3,"icon-classes":{error:"toast-error",info:"toast-info",wait:"toast-wait",success:"toast-success",warning:"toast-warning"},"body-output-type":"","body-template":"toasterBodyTmpl.html","icon-class":"toast-info","position-class":"toast-top-right","title-class":"toast-title","message-class":"toast-message","prevent-duplicates":!1,"mouseover-timer-stop":!0}).service("toaster",["$rootScope","toasterConfig",function(t,e){function o(t){return function(e,o,s,i,a,n,r,c,l){angular.isString(e)?this.pop(t,e,o,s,i,a,n,r,c,l):this.pop(angular.extend(e,{type:t}))}}this.pop=function(e,o,s,i,a,n,r,c,l,u){if(angular.isObject(e)){var d=e;this.toast={type:d.type,title:d.title,body:d.body,timeout:d.timeout,bodyOutputType:d.bodyOutputType,clickHandler:d.clickHandler,showCloseButton:d.showCloseButton,closeHtml:d.closeHtml,uid:d.toastId,onShowCallback:d.onShowCallback,onHideCallback:d.onHideCallback,directiveData:d.directiveData},l=d.toastId,r=d.toasterId}else this.toast={type:e,title:o,body:s,timeout:i,bodyOutputType:a,clickHandler:n,showCloseButton:c,uid:l,onHideCallback:u};t.$emit("toaster-newToast",r,l)},this.clear=function(e,o){t.$emit("toaster-clearToasts",e,o)};for(var s in e["icon-classes"])this[s]=o(s)}]).factory("toasterEventRegistry",["$rootScope",function(t){var e,o=null,s=null,i=[],a=[];return e={setup:function(){o||(o=t.$on("toaster-newToast",function(t,e,o){for(var s=0,a=i.length;a>s;s++)i[s](t,e,o)})),s||(s=t.$on("toaster-clearToasts",function(t,e,o){for(var s=0,i=a.length;i>s;s++)a[s](t,e,o)}))},subscribeToNewToastEvent:function(t){i.push(t)},subscribeToClearToastsEvent:function(t){a.push(t)},unsubscribeToNewToastEvent:function(t){var e=i.indexOf(t);e>=0&&i.splice(e,1),0===i.length&&(o(),o=null)},unsubscribeToClearToastsEvent:function(t){var e=a.indexOf(t);e>=0&&a.splice(e,1),0===a.length&&(s(),s=null)}},{setup:e.setup,subscribeToNewToastEvent:e.subscribeToNewToastEvent,subscribeToClearToastsEvent:e.subscribeToClearToastsEvent,unsubscribeToNewToastEvent:e.unsubscribeToNewToastEvent,unsubscribeToClearToastsEvent:e.unsubscribeToClearToastsEvent}}]).directive("directiveTemplate",["$compile","$injector",function(t,e){return{restrict:"A",scope:{directiveName:"@directiveName",directiveData:"@directiveData"},replace:!0,link:function(o,s,i){o.$watch("directiveName",function(a){if(angular.isUndefined(a)||a.length<=0)throw new Error("A valid directive name must be provided via the toast body argument when using bodyOutputType: directive");var n;try{n=e.get(i.$normalize(a)+"Directive")}catch(r){throw new Error(a+" could not be found. The name should appear as it exists in the markup, not camelCased as it would appear in the directive declaration, e.g. directive-name not directiveName.")}var c=n[0];if(c.scope!==!0&&c.scope)throw new Error("Cannot use a directive with an isolated scope. The scope must be either true or falsy (e.g. false/null/undefined). Occurred for directive "+a+".");if(c.restrict.indexOf("A")<0)throw new Error('Directives must be usable as attributes. Add "A" to the restrict option (or remove the option entirely). Occurred for directive '+a+".");o.directiveData&&(o.directiveData=angular.fromJson(o.directiveData));var l=t("")(o);s.append(l)})}}}]).directive("toasterContainer",["$parse","$rootScope","$interval","$sce","toasterConfig","toaster","toasterEventRegistry",function(t,e,o,s,i,a,n){return{replace:!0,restrict:"EA",scope:!0,link:function(e,r,c){function l(t,s){t.timeoutPromise=o(function(){e.removeToast(t.id)},s,1)}function u(o,i){if(o.type=v["icon-classes"][o.type],o.type||(o.type=v["icon-class"]),v["prevent-duplicates"]===!0)if(p(i)){if(e.toasters.length>0&&e.toasters[e.toasters.length-1].body===o.body)return}else{var a,n;for(a=0,n=e.toasters.length;n>a;a++)e.toasters[a].uid===i&&(d(a),a--,n=e.toasters.length)}o.id=++f,p(i)||(o.uid=i);var r=v["close-button"];if("boolean"==typeof o.showCloseButton);else if("boolean"==typeof r)o.showCloseButton=r;else if("object"==typeof r){var c=r[o.type];"undefined"!=typeof c&&null!==c&&(o.showCloseButton=c)}else o.showCloseButton=!1;switch(o.showCloseButton&&(o.closeHtml=s.trustAsHtml(o.closeHtml||e.config.closeHtml)),o.bodyOutputType=o.bodyOutputType||v["body-output-type"],o.bodyOutputType){case"trustedHtml":o.html=s.trustAsHtml(o.body);break;case"template":o.bodyTemplate=o.body||v["body-template"];break;case"templateWithData":var l=t(o.body||v["body-template"]),u=l(e);o.bodyTemplate=u.template,o.data=u.data;break;case"directive":o.html=o.body}e.configureTimer(o),v["newest-on-top"]===!0?(e.toasters.unshift(o),v.limit>0&&e.toasters.length>v.limit&&e.toasters.pop()):(e.toasters.push(o),v.limit>0&&e.toasters.length>v.limit&&e.toasters.shift()),angular.isFunction(o.onShowCallback)&&o.onShowCallback()}function d(t){var s=e.toasters[t];s.timeoutPromise&&o.cancel(s.timeoutPromise),e.toasters.splice(t,1),angular.isFunction(s.onHideCallback)&&s.onHideCallback()}function m(t){for(var o=e.toasters.length-1;o>=0;o--)p(t)?d(o):e.toasters[o].uid==t&&d(o)}function p(t){return angular.isUndefined(t)||null===t}var v,f=0;v=angular.extend({},i,e.$eval(c.toasterOptions)),e.config={toasterId:v["toaster-id"],position:v["position-class"],title:v["title-class"],message:v["message-class"],tap:v["tap-to-dismiss"],closeButton:v["close-button"],closeHtml:v["close-html"],animation:v["animation-class"],mouseoverTimer:v["mouseover-timer-stop"]},e.$on("$destroy",function(){n.unsubscribeToNewToastEvent(e._onNewToast),n.unsubscribeToClearToastsEvent(e._onClearToasts)}),e.configureTimer=function(t){var e=angular.isNumber(t.timeout)?t.timeout:v["time-out"];"object"==typeof e&&(e=e[t.type]),e>0&&l(t,e)},e.removeToast=function(t){var o,s;for(o=0,s=e.toasters.length;s>o;o++)if(e.toasters[o].id===t){d(o);break}},e.toasters=[],e._onNewToast=function(t,o,s){(p(e.config.toasterId)&&p(o)||!p(e.config.toasterId)&&!p(o)&&e.config.toasterId==o)&&u(a.toast,s)},e._onClearToasts=function(t,o,s){("*"==o||p(e.config.toasterId)&&p(o)||!p(e.config.toasterId)&&!p(o)&&e.config.toasterId==o)&&m(s)},n.setup(),n.subscribeToNewToastEvent(e._onNewToast),n.subscribeToClearToastsEvent(e._onClearToasts)},controller:["$scope","$element","$attrs",function(t,e,s){t.stopTimer=function(e){t.config.mouseoverTimer===!0&&e.timeoutPromise&&(o.cancel(e.timeoutPromise),e.timeoutPromise=null)},t.restartTimer=function(e){t.config.mouseoverTimer===!0?e.timeoutPromise||t.configureTimer(e):null===e.timeoutPromise&&t.removeToast(e.id)},t.click=function(e,o){if(t.config.tap===!0||e.showCloseButton===!0&&o===!0){var s=!0;e.clickHandler&&(angular.isFunction(e.clickHandler)?s=e.clickHandler(e,o):angular.isFunction(t.$parent.$eval(e.clickHandler))?s=t.$parent.$eval(e.clickHandler)(e,o):console.log("TOAST-NOTE: Your click handler is not inside a parent scope of toaster-container.")),s&&t.removeToast(e.id)}}}],template:'
{{toaster.title}}
{{toaster.body}}
'}}])}(window,document);
\ No newline at end of file
+!function(t,e){"use strict";angular.module("toaster",[]).constant("toasterConfig",{limit:0,"tap-to-dismiss":!0,"close-button":!1,"close-html":'',"newest-on-top":!0,"time-out":5e3,"icon-classes":{error:"toast-error",info:"toast-info",wait:"toast-wait",success:"toast-success",warning:"toast-warning"},"body-output-type":"","body-template":"toasterBodyTmpl.html","icon-class":"toast-info","position-class":"toast-top-right","title-class":"toast-title","message-class":"toast-message","prevent-duplicates":!1,"mouseover-timer-stop":!0}).service("toaster",["$rootScope","toasterConfig",function(t,e){function o(t){return function(e,o,s,i,a,n,r,c,l){return angular.isString(e)?this.pop(t,e,o,s,i,a,n,r,c,l):this.pop(angular.extend(e,{type:t}))}}var s=function(){var t={};return t.newGuid=function(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(t){var e=16*Math.random()|0,o="x"==t?e:3&e|8;return o.toString(16)})},t}();this.pop=function(e,o,i,a,n,r,c,l,u,d){if(angular.isObject(e)){var m=e;this.toast={type:m.type,title:m.title,body:m.body,timeout:m.timeout,bodyOutputType:m.bodyOutputType,clickHandler:m.clickHandler,showCloseButton:m.showCloseButton,closeHtml:m.closeHtml,toastId:m.toastId,onShowCallback:m.onShowCallback,onHideCallback:m.onHideCallback,directiveData:m.directiveData},c=m.toasterId}else this.toast={type:e,title:o,body:i,timeout:a,bodyOutputType:n,clickHandler:r,showCloseButton:l,toastId:u,onHideCallback:d};return this.toast.toastId&&this.toast.toastId.length||(this.toast.toastId=s.newGuid()),t.$emit("toaster-newToast",c,this.toast.toastId),{toasterId:c,toastId:this.toast.toastId}},this.clear=function(e,o){angular.isObject(e)?t.$emit("toaster-clearToasts",e.toasterId,e.toastId):t.$emit("toaster-clearToasts",e,o)};for(var i in e["icon-classes"])this[i]=o(i)}]).factory("toasterEventRegistry",["$rootScope",function(t){var e,o=null,s=null,i=[],a=[];return e={setup:function(){o||(o=t.$on("toaster-newToast",function(t,e,o){for(var s=0,a=i.length;a>s;s++)i[s](t,e,o)})),s||(s=t.$on("toaster-clearToasts",function(t,e,o){for(var s=0,i=a.length;i>s;s++)a[s](t,e,o)}))},subscribeToNewToastEvent:function(t){i.push(t)},subscribeToClearToastsEvent:function(t){a.push(t)},unsubscribeToNewToastEvent:function(t){var e=i.indexOf(t);e>=0&&i.splice(e,1),0===i.length&&(o(),o=null)},unsubscribeToClearToastsEvent:function(t){var e=a.indexOf(t);e>=0&&a.splice(e,1),0===a.length&&(s(),s=null)}},{setup:e.setup,subscribeToNewToastEvent:e.subscribeToNewToastEvent,subscribeToClearToastsEvent:e.subscribeToClearToastsEvent,unsubscribeToNewToastEvent:e.unsubscribeToNewToastEvent,unsubscribeToClearToastsEvent:e.unsubscribeToClearToastsEvent}}]).directive("directiveTemplate",["$compile","$injector",function(t,e){return{restrict:"A",scope:{directiveName:"@directiveName",directiveData:"@directiveData"},replace:!0,link:function(o,s,i){o.$watch("directiveName",function(a){if(angular.isUndefined(a)||a.length<=0)throw new Error("A valid directive name must be provided via the toast body argument when using bodyOutputType: directive");var n;try{n=e.get(i.$normalize(a)+"Directive")}catch(r){throw new Error(a+" could not be found. The name should appear as it exists in the markup, not camelCased as it would appear in the directive declaration, e.g. directive-name not directiveName.")}var c=n[0];if(c.scope!==!0&&c.scope)throw new Error("Cannot use a directive with an isolated scope. The scope must be either true or falsy (e.g. false/null/undefined). Occurred for directive "+a+".");if(c.restrict.indexOf("A")<0)throw new Error('Directives must be usable as attributes. Add "A" to the restrict option (or remove the option entirely). Occurred for directive '+a+".");o.directiveData&&(o.directiveData=angular.fromJson(o.directiveData));var l=t("")(o);s.append(l)})}}}]).directive("toasterContainer",["$parse","$rootScope","$interval","$sce","toasterConfig","toaster","toasterEventRegistry",function(t,e,o,s,i,a,n){return{replace:!0,restrict:"EA",scope:!0,link:function(e,r,c){function l(t,s){t.timeoutPromise=o(function(){e.removeToast(t.toastId)},s,1)}function u(o,i){if(o.type=v["icon-classes"][o.type],o.type||(o.type=v["icon-class"]),v["prevent-duplicates"]===!0&&e.toasters.length){if(e.toasters[e.toasters.length-1].body===o.body)return;var a,n,r=!1;for(a=0,n=e.toasters.length;n>a;a++)if(e.toasters[a].toastId===i){r=!0;break}if(r)return}var c=v["close-button"];if("boolean"==typeof o.showCloseButton);else if("boolean"==typeof c)o.showCloseButton=c;else if("object"==typeof c){var l=c[o.type];"undefined"!=typeof l&&null!==l&&(o.showCloseButton=l)}else o.showCloseButton=!1;switch(o.showCloseButton&&(o.closeHtml=s.trustAsHtml(o.closeHtml||e.config.closeHtml)),o.bodyOutputType=o.bodyOutputType||v["body-output-type"],o.bodyOutputType){case"trustedHtml":o.html=s.trustAsHtml(o.body);break;case"template":o.bodyTemplate=o.body||v["body-template"];break;case"templateWithData":var u=t(o.body||v["body-template"]),d=u(e);o.bodyTemplate=d.template,o.data=d.data;break;case"directive":o.html=o.body}e.configureTimer(o),v["newest-on-top"]===!0?(e.toasters.unshift(o),v.limit>0&&e.toasters.length>v.limit&&e.toasters.pop()):(e.toasters.push(o),v.limit>0&&e.toasters.length>v.limit&&e.toasters.shift()),angular.isFunction(o.onShowCallback)&&o.onShowCallback()}function d(t){var s=e.toasters[t];s.timeoutPromise&&o.cancel(s.timeoutPromise),e.toasters.splice(t,1),angular.isFunction(s.onHideCallback)&&s.onHideCallback()}function m(t){for(var o=e.toasters.length-1;o>=0;o--)p(t)?d(o):e.toasters[o].toastId==t&&d(o)}function p(t){return angular.isUndefined(t)||null===t}var v;v=angular.extend({},i,e.$eval(c.toasterOptions)),e.config={toasterId:v["toaster-id"],position:v["position-class"],title:v["title-class"],message:v["message-class"],tap:v["tap-to-dismiss"],closeButton:v["close-button"],closeHtml:v["close-html"],animation:v["animation-class"],mouseoverTimer:v["mouseover-timer-stop"]},e.$on("$destroy",function(){n.unsubscribeToNewToastEvent(e._onNewToast),n.unsubscribeToClearToastsEvent(e._onClearToasts)}),e.configureTimer=function(t){var e=angular.isNumber(t.timeout)?t.timeout:v["time-out"];"object"==typeof e&&(e=e[t.type]),e>0&&l(t,e)},e.removeToast=function(t){var o,s;for(o=0,s=e.toasters.length;s>o;o++)if(e.toasters[o].toastId===t){d(o);break}},e.toasters=[],e._onNewToast=function(t,o,s){(p(e.config.toasterId)&&p(o)||!p(e.config.toasterId)&&!p(o)&&e.config.toasterId==o)&&u(a.toast,s)},e._onClearToasts=function(t,o,s){("*"==o||p(e.config.toasterId)&&p(o)||!p(e.config.toasterId)&&!p(o)&&e.config.toasterId==o)&&m(s)},n.setup(),n.subscribeToNewToastEvent(e._onNewToast),n.subscribeToClearToastsEvent(e._onClearToasts)},controller:["$scope","$element","$attrs",function(t,e,s){t.stopTimer=function(e){t.config.mouseoverTimer===!0&&e.timeoutPromise&&(o.cancel(e.timeoutPromise),e.timeoutPromise=null)},t.restartTimer=function(e){t.config.mouseoverTimer===!0?e.timeoutPromise||t.configureTimer(e):null===e.timeoutPromise&&t.removeToast(e.toastId)},t.click=function(e,o){if(t.config.tap===!0||e.showCloseButton===!0&&o===!0){var s=!0;e.clickHandler&&(angular.isFunction(e.clickHandler)?s=e.clickHandler(e,o):angular.isFunction(t.$parent.$eval(e.clickHandler))?s=t.$parent.$eval(e.clickHandler)(e,o):console.log("TOAST-NOTE: Your click handler is not inside a parent scope of toaster-container.")),s&&t.removeToast(e.toastId)}}}],template:'
{{toaster.title}}
{{toaster.body}}
'}}])}(window,document);
\ No newline at end of file