[Remove] Add confirmation dialog (#1870)

* [Remove] Added confirmation dialog before the remove action is performed

Addresses #563
This commit is contained in:
tobiasbrown 2018-07-28 06:54:41 +10:00 committed by Andrew Henry
parent 15a75ac134
commit a1d206bfc3
4 changed files with 341 additions and 209 deletions

View File

@ -188,6 +188,7 @@ define([
"name": "Remove",
"description": "Remove this object from its containing object.",
"depends": [
"dialogService",
"navigationService"
]
},

View File

@ -23,9 +23,11 @@
/**
* Module defining RemoveAction. Created by vwoeltje on 11/17/14.
*/
define(
[],
function () {
define([
'./RemoveDialog'
], function (
RemoveDialog
) {
/**
* Construct an action which will remove the provided object manifestation.
@ -33,25 +35,27 @@ define(
* is looked up via the "context" capability (so this will be the
* immediate ancestor by which this specific object was reached.)
*
* @param {DomainObject} object the object to be removed
* @param {DialogService} dialogService a service which will show the dialog
* @param {NavigationService} navigationService a service that maintains the current navigation state
* @param {ActionContext} context the context in which this action is performed
* @memberof platform/commonUI/edit
* @constructor
* @implements {Action}
*/
function RemoveAction(navigationService, context) {
function RemoveAction(dialogService, navigationService, context) {
this.domainObject = (context || {}).domainObject;
this.dialogService = dialogService;
this.navigationService = navigationService;
}
/**
* Perform this action.
* @return {Promise} a promise which will be
* fulfilled when the action has completed.
*/
RemoveAction.prototype.perform = function () {
var navigationService = this.navigationService,
domainObject = this.domainObject;
var dialog,
dialogService = this.dialogService,
domainObject = this.domainObject,
navigationService = this.navigationService;
/*
* Check whether an object ID matches the ID of the object being
* removed (used to filter a parent's composition to handle the
@ -111,7 +115,12 @@ define(
return parent.useCapability('mutation', doMutate);
}
return removeFromContext(domainObject);
/*
* Pass in the function to remove the domain object so it can be
* associated with an 'OK' button press
*/
dialog = new RemoveDialog(dialogService, domainObject, removeFromContext);
dialog.show();
};
// Object needs to have a parent for Remove to be applicable
@ -129,5 +138,4 @@ define(
};
return RemoveAction;
}
);
});

View File

@ -0,0 +1,77 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2017, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT 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 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.
*****************************************************************************/
define([], function () {
/**
* @callback removeCallback
* @param {DomainObject} domainObject the domain object to be removed
*/
/**
* Construct a new Remove dialog.
*
* @param {DialogService} dialogService the service that shows the dialog
* @param {DomainObject} domainObject the domain object to be removed
* @param {removeCallback} removeCallback callback that handles removal of the domain object
* @memberof platform/commonUI/edit
* @constructor
*/
function RemoveDialog(dialogService, domainObject, removeCallback) {
this.dialogService = dialogService;
this.domainObject = domainObject;
this.removeCallback = removeCallback;
}
/**
* Display a dialog to confirm the removal of a domain object.
*/
RemoveDialog.prototype.show = function () {
var dialog,
domainObject = this.domainObject,
removeCallback = this.removeCallback,
model = {
title: 'Remove ' + domainObject.getModel().name,
actionText: 'Warning! This action will permanently remove this object. Are you sure you want to continue?',
severity: 'alert',
primaryOption: {
label: 'OK',
callback: function () {
removeCallback(domainObject);
dialog.dismiss();
}
},
options: [
{
label: 'Cancel',
callback: function () {
dialog.dismiss();
}
}
]
};
dialog = this.dialogService.showBlockingMessage(model);
};
return RemoveDialog;
});

View File

@ -25,50 +25,37 @@ define(
function (RemoveAction) {
describe("The Remove action", function () {
var mockQ,
mockNavigationService,
mockDomainObject,
mockParent,
mockChildObject,
mockGrandchildObject,
mockRootObject,
mockContext,
mockChildContext,
mockGrandchildContext,
mockRootContext,
mockMutation,
mockType,
var action,
actionContext,
model,
capabilities,
action;
function mockPromise(value) {
return {
then: function (callback) {
return mockPromise(callback(value));
}
};
}
mockContext,
mockDialogService,
mockDomainObject,
mockMutation,
mockNavigationService,
mockParent,
mockType,
model;
beforeEach(function () {
mockDomainObject = jasmine.createSpyObj(
"domainObject",
["getId", "getCapability"]
["getId", "getCapability", "getModel"]
);
mockChildObject = jasmine.createSpyObj(
"domainObject",
["getId", "getCapability"]
);
mockGrandchildObject = jasmine.createSpyObj(
"domainObject",
["getId", "getCapability"]
);
mockRootObject = jasmine.createSpyObj(
"domainObject",
["getId", "getCapability"]
);
mockQ = { when: mockPromise };
mockMutation = jasmine.createSpyObj("mutation", ["invoke"]);
mockType = jasmine.createSpyObj("type", ["hasFeature"]);
mockType.hasFeature.and.returnValue(true);
capabilities = {
mutation: mockMutation,
type: mockType
};
model = {
composition: ["a", "test", "b"]
};
mockParent = {
getModel: function () {
return model;
@ -80,12 +67,12 @@ define(
return capabilities[k].invoke(v);
}
};
mockContext = jasmine.createSpyObj("context", ["getParent"]);
mockChildContext = jasmine.createSpyObj("context", ["getParent"]);
mockGrandchildContext = jasmine.createSpyObj("context", ["getParent"]);
mockRootContext = jasmine.createSpyObj("context", ["getParent"]);
mockMutation = jasmine.createSpyObj("mutation", ["invoke"]);
mockType = jasmine.createSpyObj("type", ["hasFeature"]);
mockDialogService = jasmine.createSpyObj(
"dialogService",
["showBlockingMessage"]
);
mockNavigationService = jasmine.createSpyObj(
"navigationService",
[
@ -97,23 +84,19 @@ define(
);
mockNavigationService.getNavigation.and.returnValue(mockDomainObject);
mockContext = jasmine.createSpyObj("context", ["getParent"]);
mockContext.getParent.and.returnValue(mockParent);
mockDomainObject.getId.and.returnValue("test");
mockDomainObject.getCapability.and.returnValue(mockContext);
mockDomainObject.getModel.and.returnValue({name: 'test object'});
mockContext.getParent.and.returnValue(mockParent);
mockType.hasFeature.and.returnValue(true);
capabilities = {
mutation: mockMutation,
type: mockType
};
model = {
composition: ["a", "test", "b"]
};
actionContext = { domainObject: mockDomainObject };
action = new RemoveAction(mockNavigationService, actionContext);
action = new RemoveAction(mockDialogService, mockNavigationService, actionContext);
});
it("only applies to objects with parents", function () {
@ -127,15 +110,73 @@ define(
expect(mockType.hasFeature).toHaveBeenCalledWith('creation');
});
it("shows a blocking message dialog", function () {
mockParent = jasmine.createSpyObj(
"parent",
["getModel", "getCapability", "useCapability"]
);
action.perform();
expect(mockDialogService.showBlockingMessage).toHaveBeenCalled();
// Also check that no mutation happens at this point
expect(mockParent.useCapability).not.toHaveBeenCalledWith("mutation", jasmine.any(Function));
});
describe("after the remove callback is triggered", function () {
var mockChildContext,
mockChildObject,
mockDialogHandle,
mockGrandchildContext,
mockGrandchildObject,
mockRootContext,
mockRootObject;
beforeEach(function () {
mockChildObject = jasmine.createSpyObj(
"domainObject",
["getId", "getCapability"]
);
mockDialogHandle = jasmine.createSpyObj(
"dialogHandle",
["dismiss"]
);
mockGrandchildObject = jasmine.createSpyObj(
"domainObject",
["getId", "getCapability"]
);
mockRootObject = jasmine.createSpyObj(
"domainObject",
["getId", "getCapability"]
);
mockChildContext = jasmine.createSpyObj("context", ["getParent"]);
mockGrandchildContext = jasmine.createSpyObj("context", ["getParent"]);
mockRootContext = jasmine.createSpyObj("context", ["getParent"]);
mockDialogService.showBlockingMessage.and.returnValue(mockDialogHandle);
});
it("mutates the parent when performed", function () {
action.perform();
mockDialogService.showBlockingMessage.calls.mostRecent().args[0]
.primaryOption.callback();
expect(mockMutation.invoke)
.toHaveBeenCalledWith(jasmine.any(Function));
});
it("changes composition from its mutation function", function () {
var mutator, result;
action.perform();
mockDialogService.showBlockingMessage.calls.mostRecent().args[0]
.primaryOption.callback();
mutator = mockMutation.invoke.calls.mostRecent().args[0];
result = mutator(model);
@ -171,6 +212,8 @@ define(
mockType.hasFeature.and.returnValue(true);
action.perform();
mockDialogService.showBlockingMessage.calls.mostRecent().args[0]
.primaryOption.callback();
// Expects navigation to parent of domainObject (removed object)
expect(mockNavigationService.setNavigation).toHaveBeenCalledWith(mockParent);
@ -199,11 +242,14 @@ define(
mockType.hasFeature.and.returnValue(true);
action.perform();
mockDialogService.showBlockingMessage.calls.mostRecent().args[0]
.primaryOption.callback();
// Expects no navigation to occur
expect(mockNavigationService.setNavigation).not.toHaveBeenCalled();
});
});
});
}
);