Skip to content

Commit

Permalink
New API for the notification service, refactored the individual lists…
Browse files Browse the repository at this point in the history
… into their own objects
  • Loading branch information
petebacondarwin authored and pkozlowski-opensource committed Oct 21, 2012
1 parent 165f5d1 commit f5e9828
Show file tree
Hide file tree
Showing 2 changed files with 149 additions and 83 deletions.
164 changes: 124 additions & 40 deletions src/common/services/notifications.js
Original file line number Diff line number Diff line change
@@ -1,57 +1,141 @@
angular.module('services.notifications', []).factory('notifications', function ($rootScope) {
angular.module('services.notifications', []);

var notifications = {
'GLOBAL' : [],
'ROUTE' : [],
'NEXT_ROUTE' : []
};
var notificationsService = {};
angular.module('services.notifications').factory('notifications', function ($rootScope) {

// A factory to create a notification list object that manages its own notifications
var createNotificationList = function() {
var list = [];

var notificationList = {
// Create a new notification with a string message and optional type (defaults to 'info').
// The options can be used to attach custom fields to the notification
// The notification will contain a remove method that can be used to remove itself from the list
createNotification: function(message, type, options) {
var notification = {
message: message,
type: type || 'info',
remove: function() {
notificationList.remove(notification);
}
};
return angular.extend(notification, options);
},

// Remove a notification from this list that is matched by the matchNotificationFn function.
remove: function(matchNotificationFn) {
angular.forEach(list, function(notification, index) {
if ( matchNotificationFn(notification) ) {
list.splice(index, 1);
}
});
},

// Remove all the notifications from this list
removeAll: function() {
list.length = 0;
},

// Push a notification to the end of this list
push: function(notification) {
list.push(notification);
},

var clearMixIn = function(notification) {
//TODO: do it better, without creating new function instances each time...
notification.$clear = function() {
notificationsService.clear(this);
// Access the notifications in this list
getAll: function() {
return list;
}
};
return notification;

return notificationList;
};

$rootScope.$on('$routeChangeSuccess', function () {
notifications.ROUTE.length = 0;

notifications.ROUTE = angular.copy(notifications.NEXT_ROUTE);
notifications.NEXT_ROUTE.length = 0;
});
var notificationLists = {};
var includeInCurrent = [];
var notificationsService = {
// Returns a concatenated array of all the notifications from all the lists in the service
all: function() {
var notifications = [];
angular.forEach(notificationLists, function(list) {
notifications = notifications.concat(list.getAll());
});
return notifications;
},

notificationsService.get = function(){
return [].concat(notifications.GLOBAL).concat(notifications.ROUTE);
};
current: function() {
var notifications = [];
angular.forEach(includeInCurrent, function(listName) {
notifications = notifications.concat(notificationLists[listName].getAll());
});
return notifications;
},

notificationsService.clear = function(notification){
angular.forEach(notifications, function (notificationsByType) {
var idx = notificationsByType.indexOf(notification);
if (idx>-1){
notificationsByType.splice(idx,1);
// Remove a notification from the lists of the service, matching it either by the object itself or by the message string
remove: function(messageOrNotification) {
var matchFn;
if ( angular.isString(messageOrNotification) ) {
matchFn = function(notification) { return notification.message === messageOrNotification; };
} else {
matchFn = function(notification) { return notification === messageOrNotification; };
}
});
};
angular.forEach(notificationLists, function(list) {
list.remove(matchFn);
});
},

notificationsService.clearAll = function(){
angular.forEach(notifications, function (notificationsByType) {
notificationsByType.length = 0;
});
};
// Remove all the messages from all the lists in the service
removeAll: function(){
angular.forEach(notificationLists, function (list) {
list.removeAll();
});
},

notificationsService.addFixed = function(notification) {
notifications.GLOBAL.push(clearMixIn(notification));
};
// Push a notification to the specified list
pushNotification: function(list, message, type, options) {
list = notificationLists[list];
if ( angular.isDefined(list) ) {
var notification = list.createNotification(message, type, options);
list.push(notification);
return notification;
} else {
throw new Error('"' + list + '"" is not a valid notification list.');
}
}

notificationsService.addRouteChange = function(notification) {
notifications.ROUTE.push(clearMixIn(notification));
};
// addNotificationListToService (see below) will create a method on the service to push notifications to the list
// For example, addNotificationListToService('CurrentRoute') will create notificationsService.pushCurrentRouteNotification(message, type, options)

notificationsService.addNextRouteChange = function(notification) {
notifications.NEXT_ROUTE.push(clearMixIn(notification));
};

function pushFunctionName(listName) {
return 'push'+listName+'Notification';
}

// Add and configure a new list in the notification service.
function addNotificationListToService(name) {
notificationLists[name] = createNotificationList(name);
notificationsService[pushFunctionName(name)] = function(message, type, options) { return notificationsService.pushNotification(name, message, type, options); };
}

function moveNotificationList(oldListName, newListName) {
notificationsService[pushFunctionName(newListName)] = notificationsService[pushFunctionName(oldListName)];
notificationLists[newListName] = notificationLists[oldListName];
delete notificationsService['push'+oldListName+'Notification'];
delete notificationLists[oldListName];
}

addNotificationListToService('Sticky');
addNotificationListToService('CurrentRoute');
addNotificationListToService('NextRoute');

includeInCurrent.push('Sticky');
includeInCurrent.push('CurrentRoute');

// Rewire the CurrentRoute and NextRoute notification lists when the route changes
$rootScope.$on('$routeChangeSuccess', function () {
moveNotificationList('NextRoute', 'CurrentRoute');
addNotificationListToService('NextRoute');
});

return notificationsService;
});
68 changes: 25 additions & 43 deletions test/unit/common/services/notificationsSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,70 +2,52 @@ describe('notifications', function () {

var $scope, notifications;
beforeEach(module('services.notifications'));
beforeEach(inject(function ($rootScope, _notifications_) {
$scope = $rootScope;
notifications = _notifications_;
beforeEach(inject(function($injector) {
$scope = $injector.get('$rootScope');
notifications = $injector.get('notifications');
}));

describe('global notifications crud', function () {

it('should allow to add, get and clear all notifications', function () {
var msg1 = {type: 'alert', msg: 'Watch out!'};
var msg2 = {type: 'info', msg: 'Just an info!'};
notifications.addFixed(msg1);
notifications.addFixed(msg2);
it('should allow to add, get and remove notifications', function () {
var not1 = notifications.pushStickyNotification('Watch out!', 'alert');
var not2 = notifications.pushStickyNotification('Just an info!', 'info');

expect(notifications.get().length).toEqual(2);
expect(notifications.get()[0]).toBe(msg1);
expect(notifications.current().length).toEqual(2);
expect(notifications.current()[0]).toBe(not1);

notifications.clear(msg2);
expect(notifications.get().length).toEqual(1);
expect(notifications.get()[0]).toBe(msg1);
notifications.remove(not2);
expect(notifications.current().length).toEqual(1);
expect(notifications.current()[0]).toBe(not1);

notifications.clearAll();
expect(notifications.get().length).toEqual(0);
notifications.removeAll();
expect(notifications.current().length).toEqual(0);
});
});

describe('notifications expiring after route change', function () {

it('should clear notification after route change', function () {
var msg = {type: 'info', msg: 'Will go away after route change'};

notifications.addFixed(msg);
notifications.addRouteChange(msg);
expect(notifications.get().length).toEqual(2);
it('should remove notification after route change', function () {
var sticky = notifications.pushStickyNotification('Will stick around after route change');
var currentRoute = notifications.pushCurrentRouteNotification('Will go away after route change');
expect(notifications.current().length).toEqual(2);
$scope.$emit('$routeChangeSuccess');
expect(notifications.get().length).toEqual(1);
expect(notifications.current().length).toEqual(1);
expect(notifications.current()[0]).toBe(sticky);
});
});


describe('notifications showing on next route change and expiring on a subsequent one', function () {

it('should advertise a notification after a route change and clear on the subsequent route change', function () {
var msg = {type: 'info', msg: 'Will go away after route change'};

notifications.addFixed(msg);
notifications.addNextRouteChange(msg);
expect(notifications.get().length).toEqual(1);
it('should advertise a notification after a route change and remove on the subsequent route change', function () {
notifications.pushStickyNotification('Will stick around after route change');
notifications.pushNextRouteNotification('Will not be there till after route change');
expect(notifications.current().length).toEqual(1);
$scope.$emit('$routeChangeSuccess');
expect(notifications.get().length).toEqual(2);
expect(notifications.current().length).toEqual(2);
$scope.$emit('$routeChangeSuccess');
expect(notifications.get().length).toEqual(1);
});
});

describe('canceling a notification instance', function () {

it('should allow cancelation of notification instances', function () {
var msg = {type:'info', msg:'To be canceled'};

notifications.addFixed(msg);
expect(notifications.get().length).toEqual(1);

notifications.get()[0].$clear();
expect(notifications.get().length).toEqual(0);
expect(notifications.current().length).toEqual(1);
});
});
});

0 comments on commit f5e9828

Please sign in to comment.