mirror of
https://github.com/nasa/openmct.git
synced 2024-12-21 22:17:49 +00:00
[Edit mode] Simplify SaveAction
This commit is contained in:
parent
c591ade479
commit
26e368f52d
@ -103,7 +103,7 @@ define(
|
||||
if (countEditableViews(editableObject) > 0 && editableObject.hasCapability('composition')) {
|
||||
this.navigationService.setNavigation(editableObject);
|
||||
} else {
|
||||
return editableObject.getCapability('action').perform('save');
|
||||
return editableObject.getCapability('action').perform('save-as');
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -32,6 +32,7 @@ define([
|
||||
"./src/actions/PropertiesAction",
|
||||
"./src/actions/RemoveAction",
|
||||
"./src/actions/SaveAction",
|
||||
"./src/actions/SaveAsAction",
|
||||
"./src/actions/CancelAction",
|
||||
"./src/policies/EditActionPolicy",
|
||||
"./src/policies/EditableLinkPolicy",
|
||||
@ -56,6 +57,7 @@ define([
|
||||
PropertiesAction,
|
||||
RemoveAction,
|
||||
SaveAction,
|
||||
SaveAsAction,
|
||||
CancelAction,
|
||||
EditActionPolicy,
|
||||
EditableLinkPolicy,
|
||||
@ -165,6 +167,15 @@ define([
|
||||
"implementation": SaveAction,
|
||||
"name": "Save",
|
||||
"description": "Save changes made to these objects.",
|
||||
"depends": [],
|
||||
"priority": "mandatory"
|
||||
},
|
||||
{
|
||||
"key": "save-as",
|
||||
"category": "conclude-editing",
|
||||
"implementation": SaveAsAction,
|
||||
"name": "Save As",
|
||||
"description": "Save changes made to these objects.",
|
||||
"depends": [
|
||||
"$injector",
|
||||
"policyService",
|
||||
|
@ -24,8 +24,8 @@
|
||||
|
||||
|
||||
define(
|
||||
['../../../browse/src/creation/CreateWizard'],
|
||||
function (CreateWizard) {
|
||||
[],
|
||||
function () {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
@ -37,31 +37,11 @@ define(
|
||||
* @memberof platform/commonUI/edit
|
||||
*/
|
||||
function SaveAction(
|
||||
$injector,
|
||||
policyService,
|
||||
dialogService,
|
||||
creationService,
|
||||
copyService,
|
||||
context
|
||||
) {
|
||||
this.domainObject = (context || {}).domainObject;
|
||||
this.injectObjectService = function(){
|
||||
this.objectService = $injector.get("objectService");
|
||||
};
|
||||
this.policyService = policyService;
|
||||
this.dialogService = dialogService;
|
||||
this.creationService = creationService;
|
||||
this.copyService = copyService;
|
||||
}
|
||||
|
||||
SaveAction.prototype.getObjectService = function(){
|
||||
// Lazily acquire object service (avoids cyclical dependency)
|
||||
if (!this.objectService) {
|
||||
this.injectObjectService();
|
||||
}
|
||||
return this.objectService;
|
||||
};
|
||||
|
||||
/**
|
||||
* Save changes and conclude editing.
|
||||
*
|
||||
@ -70,9 +50,7 @@ define(
|
||||
* @memberof platform/commonUI/edit.SaveAction#
|
||||
*/
|
||||
SaveAction.prototype.perform = function () {
|
||||
var domainObject = this.domainObject,
|
||||
copyService = this.copyService,
|
||||
self = this;
|
||||
var domainObject = this.domainObject;
|
||||
|
||||
function resolveWith(object){
|
||||
return function () {
|
||||
@ -80,64 +58,13 @@ define(
|
||||
};
|
||||
}
|
||||
|
||||
function doWizardSave(parent) {
|
||||
var context = domainObject.getCapability("context"),
|
||||
wizard = new CreateWizard(
|
||||
domainObject,
|
||||
parent,
|
||||
self.policyService
|
||||
);
|
||||
|
||||
return self.dialogService
|
||||
.getUserInput(
|
||||
wizard.getFormStructure(true),
|
||||
wizard.getInitialFormValue()
|
||||
)
|
||||
.then(wizard.populateObjectFromInput.bind(wizard));
|
||||
}
|
||||
|
||||
function fetchObject(objectId){
|
||||
return self.getObjectService().getObjects([objectId]).then(function(objects){
|
||||
return objects[objectId];
|
||||
});
|
||||
}
|
||||
|
||||
function getParent(object){
|
||||
return fetchObject(object.getModel().location);
|
||||
}
|
||||
|
||||
function allowClone(objectToClone) {
|
||||
return (objectToClone.getId() === domainObject.getId()) ||
|
||||
objectToClone.getCapability('location').isOriginal();
|
||||
}
|
||||
|
||||
function cloneIntoParent(parent) {
|
||||
return copyService.perform(domainObject, parent, allowClone);
|
||||
}
|
||||
|
||||
function cancelEditingAfterClone(clonedObject) {
|
||||
return domainObject.getCapability("editor").cancel()
|
||||
.then(resolveWith(clonedObject));
|
||||
}
|
||||
|
||||
// Invoke any save behavior introduced by the editor capability;
|
||||
// this is introduced by EditableDomainObject which is
|
||||
// used to insulate underlying objects from changes made
|
||||
// during editing.
|
||||
function doSave() {
|
||||
//This is a new 'virtual object' that has not been persisted
|
||||
// yet.
|
||||
if (domainObject.getModel().persisted === undefined){
|
||||
return getParent(domainObject)
|
||||
.then(doWizardSave)
|
||||
.then(getParent)
|
||||
.then(cloneIntoParent)
|
||||
.then(cancelEditingAfterClone)
|
||||
.catch(resolveWith(false));
|
||||
} else {
|
||||
return domainObject.getCapability("editor").save()
|
||||
.then(resolveWith(domainObject.getOriginalObject()));
|
||||
}
|
||||
return domainObject.getCapability("editor").save()
|
||||
.then(resolveWith(domainObject.getOriginalObject()));
|
||||
}
|
||||
|
||||
// Discard the current root view (which will be the editing
|
||||
@ -162,7 +89,8 @@ define(
|
||||
SaveAction.appliesTo = function (context) {
|
||||
var domainObject = (context || {}).domainObject;
|
||||
return domainObject !== undefined &&
|
||||
domainObject.hasCapability("editor");
|
||||
domainObject.hasCapability("editor") &&
|
||||
domainObject.getModel().persisted !== undefined;
|
||||
};
|
||||
|
||||
return SaveAction;
|
||||
|
169
platform/commonUI/edit/src/actions/SaveAsAction.js
Normal file
169
platform/commonUI/edit/src/actions/SaveAsAction.js
Normal file
@ -0,0 +1,169 @@
|
||||
/*****************************************************************************
|
||||
* 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*/
|
||||
/*jslint es5: true */
|
||||
|
||||
|
||||
define(
|
||||
['../../../browse/src/creation/CreateWizard'],
|
||||
function (CreateWizard) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* The "Save" action; the action triggered by clicking Save from
|
||||
* Edit Mode. Exits the editing user interface and invokes object
|
||||
* capabilities to persist the changes that have been made.
|
||||
* @constructor
|
||||
* @implements {Action}
|
||||
* @memberof platform/commonUI/edit
|
||||
*/
|
||||
function SaveAsAction(
|
||||
$injector,
|
||||
policyService,
|
||||
dialogService,
|
||||
creationService,
|
||||
copyService,
|
||||
context
|
||||
) {
|
||||
this.domainObject = (context || {}).domainObject;
|
||||
this.injectObjectService = function(){
|
||||
this.objectService = $injector.get("objectService");
|
||||
};
|
||||
this.policyService = policyService;
|
||||
this.dialogService = dialogService;
|
||||
this.creationService = creationService;
|
||||
this.copyService = copyService;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
SaveAsAction.prototype.createWizard = function (parent) {
|
||||
return new CreateWizard(
|
||||
this.domainObject,
|
||||
parent,
|
||||
this.policyService
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
SaveAsAction.prototype.getObjectService = function(){
|
||||
// Lazily acquire object service (avoids cyclical dependency)
|
||||
if (!this.objectService) {
|
||||
this.injectObjectService();
|
||||
}
|
||||
return this.objectService;
|
||||
};
|
||||
|
||||
function resolveWith(object){
|
||||
return function () {
|
||||
return object;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Save changes and conclude editing.
|
||||
*
|
||||
* @returns {Promise} a promise that will be fulfilled when
|
||||
* cancellation has completed
|
||||
* @memberof platform/commonUI/edit.SaveAction#
|
||||
*/
|
||||
SaveAsAction.prototype.perform = function () {
|
||||
// Discard the current root view (which will be the editing
|
||||
// UI, which will have been pushed atop the Browse UI.)
|
||||
function returnToBrowse(object) {
|
||||
if (object) {
|
||||
object.getCapability("action").perform("navigate");
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
return this.save().then(returnToBrowse);
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
SaveAsAction.prototype.save = function () {
|
||||
var self = this,
|
||||
domainObject = this.domainObject,
|
||||
copyService = this.copyService;
|
||||
|
||||
function doWizardSave(parent) {
|
||||
var wizard = self.createWizard(parent);
|
||||
|
||||
return self.dialogService
|
||||
.getUserInput(wizard.getFormStructure(true),
|
||||
wizard.getInitialFormValue()
|
||||
).then(wizard.populateObjectFromInput.bind(wizard));
|
||||
}
|
||||
|
||||
function fetchObject(objectId){
|
||||
return self.getObjectService().getObjects([objectId]).then(function(objects){
|
||||
return objects[objectId];
|
||||
});
|
||||
}
|
||||
|
||||
function getParent(object){
|
||||
return fetchObject(object.getModel().location);
|
||||
}
|
||||
|
||||
function allowClone(objectToClone) {
|
||||
return (objectToClone.getId() === domainObject.getId()) ||
|
||||
objectToClone.getCapability('location').isOriginal();
|
||||
}
|
||||
|
||||
function cloneIntoParent(parent) {
|
||||
return copyService.perform(domainObject, parent, allowClone);
|
||||
}
|
||||
|
||||
function cancelEditingAfterClone(clonedObject) {
|
||||
return domainObject.getCapability("editor").cancel()
|
||||
.then(resolveWith(clonedObject));
|
||||
}
|
||||
|
||||
return getParent(domainObject)
|
||||
.then(doWizardSave)
|
||||
.then(getParent)
|
||||
.then(cloneIntoParent)
|
||||
.then(cancelEditingAfterClone)
|
||||
.catch(resolveWith(false));
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if this action is applicable in a given context.
|
||||
* This will ensure that a domain object is present in the context,
|
||||
* and that this domain object is in Edit mode.
|
||||
* @returns true if applicable
|
||||
*/
|
||||
SaveAsAction.appliesTo = function (context) {
|
||||
var domainObject = (context || {}).domainObject;
|
||||
return domainObject !== undefined &&
|
||||
domainObject.hasCapability("editor") &&
|
||||
domainObject.getModel().persisted === undefined;
|
||||
};
|
||||
|
||||
return SaveAsAction;
|
||||
}
|
||||
);
|
@ -27,11 +27,11 @@ define(
|
||||
"use strict";
|
||||
|
||||
describe("The Save action", function () {
|
||||
var mockLocation,
|
||||
mockDomainObject,
|
||||
var mockDomainObject,
|
||||
mockEditorCapability,
|
||||
mockUrlService,
|
||||
actionContext,
|
||||
mockActionCapability,
|
||||
capabilities = {},
|
||||
action;
|
||||
|
||||
function mockPromise(value) {
|
||||
@ -43,65 +43,62 @@ define(
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
mockLocation = jasmine.createSpyObj(
|
||||
"$location",
|
||||
[ "path" ]
|
||||
);
|
||||
mockDomainObject = jasmine.createSpyObj(
|
||||
"domainObject",
|
||||
[ "getCapability", "hasCapability" ]
|
||||
[
|
||||
"getCapability",
|
||||
"hasCapability",
|
||||
"getModel",
|
||||
"getOriginalObject"
|
||||
]
|
||||
);
|
||||
mockEditorCapability = jasmine.createSpyObj(
|
||||
"editor",
|
||||
[ "save", "cancel" ]
|
||||
);
|
||||
mockUrlService = jasmine.createSpyObj(
|
||||
"urlService",
|
||||
["urlForLocation"]
|
||||
mockActionCapability = jasmine.createSpyObj(
|
||||
"actionCapability",
|
||||
[ "perform"]
|
||||
);
|
||||
|
||||
capabilities.editor = mockEditorCapability;
|
||||
capabilities.action = mockActionCapability;
|
||||
|
||||
actionContext = {
|
||||
domainObject: mockDomainObject
|
||||
};
|
||||
|
||||
mockDomainObject.hasCapability.andReturn(true);
|
||||
mockDomainObject.getCapability.andReturn(mockEditorCapability);
|
||||
mockDomainObject.getCapability.andCallFake(function (capability) {
|
||||
return capabilities[capability];
|
||||
});
|
||||
mockDomainObject.getModel.andReturn({persisted: 0});
|
||||
mockEditorCapability.save.andReturn(mockPromise(true));
|
||||
mockDomainObject.getOriginalObject.andReturn(mockDomainObject);
|
||||
|
||||
action = new SaveAction(mockLocation, mockUrlService, actionContext);
|
||||
action = new SaveAction(actionContext);
|
||||
|
||||
});
|
||||
|
||||
it("only applies to domain object with an editor capability", function () {
|
||||
expect(SaveAction.appliesTo(actionContext)).toBeTruthy();
|
||||
expect(SaveAction.appliesTo(actionContext)).toBe(true);
|
||||
expect(mockDomainObject.hasCapability).toHaveBeenCalledWith("editor");
|
||||
|
||||
mockDomainObject.hasCapability.andReturn(false);
|
||||
mockDomainObject.getCapability.andReturn(undefined);
|
||||
expect(SaveAction.appliesTo(actionContext)).toBeFalsy();
|
||||
expect(SaveAction.appliesTo(actionContext)).toBe(false);
|
||||
});
|
||||
|
||||
//TODO: Disabled for NEM Beta
|
||||
xit("invokes the editor capability's save functionality when performed", function () {
|
||||
// Verify precondition
|
||||
expect(mockEditorCapability.save).not.toHaveBeenCalled();
|
||||
action.perform();
|
||||
|
||||
// Should have called cancel
|
||||
expect(mockEditorCapability.save).toHaveBeenCalled();
|
||||
|
||||
// Also shouldn't call cancel
|
||||
expect(mockEditorCapability.cancel).not.toHaveBeenCalled();
|
||||
it("only applies to domain object that has already been persisted",
|
||||
function () {
|
||||
mockDomainObject.getModel.andReturn({persisted: undefined});
|
||||
expect(SaveAction.appliesTo(actionContext)).toBe(false);
|
||||
});
|
||||
|
||||
//TODO: Disabled for NEM Beta
|
||||
xit("returns to browse when performed", function () {
|
||||
action.perform();
|
||||
expect(mockLocation.path).toHaveBeenCalledWith(
|
||||
mockUrlService.urlForLocation("browse", mockDomainObject)
|
||||
);
|
||||
});
|
||||
it("uses the editor capability to save the object",
|
||||
function () {
|
||||
action.perform();
|
||||
expect(mockEditorCapability.save).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
174
platform/commonUI/edit/test/actions/SaveAsActionSpec.js
Normal file
174
platform/commonUI/edit/test/actions/SaveAsActionSpec.js
Normal file
@ -0,0 +1,174 @@
|
||||
/*****************************************************************************
|
||||
* 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,jasmine,xit,xdescribe*/
|
||||
|
||||
define(
|
||||
["../../src/actions/SaveAsAction"],
|
||||
function (SaveAsAction) {
|
||||
"use strict";
|
||||
|
||||
describe("The Save As action", function () {
|
||||
var mockDomainObject,
|
||||
mockEditorCapability,
|
||||
mockActionCapability,
|
||||
mockObjectService,
|
||||
mockDialogService,
|
||||
mockCopyService,
|
||||
mockParent,
|
||||
mockUrlService,
|
||||
actionContext,
|
||||
capabilities = {},
|
||||
action;
|
||||
|
||||
function noop () {}
|
||||
|
||||
function mockPromise(value) {
|
||||
return (value || {}).then ? value :
|
||||
{
|
||||
then: function (callback) {
|
||||
return mockPromise(callback(value));
|
||||
},
|
||||
catch: function (callback) {
|
||||
return mockPromise(callback(value));
|
||||
}
|
||||
} ;
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
mockDomainObject = jasmine.createSpyObj(
|
||||
"domainObject",
|
||||
[
|
||||
"getCapability",
|
||||
"hasCapability",
|
||||
"getModel"
|
||||
]
|
||||
);
|
||||
mockDomainObject.hasCapability.andReturn(true);
|
||||
mockDomainObject.getCapability.andCallFake(function (capability) {
|
||||
return capabilities[capability];
|
||||
});
|
||||
mockDomainObject.getModel.andReturn({location: 'a', persisted: undefined});
|
||||
|
||||
mockParent = jasmine.createSpyObj(
|
||||
"parentObject",
|
||||
[
|
||||
"getCapability",
|
||||
"hasCapability",
|
||||
"getModel"
|
||||
]
|
||||
);
|
||||
|
||||
mockEditorCapability = jasmine.createSpyObj(
|
||||
"editor",
|
||||
[ "save", "cancel" ]
|
||||
);
|
||||
mockEditorCapability.cancel.andReturn(mockPromise(undefined));
|
||||
mockEditorCapability.save.andReturn(mockPromise(true));
|
||||
capabilities.editor = mockEditorCapability;
|
||||
|
||||
mockActionCapability = jasmine.createSpyObj(
|
||||
"action",
|
||||
["perform"]
|
||||
);
|
||||
capabilities.action = mockActionCapability;
|
||||
|
||||
mockObjectService = jasmine.createSpyObj(
|
||||
"objectService",
|
||||
["getObjects"]
|
||||
);
|
||||
mockObjectService.getObjects.andReturn(mockPromise({'a': mockParent}));
|
||||
|
||||
mockDialogService = jasmine.createSpyObj(
|
||||
"dialogService",
|
||||
[
|
||||
"getUserInput"
|
||||
]
|
||||
);
|
||||
mockDialogService.getUserInput.andReturn(mockPromise(undefined));
|
||||
|
||||
mockCopyService = jasmine.createSpyObj(
|
||||
"copyService",
|
||||
[
|
||||
"perform"
|
||||
]
|
||||
);
|
||||
|
||||
mockUrlService = jasmine.createSpyObj(
|
||||
"urlService",
|
||||
["urlForLocation"]
|
||||
);
|
||||
|
||||
actionContext = {
|
||||
domainObject: mockDomainObject
|
||||
};
|
||||
|
||||
action = new SaveAsAction(undefined, undefined, mockDialogService, undefined, mockCopyService, actionContext);
|
||||
|
||||
spyOn(action, "getObjectService");
|
||||
action.getObjectService.andReturn(mockObjectService);
|
||||
|
||||
spyOn(action, "createWizard");
|
||||
action.createWizard.andReturn({
|
||||
getFormStructure: noop,
|
||||
getInitialFormValue: noop,
|
||||
populateObjectFromInput: function() {
|
||||
return mockDomainObject;
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it("only applies to domain object with an editor capability", function () {
|
||||
expect(SaveAsAction.appliesTo(actionContext)).toBe(true);
|
||||
expect(mockDomainObject.hasCapability).toHaveBeenCalledWith("editor");
|
||||
|
||||
mockDomainObject.hasCapability.andReturn(false);
|
||||
mockDomainObject.getCapability.andReturn(undefined);
|
||||
expect(SaveAsAction.appliesTo(actionContext)).toBe(false);
|
||||
});
|
||||
|
||||
it("only applies to domain object that has not already been" +
|
||||
" persisted", function () {
|
||||
expect(SaveAsAction.appliesTo(actionContext)).toBe(true);
|
||||
expect(mockDomainObject.hasCapability).toHaveBeenCalledWith("editor");
|
||||
|
||||
mockDomainObject.getModel.andReturn({persisted: 0});
|
||||
expect(SaveAsAction.appliesTo(actionContext)).toBe(false);
|
||||
});
|
||||
|
||||
it("returns to browse after save", function () {
|
||||
spyOn(action, "save");
|
||||
action.save.andReturn(mockPromise(mockDomainObject));
|
||||
action.perform();
|
||||
expect(mockActionCapability.perform).toHaveBeenCalledWith(
|
||||
"navigate"
|
||||
);
|
||||
});
|
||||
|
||||
it("prompts the user for object details", function () {
|
||||
action.perform();
|
||||
expect(mockDialogService.getUserInput).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
@ -76,6 +76,11 @@ $pad: $interiorMargin * $baseRatio;
|
||||
font-family: symbolsfont;
|
||||
margin-right: $interiorMarginSm;
|
||||
}
|
||||
&.t-save-as:before {
|
||||
content:'\e612';
|
||||
font-family: symbolsfont;
|
||||
margin-right: $interiorMarginSm;
|
||||
}
|
||||
&.t-cancel {
|
||||
.title-label { display: none; }
|
||||
&:before {
|
||||
|
Loading…
Reference in New Issue
Block a user