From 7e910a7845df4e2f79588c9f6aa03dd2fd079869 Mon Sep 17 00:00:00 2001 From: Freek Boon Date: Tue, 12 Dec 2017 21:26:27 +0100 Subject: [PATCH] Added preventDuplicates to toast options --- test/toasterContainerSpec.js | 1527 +++++++++++++++++----------------- toaster.js | 446 +++++----- 2 files changed, 999 insertions(+), 974 deletions(-) diff --git a/test/toasterContainerSpec.js b/test/toasterContainerSpec.js index b59c1da..b7c6b11 100644 --- a/test/toasterContainerSpec.js +++ b/test/toasterContainerSpec.js @@ -5,795 +5,814 @@ var rootScope, toaster, $compile; describe('toasterContainer', function () { - beforeEach(function () { - module('toaster'); - - // inject the toaster service + beforeEach(function () { + module('toaster'); + + // inject the toaster service inject(function (_toaster_, _$rootScope_, _$compile_) { - toaster = _toaster_; - rootScope = _$rootScope_; - $compile = _$compile_; - }); - }); - - it('should pop a toast via individual parameters', function () { - var container = compileContainer(); - var scope = container.scope(); - - toaster.pop('info', 'test', 'test'); - - expect(scope.toasters.length).toBe(1); - }); - - it('should unsubscribe events on $destroy if handlers exist', function () { - var toasterEventRegistry; - - inject(function (_toasterEventRegistry_) { - toasterEventRegistry = _toasterEventRegistry_; - }); - - var container = compileContainer(); - var scope = container.scope(); - - spyOn(toasterEventRegistry, 'unsubscribeToNewToastEvent').and.callThrough(); - spyOn(toasterEventRegistry, 'unsubscribeToClearToastsEvent').and.callThrough(); - - scope.$destroy(); - - expect(toasterEventRegistry.unsubscribeToNewToastEvent).toHaveBeenCalled(); - expect(toasterEventRegistry.unsubscribeToClearToastsEvent).toHaveBeenCalled(); - }); - - - describe('addToast', function () { - it('should default to icon-class config value if toast.type not found in icon-classes', function () { - var toasterConfig; - - inject(function (_toasterConfig_) { - toasterConfig = _toasterConfig_; - }); - - compileContainer(); - - expect(toasterConfig['icon-class']).toBe('toast-info'); - - toaster.pop({ type: 'invalid' }); - - rootScope.$digest(); - - expect(toaster.toast.type).toBe('toast-info'); - }); - - it('should allow subsequent duplicates if prevent-duplicates is not set', function () { - var container = compileContainer(); - var scope = container.scope(); - - expect(scope.toasters.length).toBe(0); - - toaster.pop({ type: 'info', title: 'title', body: 'body' }); - toaster.pop({ type: 'info', title: 'title', body: 'body' }); - - rootScope.$digest(); - - expect(scope.toasters.length).toBe(2); - }); - - it('should not allow subsequent duplicates if prevent-duplicates is true and body matches', function () { - var container = angular.element( - ''); - - $compile(container)(rootScope); - rootScope.$digest(); - - var scope = container.scope(); - - expect(scope.toasters.length).toBe(0); - - toaster.pop({ type: 'info', title: 'title', body: 'body' }); - toaster.pop({ type: 'info', title: 'title', body: 'body' }); - - expect(scope.toasters.length).toBe(1); - }); - + toaster = _toaster_; + rootScope = _$rootScope_; + $compile = _$compile_; + }); + }); + + it('should pop a toast via individual parameters', function () { + var container = compileContainer(); + var scope = container.scope(); + + toaster.pop('info', 'test', 'test'); + + expect(scope.toasters.length).toBe(1); + }); + + it('should unsubscribe events on $destroy if handlers exist', function () { + var toasterEventRegistry; + + inject(function (_toasterEventRegistry_) { + toasterEventRegistry = _toasterEventRegistry_; + }); + + var container = compileContainer(); + var scope = container.scope(); + + spyOn(toasterEventRegistry, 'unsubscribeToNewToastEvent').and.callThrough(); + spyOn(toasterEventRegistry, 'unsubscribeToClearToastsEvent').and.callThrough(); + + scope.$destroy(); + + expect(toasterEventRegistry.unsubscribeToNewToastEvent).toHaveBeenCalled(); + expect(toasterEventRegistry.unsubscribeToClearToastsEvent).toHaveBeenCalled(); + }); + + + describe('addToast', function () { + it('should default to icon-class config value if toast.type not found in icon-classes', function () { + var toasterConfig; + + inject(function (_toasterConfig_) { + toasterConfig = _toasterConfig_; + }); + + compileContainer(); + + expect(toasterConfig['icon-class']).toBe('toast-info'); + + toaster.pop({ type: 'invalid' }); + + rootScope.$digest(); + + expect(toaster.toast.type).toBe('toast-info'); + }); + + it('should allow subsequent duplicates if prevent-duplicates is not set', function () { + var container = compileContainer(); + var scope = container.scope(); + + expect(scope.toasters.length).toBe(0); + + toaster.pop({ type: 'info', title: 'title', body: 'body' }); + toaster.pop({ type: 'info', title: 'title', body: 'body' }); + + rootScope.$digest(); + + expect(scope.toasters.length).toBe(2); + }); + + it('should not allow subsequent duplicates if prevent-duplicates is true and body matches', function () { + var container = angular.element( + ''); + + $compile(container)(rootScope); + rootScope.$digest(); + + var scope = container.scope(); + + expect(scope.toasters.length).toBe(0); + + 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 and body params', function () { - var container = angular.element( - ''); - - $compile(container)(rootScope); - rootScope.$digest(); - - var scope = container.scope(); - - expect(scope.toasters.length).toBe(0); - - toaster.pop({ type: 'info', title: 'title', body: 'body', toastId: 1 }); - toaster.pop({ type: 'info', title: 'title', body: 'body2', toastId: 2 }); - - rootScope.$digest(); - - expect(scope.toasters.length).toBe(2); - }); - - it('should not allow subsequent duplicates if prevent-duplicates is true with identical toastId params', function () { - var container = angular.element( - ''); - - $compile(container)(rootScope); - rootScope.$digest(); - - var scope = container.scope(); - - 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: 1 }); - - rootScope.$digest(); - - expect(scope.toasters.length).toBe(1); - }); - - it('should not render the close button if showCloseButton is false', function () { - var container = compileContainer(); - - toaster.pop({ type: 'info', body: 'With a close button' }); - - rootScope.$digest(); - - expect(container.find('button')[0]).toBeUndefined(); - }); - - it('should use the default close html if toast.closeHtml is undefined', function () { - var container = compileContainer(); - - toaster.pop({ type: 'info', body: 'With a close button', showCloseButton: true }); - - rootScope.$digest(); - - var buttons = container.find('button'); - - expect(buttons.length).toBe(1); - expect(buttons[0].outerHTML).toBe(''); - }); - - it('should use the toast.closeHtml argument if passed', function () { - var container = compileContainer(); - - toaster.pop({ type: 'info', body: 'With a close button', showCloseButton: true, - closeHtml: '' - }); - - rootScope.$digest(); - - var buttons = container.find('button'); - - expect(buttons.length).toBe(1); - expect(buttons[0].outerHTML).toBe(''); - }); - - it('should render toast.closeHtml argument if not a button element', function () { - var container = compileContainer(); - - toaster.pop({ type: 'info', body: 'With close text', showCloseButton: true, - closeHtml: 'Close' - }); - - rootScope.$digest(); - - var spans = container.find('span'); - - expect(spans.length).toBe(1); - expect(spans[0].outerHTML).toBe('Close'); - }); - - it('should show the close button if mergedConfig close-button is an object set to true for toast-info', function () { - var container = angular.element( - ''); - - $compile(container)(rootScope); - rootScope.$digest(); - - toaster.pop({ type: 'info' }); - - rootScope.$digest(); - - var buttons = container.find('button'); - - expect(buttons.length).toBe(1); - expect(buttons[0].outerHTML).toBe(''); - }); - - it('should not render the close button if mergedConfig close-button type cannot be found', function () { - var container = angular.element( - ''); - - $compile(container)(rootScope); - rootScope.$digest(); - - toaster.pop({ type: 'info' }); - - rootScope.$digest(); - - var buttons = container.find('button'); - - expect(buttons.length).toBe(0); - expect(buttons[0]).toBeUndefined(); - }); - - it('should not render the close button if mergedConfig close-button is not an object', function () { - var container = angular.element( - ''); - - $compile(container)(rootScope); - rootScope.$digest(); - - toaster.pop({ type: 'info' }); - - rootScope.$digest(); - - var buttons = container.find('button'); - - expect(buttons.length).toBe(0); - expect(buttons[0]).toBeUndefined(); - }); - - it('should render trustedHtml bodyOutputType', function () { - var container = compileContainer(); - - toaster.pop({ bodyOutputType: 'trustedHtml', body: '
Body
' }); - - rootScope.$digest(); - - var body = container.find('section'); - - expect(body.length).toBe(1); - expect(body[0].outerHTML).toBe('
Body
'); - }); - - it('default toast template exists', function () { - inject(function($templateCache) { - var template = $templateCache.get('angularjs-toaster/toast.html'); - - expect(template.length).toBeGreaterThan(0); - }); - }); - - it('should render template bodyOutputType when body is passed', function () { - inject(function($templateCache) { - $templateCache.put('/templatepath/template.html', '
Template
'); - }); - - var container = compileContainer(); - - toaster.pop({ bodyOutputType: 'template', body: '/templatepath/template.html' }); - - rootScope.$digest(); - - expect(toaster.toast.body).toBe('/templatepath/template.html'); - - var body = container.find('section'); - - expect(body.length).toBe(1); - expect(body[0].outerHTML).toBe('
Template
'); - }); - - it('should render default template bodyOutputType when body is not passed', function () { - inject(function($templateCache) { - $templateCache.put('toasterBodyTmpl.html', '
Template
'); - }); - - var container = compileContainer(); - - toaster.pop({ bodyOutputType: 'template' }); - - rootScope.$digest(); - - expect(toaster.toast.bodyTemplate).toBe('toasterBodyTmpl.html'); - - var body = container.find('section'); - - expect(body.length).toBe(1); - expect(body[0].outerHTML).toBe('
Template
'); - }); - - it('should render templateWithData bodyOutputType when body is passed', function () { - inject(function($templateCache) { - $templateCache.put('template.html', '
Template {{toaster.data}}
'); - }); - - var container = compileContainer(); - - toaster.pop({ bodyOutputType: 'templateWithData', body: "{template: 'template.html', data: 123 }" }); - - rootScope.$digest(); - - var body = container.find('section'); - - expect(body.length).toBe(1); - expect(body[0].outerHTML).toBe('
Template 123
'); - }); - - it('should throw exception for default templateWithData bodyOutputType when body is not passed', function () { - // TODO: If the default fallback template cannot be parsed to an object - // composed of template and data, an exception is thrown. This seems to - // be undesirable behavior. A clearer exception should be thrown, or better - // handling should be handled, or the fallback option should be removed. - inject(function($templateCache) { - $templateCache.put('template.html', '
Template {{toaster.data}}
'); - }); - - compileContainer(); - var hasException = false; - - try { - toaster.pop({ bodyOutputType: 'templateWithData' }); - } catch (e) { - expect(e.message).toBe("Cannot read property 'template' of undefined"); - hasException = true; - } - - expect(hasException).toBe(true); - }); - - it('should remove first in toast if limit is met and newest-on-top is true', function () { - var container = angular.element( - ''); - - $compile(container)(rootScope); - rootScope.$digest(); - - var scope = container.scope(); - - toaster.pop({ type: 'info', body: 'first' }); - toaster.pop({ type: 'info', body: 'second' }); - - rootScope.$digest(); - - expect(scope.toasters.length).toBe(2); - expect(scope.toasters[0].body).toBe('second'); - expect(scope.toasters[1].body).toBe('first'); - - toaster.pop({ type: 'info', body: 'third' }); - - rootScope.$digest(); - - expect(scope.toasters.length).toBe(2); - expect(scope.toasters[0].body).toBe('third'); - expect(scope.toasters[1].body).toBe('second'); - }); - - it('should remove last in toast if limit is met and newest-on-top is false', function () { - var container = angular.element( - ''); - - $compile(container)(rootScope); - rootScope.$digest(); - - var scope = container.scope(); - - toaster.pop({ type: 'info', body: 'first' }); - toaster.pop({ type: 'info', body: 'second' }); - - rootScope.$digest(); - - expect(scope.toasters.length).toBe(2); - expect(scope.toasters[0].body).toBe('first'); - expect(scope.toasters[1].body).toBe('second'); - - toaster.pop({ type: 'info', body: 'third' }); - - rootScope.$digest(); - - expect(scope.toasters.length).toBe(2); - expect(scope.toasters[0].body).toBe('second'); - expect(scope.toasters[1].body).toBe('third'); - }); - - it('should invoke onShowCallback if it exists when toast is added', function () { - compileContainer(); - var mock = { - callback : function () { } - }; - - spyOn(mock, 'callback'); - - toaster.pop({ type: 'info', body: 'toast 1', onShowCallback: mock.callback }); - - rootScope.$digest(); - - expect(mock.callback).toHaveBeenCalled(); - }); - - it('should not invoke onShowCallback if it does not exist when toast is added', function () { - compileContainer(); - var mock = { - callback : function () { } - }; - - spyOn(mock, 'callback'); - - toaster.pop({ type: 'info', body: 'toast 1' }); - - rootScope.$digest(); - - expect(mock.callback).not.toHaveBeenCalled(); - }); - - it('should invoke pass toast instance to onShowCallback', function () { - compileContainer(); - var toastSetByCallback = null; - - function callback(t) { - toastSetByCallback = t; - } - - toaster.pop({ type: 'info', body: 'toast 1', onShowCallback: callback }); - - rootScope.$digest(); - - expect(toastSetByCallback).not.toBeNull(); - }); - }); - - - describe('removeToast', function () { - it('should not remove toast if toastId does not match a toastId', function() { - var container = compileContainer(); - var scope = container.scope(); - - 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[1].toastId).toBe(toast1.toastId) - expect(scope.toasters[0].toastId).toBe(toast2.toastId) - - scope.removeToast(3); - - rootScope.$digest(); - - expect(scope.toasters.length).toBe(2); - }); - - it('should invoke onHideCallback if it exists when toast is removed', function () { - var container = compileContainer(); - var scope = container.scope(); - - var mock = { - callback : function () { } - }; - - spyOn(mock, 'callback'); - - var toast = toaster.pop({ type: 'info', body: 'toast 1', onHideCallback: mock.callback }); - - rootScope.$digest(); - scope.removeToast(toast.toastId); - rootScope.$digest(); - - expect(mock.callback).toHaveBeenCalled(); - }); - - it('should invoke pass toast instance to onHideCallback', function () { - var container = compileContainer(); - var scope = container.scope(); - - var toastSetByCallback = null; - - function callback(t) { - toastSetByCallback = t; - } - - var toast = toaster.pop({ type: 'info', body: 'toast 1', onHideCallback: callback }); - - rootScope.$digest(); - scope.removeToast(toast.toastId); - rootScope.$digest(); - - expect(toastSetByCallback).not.toBeNull(); - }); - }); - - - describe('scope._onNewTest', function () { - it('should not add toast if toasterId is passed to scope._onNewToast but toasterId is not set via config', function () { - var container = compileContainer(); - var scope = container.scope(); - - expect(scope.config.toasterId).toBeUndefined(); - - toaster.pop({ type: 'info', body: 'toast 1', toasterId: 1 }); - - rootScope.$digest(); - - expect(scope.toasters.length).toBe(0); - }); - - it('should add toast if toasterId is passed to scope._onNewToast and toasterId is set via config', function () { - var container = angular.element( - ''); - - $compile(container)(rootScope); - rootScope.$digest(); - var scope = container.scope(); - - expect(scope.config.toasterId).toBe(1); - - toaster.pop({ type: 'info', body: 'toast 1', toasterId: 1 }); - - rootScope.$digest(); - - expect(scope.toasters.length).toBe(1); - }); - - it('should add toasts to their respective container based on toasterId', function () { - var container1 = angular.element( - ''); - var container2 = angular.element( - ''); - - $compile(container1)(rootScope); - $compile(container2)(rootScope); - rootScope.$digest(); - - var scope1 = container1.scope(); - var scope2 = container2.scope(); - - toaster.pop({ type: 'info', body: 'toast 1', toasterId: 1 }); - toaster.pop({ type: 'info', body: 'toast 2', toasterId: 2 }); - - rootScope.$digest(); - - expect(scope1.toasters.length).toBe(1); - expect(scope2.toasters.length).toBe(1); - }); - }); - - describe('scope._onClearToasts', function (){ - it('should remove all toasts from all containers if toasterId is *', function () { - var container1 = angular.element( - ''); - var container2 = angular.element( - ''); - - $compile(container1)(rootScope); - $compile(container2)(rootScope); - rootScope.$digest(); - - var scope1 = container1.scope(); - var scope2 = container2.scope(); - - toaster.pop({ type: 'info', body: 'toast 1', toasterId: 1 }); - toaster.pop({ type: 'info', body: 'toast 2', toasterId: 2 }); - - rootScope.$digest(); - - expect(scope1.toasters.length).toBe(1); - expect(scope2.toasters.length).toBe(1); - - toaster.clear('*'); - - rootScope.$digest(); - - expect(scope1.toasters.length).toBe(0); - expect(scope2.toasters.length).toBe(0); - }); - - it('should remove all toasts from all containers if config.toasterId and toastId are undefined', function () { - var container1 = angular.element( - ''); - var container2 = angular.element( - ''); - - $compile(container1)(rootScope); - $compile(container2)(rootScope); - rootScope.$digest(); - - var scope1 = container1.scope(); - var scope2 = container2.scope(); - - toaster.pop({ type: 'info', body: 'toast 1' }); - toaster.pop({ type: 'info', body: 'toast 2' }); - - rootScope.$digest(); - - // since there are two separate instances of the container - // without a toasterId, both receive the newToast event - expect(scope1.toasters.length).toBe(2); - expect(scope2.toasters.length).toBe(2); - - toaster.clear(); - - rootScope.$digest(); - - expect(scope1.toasters.length).toBe(0); - expect(scope2.toasters.length).toBe(0); - }); - - it('should not remove by toasterId / toastId from the correct container if toast.toasterId is defined and toast.toastId is undefined', function () { - var container1 = angular.element( - ''); - var container2 = angular.element( - ''); - - $compile(container1)(rootScope); - $compile(container2)(rootScope); - rootScope.$digest(); - - var scope1 = container1.scope(); - var scope2 = container2.scope(); - - // 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 }); - toaster.pop({ type: 'info', body: 'toast 2', toasterId: 2 }); - toaster.pop({ type: 'info', body: 'toast 3', toasterId: 2 }); - - rootScope.$digest(); - - expect(scope1.toasters.length).toBe(1); - expect(scope2.toasters.length).toBe(2); - - toaster.clear(2, 1); - - rootScope.$digest(); - - expect(scope1.toasters.length).toBe(1); - expect(scope2.toasters.length).toBe(2); - }); - - it('should remove by toasterId / toastId from the correct container if toasterId is defined and toastId is defined', function () { - var container1 = angular.element( - ''); - var container2 = angular.element( - ''); - - $compile(container1)(rootScope); - $compile(container2)(rootScope); - rootScope.$digest(); - - var scope1 = container1.scope(); - var scope2 = container2.scope(); - - // removeAllToasts explicitly looks for toast.uid, which is only set - // if toastId is passed as a parameter - 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, toast2.toastId); - - rootScope.$digest(); - - expect(scope1.toasters.length).toBe(1); - expect(scope2.toasters.length).toBe(1); - }); - }); + 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 and body matches', function () { + var container = angular.element( + ''); + + $compile(container)(rootScope); + rootScope.$digest(); + + var scope = container.scope(); + + expect(scope.toasters.length).toBe(0); + + toaster.pop({ type: 'info', title: 'title', body: 'body', toastId: 1 }); + toaster.pop({ type: 'info', title: 'title', body: 'body2', toastId: 2 }); + + rootScope.$digest(); + + expect(scope.toasters.length).toBe(2); + }); + + it('should not allow subsequent duplicates if prevent-duplicates is true with identical toastId params', function () { + var container = angular.element( + ''); + + $compile(container)(rootScope); + rootScope.$digest(); + + var scope = container.scope(); + + 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: 1 }); + + rootScope.$digest(); + + expect(scope.toasters.length).toBe(1); + }); + + it('should not allow subsequent duplicates if prevent-duplicates is false with identical toastId params', function () { + var container = angular.element( + ''); + + $compile(container)(rootScope); + rootScope.$digest(); + + var scope = container.scope(); + + expect(scope.toasters.length).toBe(0); + + toaster.pop({ type: 'info', title: 'title', body: 'body', toastId: 1, preventDuplicates: true }); + toaster.pop({ type: 'info', title: 'title', body: 'body', toastId: 1, preventDuplicates: true }); + + rootScope.$digest(); + + expect(scope.toasters.length).toBe(1); + }); + + it('should not render the close button if showCloseButton is false', function () { + var container = compileContainer(); + + toaster.pop({ type: 'info', body: 'With a close button' }); + + rootScope.$digest(); + + expect(container.find('button')[0]).toBeUndefined(); + }); + + it('should use the default close html if toast.closeHtml is undefined', function () { + var container = compileContainer(); + + toaster.pop({ type: 'info', body: 'With a close button', showCloseButton: true }); + + rootScope.$digest(); + + var buttons = container.find('button'); + + expect(buttons.length).toBe(1); + expect(buttons[0].outerHTML).toBe(''); + }); + + it('should use the toast.closeHtml argument if passed', function () { + var container = compileContainer(); + + toaster.pop({ type: 'info', body: 'With a close button', showCloseButton: true, + closeHtml: '' + }); + + rootScope.$digest(); + + var buttons = container.find('button'); + + expect(buttons.length).toBe(1); + expect(buttons[0].outerHTML).toBe(''); + }); + + it('should render toast.closeHtml argument if not a button element', function () { + var container = compileContainer(); + + toaster.pop({ type: 'info', body: 'With close text', showCloseButton: true, + closeHtml: 'Close' + }); + + rootScope.$digest(); + + var spans = container.find('span'); + + expect(spans.length).toBe(1); + expect(spans[0].outerHTML).toBe('Close'); + }); + + it('should show the close button if mergedConfig close-button is an object set to true for toast-info', function () { + var container = angular.element( + ''); + + $compile(container)(rootScope); + rootScope.$digest(); + + toaster.pop({ type: 'info' }); + + rootScope.$digest(); + + var buttons = container.find('button'); + + expect(buttons.length).toBe(1); + expect(buttons[0].outerHTML).toBe(''); + }); + + it('should not render the close button if mergedConfig close-button type cannot be found', function () { + var container = angular.element( + ''); + + $compile(container)(rootScope); + rootScope.$digest(); + + toaster.pop({ type: 'info' }); + + rootScope.$digest(); + + var buttons = container.find('button'); + + expect(buttons.length).toBe(0); + expect(buttons[0]).toBeUndefined(); + }); + + it('should not render the close button if mergedConfig close-button is not an object', function () { + var container = angular.element( + ''); + + $compile(container)(rootScope); + rootScope.$digest(); + + toaster.pop({ type: 'info' }); + + rootScope.$digest(); + + var buttons = container.find('button'); + + expect(buttons.length).toBe(0); + expect(buttons[0]).toBeUndefined(); + }); + + it('should render trustedHtml bodyOutputType', function () { + var container = compileContainer(); + + toaster.pop({ bodyOutputType: 'trustedHtml', body: '
Body
' }); + + rootScope.$digest(); + + var body = container.find('section'); + + expect(body.length).toBe(1); + expect(body[0].outerHTML).toBe('
Body
'); + }); + + it('default toast template exists', function () { + inject(function($templateCache) { + var template = $templateCache.get('angularjs-toaster/toast.html'); + + expect(template.length).toBeGreaterThan(0); + }); + }); + + it('should render template bodyOutputType when body is passed', function () { + inject(function($templateCache) { + $templateCache.put('/templatepath/template.html', '
Template
'); + }); + + var container = compileContainer(); + + toaster.pop({ bodyOutputType: 'template', body: '/templatepath/template.html' }); + + rootScope.$digest(); + + expect(toaster.toast.body).toBe('/templatepath/template.html'); + + var body = container.find('section'); + + expect(body.length).toBe(1); + expect(body[0].outerHTML).toBe('
Template
'); + }); + + it('should render default template bodyOutputType when body is not passed', function () { + inject(function($templateCache) { + $templateCache.put('toasterBodyTmpl.html', '
Template
'); + }); + + var container = compileContainer(); + + toaster.pop({ bodyOutputType: 'template' }); + + rootScope.$digest(); + + expect(toaster.toast.bodyTemplate).toBe('toasterBodyTmpl.html'); + + var body = container.find('section'); + + expect(body.length).toBe(1); + expect(body[0].outerHTML).toBe('
Template
'); + }); + + it('should render templateWithData bodyOutputType when body is passed', function () { + inject(function($templateCache) { + $templateCache.put('template.html', '
Template {{toaster.data}}
'); + }); + + var container = compileContainer(); + + toaster.pop({ bodyOutputType: 'templateWithData', body: "{template: 'template.html', data: 123 }" }); + + rootScope.$digest(); + + var body = container.find('section'); + + expect(body.length).toBe(1); + expect(body[0].outerHTML).toBe('
Template 123
'); + }); + + it('should throw exception for default templateWithData bodyOutputType when body is not passed', function () { + // TODO: If the default fallback template cannot be parsed to an object + // composed of template and data, an exception is thrown. This seems to + // be undesirable behavior. A clearer exception should be thrown, or better + // handling should be handled, or the fallback option should be removed. + inject(function($templateCache) { + $templateCache.put('template.html', '
Template {{toaster.data}}
'); + }); + + compileContainer(); + var hasException = false; + + try { + toaster.pop({ bodyOutputType: 'templateWithData' }); + } catch (e) { + expect(e.message).toBe("Cannot read property 'template' of undefined"); + hasException = true; + } + + expect(hasException).toBe(true); + }); + + it('should remove first in toast if limit is met and newest-on-top is true', function () { + var container = angular.element( + ''); + + $compile(container)(rootScope); + rootScope.$digest(); + + var scope = container.scope(); + + toaster.pop({ type: 'info', body: 'first' }); + toaster.pop({ type: 'info', body: 'second' }); + + rootScope.$digest(); + + expect(scope.toasters.length).toBe(2); + expect(scope.toasters[0].body).toBe('second'); + expect(scope.toasters[1].body).toBe('first'); + + toaster.pop({ type: 'info', body: 'third' }); + + rootScope.$digest(); + + expect(scope.toasters.length).toBe(2); + expect(scope.toasters[0].body).toBe('third'); + expect(scope.toasters[1].body).toBe('second'); + }); + + it('should remove last in toast if limit is met and newest-on-top is false', function () { + var container = angular.element( + ''); + + $compile(container)(rootScope); + rootScope.$digest(); + + var scope = container.scope(); + + toaster.pop({ type: 'info', body: 'first' }); + toaster.pop({ type: 'info', body: 'second' }); + + rootScope.$digest(); + + expect(scope.toasters.length).toBe(2); + expect(scope.toasters[0].body).toBe('first'); + expect(scope.toasters[1].body).toBe('second'); + + toaster.pop({ type: 'info', body: 'third' }); + + rootScope.$digest(); + + expect(scope.toasters.length).toBe(2); + expect(scope.toasters[0].body).toBe('second'); + expect(scope.toasters[1].body).toBe('third'); + }); + + it('should invoke onShowCallback if it exists when toast is added', function () { + compileContainer(); + var mock = { + callback : function () { } + }; + + spyOn(mock, 'callback'); + + toaster.pop({ type: 'info', body: 'toast 1', onShowCallback: mock.callback }); + + rootScope.$digest(); + + expect(mock.callback).toHaveBeenCalled(); + }); + + it('should not invoke onShowCallback if it does not exist when toast is added', function () { + compileContainer(); + var mock = { + callback : function () { } + }; + + spyOn(mock, 'callback'); + + toaster.pop({ type: 'info', body: 'toast 1' }); + + rootScope.$digest(); + + expect(mock.callback).not.toHaveBeenCalled(); + }); + + it('should invoke pass toast instance to onShowCallback', function () { + compileContainer(); + var toastSetByCallback = null; + + function callback(t) { + toastSetByCallback = t; + } + + toaster.pop({ type: 'info', body: 'toast 1', onShowCallback: callback }); + + rootScope.$digest(); + + expect(toastSetByCallback).not.toBeNull(); + }); + }); + + + describe('removeToast', function () { + it('should not remove toast if toastId does not match a toastId', function() { + var container = compileContainer(); + var scope = container.scope(); + + 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[1].toastId).toBe(toast1.toastId) + expect(scope.toasters[0].toastId).toBe(toast2.toastId) + + scope.removeToast(3); + + rootScope.$digest(); + + expect(scope.toasters.length).toBe(2); + }); + + it('should invoke onHideCallback if it exists when toast is removed', function () { + var container = compileContainer(); + var scope = container.scope(); + + var mock = { + callback : function () { } + }; + + spyOn(mock, 'callback'); + + var toast = toaster.pop({ type: 'info', body: 'toast 1', onHideCallback: mock.callback }); + + rootScope.$digest(); + scope.removeToast(toast.toastId); + rootScope.$digest(); + + expect(mock.callback).toHaveBeenCalled(); + }); + + it('should invoke pass toast instance to onHideCallback', function () { + var container = compileContainer(); + var scope = container.scope(); + + var toastSetByCallback = null; + + function callback(t) { + toastSetByCallback = t; + } + + var toast = toaster.pop({ type: 'info', body: 'toast 1', onHideCallback: callback }); + + rootScope.$digest(); + scope.removeToast(toast.toastId); + rootScope.$digest(); + + expect(toastSetByCallback).not.toBeNull(); + }); + }); + + + describe('scope._onNewTest', function () { + it('should not add toast if toasterId is passed to scope._onNewToast but toasterId is not set via config', function () { + var container = compileContainer(); + var scope = container.scope(); + + expect(scope.config.toasterId).toBeUndefined(); + + toaster.pop({ type: 'info', body: 'toast 1', toasterId: 1 }); + + rootScope.$digest(); + + expect(scope.toasters.length).toBe(0); + }); + + it('should add toast if toasterId is passed to scope._onNewToast and toasterId is set via config', function () { + var container = angular.element( + ''); + + $compile(container)(rootScope); + rootScope.$digest(); + var scope = container.scope(); + + expect(scope.config.toasterId).toBe(1); + + toaster.pop({ type: 'info', body: 'toast 1', toasterId: 1 }); + + rootScope.$digest(); + + expect(scope.toasters.length).toBe(1); + }); + + it('should add toasts to their respective container based on toasterId', function () { + var container1 = angular.element( + ''); + var container2 = angular.element( + ''); + + $compile(container1)(rootScope); + $compile(container2)(rootScope); + rootScope.$digest(); + + var scope1 = container1.scope(); + var scope2 = container2.scope(); + + toaster.pop({ type: 'info', body: 'toast 1', toasterId: 1 }); + toaster.pop({ type: 'info', body: 'toast 2', toasterId: 2 }); + + rootScope.$digest(); + + expect(scope1.toasters.length).toBe(1); + expect(scope2.toasters.length).toBe(1); + }); + }); + + describe('scope._onClearToasts', function (){ + it('should remove all toasts from all containers if toasterId is *', function () { + var container1 = angular.element( + ''); + var container2 = angular.element( + ''); + + $compile(container1)(rootScope); + $compile(container2)(rootScope); + rootScope.$digest(); + + var scope1 = container1.scope(); + var scope2 = container2.scope(); + + toaster.pop({ type: 'info', body: 'toast 1', toasterId: 1 }); + toaster.pop({ type: 'info', body: 'toast 2', toasterId: 2 }); + + rootScope.$digest(); + + expect(scope1.toasters.length).toBe(1); + expect(scope2.toasters.length).toBe(1); + + toaster.clear('*'); + + rootScope.$digest(); + + expect(scope1.toasters.length).toBe(0); + expect(scope2.toasters.length).toBe(0); + }); + + it('should remove all toasts from all containers if config.toasterId and toastId are undefined', function () { + var container1 = angular.element( + ''); + var container2 = angular.element( + ''); + + $compile(container1)(rootScope); + $compile(container2)(rootScope); + rootScope.$digest(); + + var scope1 = container1.scope(); + var scope2 = container2.scope(); + + toaster.pop({ type: 'info', body: 'toast 1' }); + toaster.pop({ type: 'info', body: 'toast 2' }); + + rootScope.$digest(); + + // since there are two separate instances of the container + // without a toasterId, both receive the newToast event + expect(scope1.toasters.length).toBe(2); + expect(scope2.toasters.length).toBe(2); + + toaster.clear(); + + rootScope.$digest(); + + expect(scope1.toasters.length).toBe(0); + expect(scope2.toasters.length).toBe(0); + }); + + it('should not remove by toasterId / toastId from the correct container if toast.toasterId is defined and toast.toastId is undefined', function () { + var container1 = angular.element( + ''); + var container2 = angular.element( + ''); + + $compile(container1)(rootScope); + $compile(container2)(rootScope); + rootScope.$digest(); + + var scope1 = container1.scope(); + var scope2 = container2.scope(); + + // 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 }); + toaster.pop({ type: 'info', body: 'toast 2', toasterId: 2 }); + toaster.pop({ type: 'info', body: 'toast 3', toasterId: 2 }); + + rootScope.$digest(); + + expect(scope1.toasters.length).toBe(1); + expect(scope2.toasters.length).toBe(2); + + toaster.clear(2, 1); + + rootScope.$digest(); + + expect(scope1.toasters.length).toBe(1); + expect(scope2.toasters.length).toBe(2); + }); + + it('should remove by toasterId / toastId from the correct container if toasterId is defined and toastId is defined', function () { + var container1 = angular.element( + ''); + var container2 = angular.element( + ''); + + $compile(container1)(rootScope); + $compile(container2)(rootScope); + rootScope.$digest(); + + var scope1 = container1.scope(); + var scope2 = container2.scope(); + + // removeAllToasts explicitly looks for toast.uid, which is only set + // if toastId is passed as a parameter + 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, toast2.toastId); + + rootScope.$digest(); + + expect(scope1.toasters.length).toBe(1); + expect(scope2.toasters.length).toBe(1); + }); + }); }); describe('toasterContainer', function () { - var $interval, $intervalSpy; + var $interval, $intervalSpy; + + inject(function (_$interval_) { + $interval = _$interval_; + }); - inject(function (_$interval_) { - $interval = _$interval_; - }); + beforeEach(function () { + $intervalSpy = jasmine.createSpy('$interval', $interval); - beforeEach(function () { - $intervalSpy = jasmine.createSpy('$interval', $interval); + module('toaster', function ($provide) { + $provide.value('$interval', $intervalSpy); + }); - module('toaster', function ($provide) { - $provide.value('$interval', $intervalSpy); - }); + // inject the toaster service + inject(function (_toaster_, _$rootScope_, _$compile_) { + toaster = _toaster_; + rootScope = _$rootScope_; + $compile = _$compile_; + }); + }); - // inject the toaster service - inject(function (_toaster_, _$rootScope_, _$compile_) { - toaster = _toaster_; - rootScope = _$rootScope_; - $compile = _$compile_; - }); - }); + it('should use the toast.timeout argument if it is a valid number', function () { + var container = compileContainer(); + var scope = container.scope(); - it('should use the toast.timeout argument if it is a valid number', function () { - var container = compileContainer(); - var scope = container.scope(); + spyOn(scope, 'configureTimer').and.callThrough(); - spyOn(scope, 'configureTimer').and.callThrough(); + toaster.pop({ timeout: 2 }); - toaster.pop({ timeout: 2 }); + expect(scope.configureTimer).toHaveBeenCalled(); + expect(scope.configureTimer.calls.allArgs()[0][0].timeout).toBe(2); + expect($intervalSpy.calls.first().args[1]).toBe(2) + }); - expect(scope.configureTimer).toHaveBeenCalled(); - expect(scope.configureTimer.calls.allArgs()[0][0].timeout).toBe(2); - expect($intervalSpy.calls.first().args[1]).toBe(2) - }); + it('should not use the toast.timeout argument if not a valid number', function () { + var container = compileContainer(); + var scope = container.scope(); - it('should not use the toast.timeout argument if not a valid number', function () { - var container = compileContainer(); - var scope = container.scope(); + spyOn(scope, 'configureTimer').and.callThrough(); - spyOn(scope, 'configureTimer').and.callThrough(); + toaster.pop({ timeout: "2" }); - toaster.pop({ timeout: "2" }); + expect(scope.configureTimer).toHaveBeenCalled(); + expect(scope.configureTimer.calls.allArgs()[0][0].timeout).toBe("2"); + expect($intervalSpy.calls.first().args[1]).toBe(5000); + }); - expect(scope.configureTimer).toHaveBeenCalled(); - expect(scope.configureTimer.calls.allArgs()[0][0].timeout).toBe("2"); - expect($intervalSpy.calls.first().args[1]).toBe(5000); - }); + it('should call scope.removeToast when toast.timeoutPromise expires', function () { + var container = compileContainer(); + var scope = container.scope(); - it('should call scope.removeToast when toast.timeoutPromise expires', function () { - var container = compileContainer(); - var scope = container.scope(); + spyOn(scope, 'removeToast').and.callThrough(); - spyOn(scope, 'removeToast').and.callThrough(); + toaster.pop({ timeout: 2 }); - toaster.pop({ timeout: 2 }); + $intervalSpy.calls.first().args[0](); - $intervalSpy.calls.first().args[0](); + rootScope.$digest(); - rootScope.$digest(); + expect(scope.removeToast).toHaveBeenCalled(); + }); - expect(scope.removeToast).toHaveBeenCalled(); - }); + it('should retrieve timeout by toast type if mergedConfig toast-timeout is an object', function () { + var element = angular.element( + ''); - it('should retrieve timeout by toast type if mergedConfig toast-timeout is an object', function () { - var element = angular.element( - ''); + $compile(element)(rootScope); + rootScope.$digest(); - $compile(element)(rootScope); - rootScope.$digest(); + toaster.pop({ type: 'info' }); - toaster.pop({ type: 'info' }); + expect($intervalSpy.calls.first().args[1]).toBe(5); + }); - expect($intervalSpy.calls.first().args[1]).toBe(5); - }); + it('should not set a timeout if mergedConfig toast-timeout is an object and does not match toast type', function () { + // TODO: this seems to be a bug in the toast-timeout configuration option. + // It should fall back to a default value if the toast type configuration + // does not match the target toast type or throw an exception to warn of an + // invalid configuration. - it('should not set a timeout if mergedConfig toast-timeout is an object and does not match toast type', function () { - // TODO: this seems to be a bug in the toast-timeout configuration option. - // It should fall back to a default value if the toast type configuration - // does not match the target toast type or throw an exception to warn of an - // invalid configuration. - - var element = angular.element( - ''); + var element = angular.element( + ''); - $compile(element)(rootScope); - rootScope.$digest(); + $compile(element)(rootScope); + rootScope.$digest(); - toaster.pop({ type: 'warning' }); + toaster.pop({ type: 'warning' }); - expect($intervalSpy.calls.all().length).toBe(0); - }); + expect($intervalSpy.calls.all().length).toBe(0); + }); }); function compileContainer() { - var element = angular.element(''); - $compile(element)(rootScope); - rootScope.$digest(); + var element = angular.element(''); + $compile(element)(rootScope); + rootScope.$digest(); - return element; + return element; } \ No newline at end of file diff --git a/toaster.js b/toaster.js index a40f53b..d5e2240 100644 --- a/toaster.js +++ b/toaster.js @@ -43,21 +43,21 @@ 'mouseover-timer-stop': true // stop timeout on mouseover and restart timer on mouseout } ).run(['$templateCache', function($templateCache) { - $templateCache.put('angularjs-toaster/toast.html', - '
' + - '
' + - '
' + - '
{{toaster.title}}
' + - '
' + - '
' + - '
' + - '
' + - '
' + - '
{{toaster.body}}
' + - '
' + - '
' + - '
'); - } + $templateCache.put('angularjs-toaster/toast.html', + '
' + + '
' + + '
' + + '
{{toaster.title}}
' + + '
' + + '
' + + '
' + + '
' + + '
' + + '
{{toaster.body}}
' + + '
' + + '
' + + '
'); + } ]).service( 'toaster', [ '$rootScope', 'toasterConfig', function($rootScope, toasterConfig) { @@ -89,7 +89,8 @@ onShowCallback: params.onShowCallback, onHideCallback: params.onHideCallback, directiveData: params.directiveData, - tapToDismiss: params.tapToDismiss + tapToDismiss: params.tapToDismiss, + preventDuplicates: params.preventDuplicates }; toasterId = params.toasterId; } else { @@ -111,7 +112,7 @@ } $rootScope.$emit('toaster-newToast', toasterId, this.toast.toastId); - + return { toasterId: toasterId, toastId: this.toast.toastId @@ -151,7 +152,7 @@ }; } }] - ).factory( + ).factory( 'toasterEventRegistry', [ '$rootScope', function($rootScope) { var deregisterNewToast = null, deregisterClearToasts = null, newToastEventSubscribers = [], clearToastsEventSubscribers = [], toasterFactory; @@ -214,7 +215,7 @@ unsubscribeToClearToastsEvent: toasterFactory.unsubscribeToClearToastsEvent }; }] - ) + ) .directive('directiveTemplate', ['$compile', '$injector', function($compile, $injector) { return { restrict: 'A', @@ -264,253 +265,258 @@ }; }]) .directive( - 'toasterContainer', [ - '$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 mergedConfig; - - // Merges configuration set in directive with default one - mergedConfig = angular.extend({}, toasterConfig, scope.$eval(attrs.toasterOptions)); - - scope.config = { - toasterId: mergedConfig['toaster-id'], - position: mergedConfig['position-class'], - title: mergedConfig['title-class'], - message: mergedConfig['message-class'], - tap: mergedConfig['tap-to-dismiss'], - closeButton: mergedConfig['close-button'], - closeHtml: mergedConfig['close-html'], - animation: mergedConfig['animation-class'], - mouseoverTimer: mergedConfig['mouseover-timer-stop'] - }; - - scope.$on( - "$destroy", function() { - toasterEventRegistry.unsubscribeToNewToastEvent(scope._onNewToast); - toasterEventRegistry.unsubscribeToClearToastsEvent(scope._onClearToasts); - } - ); + 'toasterContainer', [ + '$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 mergedConfig; + + // Merges configuration set in directive with default one + mergedConfig = angular.extend({}, toasterConfig, scope.$eval(attrs.toasterOptions)); + + scope.config = { + toasterId: mergedConfig['toaster-id'], + position: mergedConfig['position-class'], + title: mergedConfig['title-class'], + message: mergedConfig['message-class'], + tap: mergedConfig['tap-to-dismiss'], + closeButton: mergedConfig['close-button'], + closeHtml: mergedConfig['close-html'], + animation: mergedConfig['animation-class'], + mouseoverTimer: mergedConfig['mouseover-timer-stop'], + preventDuplicates: mergedConfig['prevent-duplicates'] + }; - function setTimeout(toast, time) { - toast.timeoutPromise = $interval( - function() { - scope.removeToast(toast.toastId); - }, time, 1 + scope.$on( + "$destroy", function() { + toasterEventRegistry.unsubscribeToNewToastEvent(scope._onNewToast); + toasterEventRegistry.unsubscribeToClearToastsEvent(scope._onClearToasts); + } ); - } - 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) { - setTimeout(toast, timeout); + function setTimeout(toast, time) { + toast.timeoutPromise = $interval( + function() { + scope.removeToast(toast.toastId); + }, time, 1 + ); } - }; - function addToast(toast, toastId) { - toast.type = mergedConfig['icon-classes'][toast.type]; - if (!toast.type) { - toast.type = mergedConfig['icon-class']; - } + 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) { + setTimeout(toast, timeout); + } + }; - if (mergedConfig['prevent-duplicates'] === true && scope.toasters.length) { - if (scope.toasters[scope.toasters.length - 1].body === toast.body) { - return; - } else { - var i, len, dupFound = false; - for (i = 0, len = scope.toasters.length; i < len; i++) { - if (scope.toasters[i].toastId === toastId) { - dupFound = true; - break; + function addToast(toast, toastId) { + toast.type = mergedConfig['icon-classes'][toast.type]; + if (!toast.type) { + toast.type = mergedConfig['icon-class']; + } + + var preventDuplicates = typeof toast.preventDuplicates === "boolean" + ? toast.preventDuplicates + : mergedConfig['prevent-duplicates']; + + if (preventDuplicates === true && scope.toasters.length) { + if (scope.toasters[scope.toasters.length - 1].body === toast.body) { + return; + } else { + var i, len, dupFound = false; + for (i = 0, len = scope.toasters.length; i < len; i++) { + if (scope.toasters[i].toastId === toastId) { + dupFound = true; + break; + } } + + if (dupFound) return; } - - if (dupFound) return; } - } - // set the showCloseButton property on the toast so that - // each template can bind directly to the property to show/hide - // the close button - var closeButton = mergedConfig['close-button']; + // set the showCloseButton property on the toast so that + // each template can bind directly to the property to show/hide + // the close button + var closeButton = mergedConfig['close-button']; - // if toast.showCloseButton is a boolean value, - // it was specifically overriden in the pop arguments - if (typeof toast.showCloseButton === "boolean") { + // if toast.showCloseButton is a boolean value, + // it was specifically overriden in the pop arguments + if (typeof toast.showCloseButton === "boolean") { - } else if (typeof closeButton === "boolean") { - toast.showCloseButton = closeButton; - } else if (typeof closeButton === "object") { - var closeButtonForType = closeButton[toast.type]; + } else if (typeof closeButton === "boolean") { + toast.showCloseButton = closeButton; + } else if (typeof closeButton === "object") { + var closeButtonForType = closeButton[toast.type]; - if (typeof closeButtonForType !== "undefined" && closeButtonForType !== null) { - toast.showCloseButton = closeButtonForType; + if (typeof closeButtonForType !== "undefined" && closeButtonForType !== null) { + toast.showCloseButton = closeButtonForType; + } + } else { + // if an option was not set, default to false. + toast.showCloseButton = false; } - } else { - // if an option was not set, default to false. - toast.showCloseButton = false; - } - if (toast.showCloseButton) { - toast.closeHtml = $sce.trustAsHtml(toast.closeHtml || scope.config.closeHtml); - } + 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) { - case 'trustedHtml': - toast.html = $sce.trustAsHtml(toast.body); - break; - case 'template': - toast.bodyTemplate = toast.body || mergedConfig['body-template']; - break; - case 'templateWithData': - var fcGet = $parse(toast.body || mergedConfig['body-template']); - var templateWithData = fcGet(scope); - toast.bodyTemplate = templateWithData.template; - toast.data = templateWithData.data; - break; - case 'directive': - toast.html = toast.body; - break; - } + // Set the toast.bodyOutputType to the default if it isn't set + toast.bodyOutputType = toast.bodyOutputType || mergedConfig['body-output-type']; + switch (toast.bodyOutputType) { + case 'trustedHtml': + toast.html = $sce.trustAsHtml(toast.body); + break; + case 'template': + toast.bodyTemplate = toast.body || mergedConfig['body-template']; + break; + case 'templateWithData': + var fcGet = $parse(toast.body || mergedConfig['body-template']); + var templateWithData = fcGet(scope); + toast.bodyTemplate = templateWithData.template; + toast.data = templateWithData.data; + break; + case 'directive': + toast.html = toast.body; + break; + } - scope.configureTimer(toast); + scope.configureTimer(toast); - if (mergedConfig['newest-on-top'] === true) { - scope.toasters.unshift(toast); - if (mergedConfig['limit'] > 0 && scope.toasters.length > mergedConfig['limit']) { - scope.toasters.pop(); - } - } else { - scope.toasters.push(toast); - if (mergedConfig['limit'] > 0 && scope.toasters.length > mergedConfig['limit']) { - scope.toasters.shift(); + if (mergedConfig['newest-on-top'] === true) { + scope.toasters.unshift(toast); + if (mergedConfig['limit'] > 0 && scope.toasters.length > mergedConfig['limit']) { + scope.toasters.pop(); + } + } else { + scope.toasters.push(toast); + if (mergedConfig['limit'] > 0 && scope.toasters.length > mergedConfig['limit']) { + scope.toasters.shift(); + } } - } - if (angular.isFunction(toast.onShowCallback)) { - toast.onShowCallback(toast); + if (angular.isFunction(toast.onShowCallback)) { + toast.onShowCallback(toast); + } } - } - scope.removeToast = function(toastId) { - var i, len; - for (i = 0, len = scope.toasters.length; i < len; i++) { - if (scope.toasters[i].toastId === toastId) { - removeToast(i); - break; + scope.removeToast = function(toastId) { + var i, len; + for (i = 0, len = scope.toasters.length; i < len; i++) { + if (scope.toasters[i].toastId === toastId) { + removeToast(i); + break; + } } - } - }; + }; - function removeToast(toastIndex) { - var toast = scope.toasters[toastIndex]; + 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); - } - scope.toasters.splice(toastIndex, 1); + // toast is always defined since the index always has a match + if (toast.timeoutPromise) { + $interval.cancel(toast.timeoutPromise); + } + scope.toasters.splice(toastIndex, 1); - if (angular.isFunction(toast.onHideCallback)) { - toast.onHideCallback(toast); + if (angular.isFunction(toast.onHideCallback)) { + toast.onHideCallback(toast); + } } - } - function removeAllToasts(toastId) { - for (var i = scope.toasters.length - 1; i >= 0; i--) { - if (isUndefinedOrNull(toastId)) { - removeToast(i); - } else { - if (scope.toasters[i].toastId == toastId) { + function removeAllToasts(toastId) { + for (var i = scope.toasters.length - 1; i >= 0; i--) { + if (isUndefinedOrNull(toastId)) { removeToast(i); + } else { + if (scope.toasters[i].toastId == toastId) { + removeToast(i); + } } } } - } - scope.toasters = []; + scope.toasters = []; - function isUndefinedOrNull(val) { - return angular.isUndefined(val) || val === null; - } - - 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 - - if ((isUndefinedOrNull(scope.config.toasterId) && isUndefinedOrNull(toasterId)) || (!isUndefinedOrNull(scope.config.toasterId) && !isUndefinedOrNull(toasterId) && scope.config.toasterId == toasterId)) { - addToast(toaster.toast, 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)) { - removeAllToasts(toastId); + function isUndefinedOrNull(val) { + return angular.isUndefined(val) || val === null; } - }; - toasterEventRegistry.setup(); + 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 - toasterEventRegistry.subscribeToNewToastEvent(scope._onNewToast); - toasterEventRegistry.subscribeToClearToastsEvent(scope._onClearToasts); - }, - controller: [ - '$scope', '$element', '$attrs', function($scope, $element, $attrs) { - // Called on mouseover - $scope.stopTimer = function(toast) { - if ($scope.config.mouseoverTimer === true) { - if (toast.timeoutPromise) { - $interval.cancel(toast.timeoutPromise); - toast.timeoutPromise = null; - } + if ((isUndefinedOrNull(scope.config.toasterId) && isUndefinedOrNull(toasterId)) || (!isUndefinedOrNull(scope.config.toasterId) && !isUndefinedOrNull(toasterId) && scope.config.toasterId == toasterId)) { + addToast(toaster.toast, toastId); } }; - - // Called on mouseout - $scope.restartTimer = function(toast) { - if ($scope.config.mouseoverTimer === true) { - if (!toast.timeoutPromise) { - $scope.configureTimer(toast); - } - } else if (toast.timeoutPromise === null) { - $scope.removeToast(toast.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)) { + removeAllToasts(toastId); } }; - $scope.click = function(event, toast, isCloseButton) { - event.stopPropagation(); - - var tapToDismiss = typeof toast.tapToDismiss === "boolean" - ? toast.tapToDismiss - : $scope.config.tap; - if (tapToDismiss === true || (toast.showCloseButton === true && isCloseButton === true)) { - var removeToast = true; - if (toast.clickHandler) { - if (angular.isFunction(toast.clickHandler)) { - removeToast = toast.clickHandler(toast, isCloseButton); - } else if (angular.isFunction($scope.$parent.$eval(toast.clickHandler))) { - removeToast = $scope.$parent.$eval(toast.clickHandler)(toast, isCloseButton); - } else { - console.log("TOAST-NOTE: Your click handler is not inside a parent scope of toaster-container."); + toasterEventRegistry.setup(); + + toasterEventRegistry.subscribeToNewToastEvent(scope._onNewToast); + toasterEventRegistry.subscribeToClearToastsEvent(scope._onClearToasts); + }, + controller: [ + '$scope', '$element', '$attrs', function($scope, $element, $attrs) { + // Called on mouseover + $scope.stopTimer = function(toast) { + if ($scope.config.mouseoverTimer === true) { + if (toast.timeoutPromise) { + $interval.cancel(toast.timeoutPromise); + toast.timeoutPromise = null; } } - if (removeToast) { + }; + + // Called on mouseout + $scope.restartTimer = function(toast) { + if ($scope.config.mouseoverTimer === true) { + if (!toast.timeoutPromise) { + $scope.configureTimer(toast); + } + } else if (toast.timeoutPromise === null) { $scope.removeToast(toast.toastId); } - } - }; - }], - templateUrl: 'angularjs-toaster/toast.html' - }; - }] + }; + + $scope.click = function(event, toast, isCloseButton) { + event.stopPropagation(); + + var tapToDismiss = typeof toast.tapToDismiss === "boolean" + ? toast.tapToDismiss + : $scope.config.tap; + if (tapToDismiss === true || (toast.showCloseButton === true && isCloseButton === true)) { + var removeToast = true; + if (toast.clickHandler) { + if (angular.isFunction(toast.clickHandler)) { + removeToast = toast.clickHandler(toast, isCloseButton); + } else if (angular.isFunction($scope.$parent.$eval(toast.clickHandler))) { + removeToast = $scope.$parent.$eval(toast.clickHandler)(toast, isCloseButton); + } else { + console.log("TOAST-NOTE: Your click handler is not inside a parent scope of toaster-container."); + } + } + if (removeToast) { + $scope.removeToast(toast.toastId); + } + } + }; + }], + templateUrl: 'angularjs-toaster/toast.html' + }; + }] ); })(window, document);