[Notifications] #499 All notifications now dismissable by default.

jslint
This commit is contained in:
Henry 2016-02-05 17:40:04 -08:00
parent 2dd9a16bf3
commit 5292b27e7d
7 changed files with 106 additions and 34 deletions

View File

@ -6,7 +6,9 @@
</div> </div>
</div> </div>
<div class="abs message-body"> <div class="abs message-body">
<mct-include ng-repeat="msg in ngModel.dialog.messages | orderBy: '-'" key="'message'" ng-model="msg"></mct-include> <mct-include
ng-repeat="msg in ngModel.dialog.messages | orderBy: '-'"
key="'message'" ng-model="msg.model"></mct-include>
</div> </div>
<div class="abs bottom-bar"> <div class="abs bottom-bar">
<a ng-repeat="dialogAction in ngModel.dialog.actions" <a ng-repeat="dialogAction in ngModel.dialog.actions"

View File

@ -60,6 +60,12 @@ define(
notification.model.cancel = function(){ notification.model.cancel = function(){
dialogService.dismiss(); dialogService.dismiss();
}; };
//If the notification is dismissed by the user, close
// the dialog.
notification.onDismiss(function(){
dialogService.dismiss();
});
dialogService.showBlockingMessage(notification.model); dialogService.showBlockingMessage(notification.model);
} }
}; };

View File

