diff --git a/README.md b/README.md index 2bea184..7f64a54 100644 --- a/README.md +++ b/README.md @@ -153,11 +153,12 @@ app.config(function(toastrConfig) { angular.extend(toastrConfig, { autoDismiss: false, containerId: 'toast-container', - maxOpened: 0, + maxOpened: 0, newestOnTop: true, positionClass: 'toast-top-right', preventDuplicates: false, preventOpenDuplicates: false, + updateTimerOnDuplicates: false, target: 'body' }); }); @@ -172,6 +173,7 @@ Those are the default values, you can pick what you need from it and override wi * **positionClass**: The position where the toasts are added. * **preventDuplicates**: Prevent duplicates of the last toast. * **preventOpenDuplicates**: Prevent duplicates of open toasts. +* **updateTimerOnDuplicates**: When either preventDuplicates or preventOpenDuplicates is set, then this option will ensure that the timer on the duplicate value is refreshed rather than left alone. * **target**: The element to put the toastr container. To customize a `toast` you have two options. First, you can set a default option to be applied globally to all `toasts` in the same way you modified the `container`: @@ -188,7 +190,7 @@ app.config(function(toastrConfig) { info: 'toast-info', success: 'toast-success', warning: 'toast-warning' - }, + }, messageClass: 'toast-message', onHidden: null, onShown: null, @@ -222,7 +224,7 @@ app.config(function(toastrConfig) { Toasts have 3 different callbacks: -* **onHidden**: A callback function called when a toast gets hidden. +* **onHidden**: A callback function called when a toast gets hidden. * First parameter: A boolean to see whether or not the toast was closed via click. * Second parameter: The whole toast that got hidden. * **onShown**: A callback function called when a toast is shown. diff --git a/src/toastr.config.js b/src/toastr.config.js index e5f5ce3..3d832a4 100644 --- a/src/toastr.config.js +++ b/src/toastr.config.js @@ -9,6 +9,7 @@ closeHtml: '', containerId: 'toast-container', extendedTimeOut: 1000, + updateTimerOnDuplicates: false, iconClasses: { error: 'toast-error', info: 'toast-info', diff --git a/src/toastr.js b/src/toastr.js index 24b126c..36fb320 100644 --- a/src/toastr.js +++ b/src/toastr.js @@ -11,7 +11,7 @@ var index = 0; var toasts = []; - var previousToastMessage = ''; + var previousToastKey = ''; var openToasts = {}; var containerDefer = $q.defer(); @@ -85,7 +85,7 @@ } toast.scope.$destroy(); var index = toasts.indexOf(toast); - delete openToasts[toast.scope.message]; + delete openToasts[generateKey(toast.scope)]; toasts.splice(index, 1); var maxOpened = toastrConfig.maxOpened; if (maxOpened && toasts.length >= maxOpened) { @@ -155,7 +155,15 @@ function _notify(map) { var options = _getOptions(); - if (shouldExit()) { return; } + if (shouldExit()) { + if (options.updateTimerOnDuplicates) { + var toast = findToastByMap(map); + if (toast) { + toast.scope.refreshTimer(options.timeOut); + } + } + return; + } var newToast = createToast(); @@ -190,6 +198,18 @@ return newToast; + function findToastByMap(map) { + for (var i = 0; i < toasts.length; i++) { + if (isMatchingToast(toasts[i])) { + return toasts[i]; + } + } + + function isMatchingToast(toast) { + return map.message === toast.scope.message && map.title === toast.scope.title; + } + } + function ifMaxOpenedAndAutoDismiss() { return options.autoDismiss && options.maxOpened && toasts.length > options.maxOpened; } @@ -255,7 +275,8 @@ function cleanOptionsOverride(options) { var badOptions = ['containerId', 'iconClasses', 'maxOpened', 'newestOnTop', - 'positionClass', 'preventDuplicates', 'preventOpenDuplicates', 'templates']; + 'positionClass', + 'preventDuplicates', 'preventOpenDuplicates', 'updateTimerOnDuplicates', 'templates']; for (var i = 0, l = badOptions.length; i < l; i++) { delete options[badOptions[i]]; } @@ -275,18 +296,30 @@ } function shouldExit() { - var isDuplicateOfLast = options.preventDuplicates && map.message === previousToastMessage; - var isDuplicateOpen = options.preventOpenDuplicates && openToasts[map.message]; + var key = generateKey(map); + if (options.preventOpenDuplicates) { + console.log(key); + console.log(openToasts); + } + var isDuplicateOfLast = options.preventDuplicates && key === previousToastKey; + var isDuplicateOpen = options.preventOpenDuplicates && openToasts[key]; if (isDuplicateOfLast || isDuplicateOpen) { return true; } - previousToastMessage = map.message; - openToasts[map.message] = true; + previousToastKey = key; + openToasts[key] = true; return false; } } + function generateKey(toastOption) { + return normaliseUndefinedAndNullString(toastOption.message) + '#' + normaliseUndefinedAndNullString(toastOption.title); + } + + function normaliseUndefinedAndNullString(stringValue) { + return !stringValue ? '' : stringValue; + } } }()); diff --git a/test/toastr_spec.js b/test/toastr_spec.js index c9a0bbd..20b7e5b 100644 --- a/test/toastr_spec.js +++ b/test/toastr_spec.js @@ -667,6 +667,54 @@ describe('toastr', function() { expect($document).toHaveToastOpen(0); }); + it('can take into account the title and message when determining if a toast is the same', function() { + toastrConfig.timeOut = 15000; + toastrConfig.preventDuplicates = true; + openToast('success', 'Toast 1', 'title 1'); + expect($document).toHaveToastOpen(1); + intervalFlush(1000); + openToast('success', 'Toast 1', 'title 2'); + expect($document).toHaveToastOpen(2); + }); + + it('can refresh timer on duplicate toasts when preventDuplicates is enabled', function() { + toastrConfig.timeOut = 5000; + toastrConfig.updateTimerOnDuplicates = true; + toastrConfig.preventDuplicates = true; + var toast = openToast('success', 'foo'); + expect($document).toHaveToastOpen(1); + intervalFlush(2000); + openToast('success', 'foo'); + intervalFlush(4000); + expect($document).toHaveToastOpen(1); + }); + + it('can take into account the title when determining if duplicate', function() { + toastrConfig.preventDuplicates = false; + toastrConfig.preventOpenDuplicates = true; + var toast1 = openToast('success', 'Toast 1'); + openToast('success', 'Toast 1'); + expect($document).toHaveToastOpen(1); + openToast('success', 'Toast 1', 'title'); + expect($document).toHaveToastOpen(2); + }); + + it('can take treat null and undefined titles as the same when determining if duplicate', function() { + toastrConfig.preventDuplicates = false; + toastrConfig.preventOpenDuplicates = true; + var toast1 = openToast('success', 'Toast 1', null); + openToast('success', 'Toast 1', undefined); + expect($document).toHaveToastOpen(1); + }); + + it('can take treat null and undefined text as the same when determining if duplicate', function() { + toastrConfig.preventDuplicates = false; + toastrConfig.preventOpenDuplicates = true; + var toast1 = openToast('success', null, 'Toast 1'); + openToast('success', undefined, 'Toast 1'); + expect($document).toHaveToastOpen(1); + }); + it('can prevent duplicate of open toasts', function() { toastrConfig.preventDuplicates = false; toastrConfig.preventOpenDuplicates = true; @@ -684,6 +732,18 @@ describe('toastr', function() { expect($document).toHaveToastOpen(1); }); + it('can refresh timer on duplicate toasts when preventOpenDuplicates is enabled', function() { + toastrConfig.timeOut = 5000; + toastrConfig.updateTimerOnDuplicates = true; + toastrConfig.preventOpenDuplicates = true; + var toast = openToast('success', 'foo'); + expect($document).toHaveToastOpen(1); + intervalFlush(2000); + openToast('success', 'foo'); + intervalFlush(4000); + expect($document).toHaveToastOpen(1); + }); + it('does not merge options not meant for concrete toasts', function() { openToasts(2, { maxOpened: 2 // this is not meant for the toasts and gives weird side effects