diff --git a/platform/commonUI/browse/bundle.js b/platform/commonUI/browse/bundle.js index bf76295b18..7fd785a7f5 100644 --- a/platform/commonUI/browse/bundle.js +++ b/platform/commonUI/browse/bundle.js @@ -111,10 +111,11 @@ define([ "$scope", "$route", "$location", - "$q", + "$window", "objectService", "navigationService", "urlService", + "policyService", "DEFAULT_PATH" ] }, @@ -134,9 +135,7 @@ define([ "depends": [ "$scope", "$location", - "$route", - "$q", - "navigationService" + "$route" ] }, { @@ -170,6 +169,10 @@ define([ } ], "representations": [ + { + "key": "view-object", + "templateUrl": "templates/view-object.html" + }, { "key": "browse-object", "template": browseObjectTemplate, diff --git a/platform/commonUI/browse/res/templates/browse-object.html b/platform/commonUI/browse/res/templates/browse-object.html index 562f8bdf6a..35d0d58f3f 100644 --- a/platform/commonUI/browse/res/templates/browse-object.html +++ b/platform/commonUI/browse/res/templates/browse-object.html @@ -47,11 +47,6 @@
- - diff --git a/platform/commonUI/browse/res/templates/browse.html b/platform/commonUI/browse/res/templates/browse.html index 3d3f22bd7d..75fcaaeb0a 100644 --- a/platform/commonUI/browse/res/templates/browse.html +++ b/platform/commonUI/browse/res/templates/browse.html @@ -63,7 +63,7 @@
diff --git a/platform/commonUI/edit/res/templates/edit.html b/platform/commonUI/browse/res/templates/view-object.html similarity index 66% rename from platform/commonUI/edit/res/templates/edit.html rename to platform/commonUI/browse/res/templates/view-object.html index 07a677bce8..3dbfaab596 100644 --- a/platform/commonUI/edit/res/templates/edit.html +++ b/platform/commonUI/browse/res/templates/view-object.html @@ -19,14 +19,15 @@ this source code distribution or the Licensing information page available at runtime from the About dialog for additional information. --> -
+ + + diff --git a/platform/commonUI/browse/src/BrowseController.js b/platform/commonUI/browse/src/BrowseController.js index d1d5aa92cc..db7d9182cb 100644 --- a/platform/commonUI/browse/src/BrowseController.js +++ b/platform/commonUI/browse/src/BrowseController.js @@ -27,14 +27,12 @@ */ define( [ - '../../../representation/src/gestures/GestureConstants', - '../../edit/src/objects/EditableDomainObject' + '../../../representation/src/gestures/GestureConstants' ], - function (GestureConstants, EditableDomainObject) { + function (GestureConstants) { "use strict"; - var ROOT_ID = "ROOT", - CONFIRM_MSG = "Unsaved changes will be lost if you leave this page."; + var ROOT_ID = "ROOT"; /** * The BrowseController is used to populate the initial scope in Browse @@ -47,26 +45,20 @@ define( * @constructor */ function BrowseController( - $scope, - $route, - $location, - $q, - objectService, - navigationService, - urlService, + $scope, + $route, + $location, + $window, + objectService, + navigationService, + urlService, + policyService, defaultPath ) { var path = [ROOT_ID].concat( ($route.current.params.ids || defaultPath).split("/") ); - function isDirty(){ - var editorCapability = $scope.navigatedObject && - $scope.navigatedObject.getCapability("editor"), - hasChanges = editorCapability && editorCapability.dirty(); - return hasChanges; - } - function updateRoute(domainObject) { var priorRoute = $route.current, // Act as if params HADN'T changed to avoid page reload @@ -83,31 +75,35 @@ define( // urlService.urlForLocation used to adjust current // path to new, addressed, path based on // domainObject - $location.path(urlService.urlForLocation("browse", - domainObject.hasCapability('editor') ? - domainObject.getOriginalObject() : domainObject)); + $location.path(urlService.urlForLocation("browse", domainObject)); } // Callback for updating the in-scope reference to the object // that is currently navigated-to. function setNavigation(domainObject) { + var navigationAllowed = true; + if (domainObject === $scope.navigatedObject){ //do nothing; return; } - if (isDirty() && !confirm(CONFIRM_MSG)) { - $scope.treeModel.selectedObject = $scope.navigatedObject; - navigationService.setNavigation($scope.navigatedObject); - } else { - if ($scope.navigatedObject && $scope.navigatedObject.hasCapability("editor")){ - $scope.navigatedObject.getCapability("editor").cancel(); - } + policyService.allow("navigation", $scope.navigatedObject, domainObject, function(message){ + navigationAllowed = $window.confirm(message + "\r\n\r\n" + + " Are you sure you want to continue?"); + }); + + if (navigationAllowed) { $scope.navigatedObject = domainObject; $scope.treeModel.selectedObject = domainObject; navigationService.setNavigation(domainObject); updateRoute(domainObject); + } else { + //If navigation was unsuccessful (ie. blocked), reset + // the selected object in the tree to the currently + // navigated object + $scope.treeModel.selectedObject = $scope.navigatedObject ; } } @@ -184,18 +180,13 @@ define( selectedObject: navigationService.getNavigation() }; - $scope.beforeUnloadWarning = function() { - return isDirty() ? - "Unsaved changes will be lost if you leave this page." : - undefined; - }; - // Listen for changes in navigation state. navigationService.addListener(setNavigation); - // Also listen for changes which come from the tree + // Also listen for changes which come from the tree. Changes in + // the tree will trigger a change in browse navigation state. $scope.$watch("treeModel.selectedObject", setNavigation); - + // Clean up when the scope is destroyed $scope.$on("$destroy", function () { navigationService.removeListener(setNavigation); diff --git a/platform/commonUI/browse/src/BrowseObjectController.js b/platform/commonUI/browse/src/BrowseObjectController.js index b0a93a991a..71345d6f1b 100644 --- a/platform/commonUI/browse/src/BrowseObjectController.js +++ b/platform/commonUI/browse/src/BrowseObjectController.js @@ -22,11 +22,8 @@ /*global define,Promise*/ define( - [ - '../../../representation/src/gestures/GestureConstants', - '../../edit/src/objects/EditableDomainObject' - ], - function (GestureConstants, EditableDomainObject) { + [], + function () { "use strict"; /** @@ -35,7 +32,7 @@ define( * @memberof platform/commonUI/browse * @constructor */ - function BrowseObjectController($scope, $location, $route, $q, navigationService) { + function BrowseObjectController($scope, $location, $route) { var navigatedObject; function setViewForDomainObject(domainObject) { @@ -57,10 +54,9 @@ define( function updateQueryParam(viewKey) { var unlisten, - priorRoute = $route.current, - isEditMode = $scope.domainObject && $scope.domainObject.hasCapability('editor'); + priorRoute = $route.current; - if (viewKey && !isEditMode) { + if (viewKey) { $location.search('view', viewKey); unlisten = $scope.$on('$locationChangeSuccess', function () { // Checks path to make sure /browse/ is at front @@ -76,10 +72,6 @@ define( $scope.$watch('domainObject', setViewForDomainObject); $scope.$watch('representation.selected.key', updateQueryParam); - $scope.cancelEditing = function() { - navigationService.setNavigation($scope.domainObject.getDomainObject()); - }; - $scope.doAction = function (action){ return $scope[action] && $scope[action](); }; diff --git a/platform/commonUI/browse/src/navigation/NavigationService.js b/platform/commonUI/browse/src/navigation/NavigationService.js index 87e5582ef7..3b4d266bd1 100644 --- a/platform/commonUI/browse/src/navigation/NavigationService.js +++ b/platform/commonUI/browse/src/navigation/NavigationService.js @@ -59,6 +59,7 @@ define( callback(value); }); } + return true; }; /** diff --git a/platform/commonUI/browse/test/BrowseControllerSpec.js b/platform/commonUI/browse/test/BrowseControllerSpec.js index e4fad10544..cc14b4d0e5 100644 --- a/platform/commonUI/browse/test/BrowseControllerSpec.js +++ b/platform/commonUI/browse/test/BrowseControllerSpec.js @@ -29,8 +29,7 @@ define( function (BrowseController) { "use strict"; - //TODO: Disabled for NEM Beta - xdescribe("The browse controller", function () { + describe("The browse controller", function () { var mockScope, mockRoute, mockLocation, @@ -40,6 +39,8 @@ define( mockUrlService, mockDomainObject, mockNextObject, + mockWindow, + mockPolicyService, testDefaultRoot, controller; @@ -56,14 +57,25 @@ define( mockScope, mockRoute, mockLocation, + mockWindow, mockObjectService, mockNavigationService, mockUrlService, + mockPolicyService, testDefaultRoot ); } beforeEach(function () { + mockWindow = jasmine.createSpyObj('$window', [ + "confirm" + ]); + mockWindow.confirm.andReturn(true); + + mockPolicyService = jasmine.createSpyObj('policyService', [ + 'allow' + ]); + testDefaultRoot = "some-root-level-domain-object"; mockScope = jasmine.createSpyObj( @@ -214,7 +226,10 @@ define( // prior to setting $route.current mockLocation.path.andReturn("/browse/"); + mockNavigationService.setNavigation.andReturn(true); + // Exercise the Angular workaround + mockNavigationService.addListener.mostRecentCall.args[0](); mockScope.$on.mostRecentCall.args[1](); expect(mockUnlisten).toHaveBeenCalled(); @@ -225,6 +240,36 @@ define( ); }); + it("after successful navigation event sets the selected tree " + + "object", function () { + mockScope.navigatedObject = mockDomainObject; + mockNavigationService.setNavigation.andReturn(true); + + //Simulate a change in selected tree object + mockScope.treeModel = {selectedObject: mockDomainObject}; + mockScope.$watch.mostRecentCall.args[1](mockNextObject); + + expect(mockScope.treeModel.selectedObject).toBe(mockNextObject); + expect(mockScope.treeModel.selectedObject).not.toBe(mockDomainObject); + }); + + it("after failed navigation event resets the selected tree" + + " object", function () { + mockScope.navigatedObject = mockDomainObject; + mockWindow.confirm.andReturn(false); + mockPolicyService.allow.andCallFake(function(category, object, context, callback){ + callback("unsaved changes"); + return false; + }); + + //Simulate a change in selected tree object + mockScope.treeModel = {selectedObject: mockDomainObject}; + mockScope.$watch.mostRecentCall.args[1](mockNextObject); + + expect(mockScope.treeModel.selectedObject).not.toBe(mockNextObject); + expect(mockScope.treeModel.selectedObject).toBe(mockDomainObject); + }); + }); } ); diff --git a/platform/commonUI/browse/test/navigation/NavigationServiceSpec.js b/platform/commonUI/browse/test/navigation/NavigationServiceSpec.js index 6d7f3fd20d..410a5f1562 100644 --- a/platform/commonUI/browse/test/navigation/NavigationServiceSpec.js +++ b/platform/commonUI/browse/test/navigation/NavigationServiceSpec.js @@ -86,4 +86,4 @@ define( }); } -); \ No newline at end of file +); diff --git a/platform/commonUI/edit/bundle.js b/platform/commonUI/edit/bundle.js index c4b0da1798..80a98a6927 100644 --- a/platform/commonUI/edit/bundle.js +++ b/platform/commonUI/edit/bundle.js @@ -22,10 +22,10 @@ /*global define*/ define([ - "./src/controllers/EditController", "./src/controllers/EditActionController", "./src/controllers/EditPanesController", "./src/controllers/ElementsController", + "./src/controllers/EditObjectController", "./src/directives/MCTBeforeUnload", "./src/actions/LinkAction", "./src/actions/EditAction", @@ -34,9 +34,9 @@ define([ "./src/actions/SaveAction", "./src/actions/CancelAction", "./src/policies/EditActionPolicy", + "./src/policies/EditNavigationPolicy", "./src/representers/EditRepresenter", "./src/representers/EditToolbarRepresenter", - "text!./res/templates/edit.html", "text!./res/templates/library.html", "text!./res/templates/edit-object.html", "text!./res/templates/edit-action-buttons.html", @@ -44,10 +44,10 @@ define([ "text!./res/templates/topbar-edit.html", 'legacyRegistry' ], function ( - EditController, EditActionController, EditPanesController, ElementsController, + EditObjectController, MCTBeforeUnload, LinkAction, EditAction, @@ -56,9 +56,9 @@ define([ SaveAction, CancelAction, EditActionPolicy, + EditNavigationPolicy, EditRepresenter, EditToolbarRepresenter, - editTemplate, libraryTemplate, editObjectTemplate, editActionButtonsTemplate, @@ -70,22 +70,7 @@ define([ legacyRegistry.register("platform/commonUI/edit", { "extensions": { - "routes": [ - { - "when": "/edit", - "template": editTemplate - } - ], "controllers": [ - { - "key": "EditController", - "implementation": EditController, - "depends": [ - "$scope", - "$q", - "navigationService" - ] - }, { "key": "EditActionController", "implementation": EditActionController, @@ -106,6 +91,15 @@ define([ "depends": [ "$scope" ] + }, + { + "key": "EditObjectController", + "implementation": EditObjectController, + "depends": [ + "$scope", + "$location", + "policyService" + ] } ], "directives": [ @@ -192,7 +186,13 @@ define([ { "category": "action", "implementation": EditActionPolicy + }, + { + "category": "navigation", + "message": "There are unsaved changes.", + "implementation": EditNavigationPolicy } + ], "templates": [ { @@ -206,6 +206,9 @@ define([ "template": editObjectTemplate, "uses": [ "view" + ], + "gestures": [ + "drop" ] }, { diff --git a/platform/commonUI/edit/res/templates/edit-object.html b/platform/commonUI/edit/res/templates/edit-object.html index 71dc233a82..9da28714a7 100644 --- a/platform/commonUI/edit/res/templates/edit-object.html +++ b/platform/commonUI/edit/res/templates/edit-object.html @@ -19,50 +19,51 @@ this source code distribution or the Licensing information page available at runtime from the About dialog for additional information. --> - - -
- -
- - - +
+
+
+ +
- - +
+
+ +
+ + + + + +
+ + +
+
diff --git a/platform/commonUI/edit/src/actions/EditAction.js b/platform/commonUI/edit/src/actions/EditAction.js index 6b8ba3e042..d771f75dd4 100644 --- a/platform/commonUI/edit/src/actions/EditAction.js +++ b/platform/commonUI/edit/src/actions/EditAction.js @@ -72,13 +72,26 @@ define( * Enter edit mode. */ EditAction.prototype.perform = function () { - var editableObject; + var self = this; if (!this.domainObject.hasCapability("editor")) { - editableObject = new EditableDomainObject(this.domainObject, this.$q); - editableObject.getCapability('status').set('editing', true); - this.navigationService.setNavigation(editableObject); + //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.$location.path("/edit"); + 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); + } + } + this.navigationService.addListener(cancelEditing); }; /** diff --git a/platform/commonUI/edit/src/capabilities/EditorCapability.js b/platform/commonUI/edit/src/capabilities/EditorCapability.js index f674880203..b48c1988e6 100644 --- a/platform/commonUI/edit/src/capabilities/EditorCapability.js +++ b/platform/commonUI/edit/src/capabilities/EditorCapability.js @@ -124,7 +124,6 @@ define( */ EditorCapability.prototype.cancel = function () { this.editableObject.getCapability("status").set("editing", false); - //TODO: Reset the cache as well here. this.cache.markClean(); return resolvePromise(undefined); }; diff --git a/platform/commonUI/edit/src/controllers/EditController.js b/platform/commonUI/edit/src/controllers/EditObjectController.js similarity index 53% rename from platform/commonUI/edit/src/controllers/EditController.js rename to platform/commonUI/edit/src/controllers/EditObjectController.js index eaffe02186..d6121106ec 100644 --- a/platform/commonUI/edit/src/controllers/EditController.js +++ b/platform/commonUI/edit/src/controllers/EditObjectController.js @@ -26,41 +26,45 @@ * @namespace platform/commonUI/edit */ define( - ["../objects/EditableDomainObject"], - function (EditableDomainObject) { + [], + function () { "use strict"; /** * Controller which is responsible for populating the scope for - * Edit mode; introduces an editable version of the currently - * navigated domain object into the scope. + * Edit mode * @memberof platform/commonUI/edit * @constructor */ - function EditController($scope, $q, navigationService) { - var self = this; + function EditObjectController($scope, $location, policyService) { + this.scope = $scope; + this.policyService = policyService; - function setNavigation(domainObject) { - // Wrap the domain object such that all mutation is - // confined to edit mode (until Save) - self.navigatedDomainObject = - domainObject && new EditableDomainObject(domainObject, $q); + var navigatedObject; + function setViewForDomainObject(domainObject) { + + var locationViewKey = $location.search().view; + + function selectViewIfMatching(view) { + if (view.key === locationViewKey) { + $scope.representation = $scope.representation || {}; + $scope.representation.selected = view; + } + } + + if (locationViewKey) { + ((domainObject && domainObject.useCapability('view')) || []) + .forEach(selectViewIfMatching); + } + navigatedObject = domainObject; } - setNavigation(navigationService.getNavigation()); - navigationService.addListener(setNavigation); - $scope.$on("$destroy", function () { - navigationService.removeListener(setNavigation); - }); - } + $scope.$watch('domainObject', setViewForDomainObject); - /** - * Get the domain object which is navigated-to. - * @returns {DomainObject} the domain object that is navigated-to - */ - EditController.prototype.navigatedObject = function () { - return this.navigatedDomainObject; - }; + $scope.doAction = function (action){ + return $scope[action] && $scope[action](); + }; + } /** * Get the warning to show if the user attempts to navigate @@ -68,17 +72,18 @@ define( * @returns {string} the warning to show, or undefined if * there are no unsaved changes */ - EditController.prototype.getUnloadWarning = function () { - var navigatedObject = this.navigatedDomainObject, - editorCapability = navigatedObject && - navigatedObject.getCapability("editor"), - hasChanges = editorCapability && editorCapability.dirty(); + EditObjectController.prototype.getUnloadWarning = function () { + var navigatedObject = this.scope.domainObject, + policyMessage; + + this.policyService.allow("navigation", navigatedObject, undefined, function(message) { + policyMessage = message; + }); + + return policyMessage; - return hasChanges ? - "Unsaved changes will be lost if you leave this page." : - undefined; }; - return EditController; + return EditObjectController; } ); diff --git a/platform/commonUI/edit/src/policies/EditNavigationPolicy.js b/platform/commonUI/edit/src/policies/EditNavigationPolicy.js new file mode 100644 index 0000000000..882e64935e --- /dev/null +++ b/platform/commonUI/edit/src/policies/EditNavigationPolicy.js @@ -0,0 +1,67 @@ +/***************************************************************************** + * Open MCT Web, Copyright (c) 2014-2015, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT Web is licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Open MCT Web includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ +/*global define*/ + +define( + [], + function () { + "use strict"; + + /** + * Policy controlling whether navigation events should proceed + * when object is being edited. + * @memberof platform/commonUI/edit + * @constructor + * @implements {Policy.} + */ + function EditNavigationPolicy(policyService) { + this.policyService = policyService; + } + + /** + * @private + */ + EditNavigationPolicy.prototype.isDirty = function(domainObject) { + var navigatedObject = domainObject, + editorCapability = navigatedObject && + navigatedObject.getCapability("editor"), + statusCapability = navigatedObject && + navigatedObject.getCapability("status"); + + return statusCapability && statusCapability.get('editing') + && editorCapability && editorCapability.dirty(); + }; + + /** + * Allow navigation if an object is not dirty, or if the user elects + * to proceed anyway. + * @param currentNavigation + * @returns {boolean|*} true if the object model is clean; or if + * it's dirty and the user wishes to proceed anyway. + */ + EditNavigationPolicy.prototype.allow = function (currentNavigation) { + return !this.isDirty(currentNavigation); + }; + + return EditNavigationPolicy; + } +); diff --git a/platform/commonUI/edit/src/representers/EditRepresenter.js b/platform/commonUI/edit/src/representers/EditRepresenter.js index 0844f65e67..533c8031e0 100644 --- a/platform/commonUI/edit/src/representers/EditRepresenter.js +++ b/platform/commonUI/edit/src/representers/EditRepresenter.js @@ -49,6 +49,7 @@ define( var self = this; this.scope = scope; + this.listenHandle = undefined; // Mutate and persist a new version of a domain object's model. function doPersist(model) { @@ -100,10 +101,18 @@ define( // Place the "commit" method in the scope scope.commit = commit; scope.setEditable = setEditable; + + // Clean up when the scope is destroyed + scope.$on("$destroy", function () { + self.destroy(); + }); + } // Handle a specific representation of a specific domain object EditRepresenter.prototype.represent = function represent(representation, representedObject) { + var scope = this.scope, + self = this; // Track the key, to know which view configuration to save to. this.key = (representation || {}).key; // Track the represented object @@ -113,11 +122,32 @@ define( // Ensure existing watches are released this.destroy(); + + function setEditing(){ + scope.viewObjectTemplate = 'edit-object'; + } + + /** + * Listen for changes in object state. If the object becomes + * editable then change the view and inspector regions + * object representation accordingly + */ + this.listenHandle = this.domainObject.getCapability('status').listen(function(statuses){ + if (statuses.indexOf('editing')!=-1){ + setEditing(); + } else { + delete scope.viewObjectTemplate; + } + }); + + if (representedObject.getCapability('status').get('editing')){ + setEditing(); + } }; // Respond to the destruction of the current representation. EditRepresenter.prototype.destroy = function destroy() { - // Nothing to clean up + return this.listenHandle && this.listenHandle(); }; return EditRepresenter; diff --git a/platform/commonUI/edit/test/controllers/EditControllerSpec.js b/platform/commonUI/edit/test/controllers/EditControllerSpec.js index 4e66cebd08..0ed0ddecc7 100644 --- a/platform/commonUI/edit/test/controllers/EditControllerSpec.js +++ b/platform/commonUI/edit/test/controllers/EditControllerSpec.js @@ -22,102 +22,110 @@ /*global define,describe,it,expect,beforeEach,jasmine*/ define( - ["../../src/controllers/EditController"], - function (EditController) { + ["../../src/controllers/EditObjectController"], + function (EditObjectController) { "use strict"; describe("The Edit mode controller", function () { var mockScope, - mockQ, - mockNavigationService, mockObject, mockType, + mockLocation, + mockStatusCapability, + mockCapabilities, + mockPolicyService, controller; + // Utility function; look for a $watch on scope and fire it + function fireWatch(expr, value) { + mockScope.$watch.calls.forEach(function (call) { + if (call.args[0] === expr) { + call.args[1](value); + } + }); + } + beforeEach(function () { + mockPolicyService = jasmine.createSpyObj( + "policyService", + [ + "allow" + ] + ); mockScope = jasmine.createSpyObj( "$scope", - [ "$on" ] - ); - mockQ = jasmine.createSpyObj('$q', ['when', 'all']); - mockNavigationService = jasmine.createSpyObj( - "navigationService", - [ "getNavigation", "addListener", "removeListener" ] + [ "$on", "$watch" ] ); mockObject = jasmine.createSpyObj( "domainObject", - [ "getId", "getModel", "getCapability", "hasCapability" ] + [ "getId", "getModel", "getCapability", "hasCapability", "useCapability" ] ); mockType = jasmine.createSpyObj( "type", [ "hasFeature" ] ); + mockStatusCapability = jasmine.createSpyObj('statusCapability', + ["get"] + ); + + mockCapabilities = { + "type" : mockType, + "status": mockStatusCapability + }; + + mockLocation = jasmine.createSpyObj('$location', + ["search"] + ); + mockLocation.search.andReturn({"view": "fixed"}); - mockNavigationService.getNavigation.andReturn(mockObject); mockObject.getId.andReturn("test"); mockObject.getModel.andReturn({ name: "Test object" }); mockObject.getCapability.andCallFake(function (key) { - return key === 'type' && mockType; + return mockCapabilities[key]; }); mockType.hasFeature.andReturn(true); - controller = new EditController( + mockScope.domainObject = mockObject; + + controller = new EditObjectController( mockScope, - mockQ, - mockNavigationService + mockLocation, + mockPolicyService ); }); - it("exposes the currently-navigated object", function () { - expect(controller.navigatedObject()).toBeDefined(); - expect(controller.navigatedObject().getId()).toEqual("test"); - }); - - it("adds an editor capability to the navigated object", function () { - // Should provide an editor capability... - expect(controller.navigatedObject().getCapability("editor")) - .toBeDefined(); - // Shouldn't have been the mock capability we provided - expect(controller.navigatedObject().getCapability("editor")) - .not.toEqual(mockType); - }); - - it("detaches its navigation listener when destroyed", function () { - var navCallback = mockNavigationService - .addListener.mostRecentCall.args[0]; - - expect(mockScope.$on).toHaveBeenCalledWith( - "$destroy", - jasmine.any(Function) - ); - - // Verify precondition - expect(mockNavigationService.removeListener) - .not.toHaveBeenCalled(); - - // Trigger destroy - mockScope.$on.mostRecentCall.args[1](); - - // Listener should have been removed - expect(mockNavigationService.removeListener) - .toHaveBeenCalledWith(navCallback); - }); - it("exposes a warning message for unload", function () { - var obj = controller.navigatedObject(), - mockEditor = jasmine.createSpyObj('editor', ['dirty']); + var obj = mockObject, + errorMessage = "Unsaved changes"; // Normally, should be undefined expect(controller.getUnloadWarning()).toBeUndefined(); - // Override the object's editor capability, make it look - // like there are unsaved changes. - obj.getCapability = jasmine.createSpy(); - obj.getCapability.andReturn(mockEditor); - mockEditor.dirty.andReturn(true); + // Override the policy service to prevent navigation + mockPolicyService.allow.andCallFake(function(category, object, context, callback){ + callback(errorMessage); + }); // Should have some warning message here now - expect(controller.getUnloadWarning()).toEqual(jasmine.any(String)); + expect(controller.getUnloadWarning()).toEqual(errorMessage); + }); + + + it("sets the active view from query parameters", function () { + var testViews = [ + { key: 'abc' }, + { key: 'def', someKey: 'some value' }, + { key: 'xyz' } + ]; + + mockObject.useCapability.andCallFake(function (c) { + return (c === 'view') && testViews; + }); + mockLocation.search.andReturn({ view: 'def' }); + + fireWatch('domainObject', mockObject); + expect(mockScope.representation.selected) + .toEqual(testViews[1]); }); }); diff --git a/platform/commonUI/edit/test/representers/EditRepresenterSpec.js b/platform/commonUI/edit/test/representers/EditRepresenterSpec.js index 79b336202a..3dcaa34627 100644 --- a/platform/commonUI/edit/test/representers/EditRepresenterSpec.js +++ b/platform/commonUI/edit/test/representers/EditRepresenterSpec.js @@ -33,8 +33,8 @@ define( testRepresentation, mockDomainObject, mockPersistence, - mockCapabilities, mockStatusCapability, + mockCapabilities, representer; function mockPromise(value) { @@ -48,7 +48,7 @@ define( beforeEach(function () { mockQ = { when: mockPromise }; mockLog = jasmine.createSpyObj("$log", ["info", "debug"]); - mockScope = jasmine.createSpyObj("$scope", ["$watch"]); + mockScope = jasmine.createSpyObj("$scope", ["$watch", "$on"]); testRepresentation = { key: "test" }; mockDomainObject = jasmine.createSpyObj("domainObject", [ "getId", @@ -60,7 +60,7 @@ define( mockPersistence = jasmine.createSpyObj("persistence", ["persist"]); mockStatusCapability = - jasmine.createSpyObj("status", ["get"]); + jasmine.createSpyObj("statusCapability", ["get", "listen"]); mockStatusCapability.get.andReturn(false); mockCapabilities = { 'persistence': mockPersistence, @@ -82,6 +82,17 @@ define( expect(mockScope.commit).toEqual(jasmine.any(Function)); }); + it("Sets edit view template on edit mode", function () { + mockStatusCapability.listen.mostRecentCall.args[0](['editing']); + expect(mockScope.viewObjectTemplate).toEqual('edit-object'); + }); + + it("Cleans up listeners on scope destroy", function () { + representer.listenHandle = jasmine.createSpy('listen'); + mockScope.$on.mostRecentCall.args[1](); + expect(representer.listenHandle).toHaveBeenCalled(); + }); + it("mutates and persists upon observed changes", function () { mockScope.model = { someKey: "some value" }; mockScope.configuration = { someConfiguration: "something" }; @@ -112,4 +123,4 @@ define( }); } -); \ No newline at end of file +); diff --git a/platform/commonUI/general/res/templates/object-inspector.html b/platform/commonUI/general/res/templates/object-inspector.html index 56eaeb86cc..708814b3cc 100644 --- a/platform/commonUI/general/res/templates/object-inspector.html +++ b/platform/commonUI/general/res/templates/object-inspector.html @@ -20,7 +20,7 @@ at runtime from the About dialog for additional information. --> -
+
@@ -31,8 +31,8 @@ ng-model="ngModel" class="flex-elem grows vscroll l-flex-col"> -
-
+
+
@@ -45,5 +45,5 @@
-
+
diff --git a/platform/representation/src/gestures/DropGesture.js b/platform/representation/src/gestures/DropGesture.js index 1b7881a770..1e884c14f7 100644 --- a/platform/representation/src/gestures/DropGesture.js +++ b/platform/representation/src/gestures/DropGesture.js @@ -80,13 +80,6 @@ define( }).length > 0; } - function shouldCreateVirtualPanel(domainObject){ - return domainObject.useCapability('view').filter(function (view){ - return (view.key === 'plot' || view.key === 'scrolling') - && domainObject.getModel().type !== 'telemetry.panel'; - }).length > 0; - } - function dragOver(e) { //Refresh domain object on each dragOver to catch external // updates to the model @@ -111,9 +104,7 @@ define( key: 'compose', selectedObject: selectedObject })[0]; - //TODO: Fix this. Define an action for creating new - // virtual panel - if (action || shouldCreateVirtualPanel(domainObject, selectedObject)) { + if (action) { event.dataTransfer.dropEffect = 'move'; // Indicate that we will accept the drag @@ -123,64 +114,23 @@ define( } } - function createVirtualPanel(base, selectedObject){ - - var typeKey = 'telemetry.panel', - type = typeService.getType(typeKey), - model = type.getInitialModel(), - newPanel, - composeAction; - - model.type = typeKey; - newPanel = new EditableDomainObject(instantiate(model), $q); - if (!canCompose(newPanel, selectedObject)) { - return undefined; - } - - [base.getId(), selectedObject.getId()].forEach(function(id){ - newPanel.getCapability('composition').add(id); - }); - - newPanel.getCapability('location') - .setPrimaryLocation(base.getCapability('location') - .getContextualLocation()); - - newPanel.setOriginalObject(base); - return newPanel; - - } - function drop(e) { var event = (e || {}).originalEvent || e, id = event.dataTransfer.getData(GestureConstants.MCT_DRAG_TYPE), - domainObjectType = editableDomainObject.getModel().type, - selectedObject = dndService.getData( - GestureConstants.MCT_EXTENDED_DRAG_TYPE - ); - + domainObjectType = editableDomainObject.getModel().type; + // Handle the drop; add the dropped identifier to the // destination domain object's composition, and persist // the change. if (id) { - if (shouldCreateVirtualPanel(domainObject, selectedObject)){ - editableDomainObject = createVirtualPanel(domainObject, selectedObject); - if (editableDomainObject) { - navigationService.setNavigation(editableDomainObject); - broadcastDrop(id, event); - editableDomainObject.getCapability('status').set('editing', true); + $q.when(action && action.perform()).then(function (result) { + //Don't go into edit mode for folders + if (domainObjectType!=='folder') { + editableDomainObject.getCapability('action').perform('edit'); } - } else { - $q.when(action && action.perform()).then(function (result) { - //Don't go into edit mode for folders - if (domainObjectType!=='folder') { - navigationService.setNavigation(editableDomainObject); - editableDomainObject.getCapability('status').set('editing', true); - } - broadcastDrop(id, event); - }); - } + broadcastDrop(id, event); + }); } - // TODO: Alert user if drag and drop is not allowed } // We can only handle drops if we have access to actions...