@ -79,6 +79,7 @@ define([
"implementation": NotificationService, "implementation": NotificationService,
"depends": [ "depends": [
"$timeout", "$timeout",
"topic",
"DEFAULT_AUTO_DISMISS", "DEFAULT_AUTO_DISMISS",
"MINIMIZE_TIMEOUT" "MINIMIZE_TIMEOUT"
] ]

View File

@ -50,9 +50,7 @@ define(
title: "Messages", title: "Messages",
//Launch the message list dialog with the models //Launch the message list dialog with the models
// from the notifications // from the notifications
messages: notificationService.notifications && notificationService.notifications.map(function(notification){ messages: notificationService.notifications
return notification.model;
})
}, },
cancel: function(){ cancel: function(){
dialogService.dismiss(); dialogService.dismiss();

View File

@ -58,21 +58,23 @@ define(
* @property {string} title The title of the message * @property {string} title The title of the message
* @property {string} severity The importance of the message (one of * @property {string} severity The importance of the message (one of
* 'info', 'alert', or 'error' where info < alert <error) * 'info', 'alert', or 'error' where info < alert <error)
* @property {number} progress The completion status of a task * @property {number} [progress] The completion status of a task
* represented numerically * represented numerically
* @property {boolean} unknownProgress a boolean indicating that the * @property {boolean} [unknownProgress] a boolean indicating that the
* progress of the underlying task is unknown. This will result in a * progress of the underlying task is unknown. This will result in a
* visually distinct progress bar. * visually distinct progress bar.
* @property {boolean | number} autoDismiss If truthy, dialog will * @property {boolean | number} [autoDismiss] If truthy, dialog will
* be automatically minimized or dismissed (depending on severity). * be automatically minimized or dismissed (depending on severity).
* Additionally, if the provided value is a number, it will be used * Additionally, if the provided value is a number, it will be used
* as the delay period before being dismissed. * as the delay period before being dismissed.
* @property {NotificationOption} primaryOption the default user * @property {boolean} [dismissable=true] If truthy, notification will
* include an option to dismiss it completely.
* @property {NotificationOption} [primaryOption] the default user
* response to * response to
* this message. Will be represented as a button with the provided * this message. Will be represented as a button with the provided
* label and action. May be used by banner notifications to display * label and action. May be used by banner notifications to display
* only the most important option to users. * only the most important option to users.
* @property {NotificationOption[]} options any additional * @property {NotificationOption[]} [options] any additional
* actions the user can take. Will be represented as additional buttons * actions the user can take. Will be represented as additional buttons
* that may or may not be available from a banner. * that may or may not be available from a banner.
* @see DialogModel * @see DialogModel
@ -99,6 +101,8 @@ define(
* be used for dismissing a notification. If more control is * be used for dismissing a notification. If more control is
* required, then the minimize or dismiss functions can be called * required, then the minimize or dismiss functions can be called
* individually. * individually.
* @property {function} onDismiss Allows listening for on dismiss
* events. This allows cleanup etc. when the notification is dismissed.
*/ */
/** /**
@ -113,12 +117,13 @@ define(
* animation is shown. This animation requires some time to execute, * animation is shown. This animation requires some time to execute,
* so a timeout is required before the notification is hidden * so a timeout is required before the notification is hidden
*/ */
function NotificationService($timeout, DEFAULT_AUTO_DISMISS, MINIMIZE_TIMEOUT) { function NotificationService($timeout, topic, DEFAULT_AUTO_DISMISS, MINIMIZE_TIMEOUT) {
this.notifications = []; this.notifications = [];
this.$timeout = $timeout; this.$timeout = $timeout;
this.highest ={ severity: "info" }; this.highest ={ severity: "info" };
this.DEFAULT_AUTO_DISMISS = DEFAULT_AUTO_DISMISS; this.DEFAULT_AUTO_DISMISS = DEFAULT_AUTO_DISMISS;
this.MINIMIZE_TIMEOUT = MINIMIZE_TIMEOUT; this.MINIMIZE_TIMEOUT = MINIMIZE_TIMEOUT;
this.topic = topic;
/* /*
* A context in which to hold the active notification and a * A context in which to hold the active notification and a
@ -127,14 +132,16 @@ define(
this.active = {}; this.active = {};
} }
/* /**
* Minimize a notification. The notification will still be available * Minimize a notification. The notification will still be available
* from the notification list. Typically notifications with a * from the notification list. Typically notifications with a
* severity of 'info' should not be minimized, but rather * severity of 'info' should not be minimized, but rather
* dismissed. If you're not sure which is appropriate, * dismissed. If you're not sure which is appropriate,
* use {@link Notification#dismissOrMinimize} * use {@link Notification#dismissOrMinimize}
*
* @private
*/ */
function minimize (service, notification) { NotificationService.prototype.minimize = function (service, notification) {
//Check this is a known notification //Check this is a known notification
var index = service.notifications.indexOf(notification); var index = service.notifications.indexOf(notification);
@ -159,17 +166,19 @@ define(
service.setActiveNotification(service.selectNextNotification()); service.setActiveNotification(service.selectNextNotification());
}, service.MINIMIZE_TIMEOUT); }, service.MINIMIZE_TIMEOUT);
} }
} };
/* /**
* Completely removes a notification. This will dismiss it from the * Completely removes a notification. This will dismiss it from the
* message banner and remove it from the list of notifications. * message banner and remove it from the list of notifications.
* Typically only notifications with a severity of info should be * Typically only notifications with a severity of info should be
* dismissed. If you're not sure whether to dismiss or minimize a * dismissed. If you're not sure whether to dismiss or minimize a
* notification, use {@link Notification#dismissOrMinimize}. * notification, use {@link Notification#dismissOrMinimize}.
* dismiss * dismiss
*
* @private
*/ */
function dismiss (service, notification) { NotificationService.prototype.dismiss = function (service, notification) {
//Check this is a known notification //Check this is a known notification
var index = service.notifications.indexOf(notification); var index = service.notifications.indexOf(notification);
@ -190,19 +199,23 @@ define(
service.notifications.splice(index, 1); service.notifications.splice(index, 1);
} }
service.setActiveNotification(service.selectNextNotification()); service.setActiveNotification(service.selectNextNotification());
}
/* this.setHighestSeverity();
};
/**
* Depending on the severity of the notification will selectively * Depending on the severity of the notification will selectively
* dismiss or minimize where appropriate. * dismiss or minimize where appropriate.
*
* @private
*/ */
function dismissOrMinimize (notification){ NotificationService.prototype.dismissOrMinimize = function (notification) {
//For now minimize everything, and have discussion around which //For now minimize everything, and have discussion around which
//kind of messages should or should not be in the minimized //kind of messages should or should not be in the minimized
//notifications list //notifications list
notification.minimize(); notification.minimize();
} };
/** /**
* Returns the notification that is currently visible in the banner area * Returns the notification that is currently visible in the banner area
@ -261,6 +274,24 @@ define(
return this.notify(notificationModel); return this.notify(notificationModel);
}; };
/**
* @private
*/
NotificationService.prototype.setHighestSeverity = function () {
var severity = {
"info": 1,
"alert": 2,
"error": 3
};
this.highest.severity = this.notifications.reduce(function(previous, notification){
if (severity[notification.model.severity] > severity[previous]){
return notification.model.severity;
} else {
return previous;
}
}, "info");
};
/** /**
* Notifies the user of an event. If there is a banner notification * Notifies the user of an event. If there is a banner notification
* already active, then it will be dismissed or minimized automatically, * already active, then it will be dismissed or minimized automatically,
@ -274,23 +305,23 @@ define(
NotificationService.prototype.notify = function (notificationModel) { NotificationService.prototype.notify = function (notificationModel) {
var self = this, var self = this,
notification, notification,
ordinality = { activeNotification = self.active.notification,
"info": 1, topic = this.topic();
"alert": 2,
"error": 3
},
activeNotification = self.active.notification;
notification = { notification = {
model: notificationModel, model: notificationModel,
minimize: function() { minimize: function() {
minimize(self, notification); self.minimize(self, notification);
}, },
dismiss: function(){ dismiss: function(){
dismiss(self, notification); self.dismiss(self, notification);
topic.notify();
}, },
dismissOrMinimize: function(){ dismissOrMinimize: function(){
dismissOrMinimize(notification); self.dismissOrMinimize(notification);
},
onDismiss: function(callback) {
topic.listen(callback);
} }
}; };
@ -299,12 +330,25 @@ define(
notificationModel.autoDismiss = this.DEFAULT_AUTO_DISMISS; notificationModel.autoDismiss = this.DEFAULT_AUTO_DISMISS;
} }
if (ordinality[notificationModel.severity.toLowerCase()] > ordinality[this.highest.severity.toLowerCase()]){ //Notifications support a 'dismissable' attribute. This is a
this.highest.severity = notificationModel.severity; // convenience to support adding a 'dismiss' option to the
// notification for the common case of dismissing a
// notification. Could also be done manually by specifying an
// option on the model
if (notificationModel.dismissable !== false) {
notificationModel.options = notificationModel.options || [];
notificationModel.options.unshift({
label: "Dismiss",
callback: function() {
notification.dismiss();
}
});
} }
this.notifications.push(notification); this.notifications.push(notification);
this.setHighestSeverity();
/* /*
Check if there is already an active (ie. visible) notification Check if there is already an active (ie. visible) notification
*/ */

View File

@ -32,13 +32,19 @@ define(
mockAutoDismiss, mockAutoDismiss,
mockMinimizeTimeout, mockMinimizeTimeout,
successModel, successModel,
mockTopicFunction,
mockTopicObject,
errorModel; errorModel;
beforeEach(function(){ beforeEach(function(){
mockTimeout = jasmine.createSpy("$timeout"); mockTimeout = jasmine.createSpy("$timeout");
mockTopicFunction = jasmine.createSpy("topic");
mockTopicObject = jasmine.createSpyObj("topicObject", ["listen", "notify"]);
mockTopicFunction.andReturn(mockTopicObject);
mockAutoDismiss = mockMinimizeTimeout = 1000; mockAutoDismiss = mockMinimizeTimeout = 1000;
notificationService = new NotificationService( notificationService = new NotificationService(
mockTimeout, mockAutoDismiss, mockMinimizeTimeout); mockTimeout, mockTopicFunction, mockAutoDismiss, mockMinimizeTimeout);
successModel = { successModel = {
title: "Mock Success Notification", title: "Mock Success Notification",
severity: "info" severity: "info"
@ -57,6 +63,19 @@ define(
expect(activeNotification.model).toBe(successModel); expect(activeNotification.model).toBe(successModel);
}); });
it("notifies listeners on dismissal of notification", function() {
var notification,
dismissListener = jasmine.createSpy("ondismiss");
notification = notificationService.notify(successModel);
notification.onDismiss(dismissListener);
expect(mockTopicObject.listen).toHaveBeenCalled();
notification.dismiss();
expect(mockTopicObject.notify).toHaveBeenCalled();
mockTopicObject.listen.mostRecentCall.args[0]();
expect(dismissListener).toHaveBeenCalled();
});
it("allows specification of an info notification given just a" + it("allows specification of an info notification given just a" +
" title, making the notification active", function() { " title, making the notification active", function() {
var activeNotification, var activeNotification,

View File

@ -104,14 +104,16 @@ define(
* persistence. * persistence.
*/ */
function notifyOnError(error, domainObject, notificationService, $q){ function notifyOnError(error, domainObject, notificationService, $q){
var errorMessage = "Unable to persist " + domainObject.getModel().name; var errorMessage = "Unable to persist " + domainObject.getModel().name,
notification;
if (error) { if (error) {
errorMessage += ": " + formatError(error); errorMessage += ": " + formatError(error);
} }
notificationService.error({ notification = notificationService.error({
title: "Error persisting " + domainObject.getModel().name, title: "Error persisting " + domainObject.getModel().name,
hint: errorMessage || "Unknown error" hint: errorMessage || "Unknown error",
dismissable: true
}); });
return $q.reject(error); return $q.reject(error);