[Code Style] Use prototypes in entanglement bundle

WTD-1482
This commit is contained in:
Victor Woeltjen 2015-08-11 14:54:01 -07:00
parent 5e4dcc1e35
commit b93d752c88
9 changed files with 446 additions and 314 deletions

View File

@ -0,0 +1,126 @@
/*****************************************************************************
* 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(
function () {
"use strict";
/**
* Common interface exposed by services which support move, copy,
* and link actions.
* @interface platform/entanglement.AbstractComposeService
* @private
*/
/**
* Change the composition of the specified objects.
*
* @param {DomainObject} domainObject the domain object to
* move, copy, or link.
* @param {DomainObject} parent the domain object whose composition
* will be changed to contain the domainObject (or its duplicate)
* @returns {Promise} A promise that is fulfilled when the
* duplicate operation has completed.
* @method platform/entanglement.AbstractComposeService#perform
*/
/**
* Check if one object can be composed into another.
* @param {DomainObject} domainObject the domain object to
* move, copy, or link.
* @param {DomainObject} parent the domain object whose composition
* will be changed to contain the domainObject (or its duplicate)
* @returns {boolean} true if this composition change is allowed
* @method platform/entanglement.AbstractComposeService#validate
*/
/**
* Template class for Move, Copy, and Link actions.
*
* @implements {Action}
* @constructor
* @private
* @memberof platform/entanglement
* @param {platform/entanglement.LocationService} locationService a
* service to request destinations from the user
* @param {platform/entanglement.AbstractComposeService} composeService
* a service which will handle actual changes to composition
* @param {ActionContext} the context in which the action will be performed
* @param {string} verb the verb to display for the action (e.g. "Move")
* @param {string} [suffix] a string to display in the dialog title;
* default is "to a new location"
*/
function AbstractComposeAction(locationService, composeService, context, verb, suffix) {
if (context.selectedObject) {
this.newParent = context.domainObject;
this.object = context.selectedObject;
} else {
this.object = context.domainObject;
}
this.currentParent = this.object
.getCapability('context')
.getParent();
this.locationService = locationService;
this.composeService = composeService;
this.verb = verb || "Compose";
this.suffix = suffix || "to a new location";
}
AbstractComposeAction.prototype.perform = function () {
var dialogTitle,
label,
validateLocation,
locationService = this.locationService,
composeService = this.composeService,
currentParent = this.currentParent,
newParent = this.newParent,
object = this.object;
if (newParent) {
return composeService.perform(object, newParent);
}
dialogTitle = [this.verb, object.getModel().name, this.suffix]
.join(" ");
label = this.verb + " To";
validateLocation = function (newParent) {
return composeService.validate(object, newParent);
};
return locationService.getLocationFromUser(
dialogTitle,
label,
validateLocation,
currentParent
).then(function (newParent) {
return composeService.perform(object, newParent);
});
};
return AbstractComposeAction;
}
);

View File

