From 24e870a1267e4a13dc8c96397b54acf8925a5967 Mon Sep 17 00:00:00 2001 From: Pete Richards Date: Fri, 24 Jun 2016 18:33:24 -0700 Subject: [PATCH] [Save] Show blocking dialog Show a blocking dialog while the save action is being performed. Prevents users from pressing save a second time or performing further actions while a save is in progress. Fixes https://github.jpl.nasa.gov/MissionControl/vista/issues/362 --- platform/commonUI/edit/bundle.js | 4 ++- .../commonUI/edit/src/actions/SaveAction.js | 22 +++++++++--- .../commonUI/edit/src/actions/SaveAsAction.js | 34 ++++++++++++++++--- .../edit/src/actions/SaveInProgressDialog.js | 20 +++++++++++ .../edit/test/actions/SaveActionSpec.js | 24 +++++++++++-- .../edit/test/actions/SaveAsActionSpec.js | 17 +++++++++- 6 files changed, 107 insertions(+), 14 deletions(-) create mode 100644 platform/commonUI/edit/src/actions/SaveInProgressDialog.js diff --git a/platform/commonUI/edit/bundle.js b/platform/commonUI/edit/bundle.js index 3a9deb649e..aac5de49e5 100644 --- a/platform/commonUI/edit/bundle.js +++ b/platform/commonUI/edit/bundle.js @@ -206,7 +206,9 @@ define([ "implementation": SaveAction, "name": "Save", "description": "Save changes made to these objects.", - "depends": [], + "depends": [ + "dialogService" + ], "priority": "mandatory" }, { diff --git a/platform/commonUI/edit/src/actions/SaveAction.js b/platform/commonUI/edit/src/actions/SaveAction.js index 8a45913288..58e2fcdcfd 100644 --- a/platform/commonUI/edit/src/actions/SaveAction.js +++ b/platform/commonUI/edit/src/actions/SaveAction.js @@ -21,8 +21,8 @@ *****************************************************************************/ define( - [], - function () { + ['./SaveInProgressDialog'], + function (SaveInProgressDialog) { /** * The "Save" action; the action triggered by clicking Save from @@ -33,9 +33,11 @@ define( * @memberof platform/commonUI/edit */ function SaveAction( + dialogService, context ) { this.domainObject = (context || {}).domainObject; + this.dialogService = dialogService; } /** @@ -46,7 +48,8 @@ define( * @memberof platform/commonUI/edit.SaveAction# */ SaveAction.prototype.perform = function () { - var domainObject = this.domainObject; + var domainObject = this.domainObject, + dialog = new SaveInProgressDialog(this.dialogService); function resolveWith(object) { return function () { @@ -72,8 +75,17 @@ define( return object; } - //return doSave().then(returnToBrowse); - return doSave().then(returnToBrowse); + function hideBlockingDialog(object) { + dialog.hide(); + return object; + } + + dialog.show(); + + return doSave() + .then(hideBlockingDialog) + .then(returnToBrowse) + .catch(hideBlockingDialog); }; /** diff --git a/platform/commonUI/edit/src/actions/SaveAsAction.js b/platform/commonUI/edit/src/actions/SaveAsAction.js index cee67ebbfe..9c34c06c9a 100644 --- a/platform/commonUI/edit/src/actions/SaveAsAction.js +++ b/platform/commonUI/edit/src/actions/SaveAsAction.js @@ -21,9 +21,14 @@ *****************************************************************************/ -define( - ['../creation/CreateWizard'], - function (CreateWizard) { +define([ + '../creation/CreateWizard', + './SaveInProgressDialog' +], + function ( + CreateWizard, + SaveInProgressDialog + ) { /** * The "Save" action; the action triggered by clicking Save from @@ -105,7 +110,8 @@ define( SaveAsAction.prototype.save = function () { var self = this, domainObject = this.domainObject, - copyService = this.copyService; + copyService = this.copyService, + dialog = new SaveInProgressDialog(this.dialogService); function doWizardSave(parent) { var wizard = self.createWizard(parent); @@ -116,6 +122,16 @@ define( ).then(wizard.populateObjectFromInput.bind(wizard)); } + function showBlockingDialog(object) { + dialog.show(); + return object; + } + + function hideBlockingDialog(object) { + dialog.hide(); + return object; + } + function fetchObject(objectId) { return self.getObjectService().getObjects([objectId]).then(function (objects) { return objects[objectId]; @@ -140,14 +156,22 @@ define( .then(resolveWith(clonedObject)); } + function onFailure() { + hideBlockingDialog(); + return false; + } + return getParent(domainObject) .then(doWizardSave) + .then(showBlockingDialog) .then(getParent) .then(cloneIntoParent) .then(commitEditingAfterClone) - .catch(resolveWith(false)); + .then(hideBlockingDialog) + .catch(onFailure); }; + /** * Check if this action is applicable in a given context. * This will ensure that a domain object is present in the context, diff --git a/platform/commonUI/edit/src/actions/SaveInProgressDialog.js b/platform/commonUI/edit/src/actions/SaveInProgressDialog.js new file mode 100644 index 0000000000..c80989b8e0 --- /dev/null +++ b/platform/commonUI/edit/src/actions/SaveInProgressDialog.js @@ -0,0 +1,20 @@ +define([], function () { + function SaveInProgressDialog(dialogService) { + this.dialogService = dialogService; + } + + SaveInProgressDialog.prototype.show = function () { + this.dialogService.showBlockingMessage({ + title: "Saving...", + hint: "Do not navigate away from this page or close this browser tab while this message is displayed.", + unknownProgress: true, + severity: "info" + }); + }; + + SaveInProgressDialog.prototype.hide = function () { + this.dialogService.dismiss(); + }; + + return SaveInProgressDialog; +}); diff --git a/platform/commonUI/edit/test/actions/SaveActionSpec.js b/platform/commonUI/edit/test/actions/SaveActionSpec.js index 2d15b46679..b81da3d7de 100644 --- a/platform/commonUI/edit/test/actions/SaveActionSpec.js +++ b/platform/commonUI/edit/test/actions/SaveActionSpec.js @@ -28,6 +28,7 @@ define( var mockDomainObject, mockEditorCapability, actionContext, + dialogService, mockActionCapability, capabilities = {}, action; @@ -36,6 +37,9 @@ define( return { then: function (callback) { return mockPromise(callback(value)); + }, + catch: function (callback) { + return mockPromise(callback(value)); } }; } @@ -64,6 +68,10 @@ define( actionContext = { domainObject: mockDomainObject }; + dialogService = jasmine.createSpyObj( + "dialogService", + ["showBlockingMessage", "dismiss"] + ); mockDomainObject.hasCapability.andReturn(true); mockDomainObject.getCapability.andCallFake(function (capability) { @@ -73,8 +81,7 @@ define( mockEditorCapability.save.andReturn(mockPromise(true)); mockEditorCapability.isEditContextRoot.andReturn(true); - action = new SaveAction(actionContext); - + action = new SaveAction(dialogService, actionContext); }); it("only applies to domain object with an editor capability", function () { @@ -104,6 +111,19 @@ define( expect(mockActionCapability.perform).toHaveBeenCalledWith("navigate"); }); + it("shows a dialog while saving", function () { + mockEditorCapability.save.andReturn(new Promise(function () {})); + action.perform(); + expect(dialogService.showBlockingMessage).toHaveBeenCalled(); + expect(dialogService.dismiss).not.toHaveBeenCalled(); + }); + + it("hides a dialog when saving is complete", function () { + action.perform(); + expect(dialogService.showBlockingMessage).toHaveBeenCalled(); + expect(dialogService.dismiss).toHaveBeenCalled(); + }); + }); } ); diff --git a/platform/commonUI/edit/test/actions/SaveAsActionSpec.js b/platform/commonUI/edit/test/actions/SaveAsActionSpec.js index 8ea603b623..37e35aa5af 100644 --- a/platform/commonUI/edit/test/actions/SaveAsActionSpec.js +++ b/platform/commonUI/edit/test/actions/SaveAsActionSpec.js @@ -100,7 +100,9 @@ define( mockDialogService = jasmine.createSpyObj( "dialogService", [ - "getUserInput" + "getUserInput", + "showBlockingMessage", + "dismiss" ] ); mockDialogService.getUserInput.andReturn(mockPromise(undefined)); @@ -169,6 +171,19 @@ define( expect(mockDialogService.getUserInput).toHaveBeenCalled(); }); + it("shows a blocking dialog while waiting for save", function () { + mockEditorCapability.save.andReturn(new Promise(function () {})); + action.perform(); + expect(mockDialogService.showBlockingMessage).toHaveBeenCalled(); + expect(mockDialogService.dismiss).not.toHaveBeenCalled(); + }); + + it("hides the blocking dialog after saving", function () { + action.perform(); + expect(mockDialogService.showBlockingMessage).toHaveBeenCalled(); + expect(mockDialogService.dismiss).toHaveBeenCalled(); + }); + }); } );