Merge pull request #838 from nasa/open824_rebase

[Edit] #824 - Remove EditableDomainObject
This commit is contained in:
Victor Woeltjen 2016-05-18 17:01:24 -07:00
commit de43aa81be
59 changed files with 1067 additions and 1969 deletions

View File

@ -24,11 +24,8 @@
* Module defining CreateAction. Created by vwoeltje on 11/10/14.
*/
define(
[
'./CreateWizard',
'../../../edit/src/objects/EditableDomainObject'
],
function (CreateWizard, EditableDomainObject) {
[],
function () {
/**
* The Create Action is performed to create new instances of
@ -86,22 +83,25 @@ define(
CreateAction.prototype.perform = function () {
var newModel = this.type.getInitialModel(),
parentObject = this.navigationService.getNavigation(),
newObject,
editableObject;
editorCapability,
newObject;
newModel.type = this.type.getKey();
newModel.location = parentObject.getId();
newObject = parentObject.useCapability('instantiation', newModel);
editableObject = new EditableDomainObject(newObject, this.$q);
editableObject.setOriginalObject(parentObject);
editableObject.getCapability('status').set('editing', true);
editableObject.useCapability('mutation', function(model){
model.location = parentObject.getId();
});
if (countEditableViews(editableObject) > 0 && editableObject.hasCapability('composition')) {
this.navigationService.setNavigation(editableObject);
editorCapability = newObject.getCapability("editor");
if (countEditableViews(newObject) > 0 && newObject.hasCapability('composition')) {
this.navigationService.setNavigation(newObject);
return newObject.getCapability("action").perform("edit");
} else {
return editableObject.getCapability('action').perform('save');
editorCapability.edit();
return newObject.useCapability("action").perform("save").then(function () {
return editorCapability.save();
}, function () {
return editorCapability.cancel();
});
}
};

View File

@ -40,6 +40,9 @@ define([
"./src/policies/EditContextualActionPolicy",
"./src/representers/EditRepresenter",
"./src/representers/EditToolbarRepresenter",
"./src/capabilities/EditorCapability",
"./src/capabilities/TransactionCapabilityDecorator",
"./src/services/TransactionService",
"text!./res/templates/library.html",
"text!./res/templates/edit-object.html",
"text!./res/templates/edit-action-buttons.html",
@ -66,6 +69,9 @@ define([
EditContextualActionPolicy,
EditRepresenter,
EditToolbarRepresenter,
EditorCapability,
TransactionCapabilityDecorator,
TransactionService,
libraryTemplate,
editObjectTemplate,
editActionButtonsTemplate,
@ -128,8 +134,7 @@ define([
"depends": [
"$location",
"navigationService",
"$log",
"$q"
"$log"
],
"description": "Edit this object.",
"category": "view-control",
@ -191,10 +196,7 @@ define([
"implementation": CancelAction,
"name": "Cancel",
"description": "Discard changes made to these objects.",
"depends": [
"$injector",
"navigationService"
]
"depends": []
}
],
"policies": [
@ -261,6 +263,27 @@ define([
"template": topbarEditTemplate
}
],
"components": [
{
"type": "decorator",
"provides": "capabilityService",
"implementation": TransactionCapabilityDecorator,
"depends": [
"$q",
"transactionService"
],
"priority": "fallback"
},
{
"type": "provider",
"provides": "transactionService",
"implementation": TransactionService,
"depends": [
"$q",
"$log"
]
}
],
"representers": [
{
"implementation": EditRepresenter,
@ -282,7 +305,18 @@ define([
"key": "nonEditContextBlacklist",
"value": ["copy", "follow", "properties", "move", "link", "remove", "locate"]
}
]
],
"capabilities": [
{
"key": "editor",
"name": "Editor Capability",
"description": "Provides transactional editing capabilities",
"implementation": EditorCapability,
"depends": [
"transactionService"
]
}
],
}
});
});

View File

@ -31,10 +31,8 @@ define(
* @memberof platform/commonUI/edit
* @implements {Action}
*/
function CancelAction($injector, navigationService, context) {
function CancelAction(context) {
this.domainObject = context.domainObject;
this.navigationService = navigationService;
this.objectService = $injector.get('objectService');
}
/**
@ -44,30 +42,25 @@ define(
* cancellation has completed
*/
CancelAction.prototype.perform = function () {
var domainObject = this.domainObject,
self = this;
var domainObject = this.domainObject;
// Look up the object's "editor.completion" capability;
// this is introduced by EditableDomainObject which is
// used to insulate underlying objects from changes made
// during editing.
function getEditorCapability() {
return domainObject.getCapability("editor");
function returnToBrowse () {
var parent;
//If the object existed already, navigate to refresh view
// with previous object state.
if (domainObject.getModel().persisted) {
domainObject.getCapability("action").perform("navigate");
} else {
//If the object was new, and user has cancelled, then
//navigate back to parent because nothing to show.
domainObject.getCapability("location").getOriginal().then(function (original) {
parent = original.getCapability("context").getParent();
parent.getCapability("action").perform("navigate");
});
}
}
// Invoke any save behavior introduced by the editor.completion
// capability.
function doCancel(editor) {
return editor.cancel();
}
//Discard current 'editable' object, and retrieve original
// un-edited object.
function returnToBrowse() {
return self.navigationService.setNavigation(self.domainObject.getOriginalObject());
}
return doCancel(getEditorCapability())
return this.domainObject.getCapability("editor").cancel()
.then(returnToBrowse);
};
@ -80,7 +73,8 @@ define(
CancelAction.appliesTo = function (context) {
var domainObject = (context || {}).domainObject;
return domainObject !== undefined &&
domainObject.hasCapability("editor");
domainObject.hasCapability('editor') &&
domainObject.getCapability('editor').isEditContextRoot();
};
return CancelAction;

View File

@ -24,8 +24,8 @@
* Module defining EditAction. Created by vwoeltje on 11/14/14.
*/
define(
['../objects/EditableDomainObject'],
function (EditableDomainObject) {
[],
function () {
// A no-op action to return in the event that the action cannot
// be completed.
@ -44,7 +44,7 @@ define(
* @constructor
* @implements {Action}
*/
function EditAction($location, navigationService, $log, $q, context) {
function EditAction($location, navigationService, $log, context) {
var domainObject = (context || {}).domainObject;
// We cannot enter Edit mode if we have no domain object to
@ -63,7 +63,6 @@ define(
this.domainObject = domainObject;
this.$location = $location;
this.navigationService = navigationService;
this.$q = $q;
}
/**
@ -71,25 +70,12 @@ define(
*/
EditAction.prototype.perform = function () {
var self = this;
if (!this.domainObject.hasCapability("editor")) {
//TODO: This is only necessary because the drop gesture is
// wrapping the object itself, need to refactor this later.
// All responsibility for switching into edit mode should be
// in the edit action, and not duplicated in the gesture
this.domainObject = new EditableDomainObject(this.domainObject, this.$q);
}
this.navigationService.setNavigation(this.domainObject);
this.domainObject.getCapability('status').set('editing', true);
//Register a listener to automatically cancel this edit action
//if the user navigates away from this object.
function cancelEditing(navigatedTo){
if (!navigatedTo || navigatedTo.getId() !== self.domainObject.getId()) {
self.domainObject.getCapability('editor').cancel();
self.navigationService.removeListener(cancelEditing);
}
function cancelEditing(){
self.domainObject.getCapability('editor').cancel();
self.navigationService.removeListener(cancelEditing);
}
this.navigationService.addListener(cancelEditing);
this.domainObject.useCapability("editor");
};
/**
@ -100,11 +86,13 @@ define(
*/
EditAction.appliesTo = function (context) {
var domainObject = (context || {}).domainObject,
type = domainObject && domainObject.getCapability('type'),
isEditMode = domainObject && domainObject.getDomainObject ? true : false;
type = domainObject && domainObject.getCapability('type');
// Only allow creatable types to be edited
return type && type.hasFeature('creation') && !isEditMode;
// Only allow editing of types that support it and are not already
// being edited
return type && type.hasFeature('creation') &&
domainObject.hasCapability('editor') &&
!domainObject.getCapability('editor').isEditContextRoot();
};
return EditAction;

View File

@ -60,7 +60,7 @@ define(
// during editing.
function doSave() {
return domainObject.getCapability("editor").save()
.then(resolveWith(domainObject.getOriginalObject()));
.then(resolveWith(domainObject));
}
// Discard the current root view (which will be the editing
@ -85,7 +85,8 @@ define(
SaveAction.appliesTo = function (context) {
var domainObject = (context || {}).domainObject;
return domainObject !== undefined &&
domainObject.hasCapability("editor") &&
domainObject.hasCapability('editor') &&
domainObject.getCapability('editor').isEditContextRoot() &&
domainObject.getModel().persisted !== undefined;
};

View File

@ -135,8 +135,8 @@ define(
return copyService.perform(domainObject, parent, allowClone);
}
function cancelEditingAfterClone(clonedObject) {
return domainObject.getCapability("editor").cancel()
function commitEditingAfterClone(clonedObject) {
return domainObject.getCapability("editor").save()
.then(resolveWith(clonedObject));
}
@ -144,7 +144,7 @@ define(
.then(doWizardSave)
.then(getParent)
.then(cloneIntoParent)
.then(cancelEditingAfterClone)
.then(commitEditingAfterClone)
.catch(resolveWith(false));
};
@ -157,7 +157,8 @@ define(
SaveAsAction.appliesTo = function (context) {
var domainObject = (context || {}).domainObject;
return domainObject !== undefined &&
domainObject.hasCapability("editor") &&
domainObject.hasCapability('editor') &&
domainObject.getCapability('editor').isEditContextRoot() &&
domainObject.getModel().persisted === undefined;
};

View File

@ -1,55 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
define(
function () {
var DISALLOWED_ACTIONS = ["move", "copy", "link", "window", "follow"];
/**
* Editable Action Capability. Overrides the action capability
* normally exhibited by a domain object and filters out certain
* actions not applicable when an object is in edit mode.
*
* Meant specifically for use by EditableDomainObject and the
* associated cache; the constructor signature is particular
* to a pattern used there and may contain unused arguments.
* @constructor
* @memberof platform/commonUI/edit
* @implements {PersistenceCapability}
*/
function EditableActionCapability(
actionCapability
) {
var action = Object.create(actionCapability);
action.getActions = function(domainObject) {
return actionCapability.getActions(domainObject).filter(function(action){
return DISALLOWED_ACTIONS.indexOf(action.getMetadata().key) === -1;
});
};
return action;
}
return EditableActionCapability;
}
);

View File

@ -1,58 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
define(
['./EditableLookupCapability'],
function (EditableLookupCapability) {
/**
* Wrapper for the "composition" capability;
* ensures that any domain objects reachable in Edit mode
* are also wrapped as EditableDomainObjects.
*
* Meant specifically for use by EditableDomainObject and the
* associated cache; the constructor signature is particular
* to a pattern used there and may contain unused arguments.
* @constructor
* @memberof platform/commonUI/edit
* @implements {CompositionCapability}
*/
return function EditableCompositionCapability(
contextCapability,
editableObject,
domainObject,
cache
) {
// This is a "lookup" style capability (it looks up other
// domain objects), but we do not want to return the same
// specific value every time (composition may change)
return new EditableLookupCapability(
contextCapability,
editableObject,
domainObject,
cache,
false // Not idempotent
);
};
}
);

View File

@ -1,76 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
define(
['./EditableLookupCapability'],
function (EditableLookupCapability) {
/**
* Wrapper for the "context" capability;
* ensures that any domain objects reachable in Edit mode
* are also wrapped as EditableDomainObjects.
*
* Meant specifically for use by EditableDomainObject and the
* associated cache; the constructor signature is particular
* to a pattern used there and may contain unused arguments.
* @constructor
* @memberof platform/commonUI/edit
* @implements {ContextCapability}
*/
return function EditableContextCapability(
contextCapability,
editableObject,
domainObject,
cache
) {
// This is a "lookup" style capability (it looks up other
// domain objects), and it should be idempotent
var capability = new EditableLookupCapability(
contextCapability,
editableObject,
domainObject,
cache,
true // Idempotent
),
// Track the real root object for the Elements pane
trueRoot = capability.getRoot();
// Provide access to the real root, for the Elements pane.
capability.getTrueRoot = function () {
return trueRoot;
};
// Hide ancestry after the root of this subgraph
if (cache.isRoot(domainObject)) {
capability.getRoot = function () {
return editableObject;
};
capability.getPath = function () {
return [editableObject];
};
}
return capability;
};
}
);

View File

@ -1,58 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
define(
['./EditableLookupCapability'],
function (EditableLookupCapability) {
/**
* Wrapper for the "instantiation" capability;
* ensures that any domain objects instantiated in Edit mode
* are also wrapped as EditableDomainObjects.
*
* Meant specifically for use by EditableDomainObject and the
* associated cache; the constructor signature is particular
* to a pattern used there and may contain unused arguments.
* @constructor
* @memberof platform/commonUI/edit
* @implements {CompositionCapability}
*/
return function EditableInstantiationCapability(
contextCapability,
editableObject,
domainObject,
cache
) {
// This is a "lookup" style capability (it looks up other
// domain objects), but we do not want to return the same
// specific value every time (composition may change)
return new EditableLookupCapability(
contextCapability,
editableObject,
domainObject,
cache,
false // Not idempotent
);
};
}
);

View File

@ -1,123 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
define(
[],
function () {
/*jshint forin:false */
/**
* Wrapper for both "context" and "composition" capabilities;
* ensures that any domain objects reachable in Edit mode
* are also wrapped as EditableDomainObjects.
*
* Meant specifically for use by EditableDomainObject and the
* associated cache; the constructor signature is particular
* to a pattern used there and may contain unused arguments.
* @constructor
* @memberof platform/commonUI/edit
*/
return function EditableLookupCapability(
contextCapability,
editableObject,
domainObject,
cache,
idempotent
) {
var capability = Object.create(contextCapability),
method;
// Check for domain object interface. If something has these
// three methods, we assume it's a domain object.
function isDomainObject(obj) {
return obj !== undefined &&
typeof obj.getId === 'function' &&
typeof obj.getModel === 'function' &&
typeof obj.getCapability === 'function';
}
// Check an object returned by the wrapped capability; if it
// is a domain object, we want to make it editable and/or get
// it from the cache of editable domain objects. This will
// prevent changes made in edit mode from modifying the actual
// underlying domain object.
function makeEditableObject(obj) {
return isDomainObject(obj) ?
cache.getEditableObject(obj) :
obj;
}
// Wrap a returned value (see above); if it's an array, wrap
// all elements.
function makeEditable(returnValue) {
return Array.isArray(returnValue) ?
returnValue.map(makeEditableObject) :
makeEditableObject(returnValue);
}
// Wrap a returned value (see above); if it's a promise, wrap
// the resolved value.
function wrapResult(result) {
return (result && result.then) ? // promise-like
result.then(makeEditable) :
makeEditable(result);
}
// Return a wrapped version of a function, which ensures
// all results are editable domain objects.
function wrapFunction(fn) {
return function () {
return wrapResult(contextCapability[fn].apply(
capability,
arguments
));
};
}
// Wrap a method such that it only delegates once.
function oneTimeFunction(fn) {
return function () {
var result = wrapFunction(fn).apply(this, arguments);
capability[fn] = function () {
return result;
};
return result;
};
}
// Wrap a method of this capability
function wrapMethod(fn) {
if (typeof capability[fn] === 'function') {
capability[fn] =
(idempotent ? oneTimeFunction : wrapFunction)(fn);
}
}
// Wrap all methods; return only editable domain objects.
for (method in contextCapability) {
wrapMethod(method);
}
return capability;
};
}
);

View File

@ -1,66 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
define(
function () {
/**
* Editable Persistence Capability. Overrides the persistence capability
* normally exhibited by a domain object to ensure that changes made
* during edit mode are not immediately stored to the database or other
* backing storage.
*
* Meant specifically for use by EditableDomainObject and the
* associated cache; the constructor signature is particular
* to a pattern used there and may contain unused arguments.
* @constructor
* @memberof platform/commonUI/edit
* @implements {PersistenceCapability}
*/
function EditablePersistenceCapability(
persistenceCapability,
editableObject,
domainObject,
cache
) {
var persistence = Object.create(persistenceCapability);
// Simply trigger refresh of in-view objects; do not
// write anything to database.
persistence.persist = function () {
return cache.markDirty(editableObject);
};
// Delegate refresh to the original object; this avoids refreshing
// the editable instance of the object, and ensures that refresh
// correctly targets the "real" version of the object.
persistence.refresh = function () {
return domainObject.getCapability('persistence').refresh();
};
return persistence;
}
return EditablePersistenceCapability;
}
);

View File

@ -1,58 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
define(
['./EditableLookupCapability'],
function (EditableLookupCapability) {
/**
* Wrapper for the "relationship" capability;
* ensures that any domain objects reachable in Edit mode
* are also wrapped as EditableDomainObjects.
*
* Meant specifically for use by EditableDomainObject and the
* associated cache; the constructor signature is particular
* to a pattern used there and may contain unused arguments.
* @constructor
* @memberof platform/commonUI/edit
* @implements {RelationshipCapability}
*/
return function EditableRelationshipCapability(
relationshipCapability,
editableObject,
domainObject,
cache
) {
// This is a "lookup" style capability (it looks up other
// domain objects), but we do not want to return the same
// specific value every time (composition may change)
return new EditableLookupCapability(
relationshipCapability,
editableObject,
domainObject,
cache,
false // Not idempotent
);
};
}
);

View File

@ -24,115 +24,95 @@ define(
[],
function () {
/**
* Implements "save" and "cancel" as capabilities of
* the object. In editing mode, user is seeing/using
* a copy of the object (an EditableDomainObject)
* which is disconnected from persistence; the Save
* and Cancel actions can use this capability to
* propagate changes from edit mode to the underlying
* actual persistable object.
*
* Meant specifically for use by EditableDomainObject and the
* associated cache; the constructor signature is particular
* to a pattern used there and may contain unused arguments.
* A capability that implements an editing 'session' for a domain
* object. An editing session is initiated via a call to .edit().
* Once initiated, any persist operations will be queued pending a
* subsequent call to [.save()](@link #save) or [.cancel()](@link
* #cancel).
* @param transactionService
* @param domainObject
* @constructor
* @memberof platform/commonUI/edit
*/
function EditorCapability(
persistenceCapability,
editableObject,
domainObject,
cache
transactionService,
domainObject
) {
this.editableObject = editableObject;
this.transactionService = transactionService;
this.domainObject = domainObject;
this.cache = cache;
}
// Simulate Promise.resolve (or $q.when); the former
// causes a delayed reaction from Angular (since it
// does not trigger a digest) and the latter is not
// readily accessible, since we're a few classes
// removed from the layer which gets dependency
// injection.
function resolvePromise(value) {
return (value && value.then) ? value : {
then: function (callback) {
return resolvePromise(callback(value));
}
};
}
/**
* Save any changes that have been made to this domain object
* (as well as to others that might have been retrieved and
* modified during the editing session)
* @param {boolean} nonrecursive if true, save only this
* object (and not other objects with associated changes)
* @returns {Promise} a promise that will be fulfilled after
* persistence has completed.
* @memberof platform/commonUI/edit.EditorCapability#
* Initiate an editing session. This will start a transaction during
* which any persist operations will be deferred until either save()
* or cancel() are called.
*/
EditorCapability.prototype.save = function (nonrecursive) {
var domainObject = this.domainObject,
editableObject = this.editableObject,
self = this,
cache = this.cache,
returnPromise;
EditorCapability.prototype.edit = function () {
this.transactionService.startTransaction();
this.domainObject.getCapability('status').set('editing', true);
};
// Update the underlying, "real" domain object's model
// with changes made to the copy used for editing.
function doMutate() {
return domainObject.useCapability('mutation', function () {
return editableObject.getModel();
});
}
function isEditContextRoot (domainObject) {
return domainObject.getCapability('status').get('editing');
}
// Persist the underlying domain object
function doPersist() {
return domainObject.getCapability('persistence').persist();
}
function isEditing (domainObject) {
return isEditContextRoot(domainObject) ||
domainObject.hasCapability('context') &&
isEditing(domainObject.getCapability('context').getParent());
}
editableObject.getCapability("status").set("editing", false);
/**
* Determines whether this object, or any of its ancestors are
* currently being edited.
* @returns boolean
*/
EditorCapability.prototype.inEditContext = function () {
return isEditing(this.domainObject);
};
if (nonrecursive) {
returnPromise = resolvePromise(doMutate())
.then(doPersist)
.then(function(){
self.cancel();
});
} else {
returnPromise = resolvePromise(cache.saveAll());
}
//Return the original (non-editable) object
return returnPromise.then(function() {
return domainObject.getOriginalObject ? domainObject.getOriginalObject() : domainObject;
/**
* Is this the root editing object (ie. the object that the user
* clicked 'edit' on)?
* @returns {*}
*/
EditorCapability.prototype.isEditContextRoot = function () {
return isEditContextRoot(this.domainObject);
};
/**
* Save any changes from this editing session. This will flush all
* pending persists and end the current transaction
* @returns {*}
*/
EditorCapability.prototype.save = function () {
var domainObject = this.domainObject;
return this.transactionService.commit().then(function() {
domainObject.getCapability('status').set('editing', false);
});
};
EditorCapability.prototype.invoke = EditorCapability.prototype.edit;
/**
* Cancel the current editing session. This will discard any pending
* persist operations
* @returns {*}
*/
EditorCapability.prototype.cancel = function () {
var domainObject = this.domainObject;
return this.transactionService.cancel().then(function(){
domainObject.getCapability("status").set("editing", false);
return domainObject;
});
};
/**
* Cancel editing; Discard any changes that have been made to
* this domain object (as well as to others that might have
* been retrieved and modified during the editing session)
* @returns {Promise} a promise that will be fulfilled after
* cancellation has completed.
* @memberof platform/commonUI/edit.EditorCapability#
*/
EditorCapability.prototype.cancel = function () {
this.editableObject.getCapability("status").set("editing", false);
this.cache.markClean();
return resolvePromise(undefined);
};
/**
* Check if there are any unsaved changes.
* @returns {boolean} true if there are unsaved changes
* @memberof platform/commonUI/edit.EditorCapability#
* @returns {boolean} true if there have been any domain model
* modifications since the last persist, false otherwise.
*/
EditorCapability.prototype.dirty = function () {
return this.cache.dirty();
return this.transactionService.size() > 0;
};
return EditorCapability;

View File

@ -0,0 +1,73 @@
/*****************************************************************************
* 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(
['./TransactionalPersistenceCapability'],
function (TransactionalPersistenceCapability) {
/**
* Wraps the [PersistenceCapability]{@link PersistenceCapability} with
* transactional capabilities.
* @param $q
* @param transactionService
* @param capabilityService
* @see TransactionalPersistenceCapability
* @constructor
*/
function TransactionCapabilityDecorator(
$q,
transactionService,
capabilityService
) {
this.capabilityService = capabilityService;
this.transactionService = transactionService;
this.$q = $q;
}
/**
* Decorate PersistenceCapability to queue persistence calls when a
* transaction is in progress.
*/
TransactionCapabilityDecorator.prototype.getCapabilities = function (model) {
var self = this,
capabilities = this.capabilityService.getCapabilities(model),
persistenceCapability = capabilities.persistence;
capabilities.persistence = function (domainObject) {
var original =
(typeof persistenceCapability === 'function') ?
persistenceCapability(domainObject) :
persistenceCapability;
return new TransactionalPersistenceCapability(
self.$q,
self.transactionService,
original,
domainObject
);
};
return capabilities;
};
return TransactionCapabilityDecorator;
}
);

View File

@ -0,0 +1,98 @@
/*****************************************************************************
* 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 () {
/**
* Wraps persistence capability to enable transactions. Transactions
* will cause persist calls not to be invoked immediately, but
* rather queued until [EditorCapability.save()]{@link EditorCapability#save}
* or [EditorCapability.cancel()]{@link EditorCapability#cancel} are
* called.
* @memberof platform/commonUI/edit/capabilities
* @param $q
* @param transactionService
* @param persistenceCapability
* @param domainObject
* @constructor
*/
function TransactionalPersistenceCapability(
$q,
transactionService,
persistenceCapability,
domainObject
) {
this.transactionService = transactionService;
this.persistenceCapability = persistenceCapability;
this.domainObject = domainObject;
this.$q = $q;
this.persistPending = false;
}
/**
* The wrapped persist function. If a transaction is active, persist
* will be queued until the transaction is committed or cancelled.
* @returns {*}
*/
TransactionalPersistenceCapability.prototype.persist = function () {
var self = this;
function onCommit() {
return self.persistenceCapability.persist().then(function(result) {
self.persistPending = false;
return result;
});
}
function onCancel() {
return self.persistenceCapability.refresh().then(function(result) {
self.persistPending = false;
return result;
});
}
if (this.transactionService.isActive()) {
if (!this.persistPending) {
this.transactionService.addToTransaction(onCommit, onCancel);
this.persistPending = true;
}
//Need to return a promise from this function
return this.$q.when(true);
} else {
return this.persistenceCapability.persist();
}
};
TransactionalPersistenceCapability.prototype.refresh = function () {
return this.persistenceCapability.refresh();
};
TransactionalPersistenceCapability.prototype.getSpace = function () {
return this.persistenceCapability.getSpace();
};
return TransactionalPersistenceCapability;
}
);

View File

@ -1,130 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/**
* Defines EditableDomainObject, which wraps domain objects
* such that user code may work with and mutate a copy of the
* domain object model; these changes may then be propagated
* up to the real domain object (or not) by way of invoking
* save or cancel behaviors of the "editor.completion"
* capability (a capability intended as internal to edit
* mode; invoked by way of the Save and Cancel actions.)
*/
define(
[
'../capabilities/EditablePersistenceCapability',
'../capabilities/EditableContextCapability',
'../capabilities/EditableCompositionCapability',
'../capabilities/EditableRelationshipCapability',
'../capabilities/EditableInstantiationCapability',
'../capabilities/EditorCapability',
'../capabilities/EditableActionCapability',
'./EditableDomainObjectCache'
],
function (
EditablePersistenceCapability,
EditableContextCapability,
EditableCompositionCapability,
EditableRelationshipCapability,
EditableInstantiationCapability,
EditorCapability,
EditableActionCapability,
EditableDomainObjectCache
) {
var capabilityFactories = {
persistence: EditablePersistenceCapability,
context: EditableContextCapability,
composition: EditableCompositionCapability,
relationship: EditableRelationshipCapability,
instantiation: EditableInstantiationCapability,
editor: EditorCapability
};
// Handle special case where "editor.completion" wraps persistence
// (other capability overrides wrap capabilities of the same type.)
function getDelegateArguments(name, args) {
return name === "editor" ? ['persistence'] : args;
}
/**
* An EditableDomainObject overrides capabilities
* which need to behave differently in edit mode,
* and provides a "working copy" of the object's
* model to allow changes to be easily cancelled.
* @constructor
* @memberof platform/commonUI/edit
* @implements {DomainObject}
*/
function EditableDomainObject(domainObject, $q) {
// The cache will hold all domain objects reached from
// the initial EditableDomainObject; this ensures that
// different versions of the same editable domain object
// are not shown in different sections of the same Edit
// UI, which might thereby fall out of sync.
var cache,
originalObject = domainObject,
cachedObject;
// Constructor for EditableDomainObject, which adheres
// to the same shared cache.
function EditableDomainObjectImpl(domainObject, model) {
var editableObject = Object.create(domainObject);
// Only provide the cloned model.
editableObject.getModel = function () { return model; };
// Override certain capabilities
editableObject.getCapability = function (name) {
var delegateArguments = getDelegateArguments(name, arguments),
capability = domainObject.getCapability.apply(
this,
delegateArguments
),
Factory = capabilityFactories[name];
return (Factory && capability) ?
new Factory(capability, editableObject, domainObject, cache) :
capability;
};
editableObject.setOriginalObject = function(object) {
originalObject = object;
};
editableObject.getOriginalObject = function() {
return originalObject;
};
return editableObject;
}
cache = new EditableDomainObjectCache(EditableDomainObjectImpl, $q);
cachedObject = cache.getEditableObject(domainObject);
return cachedObject;
}
return EditableDomainObject;
}
);

View File

@ -1,170 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
/*
* An editable domain object cache stores domain objects that have been
* made editable, in a group that can be saved all-at-once. This supports
* Edit mode, which is launched for a specific object but may contain
* changes across many objects.
*
* Editable domain objects have certain specific capabilities overridden
* to ensure that changes made while in edit mode do not propagate up
* to the objects used in browse mode (or to persistence) until the user
* initiates a Save.
*/
define(
["./EditableModelCache"],
function (EditableModelCache) {
/**
* Construct a new cache for editable domain objects. This can be used
* to get-or-create editable objects, particularly to support wrapping
* of objects retrieved via composition or context capabilities as
* editable domain objects.
*
* @param {Constructor<DomainObject>} EditableDomainObject a
* constructor function which takes a regular domain object as
* an argument, and returns an editable domain object as its
* result.
* @param $q Angular's $q, for promise handling
* @memberof platform/commonUI/edit
* @constructor
*/
function EditableDomainObjectCache(EditableDomainObject, $q) {
this.cache = new EditableModelCache();
this.dirtyObjects = {};
this.root = undefined;
this.$q = $q;
this.EditableDomainObject = EditableDomainObject;
}
/**
* Wrap this domain object in an editable form, or pull such
* an object from the cache if one already exists.
*
* @param {DomainObject} domainObject the regular domain object
* @returns {DomainObject} the domain object in an editable form
*/
EditableDomainObjectCache.prototype.getEditableObject = function (domainObject) {
var type = domainObject.getCapability('type'),
EditableDomainObject = this.EditableDomainObject,
editableObject;
// Track the top-level domain object; this will have
// some special behavior for its context capability.
this.root = this.root || domainObject;
// Avoid double-wrapping (WTD-1017)
if (domainObject.hasCapability('editor')) {
return domainObject;
}
// Don't bother wrapping non-editable objects
if (!type || !type.hasFeature('creation')) {
return domainObject;
}
// Provide an editable form of the object
editableObject = new EditableDomainObject(
domainObject,
this.cache.getCachedModel(domainObject)
);
return editableObject;
};
/**
* Check if a domain object is (effectively) the top-level
* object in this editable subgraph.
* @returns {boolean} true if it is the root
*/
EditableDomainObjectCache.prototype.isRoot = function (domainObject) {
return domainObject === this.root;
};
/**
* Mark an editable domain object (presumably already cached)
* as having received modifications during editing; it should be
* included in the bulk save invoked when editing completes.
*
* @param {DomainObject} domainObject the domain object
* @memberof platform/commonUI/edit.EditableDomainObjectCache#
*/
EditableDomainObjectCache.prototype.markDirty = function (domainObject) {
this.dirtyObjects[domainObject.getId()] = domainObject;
return this.$q.when(true);
};
/**
* Mark an object (presumably already cached) as having had its
* changes saved (and thus no longer needing to be subject to a
* save operation.)
*
* @param {DomainObject} domainObject the domain object
*/
EditableDomainObjectCache.prototype.markClean = function (domainObject) {
var self = this;
if (!domainObject) {
Object.keys(this.dirtyObjects).forEach(function(key) {
delete self.dirtyObjects[key];
});
} else {
delete this.dirtyObjects[domainObject.getId()];
}
};
/**
* Initiate a save on all objects that have been cached.
* @return {Promise} A promise which will resolve when all objects are
* persisted.
*/
EditableDomainObjectCache.prototype.saveAll = function () {
// Get a list of all dirty objects
var dirty = this.dirtyObjects,
objects = Object.keys(dirty).map(function (k) {
return dirty[k];
});
// Clear dirty set, since we're about to save.
this.dirtyObjects = {};
// Most save logic is handled by the "editor.completion"
// capability, so that is delegated here.
return this.$q.all(objects.map(function (object) {
// Save; pass a nonrecursive flag to avoid looping
return object.getCapability('editor').save(true);
}));
};
/**
* Check if any objects have been marked dirty in this cache.
* @returns {boolean} true if objects are dirty
*/
EditableDomainObjectCache.prototype.dirty = function () {
return Object.keys(this.dirtyObjects).length > 0;
};
return EditableDomainObjectCache;
}
);

View File

@ -1,60 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
define(
[],
function () {
/**
* An editable model cache stores domain object models that have been
* made editable, to support a group that can be saved all-at-once.
* This is useful in Edit mode, which is launched for a specific
* object but may contain changes across many objects.
* @memberof platform/commonUI/edit
* @constructor
*/
function EditableModelCache() {
this.cache = {};
}
// Deep-copy a model. Models are JSONifiable, so this can be
// done by stringification then destringification
function clone(model) {
return JSON.parse(JSON.stringify(model));
}
/**
* Get this domain object's model from the cache (or
* place it in the cache if it isn't in the cache yet)
* @returns a clone of the domain object's model
*/
EditableModelCache.prototype.getCachedModel = function (domainObject) {
var id = domainObject.getId(),
cache = this.cache;
return (cache[id] =
cache[id] || clone(domainObject.getModel()));
};
return EditableModelCache;
}
);

View File

@ -73,8 +73,8 @@ define(
function isEditing(context) {
var domainObject = (context || {}).domainObject;
return domainObject &&
domainObject.hasCapability('status') &&
domainObject.getCapability('status').get('editing');
domainObject.hasCapability('editor') &&
domainObject.getCapability('editor').isEditContextRoot();
}
EditActionPolicy.prototype.allow = function (action, context) {

View File

@ -34,6 +34,11 @@ define(
* from context menu of non-editable objects, when navigated object
* is being edited
* @constructor
* @param editModeBlacklist A blacklist of actions disallowed from
* context menu when navigated object is being edited
* @param nonEditContextBlacklist A blacklist of actions disallowed
* from context menu of non-editable objects, when navigated object
* @implements {Policy.<Action, ActionContext>}
*/
function EditContextualActionPolicy(navigationService, editModeBlacklist, nonEditContextBlacklist) {
this.navigationService = navigationService;
@ -45,18 +50,13 @@ define(
this.nonEditContextBlacklist = nonEditContextBlacklist;
}
function isParentEditable(object) {
var parent = object.hasCapability("context") && object.getCapability("context").getParent();
return !!parent && parent.hasCapability("editor");
}
EditContextualActionPolicy.prototype.allow = function (action, context) {
var selectedObject = context.domainObject,
navigatedObject = this.navigationService.getNavigation(),
actionMetadata = action.getMetadata ? action.getMetadata() : {};
if (navigatedObject.hasCapability('editor')) {
if (selectedObject.hasCapability('editor') || isParentEditable(selectedObject)){
if (navigatedObject.hasCapability("editor") && navigatedObject.getCapability("editor").isEditContextRoot()) {
if (selectedObject.hasCapability("editor") && selectedObject.getCapability("editor").inEditContext()){
return this.editModeBlacklist.indexOf(actionMetadata.key) === -1;
} else {
//Target is in the context menu

View File

@ -41,12 +41,11 @@ define(
EditNavigationPolicy.prototype.isDirty = function(domainObject) {
var navigatedObject = domainObject,
editorCapability = navigatedObject &&
navigatedObject.getCapability("editor"),
statusCapability = navigatedObject &&
navigatedObject.getCapability("status");
navigatedObject.getCapability("editor");
return statusCapability && statusCapability.get('editing') &&
editorCapability && editorCapability.dirty();
return editorCapability &&
editorCapability.isEditContextRoot() &&
editorCapability.dirty();
};
/**

View File

@ -35,11 +35,12 @@ define([], function () {
}
EditableLinkPolicy.prototype.allow = function (action, context) {
var key = action.getMetadata().key;
var key = action.getMetadata().key,
object;
if (key === 'link') {
return !((context.selectedObject || context.domainObject)
.hasCapability('editor'));
object = context.selectedObject || context.domainObject;
return !(object.hasCapability("editor") && object.getCapability("editor").inEditContext());
}
// Like all policies, allow by default.

View File

@ -35,10 +35,13 @@ define([], function () {
EditableMovePolicy.prototype.allow = function (action, context) {
var domainObject = context.domainObject,
selectedObject = context.selectedObject,
key = action.getMetadata().key;
key = action.getMetadata().key,
isDomainObjectEditing = domainObject.hasCapability('editor') &&
domainObject.getCapability('editor').inEditContext();
if (key === 'move' && domainObject.hasCapability('editor')) {
return !!selectedObject && selectedObject.hasCapability('editor');
if (key === 'move' && isDomainObjectEditing) {
return !!selectedObject && selectedObject.hasCapability('editor') &&
selectedObject.getCapability('editor').inEditContext();
}
// Like all policies, allow by default.

View File

@ -37,7 +37,7 @@ define(
// If a view is flagged as non-editable, only allow it
// while we're not in Edit mode.
if ((view || {}).editable === false) {
return !domainObject.hasCapability('editor');
return !(domainObject.hasCapability('editor') && domainObject.getCapability('editor').inEditContext());
}
// Like all policies, allow by default.

View File

@ -136,7 +136,7 @@ define(
}
});
if (representedObject.getCapability('status').get('editing')){
if (representedObject.hasCapability('editor') && representedObject.getCapability('editor').isEditContextRoot()){
setEditing();
}
};

View File

@ -0,0 +1,148 @@
/*****************************************************************************
* 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() {
/**
* Implements an application-wide transaction state. Once a
* transaction is started, calls to
* [PersistenceCapability.persist()]{@link PersistenceCapability#persist}
* will be deferred until a subsequent call to
* [TransactionService.commit]{@link TransactionService#commit} is made.
*
* @memberof platform/commonUI/edit/services
* @param $q
* @constructor
*/
function TransactionService($q, $log) {
this.$q = $q;
this.$log = $log;
this.transaction = false;
this.onCommits = [];
this.onCancels = [];
}
/**
* Starts a transaction. While a transaction is active all calls to
* [PersistenceCapability.persist](@link PersistenceCapability#persist)
* will be queued until [commit]{@link #commit} or [cancel]{@link
* #cancel} are called
*/
TransactionService.prototype.startTransaction = function () {
if (this.transaction) {
//Log error because this is a programming error if it occurs.
this.$log.error("Transaction already in progress");
}
this.transaction = true;
};
/**
* @returns {boolean} If true, indicates that a transaction is in progress
*/
TransactionService.prototype.isActive = function () {
return this.transaction;
};
/**
* Adds provided functions to a queue to be called on
* [.commit()]{@link #commit} or
* [.cancel()]{@link #commit}
* @param onCommit A function to call on commit
* @param onCancel A function to call on cancel
*/
TransactionService.prototype.addToTransaction = function (onCommit, onCancel) {
if (this.transaction) {
this.onCommits.push(onCommit);
if (onCancel) {
this.onCancels.push(onCancel);
}
} else {
//Log error because this is a programming error if it occurs.
this.$log.error("No transaction in progress");
}
};
/**
* All persist calls deferred since the beginning of the transaction
* will be committed.
*
* @returns {Promise} resolved when all persist operations have
* completed. Will reject if any commit operations fail
*/
TransactionService.prototype.commit = function () {
var self = this,
promises = [],
onCommit;
while (this.onCommits.length > 0) { // ...using a while in case some onCommit adds to transaction
onCommit = this.onCommits.pop();
try { // ...also don't want to fail mid-loop...
promises.push(onCommit());
} catch (e) {
this.$log.error("Error committing transaction.");
}
}
return this.$q.all(promises).then( function () {
self.transaction = false;
self.onCommits = [];
self.onCancels = [];
});
};
/**
* Cancel the current transaction, replacing any dirty objects from
* persistence. Not a true rollback, as it cannot be used to undo any
* persist calls that were successful in the event one of a batch of
* persists failing.
*
* @returns {*}
*/
TransactionService.prototype.cancel = function () {
var self = this,
results = [],
onCancel;
while (this.onCancels.length > 0) {
onCancel = this.onCancels.pop();
try {
results.push(onCancel());
} catch (error) {
this.$log.error("Error committing transaction.");
}
}
return this.$q.all(results).then(function () {
self.transaction = false;
self.onCommits = [];
self.onCancels = [];
});
};
TransactionService.prototype.size = function () {
return this.onCommits.length;
};
return TransactionService;
});

View File

@ -30,7 +30,9 @@ define(
mockLog,
mockDomainObject,
mockType,
mockEditor,
actionContext,
capabilities,
action;
beforeEach(function () {
@ -40,7 +42,7 @@ define(
);
mockNavigationService = jasmine.createSpyObj(
"navigationService",
[ "setNavigation", "getNavigation" ]
[ "setNavigation", "getNavigation", "addListener", "removeListener" ]
);
mockLog = jasmine.createSpyObj(
"$log",
@ -48,14 +50,26 @@ define(
);
mockDomainObject = jasmine.createSpyObj(
"domainObject",
[ "getId", "getModel", "getCapability" ]
[ "getId", "getModel", "getCapability", "hasCapability", "useCapability" ]
);
mockType = jasmine.createSpyObj(
"type",
[ "hasFeature" ]
);
mockEditor = jasmine.createSpyObj(
"editorCapability",
["edit", "isEditContextRoot", "cancel"]
);
mockDomainObject.getCapability.andReturn(mockType);
capabilities = {
type: mockType,
editor: mockEditor
};
mockDomainObject.getCapability.andCallFake( function (name) {
return capabilities[name];
});
mockDomainObject.hasCapability.andReturn(true);
mockType.hasFeature.andReturn(true);
actionContext = { domainObject: mockDomainObject };
@ -68,51 +82,34 @@ define(
);
});
it("is only applicable when a domain object is present", function () {
it("is only applicable when an editable domain object is present", function () {
expect(EditAction.appliesTo(actionContext)).toBeTruthy();
expect(EditAction.appliesTo({})).toBeFalsy();
expect(mockDomainObject.hasCapability).toHaveBeenCalledWith('editor');
// Should have checked for creatability
expect(mockType.hasFeature).toHaveBeenCalledWith('creation');
});
//TODO: Disabled for NEM Beta
xit("changes URL path to edit mode when performed", function () {
it("is only applicable to objects not already in edit mode", function () {
mockEditor.isEditContextRoot.andReturn(false);
expect(EditAction.appliesTo(actionContext)).toBe(true);
mockEditor.isEditContextRoot.andReturn(true);
expect(EditAction.appliesTo(actionContext)).toBe(false);
});
it ("cancels editing when user navigates away", function () {
action.perform();
expect(mockLocation.path).toHaveBeenCalledWith("/edit");
expect(mockNavigationService.addListener).toHaveBeenCalled();
mockNavigationService.addListener.mostRecentCall.args[0]();
expect(mockEditor.cancel).toHaveBeenCalled();
});
//TODO: Disabled for NEM Beta
xit("ensures that the edited object is navigated-to", function () {
it ("invokes the Edit capability on the object", function () {
action.perform();
expect(mockNavigationService.setNavigation)
.toHaveBeenCalledWith(mockDomainObject);
expect(mockDomainObject.useCapability).toHaveBeenCalledWith("editor");
});
//TODO: Disabled for NEM Beta
xit("logs a warning if constructed when inapplicable", function () {
// Verify precondition (ensure warn wasn't called during setup)
expect(mockLog.warn).not.toHaveBeenCalled();
// Should not have hit an exception...
new EditAction(
mockLocation,
mockNavigationService,
mockLog,
{}
).perform();
// ...but should have logged a warning
expect(mockLog.warn).toHaveBeenCalled();
// And should not have had other interactions
expect(mockLocation.path)
.not.toHaveBeenCalled();
expect(mockNavigationService.setNavigation)
.not.toHaveBeenCalled();
});
});
}
);

View File

@ -52,7 +52,7 @@ define(
);
mockEditorCapability = jasmine.createSpyObj(
"editor",
[ "save", "cancel" ]
[ "save", "cancel", "isEditContextRoot" ]
);
mockActionCapability = jasmine.createSpyObj(
"actionCapability",
@ -71,7 +71,7 @@ define(
});
mockDomainObject.getModel.andReturn({persisted: 0});
mockEditorCapability.save.andReturn(mockPromise(true));
mockDomainObject.getOriginalObject.andReturn(mockDomainObject);
mockEditorCapability.isEditContextRoot.andReturn(true);
action = new SaveAction(actionContext);
@ -97,6 +97,13 @@ define(
action.perform();
expect(mockEditorCapability.save).toHaveBeenCalled();
});
it("navigates to the object after saving",
function () {
action.perform();
expect(mockActionCapability.perform).toHaveBeenCalledWith("navigate");
});
});
}
);

View File

@ -78,10 +78,11 @@ define(
mockEditorCapability = jasmine.createSpyObj(
"editor",
[ "save", "cancel" ]
[ "save", "cancel", "isEditContextRoot" ]
);
mockEditorCapability.cancel.andReturn(mockPromise(undefined));
mockEditorCapability.save.andReturn(mockPromise(true));
mockEditorCapability.isEditContextRoot.andReturn(true);
capabilities.editor = mockEditorCapability;
mockActionCapability = jasmine.createSpyObj(

View File

@ -1,73 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
define(
["../../src/capabilities/EditableCompositionCapability"],
function (EditableCompositionCapability) {
describe("An editable composition capability", function () {
var mockContext,
mockEditableObject,
mockDomainObject,
mockTestObject,
someValue,
mockFactory,
capability;
beforeEach(function () {
// EditableContextCapability should watch ALL
// methods for domain objects, so give it an
// arbitrary interface to wrap.
mockContext =
jasmine.createSpyObj("context", [ "getDomainObject" ]);
mockTestObject = jasmine.createSpyObj(
"domainObject",
[ "getId", "getModel", "getCapability" ]
);
mockFactory =
jasmine.createSpyObj("factory", ["getEditableObject"]);
someValue = { x: 42 };
mockContext.getDomainObject.andReturn(mockTestObject);
mockFactory.getEditableObject.andReturn(someValue);
capability = new EditableCompositionCapability(
mockContext,
mockEditableObject,
mockDomainObject,
mockFactory
);
});
// Most behavior is tested for EditableLookupCapability,
// so just verify that this isse
it("presumes non-idempotence of its wrapped capability", function () {
expect(capability.getDomainObject())
.toEqual(capability.getDomainObject());
expect(mockContext.getDomainObject.calls.length).toEqual(2);
});
});
}
);

View File

@ -1,87 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
define(
["../../src/capabilities/EditableContextCapability"],
function (EditableContextCapability) {
describe("An editable context capability", function () {
var mockContext,
mockEditableObject,
mockDomainObject,
mockTestObject,
someValue,
mockFactory,
capability;
beforeEach(function () {
// EditableContextCapability should watch ALL
// methods for domain objects, so give it an
// arbitrary interface to wrap.
mockContext =
jasmine.createSpyObj("context", [ "getDomainObject", "getRoot" ]);
mockTestObject = jasmine.createSpyObj(
"domainObject",
[ "getId", "getModel", "getCapability" ]
);
mockFactory = jasmine.createSpyObj(
"factory",
["getEditableObject", "isRoot"]
);
someValue = { x: 42 };
mockContext.getRoot.andReturn(mockTestObject);
mockContext.getDomainObject.andReturn(mockTestObject);
mockFactory.getEditableObject.andReturn(someValue);
mockFactory.isRoot.andReturn(true);
capability = new EditableContextCapability(
mockContext,
mockEditableObject,
mockDomainObject,
mockFactory
);
});
it("presumes idempotence of its wrapped capability", function () {
expect(capability.getDomainObject())
.toEqual(capability.getDomainObject());
expect(mockContext.getDomainObject.calls.length).toEqual(1);
});
it("hides the root object", function () {
expect(capability.getRoot()).toEqual(mockEditableObject);
expect(capability.getPath()).toEqual([mockEditableObject]);
});
it("exposes the root object through a different method", function () {
// Should still go through the factory...
expect(capability.getTrueRoot()).toEqual(someValue);
// ...with value of the unwrapped capability's getRoot
expect(mockFactory.getEditableObject)
.toHaveBeenCalledWith(mockTestObject);
});
});
}
);

View File

@ -1,144 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
define(
["../../src/capabilities/EditableLookupCapability"],
function (EditableLookupCapability) {
describe("An editable lookup capability", function () {
var mockContext,
mockEditableObject,
mockDomainObject,
mockTestObject,
someValue,
factory,
capability;
beforeEach(function () {
// EditableContextCapability should watch ALL
// methods for domain objects, so give it an
// arbitrary interface to wrap.
mockContext = jasmine.createSpyObj(
"context",
[
"getSomething",
"getDomainObject",
"getDomainObjectArray"
]
);
mockTestObject = jasmine.createSpyObj(
"domainObject",
[ "getId", "getModel", "getCapability" ]
);
factory = {
getEditableObject: function (v) {
return {
isFromTestFactory: true,
calledWith: v
};
}
};
someValue = { x: 42 };
mockContext.getSomething.andReturn(someValue);
mockContext.getDomainObject.andReturn(mockTestObject);
mockContext.getDomainObjectArray.andReturn([mockTestObject]);
capability = new EditableLookupCapability(
mockContext,
mockEditableObject,
mockDomainObject,
factory,
false
);
});
it("wraps retrieved domain objects", function () {
var object = capability.getDomainObject();
expect(object.isFromTestFactory).toBe(true);
expect(object.calledWith).toEqual(mockTestObject);
});
it("wraps retrieved domain object arrays", function () {
var object = capability.getDomainObjectArray()[0];
expect(object.isFromTestFactory).toBe(true);
expect(object.calledWith).toEqual(mockTestObject);
});
it("does not wrap non-domain-objects", function () {
expect(capability.getSomething()).toEqual(someValue);
});
it("caches idempotent lookups", function () {
capability = new EditableLookupCapability(
mockContext,
mockEditableObject,
mockDomainObject,
factory,
true // idempotent
);
expect(capability.getDomainObject())
.toEqual(capability.getDomainObject());
expect(mockContext.getDomainObject.calls.length).toEqual(1);
});
it("does not cache non-idempotent lookups", function () {
capability = new EditableLookupCapability(
mockContext,
mockEditableObject,
mockDomainObject,
factory,
false // Not idempotent
);
expect(capability.getDomainObject())
.toEqual(capability.getDomainObject());
expect(mockContext.getDomainObject.calls.length).toEqual(2);
});
it("wraps inherited methods", function () {
var CapabilityClass = function(){
};
CapabilityClass.prototype.inheritedMethod=function () {
return "an inherited method";
};
mockContext = new CapabilityClass();
capability = new EditableLookupCapability(
mockContext,
mockEditableObject,
mockDomainObject,
factory,
false
);
expect(capability.inheritedMethod()).toEqual("an inherited method");
expect(capability.hasOwnProperty('inheritedMethod')).toBe(true);
// The presence of an own property indicates that the method
// has been wrapped on the object itself and this is a valid
// test that the inherited method has been wrapped.
});
});
}
);

View File

@ -1,94 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
define(
["../../src/capabilities/EditablePersistenceCapability"],
function (EditablePersistenceCapability) {
describe("An editable persistence capability", function () {
var mockPersistence,
mockEditableObject,
mockDomainObject,
mockCache,
mockPromise,
capability;
beforeEach(function () {
mockPersistence = jasmine.createSpyObj(
"persistence",
[ "persist", "refresh" ]
);
mockEditableObject = jasmine.createSpyObj(
"editableObject",
[ "getId", "getModel", "getCapability" ]
);
mockDomainObject = jasmine.createSpyObj(
"editableObject",
[ "getId", "getModel", "getCapability" ]
);
mockCache = jasmine.createSpyObj(
"cache",
[ "markDirty" ]
);
mockPromise = jasmine.createSpyObj("promise", ["then"]);
mockCache.markDirty.andReturn(mockPromise);
mockDomainObject.getCapability.andReturn(mockPersistence);
capability = new EditablePersistenceCapability(
mockPersistence,
mockEditableObject,
mockDomainObject,
mockCache
);
});
it("marks objects as dirty (in the cache) upon persist", function () {
capability.persist();
expect(mockCache.markDirty)
.toHaveBeenCalledWith(mockEditableObject);
});
it("does not invoke the underlying persistence capability", function () {
capability.persist();
expect(mockPersistence.persist).not.toHaveBeenCalled();
});
it("refreshes using the original domain object's persistence", function () {
// Refreshing needs to delegate via the unwrapped domain object.
// Otherwise, only the editable version of the object will be updated;
// we instead want the real version of the object to receive these
// changes.
expect(mockDomainObject.getCapability).not.toHaveBeenCalled();
expect(mockPersistence.refresh).not.toHaveBeenCalled();
capability.refresh();
expect(mockDomainObject.getCapability).toHaveBeenCalledWith('persistence');
expect(mockPersistence.refresh).toHaveBeenCalled();
});
it("returns a promise from persist", function () {
expect(capability.persist().then).toEqual(jasmine.any(Function));
});
});
}
);

View File

@ -1,73 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
define(
["../../src/capabilities/EditableRelationshipCapability"],
function (EditableRelationshipCapability) {
describe("An editable relationship capability", function () {
var mockContext,
mockEditableObject,
mockDomainObject,
mockTestObject,
someValue,
mockFactory,
capability;
beforeEach(function () {
// EditableContextCapability should watch ALL
// methods for domain objects, so give it an
// arbitrary interface to wrap.
mockContext =
jasmine.createSpyObj("context", [ "getDomainObject" ]);
mockTestObject = jasmine.createSpyObj(
"domainObject",
[ "getId", "getModel", "getCapability" ]
);
mockFactory =
jasmine.createSpyObj("factory", ["getEditableObject"]);
someValue = { x: 42 };
mockContext.getDomainObject.andReturn(mockTestObject);
mockFactory.getEditableObject.andReturn(someValue);
capability = new EditableRelationshipCapability(
mockContext,
mockEditableObject,
mockDomainObject,
mockFactory
);
});
// Most behavior is tested for EditableLookupCapability,
// so just verify that this isse
it("presumes non-idempotence of its wrapped capability", function () {
expect(capability.getDomainObject())
.toEqual(capability.getDomainObject());
expect(mockContext.getDomainObject.calls.length).toEqual(2);
});
});
}
);

View File

@ -25,94 +25,149 @@ define(
function (EditorCapability) {
describe("The editor capability", function () {
var mockPersistence,
mockEditableObject,
mockDomainObject,
mockCache,
mockCallback,
model,
var mockDomainObject,
capabilities,
mockParentObject,
mockTransactionService,
mockStatusCapability,
mockParentStatus,
mockContextCapability,
capability;
beforeEach(function () {
mockPersistence = jasmine.createSpyObj(
"persistence",
[ "persist" ]
);
mockEditableObject = {
getModel: function () { return model; }
function fastPromise(val) {
return {
then: function (callback) {
return callback(val);
}
};
}
beforeEach(function () {
mockDomainObject = jasmine.createSpyObj(
"domainObject",
[ "getId", "getModel", "getCapability", "useCapability" ]
["getId", "getModel", "hasCapability", "getCapability", "useCapability"]
);
mockCache = jasmine.createSpyObj(
"cache",
[ "saveAll", "markClean" ]
mockParentObject = jasmine.createSpyObj(
"domainObject",
["getId", "getModel", "hasCapability", "getCapability", "useCapability"]
);
mockCallback = jasmine.createSpy("callback");
mockTransactionService = jasmine.createSpyObj(
"transactionService",
[
"startTransaction",
"size",
"commit",
"cancel"
]
);
mockTransactionService.commit.andReturn(fastPromise());
mockTransactionService.cancel.andReturn(fastPromise());
mockDomainObject.getCapability.andReturn(mockPersistence);
mockStatusCapability = jasmine.createSpyObj(
"statusCapability",
["get", "set"]
);
mockParentStatus = jasmine.createSpyObj(
"statusCapability",
["get", "set"]
);
mockContextCapability = jasmine.createSpyObj(
"contextCapability",
["getParent"]
);
mockContextCapability.getParent.andReturn(mockParentObject);
model = { someKey: "some value", x: 42 };
capabilities = {
context: mockContextCapability,
status: mockStatusCapability
};
mockDomainObject.hasCapability.andCallFake(function(name) {
return capabilities[name] !== undefined;
});
mockDomainObject.getCapability.andCallFake(function (name) {
return capabilities[name];
});
mockParentObject.getCapability.andReturn(mockParentStatus);
mockParentObject.hasCapability.andReturn(false);
capability = new EditorCapability(
mockPersistence,
mockEditableObject,
mockDomainObject,
mockCache
mockTransactionService,
mockDomainObject
);
});
//TODO: Disabled for NEM Beta
xit("mutates the real domain object on nonrecursive save", function () {
capability.save(true).then(mockCallback);
it("starts a transaction when edit is invoked", function () {
capability.edit();
expect(mockTransactionService.startTransaction).toHaveBeenCalled();
});
// Wait for promise to resolve
waitsFor(function () {
return mockCallback.calls.length > 0;
}, 250);
it("sets editing status on object", function () {
capability.edit();
expect(mockStatusCapability.set).toHaveBeenCalledWith("editing", true);
});
runs(function () {
expect(mockDomainObject.useCapability)
.toHaveBeenCalledWith("mutation", jasmine.any(Function));
// We should get the model from the editable object back
expect(
mockDomainObject.useCapability.mostRecentCall.args[1]()
).toEqual(model);
it("uses editing status to determine editing context root", function () {
capability.edit();
mockStatusCapability.get.andReturn(false);
expect(capability.isEditContextRoot()).toBe(false);
mockStatusCapability.get.andReturn(true);
expect(capability.isEditContextRoot()).toBe(true);
});
it("inEditingContext returns true if parent object is being" +
" edited", function () {
mockStatusCapability.get.andReturn(false);
mockParentStatus.get.andReturn(false);
expect(capability.inEditContext()).toBe(false);
mockParentStatus.get.andReturn(true);
expect(capability.inEditContext()).toBe(true);
});
describe("save", function() {
beforeEach(function() {
capability.edit();
capability.save();
});
it("commits the transaction", function () {
expect(mockTransactionService.commit).toHaveBeenCalled();
});
it("resets the edit state", function () {
expect(mockStatusCapability.set).toHaveBeenCalledWith('editing', false);
});
});
//TODO: Disabled for NEM Beta
xit("tells the cache to save others", function () {
capability.save().then(mockCallback);
// Wait for promise to resolve
waitsFor(function () {
return mockCallback.calls.length > 0;
}, 250);
runs(function () {
expect(mockCache.saveAll).toHaveBeenCalled();
describe("cancel", function() {
beforeEach(function() {
capability.edit();
capability.cancel();
});
it("cancels the transaction", function () {
expect(mockTransactionService.cancel).toHaveBeenCalled();
});
it("resets the edit state", function () {
expect(mockStatusCapability.set).toHaveBeenCalledWith('editing', false);
});
});
//TODO: Disabled for NEM Beta
xit("has no interactions on cancel", function () {
capability.cancel().then(mockCallback);
describe("dirty", function() {
var model = {};
// Wait for promise to resolve
waitsFor(function () {
return mockCallback.calls.length > 0;
}, 250);
runs(function () {
expect(mockDomainObject.useCapability).not.toHaveBeenCalled();
expect(mockCache.markClean).not.toHaveBeenCalled();
expect(mockCache.saveAll).not.toHaveBeenCalled();
beforeEach(function() {
mockDomainObject.getModel.andReturn(model);
capability.edit();
capability.cancel();
});
it("returns true if the object has been modified since it" +
" was last persisted", function () {
mockTransactionService.size.andReturn(0);
expect(capability.dirty()).toBe(false);
mockTransactionService.size.andReturn(1);
expect(capability.dirty()).toBe(true);
});
});
});
}
);

View File

@ -21,15 +21,34 @@
*****************************************************************************/
define(
["../../src/objects/EditableDomainObject"],
function (EditableDomainObject) {
[
"../../src/capabilities/TransactionalPersistenceCapability",
"../../src/capabilities/TransactionCapabilityDecorator"
],
function (TransactionalPersistenceCapability, TransactionCapabilityDecorator) {
describe("Editable domain object", function () {
var object;
describe("The transaction capability decorator", function () {
var mockQ,
mockTransactionService,
mockCapabilityService,
provider;
beforeEach(function() {
mockQ = {};
mockTransactionService = {};
mockCapabilityService = jasmine.createSpyObj("capabilityService", ["getCapabilities"]);
mockCapabilityService.getCapabilities.andReturn({
persistence: function() {}
});
provider = new TransactionCapabilityDecorator(mockQ, mockTransactionService, mockCapabilityService);
beforeEach(function () {
object = new EditableDomainObject();
});
it("decorates the persistence capability", function() {
var capabilities = provider.getCapabilities();
expect(capabilities.persistence({}) instanceof TransactionalPersistenceCapability).toBe(true);
});
});
}
);

View File

@ -0,0 +1,92 @@
/*****************************************************************************
* 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*/
define(
[
"../../src/capabilities/TransactionalPersistenceCapability"
],
function (TransactionalPersistenceCapability) {
function fastPromise(val) {
return {
then: function(callback) {
return callback(val);
}
};
}
describe("The transactional persistence decorator", function () {
var mockQ,
mockTransactionService,
mockPersistence,
mockDomainObject,
capability;
beforeEach(function() {
mockQ = jasmine.createSpyObj("$q", ["when"]);
mockQ.when.andCallFake(function (val) {
return fastPromise(val);
});
mockTransactionService = jasmine.createSpyObj(
"transactionService",
["isActive", "addToTransaction"]
);
mockPersistence = jasmine.createSpyObj(
"persistenceCapability",
["persist", "refresh"]
);
mockPersistence.persist.andReturn(fastPromise());
mockPersistence.refresh.andReturn(fastPromise());
capability = new TransactionalPersistenceCapability(mockQ, mockTransactionService, mockPersistence, mockDomainObject);
});
it("if no transaction is active, passes through to persistence" +
" provider", function() {
mockTransactionService.isActive.andReturn(false);
capability.persist();
expect(mockPersistence.persist).toHaveBeenCalled();
});
it("if transaction is active, persist and cancel calls are" +
" queued", function() {
mockTransactionService.isActive.andReturn(true);
capability.persist();
expect(mockTransactionService.addToTransaction).toHaveBeenCalled();
mockTransactionService.addToTransaction.mostRecentCall.args[0]();
expect(mockPersistence.persist).toHaveBeenCalled();
mockTransactionService.addToTransaction.mostRecentCall.args[1]();
expect(mockPersistence.refresh).toHaveBeenCalled();
});
it("persist call is only added to transaction once", function() {
mockTransactionService.isActive.andReturn(true);
capability.persist();
expect(mockTransactionService.addToTransaction).toHaveBeenCalled();
capability.persist();
expect(mockTransactionService.addToTransaction.calls.length).toBe(1);
});
});
}
);

View File

@ -1,177 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
define(
["../../src/objects/EditableDomainObjectCache"],
function (EditableDomainObjectCache) {
describe("Editable domain object cache", function () {
var captured,
completionCapability,
mockQ,
mockType,
cache;
// Constructors for test objects
function TestObject(id) {
return {
getId: function () { return id; },
getModel: function () { return {}; },
getCapability: function (key) {
return {
editor: completionCapability,
type: mockType
}[key];
},
hasCapability: function () {
return false;
}
};
}
function WrapObject(domainObject, model) {
var result = Object.create(domainObject);
result.wrapped = true;
result.wrappedModel = model;
result.hasCapability = function (name) {
return name === 'editor';
};
captured.wraps = (captured.wraps || 0) + 1;
return result;
}
beforeEach(function () {
mockQ = jasmine.createSpyObj('$q', ['when', 'all']);
mockType = jasmine.createSpyObj('type', ['hasFeature']);
mockType.hasFeature.andReturn(true);
captured = {};
completionCapability = {
save: function () {
captured.saved = (captured.saved || 0) + 1;
}
};
cache = new EditableDomainObjectCache(WrapObject, mockQ);
});
it("wraps objects using provided constructor", function () {
var domainObject = new TestObject('test-id'),
wrappedObject = cache.getEditableObject(domainObject);
expect(wrappedObject.wrapped).toBeTruthy();
expect(wrappedObject.getId()).toEqual(domainObject.getId());
});
it("wraps objects repeatedly, wraps models once", function () {
var domainObject = new TestObject('test-id'),
wrappedObjects = [];
// Verify precondition
expect(captured.wraps).toBeUndefined();
// Invoke a few more times; expect count not to increment
wrappedObjects.push(cache.getEditableObject(domainObject));
expect(captured.wraps).toEqual(1);
wrappedObjects.push(cache.getEditableObject(domainObject));
expect(captured.wraps).toEqual(2);
wrappedObjects.push(cache.getEditableObject(domainObject));
expect(captured.wraps).toEqual(3);
// Verify that the last call still gave us a wrapped object
expect(wrappedObjects[0].wrapped).toBeTruthy();
expect(wrappedObjects[0].getId()).toEqual(domainObject.getId());
// Verify that objects are distinct but models are identical
expect(wrappedObjects[0].wrappedModel)
.toBe(wrappedObjects[1].wrappedModel);
expect(wrappedObjects[0]).not
.toBe(wrappedObjects[1]);
});
it("saves objects that have been marked dirty", function () {
var objects = ['a', 'b', 'c'].map(TestObject).map(function (domainObject) {
return cache.getEditableObject(domainObject);
});
cache.markDirty(objects[0]);
cache.markDirty(objects[2]);
cache.saveAll();
expect(captured.saved).toEqual(2);
});
it("does not save objects that have been marked clean", function () {
var objects = ['a', 'b', 'c'].map(TestObject).map(function (domainObject) {
return cache.getEditableObject(domainObject);
});
cache.markDirty(objects[0]);
cache.markDirty(objects[2]);
cache.markClean(objects[0]);
cache.saveAll();
expect(captured.saved).toEqual(1);
});
it("tracks the root object of the Edit mode subgraph", function () {
// Root object is the first object exposed to the cache
var domainObjects = ['a', 'b', 'c'].map(TestObject);
domainObjects.forEach(function (obj) {
cache.getEditableObject(obj);
});
expect(cache.isRoot(domainObjects[0])).toBeTruthy();
expect(cache.isRoot(domainObjects[1])).toBeFalsy();
expect(cache.isRoot(domainObjects[2])).toBeFalsy();
});
it("does not double-wrap objects", function () {
var domainObject = new TestObject('test-id'),
wrappedObject = cache.getEditableObject(domainObject);
// Same instance should be returned if you try to wrap
// twice. This is necessary, since it's possible to (e.g.)
// use a context capability on an object retrieved via
// composition, in which case a result will already be
// wrapped.
expect(cache.getEditableObject(wrappedObject))
.toBe(wrappedObject);
});
it("does not wrap non-editable objects", function () {
var domainObject = new TestObject('test-id');
mockType.hasFeature.andCallFake(function (key) {
return key !== 'creation';
});
expect(cache.getEditableObject(domainObject))
.toBe(domainObject);
});
});
}
);

View File

@ -1,79 +0,0 @@
/*****************************************************************************
* 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.
*****************************************************************************/
define(
["../../src/objects/EditableModelCache"],
function (EditableModelCache) {
describe("The editable model cache", function () {
var mockObject,
mockOtherObject,
testModel,
testId,
otherModel,
otherId,
cache;
beforeEach(function () {
testId = "test";
testModel = { someKey: "some value" };
otherId = "other";
otherModel = { someKey: "some other value" };
mockObject = jasmine.createSpyObj(
"domainObject",
[ "getId", "getModel" ]
);
mockOtherObject = jasmine.createSpyObj(
"domainObject",
[ "getId", "getModel" ]
);
mockObject.getId.andReturn(testId);
mockObject.getModel.andReturn(testModel);
mockOtherObject.getId.andReturn(otherId);
mockOtherObject.getModel.andReturn(otherModel);
cache = new EditableModelCache();
});
it("provides clones of domain object models", function () {
var model = cache.getCachedModel(mockObject);
// Should be identical...
expect(model).toEqual(testModel);
// ...but not pointer-identical
expect(model).not.toBe(testModel);
});
it("provides only one clone per object", function () {
var model = cache.getCachedModel(mockObject);
expect(cache.getCachedModel(mockObject)).toBe(model);
});
it("maintains separate caches per-object", function () {
expect(cache.getCachedModel(mockObject))
.not.toEqual(cache.getCachedModel(mockOtherObject));
});
});
}
);

View File

@ -34,7 +34,7 @@ define(
mockEditAction,
mockPropertiesAction,
mockTypeCapability,
mockStatusCapability,
mockEditorCapability,
capabilities,
plotView,
policy;
@ -48,11 +48,10 @@ define(
'getCapability'
]
);
mockStatusCapability = jasmine.createSpyObj('statusCapability', ['get']);
mockStatusCapability.get.andReturn(false);
mockEditorCapability = jasmine.createSpyObj('editorCapability', ['isEditContextRoot']);
mockTypeCapability = jasmine.createSpyObj('type', ['getKey']);
capabilities = {
'status': mockStatusCapability,
'editor': mockEditorCapability,
'type': mockTypeCapability
};
@ -112,7 +111,7 @@ define(
it("disallows the edit action when object is already being" +
" edited", function () {
testViews = [ editableView ];
mockStatusCapability.get.andReturn(true);
mockEditorCapability.isEditContextRoot.andReturn(true);
expect(policy.allow(mockEditAction, testContext)).toBe(false);
});

View File

@ -32,16 +32,22 @@ define(
context,
navigatedObject,
mockDomainObject,
mockEditorCapability,
metadata,
editModeBlacklist = ["copy", "follow", "window", "link", "locate"],
nonEditContextBlacklist = ["copy", "follow", "properties", "move", "link", "remove", "locate"];
beforeEach(function () {
navigatedObject = jasmine.createSpyObj("navigatedObject", ["hasCapability"]);
mockEditorCapability = jasmine.createSpyObj("editorCapability", ["isEditContextRoot", "inEditContext"]);
navigatedObject = jasmine.createSpyObj("navigatedObject", ["hasCapability", "getCapability"]);
navigatedObject.getCapability.andReturn(mockEditorCapability);
navigatedObject.hasCapability.andReturn(false);
mockDomainObject = jasmine.createSpyObj("domainObject", ["hasCapability", "getCapability"]);
mockDomainObject.hasCapability.andReturn(false);
mockDomainObject.getCapability.andReturn(mockEditorCapability);
navigationService = jasmine.createSpyObj("navigationService", ["getNavigation"]);
navigationService.getNavigation.andReturn(navigatedObject);
@ -62,6 +68,7 @@ define(
it('Allows "window" action when navigated object in edit mode,' +
' but selected object not in edit mode ', function() {
navigatedObject.hasCapability.andReturn(true);
mockEditorCapability.isEditContextRoot.andReturn(true);
metadata.key = "window";
expect(policy.allow(mockAction, context)).toBe(true);
});
@ -91,6 +98,8 @@ define(
it('Disallows "move" action when navigated object in edit mode,' +
' but selected object not in edit mode ', function() {
navigatedObject.hasCapability.andReturn(true);
mockEditorCapability.isEditContextRoot.andReturn(true);
mockEditorCapability.inEditContext.andReturn(false);
metadata.key = "move";
expect(policy.allow(mockAction, context)).toBe(false);
});
@ -99,6 +108,9 @@ define(
' selected object in edit mode', function() {
navigatedObject.hasCapability.andReturn(true);
mockDomainObject.hasCapability.andReturn(true);
mockEditorCapability.isEditContextRoot.andReturn(true);
mockEditorCapability.inEditContext.andReturn(true);
metadata.key = "copy";
expect(policy.allow(mockAction, context)).toBe(false);
});

View File

@ -33,8 +33,13 @@ define(
testMode = true; // Act as if we're in Edit mode by default
mockDomainObject = jasmine.createSpyObj(
'domainObject',
['hasCapability']
['hasCapability', 'getCapability']
);
mockDomainObject.getCapability.andReturn({
inEditContext: function () {
return true;
}
});
mockDomainObject.hasCapability.andCallFake(function (c) {
return (c === 'editor') && testMode;
});

View File

@ -32,6 +32,7 @@ define(
mockDomainObject,
mockPersistence,
mockStatusCapability,
mockEditorCapability,
mockCapabilities,
representer;
@ -58,11 +59,14 @@ define(
mockPersistence =
jasmine.createSpyObj("persistence", ["persist"]);
mockStatusCapability =
jasmine.createSpyObj("statusCapability", ["get", "listen"]);
mockStatusCapability.get.andReturn(false);
jasmine.createSpyObj("statusCapability", ["listen"]);
mockEditorCapability =
jasmine.createSpyObj("editorCapability", ["isEditContextRoot"]);
mockCapabilities = {
'persistence': mockPersistence,
'status': mockStatusCapability
'status': mockStatusCapability,
'editor': mockEditorCapability
};
mockDomainObject.getModel.andReturn({});
@ -82,6 +86,7 @@ define(
it("Sets edit view template on edit mode", function () {
mockStatusCapability.listen.mostRecentCall.args[0](['editing']);
mockEditorCapability.isEditContextRoot.andReturn(true);
expect(mockScope.viewObjectTemplate).toEqual('edit-object');
});

View File

@ -0,0 +1,137 @@
/*****************************************************************************
* 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*/
define(
["../../src/services/TransactionService"],
function (TransactionService) {
describe("The Transaction Service", function () {
var mockQ,
mockLog,
transactionService;
function fastPromise (val) {
return {
then: function (callback) {
return fastPromise(callback(val));
}
};
}
beforeEach(function () {
mockQ = jasmine.createSpyObj("$q", ["all"]);
mockQ.all.andReturn(fastPromise());
mockLog = jasmine.createSpyObj("$log", ["error"]);
transactionService = new TransactionService(mockQ, mockLog);
});
it("isActive returns true if a transaction is in progress", function () {
expect(transactionService.isActive()).toBe(false);
transactionService.startTransaction();
expect(transactionService.isActive()).toBe(true);
});
it("addToTransaction queues onCommit and onCancel functions", function () {
var onCommit = jasmine.createSpy('onCommit'),
onCancel = jasmine.createSpy('onCancel');
transactionService.startTransaction();
transactionService.addToTransaction(onCommit, onCancel);
expect(transactionService.onCommits.length).toBe(1);
expect(transactionService.onCancels.length).toBe(1);
});
it("size function returns size of commit and cancel queues", function () {
var onCommit = jasmine.createSpy('onCommit'),
onCancel = jasmine.createSpy('onCancel');
transactionService.startTransaction();
transactionService.addToTransaction(onCommit, onCancel);
transactionService.addToTransaction(onCommit, onCancel);
transactionService.addToTransaction(onCommit, onCancel);
expect(transactionService.size()).toBe(3);
});
describe("commit", function () {
var onCommits;
beforeEach(function() {
onCommits = [0, 1, 2].map(function(val) {
return jasmine.createSpy("onCommit" + val);
});
transactionService.startTransaction();
onCommits.forEach(transactionService.addToTransaction.bind(transactionService));
});
it("commit calls all queued commit functions", function () {
expect(transactionService.onCommits.length).toBe(3);
transactionService.commit();
onCommits.forEach( function (spy) {
expect(spy).toHaveBeenCalled();
});
});
it("commit resets active state and clears queues", function () {
transactionService.commit();
expect(transactionService.isActive()).toBe(false);
expect(transactionService.onCommits.length).toBe(0);
expect(transactionService.onCancels.length).toBe(0);
});
});
describe("cancel", function () {
var onCancels;
beforeEach(function() {
onCancels = [0, 1, 2].map(function(val) {
return jasmine.createSpy("onCancel" + val);
});
transactionService.startTransaction();
onCancels.forEach(function (onCancel) {
transactionService.addToTransaction(undefined, onCancel);
});
});
it("cancel calls all queued cancel functions", function () {
expect(transactionService.onCancels.length).toBe(3);
transactionService.cancel();
onCancels.forEach( function (spy) {
expect(spy).toHaveBeenCalled();
});
});
it("cancel resets active state and clears queues", function () {
transactionService.cancel();
expect(transactionService.isActive()).toBe(false);
expect(transactionService.onCommits.length).toBe(0);
expect(transactionService.onCancels.length).toBe(0);
});
});
});
}
);

View File

@ -39,7 +39,7 @@ define(
if (!regionPart.modes){
return true;
}
if (domainObject.getCapability('status').get('editing')){
if (domainObject.hasCapability('editor') && domainObject.getCapability('editor').inEditContext()){
//If the domain object is in edit mode, only include a part
// if it is marked editable
return regionPart.modes.indexOf('edit') !== -1;

View File

@ -46,7 +46,11 @@ define(
});
}
$scope.regions = filterRegions(typeCapability.getDefinition().inspector || new InspectorRegion());
function setRegions() {
$scope.regions = filterRegions(typeCapability.getDefinition().inspector || new InspectorRegion());
}
setRegions();
}
return InspectorController;

View File

@ -28,7 +28,7 @@ define(
var editableRegionPolicy,
mockDomainObject,
mockStatusCapability,
mockEditorCapability,
mockBrowseRegionPart = {
modes: 'browse'
},
@ -40,31 +40,32 @@ define(
beforeEach(function(){
editableRegionPolicy = new EditableRegionPolicy();
mockStatusCapability = jasmine.createSpyObj("statusCapability", [
"get"
mockEditorCapability = jasmine.createSpyObj("editorCapability", [
"inEditContext"
]);
mockDomainObject = jasmine.createSpyObj("domainObject", [
"getCapability"
"hasCapability", "getCapability"
]);
mockDomainObject.getCapability.andReturn(mockStatusCapability);
mockDomainObject.hasCapability.andReturn(true);
mockDomainObject.getCapability.andReturn(mockEditorCapability);
});
it("includes only browse region parts for object not in edit mode", function() {
mockStatusCapability.get.andReturn(false);
mockEditorCapability.inEditContext.andReturn(false);
expect(editableRegionPolicy.allow(mockBrowseRegionPart, mockDomainObject)).toBe(true);
expect(editableRegionPolicy.allow(mockEditRegionPart, mockDomainObject)).toBe(false);
});
it("includes only edit region parts for object in edit mode", function() {
mockStatusCapability.get.andReturn(true);
mockEditorCapability.inEditContext.andReturn(true);
expect(editableRegionPolicy.allow(mockBrowseRegionPart, mockDomainObject)).toBe(false);
expect(editableRegionPolicy.allow(mockEditRegionPart, mockDomainObject)).toBe(true);
});
it("includes region parts with no mode specification", function() {
mockStatusCapability.get.andReturn(false);
mockEditorCapability.inEditContext.andReturn(false);
expect(editableRegionPolicy.allow(mockAllModesRegionPart, mockDomainObject)).toBe(true);
mockStatusCapability.get.andReturn(true);
mockEditorCapability.inEditContext.andReturn(true);
expect(editableRegionPolicy.allow(mockAllModesRegionPart, mockDomainObject)).toBe(true);
});

View File

@ -60,16 +60,6 @@ define(
this.$q = $q;
}
// Utility function for creating promise-like objects which
// resolve synchronously when possible
function fastPromise(value) {
return (value || {}).then ? value : {
then: function (callback) {
return fastPromise(callback(value));
}
};
}
function getKey(id) {
var parts = id.split(":");
return parts.length > 1 ? parts.slice(1).join(":") : id;
@ -157,8 +147,7 @@ define(
* when the update is complete
*/
PersistenceCapability.prototype.refresh = function () {
var domainObject = this.domainObject,
model = domainObject.getModel();
var domainObject = this.domainObject;
// Update a domain object's model upon refresh
function updateModel(model) {
@ -168,13 +157,10 @@ define(
}, modified);
}
// Only update if we don't have unsaved changes
return (model.modified === model.persisted) ?
this.persistenceService.readObject(
return this.persistenceService.readObject(
this.getSpace(),
this.domainObject.getId()
).then(updateModel) :
fastPromise(false);
).then(updateModel);
};
/**

View File

@ -155,18 +155,6 @@ define(
expect(model).toEqual(refreshModel);
});
it("does not overwrite unpersisted changes on refresh", function () {
var refreshModel = {someOtherKey: "some other value"},
mockCallback = jasmine.createSpy();
model.modified = 2;
model.persisted = 1;
mockPersistenceService.readObject.andReturn(asPromise(refreshModel));
persistence.refresh().then(mockCallback);
expect(model).not.toEqual(refreshModel);
// Should have also indicated that no changes were actually made
expect(mockCallback).toHaveBeenCalledWith(false);
});
it("does not trigger error notification on successful" +
" persistence", function () {
persistence.persist();

View File

@ -57,7 +57,8 @@ define(
function storeZoom() {
var isEditMode = $scope.commit &&
$scope.domainObject &&
$scope.domainObject.hasCapability('editor');
$scope.domainObject.hasCapability('editor') &&
$scope.domainObject.getCapability('editor').inEditContext();
if (isEditMode) {
$scope.configuration = $scope.configuration || {};
$scope.configuration.zoomLevel = zoomIndex;

View File

@ -39,9 +39,10 @@ define(
};
}
// Check if we are in edit mode
// Check if we are in edit mode (also check parents)
function inEditMode() {
return swimlane.domainObject.hasCapability("editor");
return swimlane.domainObject.hasCapability('editor') &&
swimlane.domainObject.getCapability('editor').inEditContext();
}
// Boolean and (for reduce below)

View File

@ -82,11 +82,18 @@ define(
it("persists zoom changes in Edit mode", function () {
mockScope.domainObject = jasmine.createSpyObj(
'domainObject',
['hasCapability']
['hasCapability', 'getCapability']
);
mockScope.domainObject.hasCapability.andCallFake(function (c) {
return c === 'editor';
});
mockScope.domainObject.getCapability.andCallFake(function (c) {
if (c === 'editor') {
return {
inEditContext: function () {return true;}
};
}
});
controller.zoom(1);
expect(mockScope.commit).toHaveBeenCalled();
expect(mockScope.configuration.zoomLevel)

View File

@ -28,6 +28,7 @@ define(
var mockSwimlane,
mockOtherObject,
mockActionCapability,
mockEditorCapability,
mockPersistence,
mockContext,
mockAction,
@ -36,6 +37,8 @@ define(
beforeEach(function () {
var mockPromise = jasmine.createSpyObj('promise', ['then']);
mockEditorCapability = jasmine.createSpyObj('editorCapability', ['inEditContext']);
mockSwimlane = jasmine.createSpyObj(
"swimlane",
[ "highlight", "highlightBottom" ]
@ -86,19 +89,22 @@ define(
mockSwimlane.domainObject.getCapability.andCallFake(function (c) {
return {
action: mockActionCapability,
persistence: mockPersistence
persistence: mockPersistence,
editor: mockEditorCapability
}[c];
});
mockSwimlane.parent.domainObject.getCapability.andCallFake(function (c) {
return {
action: mockActionCapability,
persistence: mockPersistence
persistence: mockPersistence,
editor: mockEditorCapability
}[c];
});
mockOtherObject.getCapability.andCallFake(function (c) {
return {
action: mockActionCapability,
context: mockContext
context: mockContext,
editor: mockEditorCapability
}[c];
});
mockContext.getParent.andReturn(mockOtherObject);
@ -109,13 +115,14 @@ define(
});
it("disallows drop outside of edit mode", function () {
mockEditorCapability.inEditContext.andReturn(true);
// Verify precondition
expect(handler.allowDropIn('d', mockSwimlane.domainObject))
.toBeTruthy();
expect(handler.allowDropAfter('d', mockSwimlane.domainObject))
.toBeTruthy();
// Act as if we're not in edit mode
mockSwimlane.domainObject.hasCapability.andReturn(false);
mockEditorCapability.inEditContext.andReturn(false);
// Now, they should be disallowed
expect(handler.allowDropIn('d', mockSwimlane.domainObject))
.toBeFalsy();

View File

@ -86,10 +86,7 @@ define([
"implementation": DropGesture,
"depends": [
"dndService",
"$q",
"navigationService",
"instantiate",
"typeService"
"$q"
]
},
{

View File

@ -91,6 +91,7 @@ define(
couldEdit = false,
lastIdPath = [],
lastKey,
statusListener,
changeTemplate = templateLinker.link($scope, element);
// Populate scope with any capabilities indicated by the
@ -167,7 +168,7 @@ define(
representation = lookup($scope.key, domainObject),
uses = ((representation || {}).uses || []),
canRepresent = !!(representation && domainObject),
canEdit = !!(domainObject && domainObject.hasCapability('editor')),
canEdit = !!(domainObject && domainObject.hasCapability('editor') && domainObject.getCapability('editor').inEditContext()),
idPath = getIdPath(domainObject),
key = $scope.key;
@ -239,6 +240,25 @@ define(
// (to a different object)
$scope.$watch("domainObject", refresh);
function listenForStatusChange(object) {
if (statusListener) {
statusListener();
}
statusListener = object.getCapability("status").listen(refresh);
}
/**
* Add a listener to the object for status changes.
*/
$scope.$watch("domainObject", function (domainObject, oldDomainObject) {
if (domainObject !== oldDomainObject){
listenForStatusChange(domainObject);
}
});
if ($scope.domainObject) {
listenForStatusChange($scope.domainObject);
}
// Finally, also update when there is a new version of that
// same domain object; these changes should be tracked in the
// model's "modified" field, by the mutation capability.
@ -247,6 +267,11 @@ define(
// Make sure any resources allocated by representers also get
// released.
$scope.$on("$destroy", destroyRepresenters);
$scope.$on("$destroy", function () {
if (statusListener) {
statusListener();
}
});
// Do one initial refresh, so that we don't need another
// digest iteration just to populate the scope. Failure to

View File

@ -24,9 +24,8 @@
* Module defining DropGesture. Created by vwoeltje on 11/17/14.
*/
define(
['./GestureConstants',
'../../../commonUI/edit/src/objects/EditableDomainObject'],
function (GestureConstants, EditableDomainObject) {
['./GestureConstants'],
function (GestureConstants) {
/**
* A DropGesture adds and maintains event handlers upon an element
@ -39,9 +38,8 @@ define(
* @param {DomainObject} domainObject the domain object whose
* composition should be modified as a result of the drop.
*/
function DropGesture(dndService, $q, navigationService, instantiate, typeService, element, domainObject) {
function DropGesture(dndService, $q, element, domainObject) {
var actionCapability = domainObject.getCapability('action'),
editableDomainObject,
scope = element.scope && element.scope(),
action; // Action for the drop, when it occurs
@ -66,24 +64,12 @@ define(
x: event.pageX - rect.left,
y: event.pageY - rect.top
},
editableDomainObject
domainObject
);
}
}
function dragOver(e) {
//Refresh domain object on each dragOver to catch external
// updates to the model
//Don't use EditableDomainObject for folders, allow immediate persistence
if (domainObject.hasCapability('editor') ||
domainObject.getModel().type==='folder') {
editableDomainObject = domainObject;
} else {
editableDomainObject = new EditableDomainObject(domainObject, $q);
}
actionCapability = editableDomainObject.getCapability('action');
var event = (e || {}).originalEvent || e,
selectedObject = dndService.getData(
GestureConstants.MCT_EXTENDED_DRAG_TYPE
@ -108,20 +94,24 @@ define(
function drop(e) {
var event = (e || {}).originalEvent || e,
id = event.dataTransfer.getData(GestureConstants.MCT_DRAG_TYPE),
domainObjectType = editableDomainObject.getModel().type;
domainObjectType = domainObject.getModel().type;
// Handle the drop; add the dropped identifier to the
// destination domain object's composition, and persist
// the change.
if (id) {
e.preventDefault();
$q.when(action && action.perform()).then(function (result) {
//Don't go into edit mode for folders
if (domainObjectType!=='folder') {
editableDomainObject.getCapability('action').perform('edit');
//Use scope.apply, drop event is outside digest cycle
scope.$apply(function () {
if (domainObjectType !== 'folder') {
domainObject.getCapability('action').perform('edit');
}
});
$q.when(action && action.perform()).then(function () {
broadcastDrop(id, event);
});
}
}

View File

@ -36,6 +36,7 @@ define(
testViews,
testUrls,
mockRepresenters,
mockStatusCapability,
mockQ,
mockLinker,
mockLog,
@ -118,6 +119,8 @@ define(
mockChangeTemplate = jasmine.createSpy('changeTemplate');
mockLog = jasmine.createSpyObj("$log", LOG_FUNCTIONS);
mockStatusCapability = jasmine.createSpyObj("statusCapability", ["listen"]);
mockScope = jasmine.createSpyObj("scope", [ "$watch", "$on" ]);
mockElement = jasmine.createSpyObj("element", JQLITE_FUNCTIONS);
mockDomainObject = jasmine.createSpyObj("domainObject", DOMAIN_OBJECT_METHODS);
@ -128,6 +131,10 @@ define(
return testUrls[ext.key];
});
mockDomainObject.getCapability.andCallFake(function (c) {
return c === 'status' && mockStatusCapability;
});
mctRepresentation = new MCTRepresentation(
testRepresentations,
testViews,
@ -229,7 +236,7 @@ define(
expect(mockLog.warn).toHaveBeenCalled();
});
it("clears out obsolete peroperties from scope", function () {
it("clears out obsolete properties from scope", function () {
mockScope.key = "def";
mockScope.domainObject = mockDomainObject;
mockDomainObject.useCapability.andReturn("some value");
@ -246,6 +253,21 @@ define(
expect(mockScope.testCapability).toBeUndefined();
});
it("registers a status change listener", function () {
mockScope.$watch.calls[2].args[1](mockDomainObject);
expect(mockStatusCapability.listen).toHaveBeenCalled();
});
it("unlistens for status change on scope destruction", function () {
var mockUnlistener = jasmine.createSpy("unlisten");
mockStatusCapability.listen.andReturn(mockUnlistener);
mockScope.$watch.calls[2].args[1](mockDomainObject);
expect(mockStatusCapability.listen).toHaveBeenCalled();
mockScope.$on.calls[1].args[1]();
expect(mockUnlistener).toHaveBeenCalled();
});
describe("when a domain object has been observed", function () {
var mockContext,
mockContext2,
@ -307,6 +329,7 @@ define(
mockScope.$watch.calls[0].args[1]();
expect(mockChangeTemplate.calls.length).toEqual(callCount);
});
});

View File

@ -120,8 +120,8 @@ define([
provider = this;
mutationTopic.listen(function (mutatedObject) {
var status = mutatedObject.getCapability('status');
if (!status || !status.get('editing')) {
var editor = mutatedObject.getCapability('editor');
if (!editor || !editor.inEditContext()) {
provider.index(
mutatedObject.getId(),
mutatedObject.getModel()