@ -22,71 +22,26 @@
/*global define */
define(
function () {
['./AbstractComposeAction'],
function (AbstractComposeAction) {
"use strict";
/**
* The CopyAction is available from context menus and allows a user to
* deep copy an object to another location of their choosing.
*
* @implements Action
* @implements {Action}
* @constructor
* @memberof platform/entanglement
*/
function CopyAction(locationService, copyService, context) {
var object,
newParent,
currentParent;
if (context.selectedObject) {
newParent = context.domainObject;
object = context.selectedObject;
} else {
object = context.domainObject;
}
currentParent = object
.getCapability('context')
.getParent();
return {
perform: function () {
if (newParent) {
return copyService
.perform(object, newParent);
}
var dialogTitle,
label,
validateLocation;
dialogTitle = [
"Duplicate ",
object.getModel().name,
" to a location"
].join("");
label = "Duplicate To";
validateLocation = function (newParent) {
return copyService
.validate(object, newParent);
};
return locationService.getLocationFromUser(
dialogTitle,
label,
validateLocation,
currentParent
).then(function (newParent) {
return copyService
.perform(object, newParent);
});
}
};
return new AbstractComposeAction(
locationService,
copyService,
context,
"Duplicate",
"to a location"
);
}
return CopyAction;

View File

@ -22,68 +22,25 @@
/*global define */
define(
function () {
['./AbstractComposeAction'],
function (AbstractComposeAction) {
"use strict";
/**
* The LinkAction is available from context menus and allows a user to
* link an object to another location of their choosing.
*
* @implements Action
* @implements {Action}
* @constructor
* @memberof platform/entanglement
*/
function LinkAction(locationService, linkService, context) {
var object,
newParent,
currentParent;
if (context.selectedObject) {
newParent = context.domainObject;
object = context.selectedObject;
} else {
object = context.domainObject;
}
currentParent = object
.getCapability('context')
.getParent();
return {
perform: function () {
if (newParent) {
return linkService
.perform(object, newParent);
}
var dialogTitle,
label,
validateLocation;
dialogTitle = [
"Link ",
object.getModel().name,
" to a new location"
].join("");
label = "Link To";
validateLocation = function (newParent) {
return linkService
.validate(object, newParent);
};
return locationService.getLocationFromUser(
dialogTitle,
label,
validateLocation,
currentParent
).then(function (newParent) {
return linkService
.perform(object, newParent);
});
}
};
return new AbstractComposeAction(
locationService,
linkService,
context,
"Link"
);
}
return LinkAction;

View File

@ -22,69 +22,25 @@
/*global define */
define(
function () {
['./AbstractComposeAction'],
function (AbstractComposeAction) {
"use strict";
/**
* The MoveAction is available from context menus and allows a user to
* move an object to another location of their choosing.
*
* @implements Action
* @implements {Action}
* @constructor
* @memberof platform/entanglement
*/
function MoveAction(locationService, moveService, context) {
var object,
newParent,
currentParent;
if (context.selectedObject) {
newParent = context.domainObject;
object = context.selectedObject;
} else {
object = context.domainObject;
}
currentParent = object
.getCapability('context')
.getParent();
return {
perform: function () {
if (newParent) {
return moveService
.perform(object, newParent);
}
var dialogTitle,
label,
validateLocation;
dialogTitle = [
"Move ",
object.getModel().name,
" to a new location"
].join("");
label = "Move To";
validateLocation = function (newParent) {
return moveService
.validate(object, newParent);
};
return locationService.getLocationFromUser(
dialogTitle,
label,
validateLocation,
currentParent
).then(function (newParent) {
return moveService
.perform(object, newParent);
});
}
};
return new AbstractComposeAction(
locationService,
moveService,
context,
"Move"
);
}
return MoveAction;

View File

@ -32,31 +32,43 @@ define(
* an object can be copied to a specific location.
* @constructor
* @memberof platform/entanglement
* @implements {platform/entanglement.AbstractComposeService}
*/
function CopyService($q, creationService, policyService) {
this.$q = $q;
this.creationService = creationService;
this.policyService = policyService;
}
/**
* duplicateObject duplicates a `domainObject` into the composition
* of `parent`, and then duplicates the composition of
* `domainObject` into the new object.
*
* This function is a recursive deep copy.
*
* @param {DomainObject} domainObject - the domain object to
* duplicate.
* @param {DomainObject} parent - the parent domain object to
* create the duplicate in.
* @returns {Promise} A promise that is fulfilled when the
* duplicate operation has completed.
* @memberof platform/entanglement.CopyService#
*/
CopyService.prototype.validate = function (object, parentCandidate) {
if (!parentCandidate || !parentCandidate.getId) {
return false;
}
if (parentCandidate.getId() === object.getId()) {
return false;
}
return this.policyService.allow(
"composition",
parentCandidate.getCapability('type'),
object.getCapability('type')
);
};
CopyService.prototype.perform = function (domainObject, parent) {
var model = JSON.parse(JSON.stringify(domainObject.getModel())),
$q = this.$q,
self = this;
// Wrapper for the recursive step
function duplicateObject(domainObject, parent) {
var model = JSON.parse(JSON.stringify(domainObject.getModel()));
return self.perform(domainObject, parent);
}
if (domainObject.hasCapability('composition')) {
model.composition = [];
}
return creationService
return this.creationService
.createObject(model, parent)
.then(function (newObject) {
if (!domainObject.hasCapability('composition')) {
@ -75,36 +87,7 @@ define(
}, $q.when(undefined));
});
});
}
return {
/**
* Returns true if `object` can be copied into
* `parentCandidate`'s composition.
* @memberof platform/entanglement.CopyService#
*/
validate: function (object, parentCandidate) {
if (!parentCandidate || !parentCandidate.getId) {
return false;
}
if (parentCandidate.getId() === object.getId()) {
return false;
}
return policyService.allow(
"composition",
parentCandidate.getCapability('type'),
object.getCapability('type')
);
},
/**
* Wrapper, @see {@link duplicateObject} for implementation.
* @memberof platform/entanglement.CopyService#
*/
perform: function (object, parentObject) {
return duplicateObject(object, parentObject);
}
};
}
return CopyService;
}

View File

@ -32,15 +32,13 @@ define(
* can be copied to a specific location.
* @constructor
* @memberof platform/entanglement
* @implements {platform/entanglement.AbstractComposeService}
*/
function LinkService(policyService) {
return {
/**
* Returns `true` if `object` can be linked into
* `parentCandidate`'s composition.
* @memberof platform/entanglement.LinkService#
*/
validate: function (object, parentCandidate) {
this.policyService = policyService;
}
LinkService.prototype.validate = function (object, parentCandidate) {
if (!parentCandidate || !parentCandidate.getId) {
return false;
}
@ -50,20 +48,14 @@ define(
if (parentCandidate.getModel().composition.indexOf(object.getId()) !== -1) {
return false;
}
return policyService.allow(
return this.policyService.allow(
"composition",
parentCandidate.getCapability('type'),
object.getCapability('type')
);
},
/**
* Link `object` into `parentObject`'s composition.
*
* @returns {Promise} A promise that is fulfilled when the
* linking operation has completed.
* @memberof platform/entanglement.LinkService#
*/
perform: function (object, parentObject) {
};
LinkService.prototype.perform = function (object, parentObject) {
return parentObject.useCapability('mutation', function (model) {
if (model.composition.indexOf(object.getId()) === -1) {
model.composition.push(object.getId());
@ -71,9 +63,7 @@ define(
}).then(function () {
return parentObject.getCapability('persistence').persist();
});
}
};
}
return LinkService;
}

View File

@ -32,15 +32,14 @@ define(
* an object can be copied to a specific location.
* @constructor
* @memberof platform/entanglement
* @implements {platform/entanglement.AbstractComposeService}
*/
function MoveService(policyService, linkService) {
return {
/**
* Returns `true` if `object` can be moved into
* `parentCandidate`'s composition.
* @memberof platform/entanglement.MoveService#
*/
validate: function (object, parentCandidate) {
this.policyService = policyService;
this.linkService = linkService;
}
MoveService.prototype.validate = function (object, parentCandidate) {
var currentParent = object
.getCapability('context')
.getParent();
@ -57,30 +56,22 @@ define(
if (parentCandidate.getModel().composition.indexOf(object.getId()) !== -1) {
return false;
}
return policyService.allow(
return this.policyService.allow(
"composition",
parentCandidate.getCapability('type'),
object.getCapability('type')
);
},
/**
* Move `object` into `parentObject`'s composition.
*
* @returns {Promise} A promise that is fulfilled when the
* move operation has completed.
* @memberof platform/entanglement.MoveService#
*/
perform: function (object, parentObject) {
return linkService
};
MoveService.prototype.perform = function (object, parentObject) {
return this.linkService
.perform(object, parentObject)
.then(function () {
return object
.getCapability('action')
.perform('remove');
});
}
};
}
return MoveService;
}

View File

@ -0,0 +1,176 @@
/*****************************************************************************
* 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,beforeEach,it,jasmine,expect */
define(
[
'../../src/actions/AbstractComposeAction',
'../services/MockCopyService',
'../DomainObjectFactory'
],
function (AbstractComposeAction, MockCopyService, domainObjectFactory) {
"use strict";
describe("Move/copy/link Actions", function () {
var action,
locationService,
locationServicePromise,
composeService,
context,
selectedObject,
selectedObjectContextCapability,
currentParent,
newParent;
beforeEach(function () {
selectedObjectContextCapability = jasmine.createSpyObj(
'selectedObjectContextCapability',
[
'getParent'
]
);
selectedObject = domainObjectFactory({
name: 'selectedObject',
model: {
name: 'selectedObject'
},
capabilities: {
context: selectedObjectContextCapability
}
});
currentParent = domainObjectFactory({
name: 'currentParent'
});
selectedObjectContextCapability
.getParent
.andReturn(currentParent);
newParent = domainObjectFactory({
name: 'newParent'
});
locationService = jasmine.createSpyObj(
'locationService',
[
'getLocationFromUser'
]
);
locationServicePromise = jasmine.createSpyObj(
'locationServicePromise',
[
'then'
]
);
locationService
.getLocationFromUser
.andReturn(locationServicePromise);
composeService = new MockCopyService();
});
describe("with context from context-action", function () {
beforeEach(function () {
context = {
domainObject: selectedObject
};
action = new AbstractComposeAction(
locationService,
composeService,
context,
"Compose"
);
});
it("initializes happily", function () {
expect(action).toBeDefined();
});
describe("when performed it", function () {
beforeEach(function () {
action.perform();
});
it("prompts for location", function () {
expect(locationService.getLocationFromUser)
.toHaveBeenCalledWith(
"Compose selectedObject to a new location",
"Compose To",
jasmine.any(Function),
currentParent
);
});
it("waits for location from user", function () {
expect(locationServicePromise.then)
.toHaveBeenCalledWith(jasmine.any(Function));
});
it("copies object to selected location", function () {
locationServicePromise
.then
.mostRecentCall
.args[0](newParent);
expect(composeService.perform)
.toHaveBeenCalledWith(selectedObject, newParent);
});
});
});
describe("with context from drag-drop", function () {
beforeEach(function () {
context = {
selectedObject: selectedObject,
domainObject: newParent
};
action = new AbstractComposeAction(
locationService,
composeService,
context,
"Compose"
);
});
it("initializes happily", function () {
expect(action).toBeDefined();
});
it("performs copy immediately", function () {
action.perform();
expect(composeService.perform)
.toHaveBeenCalledWith(selectedObject, newParent);
});
});
});
}
);

View File

@ -1,7 +1,5 @@
[
"actions/CopyAction",
"actions/LinkAction",
"actions/MoveAction",
"actions/AbstractComposeAction",
"services/CopyService",
"services/LinkService",
"services/MoveService",