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. [![Build Status](https://travis-ci.org/jirikavi/AngularJS-Toaster.svg)](https://travis-ci.org/jirikavi/AngularJS-Toaster) -[![Coverage Status](https://coveralls.io/repos/jirikavi/AngularJS-Toaster/badge.svg?branch=master&service=github&busting=3)](https://coveralls.io/github/jirikavi/AngularJS-Toaster?branch=master) +[![Coverage Status](https://coveralls.io/repos/jirikavi/AngularJS-Toaster/badge.svg?branch=master&service=github&busted=1)](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