diff --git a/platform/commonUI/browse/src/creation/CreateAction.js b/platform/commonUI/browse/src/creation/CreateAction.js index 00b7c09fa4..9ac86d2dc8 100644 --- a/platform/commonUI/browse/src/creation/CreateAction.js +++ b/platform/commonUI/browse/src/creation/CreateAction.js @@ -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(); + }); } }; diff --git a/platform/commonUI/edit/bundle.js b/platform/commonUI/edit/bundle.js index 8eac4d5012..fb357c0934 100644 --- a/platform/commonUI/edit/bundle.js +++ b/platform/commonUI/edit/bundle.js @@ -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" + ] + } + ], } }); }); diff --git a/platform/commonUI/edit/src/actions/CancelAction.js b/platform/commonUI/edit/src/actions/CancelAction.js index da3fea8046..77013ee8f5 100644 --- a/platform/commonUI/edit/src/actions/CancelAction.js +++ b/platform/commonUI/edit/src/actions/CancelAction.js @@ -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; diff --git a/platform/commonUI/edit/src/actions/EditAction.js b/platform/commonUI/edit/src/actions/EditAction.js index 17fd34156c..acb83c0407 100644 --- a/platform/commonUI/edit/src/actions/EditAction.js +++ b/platform/commonUI/edit/src/actions/EditAction.js @@ -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; diff --git a/platform/commonUI/edit/src/actions/SaveAction.js b/platform/commonUI/edit/src/actions/SaveAction.js index 3c5bb86e77..3879685b9f 100644 --- a/platform/commonUI/edit/src/actions/SaveAction.js +++ b/platform/commonUI/edit/src/actions/SaveAction.js @@ -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; }; diff --git a/platform/commonUI/edit/src/actions/SaveAsAction.js b/platform/commonUI/edit/src/actions/SaveAsAction.js index 7e52a3f486..f347cad899 100644 --- a/platform/commonUI/edit/src/actions/SaveAsAction.js +++ b/platform/commonUI/edit/src/actions/SaveAsAction.js @@ -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; }; diff --git a/platform/commonUI/edit/src/capabilities/EditableActionCapability.js b/platform/commonUI/edit/src/capabilities/EditableActionCapability.js deleted file mode 100644 index bdfa4d3f59..0000000000 --- a/platform/commonUI/edit/src/capabilities/EditableActionCapability.js +++ /dev/null @@ -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; - } -); diff --git a/platform/commonUI/edit/src/capabilities/EditableCompositionCapability.js b/platform/commonUI/edit/src/capabilities/EditableCompositionCapability.js deleted file mode 100644 index 343c6a03a2..0000000000 --- a/platform/commonUI/edit/src/capabilities/EditableCompositionCapability.js +++ /dev/null @@ -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 - ); - }; - } -); diff --git a/platform/commonUI/edit/src/capabilities/EditableContextCapability.js b/platform/commonUI/edit/src/capabilities/EditableContextCapability.js deleted file mode 100644 index d20971fb04..0000000000 --- a/platform/commonUI/edit/src/capabilities/EditableContextCapability.js +++ /dev/null @@ -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; - }; - } -); diff --git a/platform/commonUI/edit/src/capabilities/EditableInstantiationCapability.js b/platform/commonUI/edit/src/capabilities/EditableInstantiationCapability.js deleted file mode 100644 index 4376a9310e..0000000000 --- a/platform/commonUI/edit/src/capabilities/EditableInstantiationCapability.js +++ /dev/null @@ -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 - ); - }; - } -); diff --git a/platform/commonUI/edit/src/capabilities/EditableLookupCapability.js b/platform/commonUI/edit/src/capabilities/EditableLookupCapability.js deleted file mode 100644 index 0abde97c5a..0000000000 --- a/platform/commonUI/edit/src/capabilities/EditableLookupCapability.js +++ /dev/null @@ -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; - }; - } -); diff --git a/platform/commonUI/edit/src/capabilities/EditablePersistenceCapability.js b/platform/commonUI/edit/src/capabilities/EditablePersistenceCapability.js deleted file mode 100644 index e6c32e2bf4..0000000000 --- a/platform/commonUI/edit/src/capabilities/EditablePersistenceCapability.js +++ /dev/null @@ -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; - } -); diff --git a/platform/commonUI/edit/src/capabilities/EditableRelationshipCapability.js b/platform/commonUI/edit/src/capabilities/EditableRelationshipCapability.js deleted file mode 100644 index af8c142338..0000000000 --- a/platform/commonUI/edit/src/capabilities/EditableRelationshipCapability.js +++ /dev/null @@ -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 - ); - }; - } -); diff --git a/platform/commonUI/edit/src/capabilities/EditorCapability.js b/platform/commonUI/edit/src/capabilities/EditorCapability.js index ff9dfc19ed..69eb773d73 100644 --- a/platform/commonUI/edit/src/capabilities/EditorCapability.js +++ b/platform/commonUI/edit/src/capabilities/EditorCapability.js @@ -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; diff --git a/platform/commonUI/edit/src/capabilities/TransactionCapabilityDecorator.js b/platform/commonUI/edit/src/capabilities/TransactionCapabilityDecorator.js new file mode 100644 index 0000000000..c3a405f099 --- /dev/null +++ b/platform/commonUI/edit/src/capabilities/TransactionCapabilityDecorator.js @@ -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; + } +); diff --git a/platform/commonUI/edit/src/capabilities/TransactionalPersistenceCapability.js b/platform/commonUI/edit/src/capabilities/TransactionalPersistenceCapability.js new file mode 100644 index 0000000000..9dc7968d3b --- /dev/null +++ b/platform/commonUI/edit/src/capabilities/TransactionalPersistenceCapability.js @@ -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; + } +); diff --git a/platform/commonUI/edit/src/objects/EditableDomainObject.js b/platform/commonUI/edit/src/objects/EditableDomainObject.js deleted file mode 100644 index 1eedcd563b..0000000000 --- a/platform/commonUI/edit/src/objects/EditableDomainObject.js +++ /dev/null @@ -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; - } -); diff --git a/platform/commonUI/edit/src/objects/EditableDomainObjectCache.js b/platform/commonUI/edit/src/objects/EditableDomainObjectCache.js deleted file mode 100644 index 774e562e61..0000000000 --- a/platform/commonUI/edit/src/objects/EditableDomainObjectCache.js +++ /dev/null @@ -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} 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; - } -); - diff --git a/platform/commonUI/edit/src/objects/EditableModelCache.js b/platform/commonUI/edit/src/objects/EditableModelCache.js deleted file mode 100644 index 702cfbe6c7..0000000000 --- a/platform/commonUI/edit/src/objects/EditableModelCache.js +++ /dev/null @@ -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; - } -); diff --git a/platform/commonUI/edit/src/policies/EditActionPolicy.js b/platform/commonUI/edit/src/policies/EditActionPolicy.js index 0e5af528e7..f266d580eb 100644 --- a/platform/commonUI/edit/src/policies/EditActionPolicy.js +++ b/platform/commonUI/edit/src/policies/EditActionPolicy.js @@ -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) { diff --git a/platform/commonUI/edit/src/policies/EditContextualActionPolicy.js b/platform/commonUI/edit/src/policies/EditContextualActionPolicy.js index 32a24050e0..af30b5933e 100644 --- a/platform/commonUI/edit/src/policies/EditContextualActionPolicy.js +++ b/platform/commonUI/edit/src/policies/EditContextualActionPolicy.js @@ -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.} */ 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 diff --git a/platform/commonUI/edit/src/policies/EditNavigationPolicy.js b/platform/commonUI/edit/src/policies/EditNavigationPolicy.js index 62c489b35d..83563b5ced 100644 --- a/platform/commonUI/edit/src/policies/EditNavigationPolicy.js +++ b/platform/commonUI/edit/src/policies/EditNavigationPolicy.js @@ -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(); }; /** diff --git a/platform/commonUI/edit/src/policies/EditableLinkPolicy.js b/platform/commonUI/edit/src/policies/EditableLinkPolicy.js index c311266cf8..c6a2a36290 100644 --- a/platform/commonUI/edit/src/policies/EditableLinkPolicy.js +++ b/platform/commonUI/edit/src/policies/EditableLinkPolicy.js @@ -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. diff --git a/platform/commonUI/edit/src/policies/EditableMovePolicy.js b/platform/commonUI/edit/src/policies/EditableMovePolicy.js index e89113ea60..bb36c86746 100644 --- a/platform/commonUI/edit/src/policies/EditableMovePolicy.js +++ b/platform/commonUI/edit/src/policies/EditableMovePolicy.js @@ -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. diff --git a/platform/commonUI/edit/src/policies/EditableViewPolicy.js b/platform/commonUI/edit/src/policies/EditableViewPolicy.js index 7c9742e2d3..312bafff05 100644 --- a/platform/commonUI/edit/src/policies/EditableViewPolicy.js +++ b/platform/commonUI/edit/src/policies/EditableViewPolicy.js @@ -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. diff --git a/platform/commonUI/edit/src/representers/EditRepresenter.js b/platform/commonUI/edit/src/representers/EditRepresenter.js index 270f9ecc52..e853669eff 100644 --- a/platform/commonUI/edit/src/representers/EditRepresenter.js +++ b/platform/commonUI/edit/src/representers/EditRepresenter.js @@ -136,7 +136,7 @@ define( } }); - if (representedObject.getCapability('status').get('editing')){ + if (representedObject.hasCapability('editor') && representedObject.getCapability('editor').isEditContextRoot()){ setEditing(); } }; diff --git a/platform/commonUI/edit/src/services/TransactionService.js b/platform/commonUI/edit/src/services/TransactionService.js new file mode 100644 index 0000000000..8d57e1e809 --- /dev/null +++ b/platform/commonUI/edit/src/services/TransactionService.js @@ -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; +}); diff --git a/platform/commonUI/edit/test/actions/EditActionSpec.js b/platform/commonUI/edit/test/actions/EditActionSpec.js index 7ed8b672bd..f645e1cd08 100644 --- a/platform/commonUI/edit/test/actions/EditActionSpec.js +++ b/platform/commonUI/edit/test/actions/EditActionSpec.js @@ -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(); - }); - - - }); } ); \ No newline at end of file diff --git a/platform/commonUI/edit/test/actions/SaveActionSpec.js b/platform/commonUI/edit/test/actions/SaveActionSpec.js index 1c94bdfe60..09df5e53b5 100644 --- a/platform/commonUI/edit/test/actions/SaveActionSpec.js +++ b/platform/commonUI/edit/test/actions/SaveActionSpec.js @@ -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"); + }); + }); } ); \ No newline at end of file diff --git a/platform/commonUI/edit/test/actions/SaveAsActionSpec.js b/platform/commonUI/edit/test/actions/SaveAsActionSpec.js index 47b8440353..bac173ddcc 100644 --- a/platform/commonUI/edit/test/actions/SaveAsActionSpec.js +++ b/platform/commonUI/edit/test/actions/SaveAsActionSpec.js @@ -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( diff --git a/platform/commonUI/edit/test/capabilities/EditableCompositionCapabilitySpec.js b/platform/commonUI/edit/test/capabilities/EditableCompositionCapabilitySpec.js deleted file mode 100644 index 3e4083d2e8..0000000000 --- a/platform/commonUI/edit/test/capabilities/EditableCompositionCapabilitySpec.js +++ /dev/null @@ -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); - }); - - }); - } -); \ No newline at end of file diff --git a/platform/commonUI/edit/test/capabilities/EditableContextCapabilitySpec.js b/platform/commonUI/edit/test/capabilities/EditableContextCapabilitySpec.js deleted file mode 100644 index b18a02e881..0000000000 --- a/platform/commonUI/edit/test/capabilities/EditableContextCapabilitySpec.js +++ /dev/null @@ -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); - }); - }); - } -); \ No newline at end of file diff --git a/platform/commonUI/edit/test/capabilities/EditableLookupCapabilitySpec.js b/platform/commonUI/edit/test/capabilities/EditableLookupCapabilitySpec.js deleted file mode 100644 index dc178da449..0000000000 --- a/platform/commonUI/edit/test/capabilities/EditableLookupCapabilitySpec.js +++ /dev/null @@ -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. - }); - - }); - } -); \ No newline at end of file diff --git a/platform/commonUI/edit/test/capabilities/EditablePersistenceCapabilitySpec.js b/platform/commonUI/edit/test/capabilities/EditablePersistenceCapabilitySpec.js deleted file mode 100644 index 4ce4a2f75d..0000000000 --- a/platform/commonUI/edit/test/capabilities/EditablePersistenceCapabilitySpec.js +++ /dev/null @@ -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)); - }); - - }); - } -); \ No newline at end of file diff --git a/platform/commonUI/edit/test/capabilities/EditableRelationshipCapabilitySpec.js b/platform/commonUI/edit/test/capabilities/EditableRelationshipCapabilitySpec.js deleted file mode 100644 index 9a6c17d944..0000000000 --- a/platform/commonUI/edit/test/capabilities/EditableRelationshipCapabilitySpec.js +++ /dev/null @@ -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); - }); - - }); - } -); \ No newline at end of file diff --git a/platform/commonUI/edit/test/capabilities/EditorCapabilitySpec.js b/platform/commonUI/edit/test/capabilities/EditorCapabilitySpec.js index d18cdcd931..fe42ec92f7 100644 --- a/platform/commonUI/edit/test/capabilities/EditorCapabilitySpec.js +++ b/platform/commonUI/edit/test/capabilities/EditorCapabilitySpec.js @@ -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); }); }); - - }); } ); \ No newline at end of file diff --git a/platform/commonUI/edit/test/objects/EditableDomainObjectSpec.js b/platform/commonUI/edit/test/capabilities/TransactionCapabilityDecoratorSpec.js similarity index 51% rename from platform/commonUI/edit/test/objects/EditableDomainObjectSpec.js rename to platform/commonUI/edit/test/capabilities/TransactionCapabilityDecoratorSpec.js index 9b1095f68d..f710cf215e 100644 --- a/platform/commonUI/edit/test/objects/EditableDomainObjectSpec.js +++ b/platform/commonUI/edit/test/capabilities/TransactionCapabilityDecoratorSpec.js @@ -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); + }); + }); } ); \ No newline at end of file diff --git a/platform/commonUI/edit/test/capabilities/TransactionalPersistenceCapabilitySpec.js b/platform/commonUI/edit/test/capabilities/TransactionalPersistenceCapabilitySpec.js new file mode 100644 index 0000000000..c0892e6db2 --- /dev/null +++ b/platform/commonUI/edit/test/capabilities/TransactionalPersistenceCapabilitySpec.js @@ -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); + + }); + + }); + } +); \ No newline at end of file diff --git a/platform/commonUI/edit/test/objects/EditableDomainObjectCacheSpec.js b/platform/commonUI/edit/test/objects/EditableDomainObjectCacheSpec.js deleted file mode 100644 index edbfd3edc4..0000000000 --- a/platform/commonUI/edit/test/objects/EditableDomainObjectCacheSpec.js +++ /dev/null @@ -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); - }); - - - }); - } - -); diff --git a/platform/commonUI/edit/test/objects/EditableModelCacheSpec.js b/platform/commonUI/edit/test/objects/EditableModelCacheSpec.js deleted file mode 100644 index 1fe8db0262..0000000000 --- a/platform/commonUI/edit/test/objects/EditableModelCacheSpec.js +++ /dev/null @@ -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)); - }); - }); - - } -); \ No newline at end of file diff --git a/platform/commonUI/edit/test/policies/EditActionPolicySpec.js b/platform/commonUI/edit/test/policies/EditActionPolicySpec.js index dfe4e8fc4c..0cdf1be85d 100644 --- a/platform/commonUI/edit/test/policies/EditActionPolicySpec.js +++ b/platform/commonUI/edit/test/policies/EditActionPolicySpec.js @@ -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); }); diff --git a/platform/commonUI/edit/test/policies/EditContextualActionPolicySpec.js b/platform/commonUI/edit/test/policies/EditContextualActionPolicySpec.js index c1c9878e6e..a516061e80 100644 --- a/platform/commonUI/edit/test/policies/EditContextualActionPolicySpec.js +++ b/platform/commonUI/edit/test/policies/EditContextualActionPolicySpec.js @@ -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); }); diff --git a/platform/commonUI/edit/test/policies/EditableViewPolicySpec.js b/platform/commonUI/edit/test/policies/EditableViewPolicySpec.js index 2194a8c45a..32400a7453 100644 --- a/platform/commonUI/edit/test/policies/EditableViewPolicySpec.js +++ b/platform/commonUI/edit/test/policies/EditableViewPolicySpec.js @@ -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; }); diff --git a/platform/commonUI/edit/test/representers/EditRepresenterSpec.js b/platform/commonUI/edit/test/representers/EditRepresenterSpec.js index b4c2f4ce7f..fe0c17c5bd 100644 --- a/platform/commonUI/edit/test/representers/EditRepresenterSpec.js +++ b/platform/commonUI/edit/test/representers/EditRepresenterSpec.js @@ -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'); }); diff --git a/platform/commonUI/edit/test/services/TransactionServiceSpec.js b/platform/commonUI/edit/test/services/TransactionServiceSpec.js new file mode 100644 index 0000000000..5f965decf9 --- /dev/null +++ b/platform/commonUI/edit/test/services/TransactionServiceSpec.js @@ -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); + }); + + }); + + }); + } +); \ No newline at end of file diff --git a/platform/commonUI/regions/src/EditableRegionPolicy.js b/platform/commonUI/regions/src/EditableRegionPolicy.js index 5b787a00d8..a63bed65b1 100644 --- a/platform/commonUI/regions/src/EditableRegionPolicy.js +++ b/platform/commonUI/regions/src/EditableRegionPolicy.js @@ -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; diff --git a/platform/commonUI/regions/src/InspectorController.js b/platform/commonUI/regions/src/InspectorController.js index c97b055462..32b0e1903d 100644 --- a/platform/commonUI/regions/src/InspectorController.js +++ b/platform/commonUI/regions/src/InspectorController.js @@ -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; diff --git a/platform/commonUI/regions/test/EditableRegionPolicySpec.js b/platform/commonUI/regions/test/EditableRegionPolicySpec.js index 1692f72288..e9a6a97404 100644 --- a/platform/commonUI/regions/test/EditableRegionPolicySpec.js +++ b/platform/commonUI/regions/test/EditableRegionPolicySpec.js @@ -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); }); diff --git a/platform/core/src/capabilities/PersistenceCapability.js b/platform/core/src/capabilities/PersistenceCapability.js index 4e4e753e0c..2f4f3cd53b 100644 --- a/platform/core/src/capabilities/PersistenceCapability.js +++ b/platform/core/src/capabilities/PersistenceCapability.js @@ -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); }; /** diff --git a/platform/core/test/capabilities/PersistenceCapabilitySpec.js b/platform/core/test/capabilities/PersistenceCapabilitySpec.js index 16f5d34e61..d2bafd26e6 100644 --- a/platform/core/test/capabilities/PersistenceCapabilitySpec.js +++ b/platform/core/test/capabilities/PersistenceCapabilitySpec.js @@ -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(); diff --git a/platform/features/timeline/src/controllers/TimelineZoomController.js b/platform/features/timeline/src/controllers/TimelineZoomController.js index 1488d44aeb..43abb8419c 100644 --- a/platform/features/timeline/src/controllers/TimelineZoomController.js +++ b/platform/features/timeline/src/controllers/TimelineZoomController.js @@ -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; diff --git a/platform/features/timeline/src/controllers/swimlane/TimelineSwimlaneDropHandler.js b/platform/features/timeline/src/controllers/swimlane/TimelineSwimlaneDropHandler.js index 59b437d15c..e655741342 100644 --- a/platform/features/timeline/src/controllers/swimlane/TimelineSwimlaneDropHandler.js +++ b/platform/features/timeline/src/controllers/swimlane/TimelineSwimlaneDropHandler.js @@ -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) diff --git a/platform/features/timeline/test/controllers/TimelineZoomControllerSpec.js b/platform/features/timeline/test/controllers/TimelineZoomControllerSpec.js index 6bc922731d..d7322512d5 100644 --- a/platform/features/timeline/test/controllers/TimelineZoomControllerSpec.js +++ b/platform/features/timeline/test/controllers/TimelineZoomControllerSpec.js @@ -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) diff --git a/platform/features/timeline/test/controllers/swimlane/TimelineSwimlaneDropHandlerSpec.js b/platform/features/timeline/test/controllers/swimlane/TimelineSwimlaneDropHandlerSpec.js index 4c8d063f29..3eed051f45 100644 --- a/platform/features/timeline/test/controllers/swimlane/TimelineSwimlaneDropHandlerSpec.js +++ b/platform/features/timeline/test/controllers/swimlane/TimelineSwimlaneDropHandlerSpec.js @@ -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(); diff --git a/platform/representation/bundle.js b/platform/representation/bundle.js index ae8cb0ed8e..01484ccb78 100644 --- a/platform/representation/bundle.js +++ b/platform/representation/bundle.js @@ -86,10 +86,7 @@ define([ "implementation": DropGesture, "depends": [ "dndService", - "$q", - "navigationService", - "instantiate", - "typeService" + "$q" ] }, { diff --git a/platform/representation/src/MCTRepresentation.js b/platform/representation/src/MCTRepresentation.js index b0c0518d24..bcaa90e4e7 100644 --- a/platform/representation/src/MCTRepresentation.js +++ b/platform/representation/src/MCTRepresentation.js @@ -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 diff --git a/platform/representation/src/gestures/DropGesture.js b/platform/representation/src/gestures/DropGesture.js index 76df23b043..f2d64024a3 100644 --- a/platform/representation/src/gestures/DropGesture.js +++ b/platform/representation/src/gestures/DropGesture.js @@ -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); }); + } } diff --git a/platform/representation/test/MCTRepresentationSpec.js b/platform/representation/test/MCTRepresentationSpec.js index 7608070d3e..52d6c70d55 100644 --- a/platform/representation/test/MCTRepresentationSpec.js +++ b/platform/representation/test/MCTRepresentationSpec.js @@ -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); }); + }); diff --git a/platform/search/src/services/GenericSearchProvider.js b/platform/search/src/services/GenericSearchProvider.js index fbe45ae6d8..101c718669 100644 --- a/platform/search/src/services/GenericSearchProvider.js +++ b/platform/search/src/services/GenericSearchProvider.js @@ -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()