Added basic notifications on copy

This commit is contained in:
Andrew Henry 2015-10-28 17:16:53 -07:00
commit e37fa75289
25 changed files with 503 additions and 423 deletions

View File

@ -30,7 +30,5 @@
"example/imagery",
"example/eventGenerator",
"example/generator",
"testing/dialogTest"
"example/generator"
]

View File

@ -3,7 +3,8 @@
<span class="label">
<a ng-click="launchProgress(true)">Known</a> |
<a ng-click="launchProgress(false)">Unknown</a> |
<a ng-click="launchError()">Error</a>
<a ng-click="launchError()">Error</a> |
<a ng-click="launchInfo()">Info</a>
</span>
<span class="count">Dialogs</span>
</span>

View File

@ -1,7 +1,7 @@
<span class="status block ok" ng-controller="NotificationLaunchController">
<span class="ui-symbol status-indicator">&#xe600;</span>
<span class="label">
<a ng-click="newSuccess()">Success</a> |
<a ng-click="newInfo()">Success</a> |
<a ng-click="newError()">Error</a> |
<a ng-click="newAlert()">Alert</a> |
<a ng-click="newProgress()">Progress</a>

View File

@ -22,11 +22,27 @@
/*global define*/
define(
['../../../platform/commonUI/notification/src/MessageSeverity'],
function (MessageSeverity) {
[],
function () {
"use strict";
/**
* A controller for the dialog launch view. This view allows manual
* launching of dialogs for demonstration and testing purposes. It
* also demonstrates the use of the DialogService.
* @param $scope
* @param $timeout
* @param $log
* @param dialogService
* @param notificationService
* @constructor
*/
function DialogLaunchController($scope, $timeout, $log, dialogService, notificationService) {
/*
Demonstrates launching a progress dialog and updating it
periodically with the progress of an ongoing process.
*/
$scope.launchProgress = function (knownProgress) {
var model = {
title: "Progress Dialog Example",
@ -34,18 +50,19 @@ define(
hint: "Do not navigate away from this page or close this browser tab while this operation is in progress.",
actionText: "Calculating...",
unknownProgress: !knownProgress,
severity: MessageSeverity.INFO,
actions: [
unknownDuration: false,
severity: "info",
options: [
{
label: "Cancel Operation",
action: function () {
callback: function () {
$log.debug("Operation cancelled");
dialogService.dismiss();
}
},
{
label: "Do something else...",
action: function () {
callback: function () {
$log.debug("Something else pressed");
}
}
@ -71,22 +88,26 @@ define(
}
};
/*
Demonstrates launching an error dialog
*/
$scope.launchError = function () {
var model = {
title: "Error Dialog Example",
actionText: "Something happened, and it was not good.",
severity: MessageSeverity.ERROR,
actions: [
severity: "error",
options: [
{
label: "Try Again",
action: function () {
callback: function () {
$log.debug("Try Again Pressed");
dialogService.dismiss();
}
},
{
label: "Cancel",
action: function () {
callback: function () {
$log.debug("Cancel Pressed");
dialogService.dismiss();
}
@ -99,87 +120,30 @@ define(
}
};
$scope.launchMessages = function () {
/*
Demonstrates launching an error dialog
*/
$scope.launchInfo = function () {
var model = {
title: "Messages",
severity: MessageSeverity.INFO,
actions: [
{
label: "Done",
action: function () {
title: "Info Dialog Example",
actionText: "This is an example of a blocking info" +
" dialog. This dialog can be used to draw the user's" +
" attention to an event.",
severity: "info",
primaryOption: {
label: "OK",
callback: function () {
$log.debug("OK Pressed");
dialogService.dismiss();
}
}
],
messages: []
};
function getExampleActionText() {
var actionTexts = [
"Adipiscing turpis mauris in enim elementu hac, enim aliquam etiam.",
"Eros turpis, pulvinar turpis eros eu",
"Lundium nascetur a, lectus montes ac, parturient in natoque, duis risus risus pulvinar pid rhoncus, habitasse auctor natoque!"
];
return actionTexts[Math.floor(Math.random()*3)];
if (!dialogService.showBlockingMessage(model)) {
$log.error("Could not display modal dialog");
}
function getExampleActions() {
var actions = [
{
label: "Try Again",
action: function () {
$log.debug("Try Again pressed");
}
},
{
label: "Remove",
action: function () {
$log.debug("Remove pressed");
}
},
{
label: "Cancel",
action: function () {
$log.debug("Cancel pressed");
}
}
];
// Randomly remove some actions off the top; leave at least one
actions.splice(0,Math.floor(Math.random() * actions.length));
return actions;
}
function getExampleSeverity() {
var severities = [
MessageSeverity.INFO,
MessageSeverity.ALERT,
MessageSeverity.ERROR
];
return severities[Math.floor(Math.random() * severities.length)];
}
function createMessage (messageNumber) {
var messageModel = {
title: "Message Title " + messageNumber,
actionText: getExampleActionText(),
severity: getExampleSeverity(),
actions: getExampleActions()
};
return messageModel;
}
function dismiss() {
dialogService.dismiss();
}
model.messages = notificationService.notifications;
dialogService.getDialogResponse('overlay-message-list', {
dialog: model,
cancel: dismiss
});
};
}
return DialogLaunchController;
}

View File

@ -26,6 +26,12 @@ define(
function () {
"use strict";
/**
* A tool for manually invoking dialogs. When included this
* indicator will allow for dialogs of different types to be
* launched for demonstration and testing purposes.
* @constructor
*/
function DialogLaunchIndicator() {
}

View File

@ -22,18 +22,25 @@
/*global define*/
define(
['../../../platform/commonUI/notification/src/MessageSeverity'],
function (MessageSeverity) {
[],
function () {
"use strict";
/**
* Allows launching of notification messages for the purposes of
* demonstration and testing. Also demonstrates use of
* the NotificationService. Notifications are non-blocking messages that
* appear at the bottom of the screen to inform the user of events
* in a non-intrusive way. For more information see the
* {@link NotificationService}
* @param $scope
* @param $timeout
* @param $log
* @param notificationService
* @constructor
*/
function NotificationLaunchController($scope, $timeout, $log, notificationService) {
var messageCounter = 1;
$scope.newSuccess = function(){
notificationService.info({
title: "Success notification!"
});
};
function getExampleActionText() {
var actionTexts = [
@ -48,19 +55,19 @@ define(
var actions = [
{
label: "Try Again",
action: function () {
callback: function () {
$log.debug("Try Again pressed");
}
},
{
label: "Remove",
action: function () {
callback: function () {
$log.debug("Remove pressed");
}
},
{
label: "Cancel",
action: function () {
callback: function () {
$log.debug("Cancel pressed");
}
}
@ -74,65 +81,89 @@ define(
function getExampleSeverity() {
var severities = [
MessageSeverity.INFO,
MessageSeverity.ALERT,
MessageSeverity.ERROR
"info",
"alert",
"error"
];
return severities[Math.floor(Math.random() * severities.length)];
}
/**
* Launch a new notification with a severity level of 'Error'.
*/
$scope.newError = function(){
notificationService.notify({
title: "Error notification " + messageCounter++ + "!",
title: "Example error notification " + messageCounter++,
hint: "An error has occurred",
severity: MessageSeverity.ERROR,
primaryAction: {
severity: "error",
primaryOption: {
label: 'Retry',
action: function() {
callback: function() {
$log.info('Retry clicked');
}
},
actions: getExampleActions()});
options: getExampleActions()});
};
/**
* Launch a new notification with a severity of 'Alert'.
*/
$scope.newAlert = function(){
notificationService.notify({
title: "Alert notification " + (messageCounter++) + "!",
title: "Alert notification " + (messageCounter++),
hint: "This is an alert message",
severity: MessageSeverity.ALERT,
primaryAction: {
severity: "alert",
primaryOption: {
label: 'Retry',
action: function() {
callback: function() {
$log.info('Retry clicked');
}
},
actions: getExampleActions()});
options: getExampleActions()});
};
/**
* Launch a new notification with a progress bar that is updated
* periodically, tracking an ongoing process.
*/
$scope.newProgress = function(){
var notification = {
title: "Progress notification!",
severity: MessageSeverity.INFO,
var notificationModel = {
title: "Progress notification example",
severity: "info",
progress: 0,
actionText: getExampleActionText(),
unknownProgress: false
};
function incrementProgress(notification) {
notification.progress = Math.min(100, Math.floor(notification.progress + Math.random() * 30));
notification.progressText = ["Estimated time remaining:" +
" about ", 60 - Math.floor((notification.progress / 100) * 60), " seconds"].join(" ");
if (notification.progress < 100) {
$timeout(function(){incrementProgress(notification);}, 1000);
/**
* Simulate an ongoing process and update the progress bar.
* @param notification
*/
function incrementProgress(notificationModel) {
notificationModel.progress = Math.min(100, Math.floor(notificationModel.progress + Math.random() * 30));
notificationModel.progressText = ["Estimated time" +
" remaining:" +
" about ", 60 - Math.floor((notificationModel.progress / 100) * 60), " seconds"].join(" ");
if (notificationModel.progress < 100) {
$timeout(function(){incrementProgress(notificationModel);}, 1000);
}
}
notificationService.notify(notification);
incrementProgress(notification);
notificationService.notify(notificationModel);
incrementProgress(notificationModel);
};
/**
* Launch a new notification with severity level of INFO.
*/
$scope.newInfo = function(){
notificationService.info({
title: "Example Info notification " + messageCounter++
});
};
}

View File

@ -1,9 +1,5 @@
<div ng-controller="MessageController"
class="l-message"
ng-class="{
'message-severity-info': ngModel.severity===MessageSeverity.INFO,
'message-severity-error': ngModel.severity===MessageSeverity.ERROR,
'message-severity-alert': ngModel.severity===MessageSeverity.ALERT}">
<div class="l-message"
ng-class="'message-severity-' + ngModel.severity">
<div class="ui-symbol type-icon message-type"></div>
<div class="message-contents">
<div class="top-bar">
@ -19,15 +15,15 @@
ng-show="ngModel.progress !== undefined || ngModel.unknownProgress"></mct-include>
</div>
<div class="bottom-bar">
<a ng-repeat="dialogAction in ngModel.actions"
<a ng-repeat="dialogOption in ngModel.options"
class="s-btn major"
ng-click="dialogAction.action()">
{{dialogAction.label}}
ng-click="dialogOption.callback()">
{{dialogOption.label}}
</a>
<a class="s-btn major"
ng-if="ngModel.primaryAction"
ng-click="ngModel.primaryAction.action()">
{{ngModel.primaryAction.label}}
ng-if="ngModel.primaryOption"
ng-click="ngModel.primaryOption.callback()">
{{ngModel.primaryOption.label}}
</a>
</div>

View File

@ -178,7 +178,7 @@ define(
* A user action that can be performed from a blocking dialog. These
* actions will be rendered as buttons within a blocking dialog.
*
* @typedef DialogAction
* @typedef DialogOption
* @property {string} label a label to be displayed as the button
* text for this action
* @property {function} action a function to be called when the
@ -207,12 +207,12 @@ define(
* impossible to provide an estimate for. Providing a true value for
* this attribute will indicate to the user that the progress and
* duration cannot be estimated.
* @property {DialogAction} primaryAction an action that will
* @property {DialogOption} primaryOption an action that will
* be added to the dialog as a button. The primary action can be
* used as the suggested course of action for the user. Making it
* distinct from other actions allows it to be styled differently,
* and treated preferentially in banner mode.
* @property {DialogAction[]} actions a list of actions that will
* @property {DialogOption[]} options a list of actions that will
* be added to the dialog as buttons.
*/

View File

@ -121,6 +121,17 @@ define(
);
});
it("invokes the overlay service with the correct parameters when" +
" a blocking dialog is requested", function() {
var dialogModel = {};
expect(dialogService.showBlockingMessage(dialogModel)).toBe(true);
expect(mockOverlayService.createOverlay).toHaveBeenCalledWith(
"overlay-blocking-message",
dialogModel,
"t-dialog-sm"
);
});
});
}
);
);

View File

@ -1,23 +1,20 @@
<div ng-controller="BannerController" ng-show="active.notification"
class="l-message-banner s-message-banner" ng-class="{
'info': active.notification.severity===MessageSeverity.INFO,
'alert': active.notification.severity===MessageSeverity.ALERT,
'error': active.notification.severity===MessageSeverity.ERROR,
'minimized': active.notification.minimized,
'new': !active.notification.minimized}"
class="l-message-banner s-message-banner {{active.notification.model.severity}}" ng-class="{
'minimized': active.notification.model.minimized,
'new': !active.notification.model.minimized}"
ng-click="maximize(active.notification)">
<span class="banner-elem label">
{{active.notification.title}}
{{active.notification.model.title}}
</span>
<span ng-show="active.notification.progress !== undefined || active.notification.unknownProgress">
<span ng-show="active.notification.model.progress !== undefined || active.notification.model.unknownProgress">
<mct-include key="'progress-bar'" class="banner-elem"
ng-model="active.notification">
ng-model="active.notification.model">
</mct-include>
</span>
<a ng-hide="active.notification.primaryAction === undefined"
<a ng-hide="active.notification.model.primaryOption === undefined"
class="banner-elem l-action s-action"
ng-click="action(active.notification.primaryAction.action, $event)">
{{active.notification.primaryAction.label}}
ng-click="action(active.notification.model.primaryOption.callback, $event)">
{{active.notification.model.primaryOption.label}}
</a>
<a class="banner-elem ui-symbol close" ng-click="dismiss(active.notification, $event)">
&#x78;</a>

View File

@ -22,26 +22,45 @@
/*global define*/
define(
['../../../notification/src/MessageSeverity'],
function (MessageSeverity) {
[],
function () {
"use strict";
/**
* A controller for banner notifications. Banner notifications are a
* non-blocking way of drawing the user's attention to an event such
* as system errors, or the progress or successful completion of an
* ongoing task. This controller provides scoped functions for
* dismissing and 'maximizing' notifications. See {@link NotificationService}
* for more details on Notifications.
*
* @param $scope
* @param notificationService
* @param dialogService
* @constructor
*/
function BannerController($scope, notificationService, dialogService) {
$scope.active = notificationService.active;
$scope.MessageSeverity = MessageSeverity;
$scope.action = function (action, $event){
/*
Prevents default 'maximize' behaviour when clicking on
notification button
*/
$event.stopPropagation();
return action();
};
$scope.dismiss = function(notification, $event) {
$event.stopPropagation();
notificationService.dismissOrMinimize(notification);
notification.dismissOrMinimize();
};
$scope.maximize = function(notification) {
if (notification.severity > MessageSeverity.INFO){
notification.cancel = function(){
if (notification.model.severity !== "info"){
notification.model.cancel = function(){
dialogService.dismiss();
};
dialogService.showBlockingMessage(notification);
dialogService.showBlockingMessage(notification.model);
}
};
}

View File

@ -21,11 +21,6 @@
}
],
"controllers": [
{
"key": "MessageController",
"implementation": "MessageController.js",
"depends": ["$scope"]
},
{
"key": "NotificationIndicatorController",
"implementation": "NotificationIndicatorController.js",
@ -47,4 +42,4 @@
}
]
}
}
}

View File

@ -1,8 +1,5 @@
<span ng-show="notifications.length > 0" class="status block"
ng-class="{
'info': highest.severity===MessageSeverity.INFO,
'error': highest.severity===MessageSeverity.ERROR,
'alert': highest.severity===MessageSeverity.ALERT }"
ng-class="highest.severity"
ng-controller="NotificationIndicatorController">
<span class="ui-symbol status-indicator">&#xe610;</span>
<span class="label">

View File

@ -1,34 +0,0 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define(
['./MessageSeverity'],
function (MessageSeverity) {
"use strict";
function MessageController($scope) {
$scope.MessageSeverity = MessageSeverity;
}
return MessageController;
}
);

View File

@ -1,11 +0,0 @@
/**
* Created by akhenry on 10/7/15.
*/
/*global define*/
define(function(){
return {
INFO: 0,
ALERT: 1,
ERROR: 2
};
});

View File

@ -26,25 +26,10 @@ define(
function () {
"use strict";
function NotificationIndicator() {
}
function NotificationIndicator() {}
NotificationIndicator.template = 'notificationIndicatorTemplate';
NotificationIndicator.prototype.getGlyph = function () {
return "A";
};
NotificationIndicator.prototype.getGlyphClass = function () {
return 'caution';
};
NotificationIndicator.prototype.getText = function () {
return "Notifications";
};
NotificationIndicator.prototype.getDescription = function () {
return "Notifications";
};
return NotificationIndicator;
}
);

View File

@ -22,34 +22,38 @@
/*global define*/
define(
['./MessageSeverity'],
function (MessageSeverity) {
[],
function () {
"use strict";
/**
* Provides an indicator that is visible when there are
* banner notifications that have been minimized. Will also indicate
* the number of notifications. Notifications can be viewed by
* clicking on the indicator to launch a dialog showing a list of
* notifications.
* @param $scope
* @param notificationService
* @param dialogService
* @constructor
*/
function NotificationIndicatorController($scope, notificationService, dialogService) {
$scope.notifications = notificationService.notifications;
$scope.highest = notificationService.highest;
$scope.MessageSeverity = MessageSeverity;
/**
* Launch a dialog showing a list of current notifications.
*/
$scope.showNotificationsList = function(){
var model = {
title: "Messages",
severity: MessageSeverity.INFO,
actions: [
{
label: "Done",
action: function () {
dialogService.dismiss();
}
}
],
messages: []
};
model.messages = notificationService.notifications;
dialogService.getDialogResponse('overlay-message-list', {
dialog: model,
dialog: {
title: "Messages",
//Launch the message list dialog with the models
// from the notifications
messages: notificationService.notifications && notificationService.notifications.map(function(notification){
return notification.model;
})
},
cancel: function(){
dialogService.dismiss();
}
@ -60,3 +64,4 @@ define(
return NotificationIndicatorController;
}
);

View File

@ -29,21 +29,21 @@
* dialogs so that the same information can be provided in a dialog
* and then minimized to a banner notification if needed.
*
* @namespace platform/commonUI/dialog
* @namespace platform/commonUI/notification
*/
define(
["./MessageSeverity"],
function (MessageSeverity) {
[],
function () {
"use strict";
/**
* A representation of a user action. Actions are provided to
* A representation of a user action. Options are provided to
* dialogs and notifications and are shown as buttons.
*
* @typedef {object} NotificationAction
* @typedef {object} NotificationOption
* @property {string} label the label to appear on the button for
* this action
* @property {function} action a callback function to be invoked
* @property {function} callback a callback function to be invoked
* when the button is clicked
*/
@ -54,10 +54,10 @@ define(
* dialogs so that the same information can be provided in a dialog
* and then minimized to a banner notification if needed.
*
* @typedef {object} Notification
* @typedef {object} NotificationModel
* @property {string} title The title of the message
* @property {MessageSeverity} severity The importance of the
* message (eg. error, info)
* @property {string} severity The importance of the message (one of
* 'info', 'alert', or 'error' where info < alert <error)
* @property {number} progress The completion status of a task
* represented numerically
* @property {boolean} unknownProgress a boolean indicating that the
@ -67,31 +67,55 @@ define(
* be automatically minimized or dismissed (depending on severity).
* Additionally, if the provided value is a number, it will be used
* as the delay period before being dismissed.
* @property {NotificationAction} primaryAction the default user
* @property {NotificationOption} primaryOption the default user
* response to
* this message. Will be represented as a button with the provided
* label and action. May be used by banner notifications to display
* only the most important option to users.
* @property {NotificationAction[]} actions any additional
* @property {NotificationOption[]} options any additional
* actions the user can take. Will be represented as additional buttons
* that may or may not be available from a banner.
*/
/**
* A wrapper object that is returned as a handle to a newly created
* notification. Wraps the notifications model and decorates with
* functions to dismiss or minimize the notification.
*
* @typedef {object} Notification
* @property {function} dismiss This method is added to the object
* returned by {@link NotificationService#notify} and can be used to
* dismiss this notification. Dismissing a notification will remove
* it completely and it will not appear in the notification indicator
* @property {function} minimize This method is added to the object
* returned by {@link NotificationService#notify} and can be used to
* minimize this notification. Minimizing a notification will send
* it to the notification indicator
* @property {function} dismissOrMinimize This method is added to the
* object returned by {@link NotificationService#notify}. It will
* hide the notification by either dismissing or minimizing it,
* depending on severity. Typically this is the method that should
* be used for dismissing a notification. If more control is
* required, then the minimize or dismiss functions can be called
* individually.
*/
/**
* The notification service is responsible for informing the user of
* events via the use of banner notifications.
* @memberof platform/commonUI/notification
* @constructor
* @param $timeout the Angular $timeout service
* @param DEFAULT_AUTO_DISMISS The period of time that an
* auto-dismissed message will be displayed for.
* @param MINIMIZE_TIMEOUT When notifications are minimized, a brief
* animation is shown. This animation requires some time to execute,
* so a timeout is required before the notification is hidden
* @constructor
*/
function NotificationService($timeout, DEFAULT_AUTO_DISMISS, MINIMIZE_TIMEOUT) {
this.notifications = [];
this.$timeout = $timeout;
this.highest ={ severity: MessageSeverity.INFO };
this.highest ={ severity: "info" };
this.DEFAULT_AUTO_DISMISS = DEFAULT_AUTO_DISMISS;
this.MINIMIZE_TIMEOUT = MINIMIZE_TIMEOUT;
@ -102,6 +126,83 @@ define(
this.active = {};
}
/*
* Minimize a notification. The notification will still be available
* from the notification list. Typically notifications with a
* severity of 'info' should not be minimized, but rather
* dismissed. If you're not sure which is appropriate,
* use {@link Notification#dismissOrMinimize}
*/
function minimize (service, notification) {
//Check this is a known notification
var index = service.notifications.indexOf(notification);
if (service.active.timeout){
/*
Method can be called manually (clicking dismiss) or
automatically from an auto-timeout. this.active.timeout
acts as a semaphore to prevent race conditions. Cancel any
timeout in progress (for the case where a manual dismiss
has shortcut an active auto-dismiss), and clear the
semaphore.
*/
service.$timeout.cancel(service.active.timeout);
delete service.active.timeout;
}
if (index >= 0) {
notification.model.minimized=true;
//Add a brief timeout before showing the next notification
// in order to allow the minimize animation to run through.
service.$timeout(function() {
service.setActiveNotification(service.selectNextNotification());
}, service.MINIMIZE_TIMEOUT);
}
}
/*
* Completely removes a notification. This will dismiss it from the
* message banner and remove it from the list of notifications.
* Typically only notifications with a severity of info should be
* dismissed. If you're not sure whether to dismiss or minimize a
* notification, use {@link Notification#dismissOrMinimize}.
* dismiss
*/
function dismiss (service, notification) {
//Check this is a known notification
var index = service.notifications.indexOf(notification);
if (service.active.timeout){
/* Method can be called manually (clicking dismiss) or
* automatically from an auto-timeout. this.active.timeout
* acts as a semaphore to prevent race conditions. Cancel any
* timeout in progress (for the case where a manual dismiss
* has shortcut an active auto-dismiss), and clear the
* semaphore.
*/
service.$timeout.cancel(service.active.timeout);
delete service.active.timeout;
}
if (index >= 0) {
service.notifications.splice(index, 1);
}
service.setActiveNotification(service.selectNextNotification());
}
/*
* Depending on the severity of the notification will selectively
* dismiss or minimize where appropriate.
*/
function dismissOrMinimize (notification){
//For now minimize everything, and have discussion around which
//kind of messages should or should not be in the minimized
//notifications list
notification.minimize();
}
/**
* Returns the notification that is currently visible in the banner area
* @returns {Notification}
@ -114,12 +215,15 @@ define(
* A convenience method for info notifications. Notifications
* created via this method will be auto-dismissed after a default
* wait period
* @param {Notification} notification The notification to display
* @param {NotificationModel} notificationModel Options describing the
* notification to display
* @returns {Notification} the provided notification decorated with
* functions to dismiss or minimize
*/
NotificationService.prototype.info = function (notification) {
notification.autoDismiss = notification.autoDismiss || true;
notification.severity = MessageSeverity.INFO;
this.notify(notification);
NotificationService.prototype.info = function (notificationModel) {
notificationModel.autoDismiss = notificationModel.autoDismiss || true;
notificationModel.severity = "info";
return this.notify(notificationModel);
};
/**
@ -127,20 +231,45 @@ define(
* already active, then it will be dismissed or minimized automatically,
* and the provided notification displayed in its place.
*
* @param {Notification} notification The notification to display
* @param {NotificationModel} notificationModel The notification to
* display
* @returns {Notification} the provided notification decorated with
* functions to {@link Notification#dismiss} or {@link Notification#minimize}
*/
NotificationService.prototype.notify = function (notification) {
var self = this;
NotificationService.prototype.notify = function (notificationModel) {
var self = this,
notification,
ordinality = {
"info": 1,
"alert": 2,
"error": 3
},
activeNotification = self.active.notification;
if (notification.autoDismiss === true){
notification.autoDismiss = this.DEFAULT_AUTO_DISMISS;
notification = {
model: notificationModel,
minimize: function() {
minimize(self, notification);
},
dismiss: function(){
dismiss(self, notification);
},
dismissOrMinimize: function(){
dismissOrMinimize(notification);
}
};
notificationModel.severity = notificationModel.severity || "info";
if (notificationModel.autoDismiss === true){
notificationModel.autoDismiss = this.DEFAULT_AUTO_DISMISS;
}
if (notification.severity > this.highest.severity){
this.highest.severity = notification.severity;
if (ordinality[notificationModel.severity.toLowerCase()] > ordinality[this.highest.severity.toLowerCase()]){
this.highest.severity = notificationModel.severity;
}
this.notifications.push(notification);
/*
Check if there is already an active (ie. visible) notification
*/
@ -159,10 +288,12 @@ define(
serviced as soon as possible.
*/
this.active.timeout = this.$timeout(function () {
self.dismissOrMinimize(self.active.notification);
activeNotification.dismissOrMinimize();
}, this.DEFAULT_AUTO_DISMISS);
}
return notification;
};
/**
@ -180,12 +311,12 @@ define(
notifications queued for display, setup a timeout to
dismiss the dialog.
*/
if (notification && (notification.autoDismiss
if (notification && (notification.model.autoDismiss
|| this.selectNextNotification())) {
timeout = notification.autoDismiss || this.DEFAULT_AUTO_DISMISS;
timeout = notification.model.autoDismiss || this.DEFAULT_AUTO_DISMISS;
this.active.timeout = this.$timeout(function () {
self.dismissOrMinimize(notification);
notification.dismissOrMinimize();
}, timeout);
} else {
delete this.active.timeout;
@ -208,7 +339,7 @@ define(
for (; i< this.notifications.length; i++) {
notification = this.notifications[i];
if (!notification.minimized
if (!notification.model.minimized
&& notification!== this.active.notification) {
return notification;
@ -216,69 +347,6 @@ define(
}
};
/**
* Minimize a notification. The notification will still be available
* from the notification list. Typically notifications with a
* severity of 'info' should not be minimized, but rather
* dismissed. If you're not sure which is appropriate,
* use {@link NotificationService#dismissOrMinimize}
* @see dismiss
* @see dismissOrMinimize
* @param notification
*/
NotificationService.prototype.minimize = function (notification) {
//Check this is a known notification
var index = this.notifications.indexOf(notification),
self = this;
if (index >= 0) {
notification.minimized=true;
//Add a brief timeout before showing the next notification
// in order to allow the minimize animation to run through.
this.$timeout(function() {
self.setActiveNotification(self.selectNextNotification());
}, this.MINIMIZE_TIMEOUT);
}
};
/**
* Completely removes a notification. This will dismiss it from the
* message banner and remove it from the list of notifications.
* Typically only notifications with a severity of info should be
* dismissed. If you're not sure whether to dismiss or minimize a
* notification, use {@link NotificationService#dismissOrMinimize}.
* dismiss
* @see dismissOrMinimize
* @param notification The notification to dismiss
*/
NotificationService.prototype.dismiss = function (notification) {
//Check this is a known notification
var index = this.notifications.indexOf(notification);
if (index >= 0) {
this.notifications.splice(index, 1);
}
this.setActiveNotification(this.selectNextNotification());
};
/**
* Depending on the severity of the notification will selectively
* dismiss or minimize where appropriate.
* @see dismiss
* @see minimize
* @param notification
*/
NotificationService.prototype.dismissOrMinimize = function (notification){
//For now minimize everything, and have discussion around which
//kind of messages should or should not be in the minimized
//notifications list
/*if (notification.severity > MessageSeverity.INFO){
this.minimize(notification);
} else {
this.dismiss(notification);
}*/
this.minimize(notification);
};
return NotificationService;
}
);

View File

@ -0,0 +1,78 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define,describe,it,expect,beforeEach,waitsFor,jasmine */
define(
['../src/NotificationIndicatorController'],
function (NotificationIndicatorController) {
"use strict";
describe("The notification indicator controller ", function () {
var mockNotificationService,
mockScope,
mockDialogService;
beforeEach(function(){
mockNotificationService = jasmine.createSpy("notificationService");
mockScope = jasmine.createSpy("$scope");
mockDialogService = jasmine.createSpyObj(
"dialogService",
["getDialogResponse","dismiss"]
);
});
it("exposes the highest notification severity to the template", function() {
mockNotificationService.highest = {
severity: "error"
};
var controller = new NotificationIndicatorController(mockScope, mockNotificationService, mockDialogService);
expect(mockScope.highest).toBeTruthy();
expect(mockScope.highest.severity).toBe("error");
});
it("invokes the dialog service to show list of messages", function() {
var controller = new NotificationIndicatorController(mockScope, mockNotificationService, mockDialogService);
expect(mockScope.showNotificationsList).toBeDefined();
mockScope.showNotificationsList();
expect(mockDialogService.getDialogResponse).toHaveBeenCalled();
expect(mockDialogService.getDialogResponse.mostRecentCall.args[0]).toBe('overlay-message-list');
expect(mockDialogService.getDialogResponse.mostRecentCall.args[1].dialog).toBeDefined();
expect(mockDialogService.getDialogResponse.mostRecentCall.args[1].cancel).toBeDefined();
//Invoke the cancel callback
mockDialogService.getDialogResponse.mostRecentCall.args[1].cancel();
expect(mockDialogService.dismiss).toHaveBeenCalled();
});
it("provides a means of dismissing the message list", function() {
var controller = new NotificationIndicatorController(mockScope, mockNotificationService, mockDialogService);
expect(mockScope.showNotificationsList).toBeDefined();
mockScope.showNotificationsList();
expect(mockDialogService.getDialogResponse).toHaveBeenCalled();
expect(mockDialogService.getDialogResponse.mostRecentCall.args[1].cancel).toBeDefined();
//Invoke the cancel callback
mockDialogService.getDialogResponse.mostRecentCall.args[1].cancel();
expect(mockDialogService.dismiss).toHaveBeenCalled();
});
});
}
);

View File

@ -22,8 +22,8 @@
/*global define,describe,it,expect,beforeEach,waitsFor,jasmine */
define(
['../src/NotificationService','../src/MessageSeverity'],
function (NotificationService, MessageSeverity) {
['../src/NotificationService'],
function (NotificationService) {
"use strict";
describe("The notification service ", function () {
@ -34,46 +34,6 @@ define(
successModel,
errorModel;
/**
* 1) Calling .notify results in a new notification being created
* with the provided model and set to the active notification. DONE
*
* 2) Calling .notify with autoDismiss results in a SUCCESS notification
* becoming dismissed after timeout has elapsed DONE
*
* 3) Calling .notify with autoDismiss results in an ERROR notification
* being MINIMIZED after a timeout has elapsed DONE
*
* 4) Calling .notify with an active info notification results in that
* notification being auto-dismissed, and the new notification becoming
* active. DONE
*
* 5) Calling .notify with an active error notification results in that
* notification being auto-minimized and the new notification becoming
* active. DONE
*
* 6) Calling .notify with an active error notification, AND a
* queued error notification results in the active notification
* being auto-dismissed, the next message in the queue becoming
* active, then auto-dismissed, and then the provided notification
* becoming active.
*/
/**
var model = {
title: string,
progress: number,
severity: MessageSeverity,
unknownProgress: boolean,
minimized: boolean,
autoDismiss: boolean | number,
actions: {
label: string,
action: function
}
}
*/
beforeEach(function(){
mockTimeout = jasmine.createSpy("$timeout");
mockAutoDismiss = mockMinimizeTimeout = 1000;
@ -81,11 +41,11 @@ define(
mockTimeout, mockAutoDismiss, mockMinimizeTimeout);
successModel = {
title: "Mock Success Notification",
severity: MessageSeverity.INFO
severity: "info"
};
errorModel = {
title: "Mock Error Notification",
severity: MessageSeverity.ERROR
severity: "error"
};
});
@ -94,7 +54,7 @@ define(
var activeNotification;
notificationService.notify(successModel);
activeNotification = notificationService.getActiveNotification();
expect(activeNotification).toBe(successModel);
expect(activeNotification.model).toBe(successModel);
});
it("gets a new success notification with" +
@ -103,7 +63,7 @@ define(
successModel.autoDismiss = 1000;
notificationService.notify(successModel);
activeNotification = notificationService.getActiveNotification();
expect(activeNotification).toBe(successModel);
expect(activeNotification.model).toBe(successModel);
mockTimeout.mostRecentCall.args[0]();
expect(mockTimeout.calls.length).toBe(2);
mockTimeout.mostRecentCall.args[0]();
@ -117,7 +77,7 @@ define(
successModel.autoDismiss = true;
notificationService.notify(successModel);
activeNotification = notificationService.getActiveNotification();
expect(activeNotification).toBe(successModel);
expect(activeNotification.model).toBe(successModel);
mockTimeout.mostRecentCall.args[0]();
expect(mockTimeout.calls.length).toBe(2);
mockTimeout.mostRecentCall.args[0]();
@ -125,6 +85,37 @@ define(
expect(activeNotification).toBeUndefined();
});
it("allows minimization of notifications", function() {
var notification,
activeNotification;
successModel.autoDismiss = false;
notification = notificationService.notify(successModel);
activeNotification = notificationService.getActiveNotification();
expect(activeNotification.model).toBe(successModel);
notification.minimize();
mockTimeout.mostRecentCall.args[0]();
activeNotification = notificationService.getActiveNotification();
expect(activeNotification).toBeUndefined();
expect(notificationService.notifications.length).toBe(1);
});
it("allows dismissal of notifications", function() {
var notification,
activeNotification;
successModel.autoDismiss = false;
notification = notificationService.notify(successModel);
activeNotification = notificationService.getActiveNotification();
expect(activeNotification.model).toBe(successModel);
notification.dismiss();
activeNotification = notificationService.getActiveNotification();
expect(activeNotification).toBeUndefined();
expect(notificationService.notifications.length).toBe(0);
});
describe(" gets called with multiple notifications", function(){
it("auto-dismisses the previously active notification, making" +
" the new notification active", function() {
@ -134,7 +125,7 @@ define(
activeNotification =
notificationService.getActiveNotification();
//Initially expect the active notification to be info
expect(activeNotification).toBe(successModel);
expect(activeNotification.model).toBe(successModel);
//Then notify of an error
notificationService.notify(errorModel);
//But it should be auto-dismissed and replaced with the
@ -145,25 +136,8 @@ define(
// second is to allow minimization animation to take place.
mockTimeout.mostRecentCall.args[0]();
activeNotification = notificationService.getActiveNotification();
expect(activeNotification).toBe(errorModel);
expect(activeNotification.model).toBe(errorModel);
});
/* Test is temporarily invalid as info messages are being
minimized
it("auto-dismisses an active success notification, removing" +
" it completely", function() {
//First pre-load with a info message
notificationService.notify(successModel);
//Then notify of an error
notificationService.notify(errorModel);
expect(notificationService.notifications.length).toEqual(2);
mockTimeout.mostRecentCall.args[0]();
//Two timeouts, one is to force minimization after
// displaying the message for a minimum period, the
// second is to allow minimization animation to take place.
mockTimeout.mostRecentCall.args[0]();
//Previous info message should be completely dismissed
expect(notificationService.notifications.length).toEqual(1);
});*/
it("auto-minimizes an active error notification", function() {
var activeNotification;
//First pre-load with an error message
@ -182,7 +156,7 @@ define(
expect(notificationService.notifications.length).toEqual(2);
activeNotification =
notificationService.getActiveNotification();
expect(activeNotification).toBe(successModel);
expect(activeNotification.model).toBe(successModel);
expect(errorModel.minimized).toEqual(true);
});
it("auto-minimizes errors when a number of them arrive in" +
@ -190,11 +164,11 @@ define(
var activeNotification,
error2 = {
title: "Second Mock Error Notification",
severity: MessageSeverity.ERROR
severity: "error"
},
error3 = {
title: "Third Mock Error Notification",
severity: MessageSeverity.ERROR
severity: "error"
};
//First pre-load with a info message
@ -214,7 +188,7 @@ define(
expect(notificationService.notifications.length).toEqual(3);
activeNotification =
notificationService.getActiveNotification();
expect(activeNotification).toBe(error2);
expect(activeNotification.model).toBe(error2);
expect(errorModel.minimized).toEqual(true);
//Mock the second auto-minimize
@ -225,10 +199,11 @@ define(
mockTimeout.mostRecentCall.args[0]();
activeNotification =
notificationService.getActiveNotification();
expect(activeNotification).toBe(error3);
expect(activeNotification.model).toBe(error3);
expect(error2.minimized).toEqual(true);
});
});
});
});
}
);

View File

@ -1,3 +1,4 @@
[
"NotificationService"
]
"NotificationService",
"NotificationIndicatorController"
]

View File

@ -44,14 +44,12 @@ define(
function progress(phase, totalObjects, processed){
if (phase.toLowerCase() === 'preparing'){
console.log('preparing');
dialogService.showBlockingMessage({
title: "Preparing to copy objects",
unknownProgress: true,
severity: "info",
});
} else if (phase.toLowerCase() === "copying") {
console.log('copying');
dialogService.dismiss();
if (!notification) {
notification = notificationService.notify(notificationModel);

View File

@ -113,14 +113,14 @@ define(
function newPerform (domainObject, parent, progress) {
var $q = this.$q,
processed = 0,
self = this;
if (this.validate(domainObject, parent)) {
progress("preparing");
return this.buildCopyGraph(domainObject, parent)
.then(function(clones){
return $q.all(clones.map(function(clone, index){
progress("copying", clones.length, index);
return self.persistenceService.createObject(clone.persistence.getSpace(), clone.model.id, clone.model);
return self.persistenceService.createObject(clone.persistence.getSpace(), clone.model.id, clone.model).then(function(){progress("copying", clones.length, processed++);});
})).then(function(){ return clones});
})
.then(function(clones) {