[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 */ /*global define */
define( define(
function () { ['./AbstractComposeAction'],
function (AbstractComposeAction) {
"use strict"; "use strict";
/** /**
* The CopyAction is available from context menus and allows a user to * The CopyAction is available from context menus and allows a user to
* deep copy an object to another location of their choosing. * deep copy an object to another location of their choosing.
* *
* @implements Action * @implements {Action}
* @constructor * @constructor
* @memberof platform/entanglement * @memberof platform/entanglement
*/ */
function CopyAction(locationService, copyService, context) { function CopyAction(locationService, copyService, context) {
return new AbstractComposeAction(
var object, locationService,
newParent, copyService,
currentParent; context,
"Duplicate",
if (context.selectedObject) { "to a location"
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 CopyAction; return CopyAction;

View File

@ -22,68 +22,25 @@
/*global define */ /*global define */
define( define(
function () { ['./AbstractComposeAction'],
function (AbstractComposeAction) {
"use strict"; "use strict";
/** /**
* The LinkAction is available from context menus and allows a user to * The LinkAction is available from context menus and allows a user to
* link an object to another location of their choosing. * link an object to another location of their choosing.
* *
* @implements Action * @implements {Action}
* @constructor * @constructor
* @memberof platform/entanglement * @memberof platform/entanglement
*/ */
function LinkAction(locationService, linkService, context) { function LinkAction(locationService, linkService, context) {
return new AbstractComposeAction(
var object, locationService,
newParent, linkService,
currentParent; context,
"Link"
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 LinkAction; return LinkAction;

View File

@ -22,69 +22,25 @@
/*global define */ /*global define */
define( define(
function () { ['./AbstractComposeAction'],
function (AbstractComposeAction) {
"use strict"; "use strict";
/** /**
* The MoveAction is available from context menus and allows a user to * The MoveAction is available from context menus and allows a user to
* move an object to another location of their choosing. * move an object to another location of their choosing.
* *
* @implements Action * @implements {Action}
* @constructor * @constructor
* @memberof platform/entanglement * @memberof platform/entanglement
*/ */
function MoveAction(locationService, moveService, context) { function MoveAction(locationService, moveService, context) {
return new AbstractComposeAction(
var object, locationService,
newParent, moveService,
currentParent; context,
"Move"
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 MoveAction; return MoveAction;

View File

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

View File

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

View File

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