diff --git a/platform/commonUI/browse/bundle.js b/platform/commonUI/browse/bundle.js index bfdcfa54d9..f70abd66d1 100644 --- a/platform/commonUI/browse/bundle.js +++ b/platform/commonUI/browse/bundle.js @@ -80,7 +80,6 @@ define([ "$scope", "$route", "$location", - "$q", "objectService", "navigationService", "urlService" @@ -137,10 +136,6 @@ define([ } ], "representations": [ - { - "key": "view-region", - "templateUrl": "templates/view-object.html" - }, { "key": "browse-object", "templateUrl": "templates/browse-object.html", @@ -206,6 +201,10 @@ define([ { "key": "inspector-region", "templateUrl": "templates/browse/inspector-region.html" + }, + { + "key": "view-region", + "templateUrl": "templates/view-region.html" } ], "services": [ diff --git a/platform/commonUI/browse/res/templates/browse.html b/platform/commonUI/browse/res/templates/browse.html index 5b74709b2e..81d3bdd9a6 100644 --- a/platform/commonUI/browse/res/templates/browse.html +++ b/platform/commonUI/browse/res/templates/browse.html @@ -71,7 +71,7 @@
- @@ -87,4 +87,3 @@
- diff --git a/platform/commonUI/browse/res/templates/view-object.html b/platform/commonUI/browse/res/templates/view-region.html similarity index 66% rename from platform/commonUI/browse/res/templates/view-object.html rename to platform/commonUI/browse/res/templates/view-region.html index b670e1645e..52fd3af83e 100644 --- a/platform/commonUI/browse/res/templates/view-object.html +++ b/platform/commonUI/browse/res/templates/view-region.html @@ -19,15 +19,12 @@ 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 50a7efa04c..3525081518 100644 --- a/platform/commonUI/browse/src/BrowseController.js +++ b/platform/commonUI/browse/src/BrowseController.js @@ -46,7 +46,7 @@ define( * @memberof platform/commonUI/browse * @constructor */ - function BrowseController($scope, $route, $location, $q, objectService, navigationService, urlService) { + function BrowseController($scope, $route, $location, objectService, navigationService, urlService) { var path = [ROOT_ID].concat( ($route.current.params.ids || DEFAULT_PATH).split("/") ); @@ -174,4 +174,3 @@ define( return BrowseController; } ); - diff --git a/platform/commonUI/browse/src/BrowseObjectController.js b/platform/commonUI/browse/src/BrowseObjectController.js index daf157e2c3..db82b467d7 100644 --- a/platform/commonUI/browse/src/BrowseObjectController.js +++ b/platform/commonUI/browse/src/BrowseObjectController.js @@ -81,4 +81,3 @@ define( return BrowseObjectController; } ); - diff --git a/platform/commonUI/browse/src/TypeRegionDecorator.js b/platform/commonUI/browse/src/TypeRegionDecorator.js index 7c3f60994f..60b86a0270 100644 --- a/platform/commonUI/browse/src/TypeRegionDecorator.js +++ b/platform/commonUI/browse/src/TypeRegionDecorator.js @@ -23,9 +23,10 @@ define( [ - './InspectorRegion' + './InspectorRegion', + './ViewRegion' ], - function (InspectorRegion) { + function (InspectorRegion, ViewRegion) { "use strict"; /** @@ -53,6 +54,7 @@ define( var regions = type.getDefinition().regions || {}; regions.inspector = regions.inspector || new InspectorRegion(); + regions.view = regions.view || new ViewRegion(); type.getDefinition().regions = regions; diff --git a/platform/commonUI/browse/src/ViewRegion.js b/platform/commonUI/browse/src/ViewRegion.js new file mode 100644 index 0000000000..2f4bd42e8e --- /dev/null +++ b/platform/commonUI/browse/src/ViewRegion.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,window*/ + +define( + [ + '../../regions/src/Region' + ], + function (Region) { + "use strict"; + + /** + * Defines the default View region. Captured in a class to + * allow for modular extension and customization of regions based on + * the typical case. + * @memberOf platform/commonUI/regions + * @constructor + */ + function ViewRegion() { + Region.call(this); + + this.buildRegion(); + } + + ViewRegion.prototype = Object.create(Region.prototype); + ViewRegion.prototype.constructor = Region; + + /** + * @private + */ + ViewRegion.prototype.buildRegion = function() { + var browseViewPart = { + name: 'browse-view', + title: 'Browse Object View', + modes: ['browse'], + content: { + key: 'browse-object' + } + }, + editViewPart = { + name: 'edit-view', + title: 'Edit Object View', + modes: ['edit'], + content: { + key: 'edit-object' + } + }; + this.addPart(browseViewPart); + this.addPart(editViewPart); + }; + + return ViewRegion; + } +); diff --git a/platform/commonUI/browse/src/navigation/NavigationService.js b/platform/commonUI/browse/src/navigation/NavigationService.js index 1d43971166..858a5d80eb 100644 --- a/platform/commonUI/browse/src/navigation/NavigationService.js +++ b/platform/commonUI/browse/src/navigation/NavigationService.js @@ -57,7 +57,7 @@ define( NavigationService.prototype.setNavigation = function (value) { var canNavigate = true; if (this.navigated !== value) { - canNavigate = (this.callbacks['before'] || []) + canNavigate = (this.callbacks.before || []) .reduce(function (previous, callback) { //Check whether the callback returned a value of // 'false' indicating that navigation should not @@ -67,7 +67,7 @@ define( }, true); if (canNavigate) { this.navigated = value; - this.callbacks['after'].forEach(function (callback) { + (this.callbacks.after || []).forEach(function (callback) { callback(value); }); } diff --git a/platform/commonUI/browse/test/BrowseControllerSpec.js b/platform/commonUI/browse/test/BrowseControllerSpec.js index 582993e5a7..79a3c874ca 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, @@ -230,7 +229,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(); @@ -241,6 +243,32 @@ 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; + mockNavigationService.setNavigation.andReturn(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..3a354dee78 100644 --- a/platform/commonUI/browse/test/navigation/NavigationServiceSpec.js +++ b/platform/commonUI/browse/test/navigation/NavigationServiceSpec.js @@ -84,6 +84,24 @@ define( expect(callback).not.toHaveBeenCalled(); }); + it("adds listeners to the 'after' state by default", function(){ + expect(navigationService.callbacks.after).toBeUndefined(); + navigationService.addListener(function(){}); + expect(navigationService.callbacks.after).toBeDefined(); + expect(navigationService.callbacks.after.length).toBe(1); + }); + + it("allows navigationService events to be prevented", function(){ + var callback = jasmine.createSpy("callback"), + navigationResult; + callback.andReturn(false); + navigationService.addListener(callback, "before"); + navigationResult = navigationService.setNavigation({}); + expect(callback).toHaveBeenCalled(); + expect(navigationResult).toBe(false); + + }); + }); } -); \ No newline at end of file +); diff --git a/platform/commonUI/edit/bundle.js b/platform/commonUI/edit/bundle.js index 3b52e6c6e1..d19565e580 100644 --- a/platform/commonUI/edit/bundle.js +++ b/platform/commonUI/edit/bundle.js @@ -84,7 +84,8 @@ define([ "key": "EditObjectController", "implementation": EditObjectController, "depends": [ - "$scope" + "$scope", + "$location" ] } ], @@ -215,10 +216,6 @@ define([ { "key": "topbar-edit", "templateUrl": "templates/topbar-edit.html" - }, - { - "key": "inspector-edit", - "templateUrl": "templates/inspector-edit.html" } ], "representers": [ diff --git a/platform/commonUI/edit/res/templates/inspector-edit.html b/platform/commonUI/edit/res/templates/inspector-edit.html deleted file mode 100644 index 5f3e4d522e..0000000000 --- a/platform/commonUI/edit/res/templates/inspector-edit.html +++ /dev/null @@ -1,80 +0,0 @@ - - -
- -
-
-
Inspection
-
    -
  • - Properties -
    -
    {{ data.name }}
    -
    {{ data.value }}
    -
    -
  • -
  • - Location - - - - -
  • -
  • - Original Location - - - - -
  • -
-
-
- -
-
- Elements - - -
-
-
-
-
diff --git a/platform/commonUI/edit/src/controllers/EditObjectController.js b/platform/commonUI/edit/src/controllers/EditObjectController.js index 96c843b6ea..e46d5cffe1 100644 --- a/platform/commonUI/edit/src/controllers/EditObjectController.js +++ b/platform/commonUI/edit/src/controllers/EditObjectController.js @@ -36,7 +36,7 @@ define( * @memberof platform/commonUI/edit * @constructor */ - function EditObjectController($scope) { + function EditObjectController($scope, $location) { this.scope = $scope; var navigatedObject; diff --git a/platform/commonUI/edit/src/directives/MCTBeforeUnload.js b/platform/commonUI/edit/src/directives/MCTBeforeUnload.js index 226e85f4a0..2312678c6b 100644 --- a/platform/commonUI/edit/src/directives/MCTBeforeUnload.js +++ b/platform/commonUI/edit/src/directives/MCTBeforeUnload.js @@ -55,6 +55,20 @@ define( return scope.$eval(attrs.mctBeforeUnload); } + function shouldAllowNavigation(){ + // Get an unload message (if any) + var warning = unload(); + // Prompt the user if there's an unload message + return !warning || $window.confirm(warning); + } + + // Show a dialog before allowing a location change + function checkNavigationEvent(event) { + // Return a false value to the navigationService to + // indicate that the navigation event should be prevented + return shouldAllowNavigation(); + } + // Stop using this unload expression function removeUnload() { navigationService.removeListener(checkNavigationEvent, "before"); @@ -66,13 +80,6 @@ define( } } - function shouldAllowNavigation(){ - // Get an unload message (if any) - var warning = unload(); - // Prompt the user if there's an unload message - return !warning || $window.confirm(warning); - } - // Show a dialog before allowing a location change function checkLocationChange(event) { if (!shouldAllowNavigation()) { @@ -81,13 +88,6 @@ define( } } - // Show a dialog before allowing a location change - function checkNavigationEvent(event) { - // Return a false value to the navigationService to - // indicate that the navigation event should be prevented - return shouldAllowNavigation(); - } - // If this is the first active instance of this directive, // register as the window's beforeunload handler if (unloads.length === 0) { diff --git a/platform/commonUI/edit/src/representers/EditRepresenter.js b/platform/commonUI/edit/src/representers/EditRepresenter.js index 2ce4a43110..c6f890af0e 100644 --- a/platform/commonUI/edit/src/representers/EditRepresenter.js +++ b/platform/commonUI/edit/src/representers/EditRepresenter.js @@ -49,7 +49,6 @@ 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) { @@ -101,13 +100,10 @@ define( // Place the "commit" method in the scope scope.commit = commit; scope.setEditable = setEditable; - } // 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 @@ -115,34 +111,11 @@ define( // Ensure existing watches are released this.destroy(); - - function setEditing(){ - scope.viewRegionTemplate = 'edit-object'; - scope.inspectorRegionTemplate = 'inspector-edit' - } - - /** - * 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.viewRegionTemplate; - } - }); - - if (representedObject.getCapability('status').get('editing')){ - setEditing(); - } }; // Respond to the destruction of the current representation. EditRepresenter.prototype.destroy = function destroy() { // Nothing to clean up - 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..0a39874eb8 100644 --- a/platform/commonUI/edit/test/controllers/EditControllerSpec.js +++ b/platform/commonUI/edit/test/controllers/EditControllerSpec.js @@ -22,89 +22,72 @@ /*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, 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 () { 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 ); }); - 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(), + var obj = mockObject, mockEditor = jasmine.createSpyObj('editor', ['dirty']); // Normally, should be undefined @@ -112,14 +95,32 @@ define( // Override the object's editor capability, make it look // like there are unsaved changes. - obj.getCapability = jasmine.createSpy(); - obj.getCapability.andReturn(mockEditor); + mockCapabilities.editor = mockEditor; mockEditor.dirty.andReturn(true); + mockStatusCapability.get.andReturn(true); // Should have some warning message here now expect(controller.getUnloadWarning()).toEqual(jasmine.any(String)); }); + + 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/directives/MCTBeforeUnloadSpec.js b/platform/commonUI/edit/test/directives/MCTBeforeUnloadSpec.js index 41070d76f5..fd94e3e90d 100644 --- a/platform/commonUI/edit/test/directives/MCTBeforeUnloadSpec.js +++ b/platform/commonUI/edit/test/directives/MCTBeforeUnloadSpec.js @@ -31,6 +31,7 @@ define( mockScope, testAttrs, mockEvent, + mockNavigationService, directive; function fireListener(eventType, value) { @@ -46,7 +47,8 @@ define( mockScope = jasmine.createSpyObj("$scope", ['$eval', '$on']); testAttrs = { mctBeforeUnload: "someExpression" }; mockEvent = jasmine.createSpyObj("event", ["preventDefault"]); - directive = new MCTBeforeUnload(mockWindow); + mockNavigationService = jasmine.createSpyObj("navigationService", ["addListener", "removeListener"]); + directive = new MCTBeforeUnload(mockWindow, mockNavigationService); directive.link(mockScope, {}, testAttrs); }); @@ -65,6 +67,10 @@ define( ); }); + it("listens for navigation changes", function () { + expect(mockNavigationService.addListener).toHaveBeenCalledWith(jasmine.any(Function), "before"); + }); + it("listens for its scope's destroy event", function () { expect(mockScope.$on).toHaveBeenCalledWith( "$destroy", @@ -108,9 +114,10 @@ define( it("cleans up listeners when destroyed", function () { fireListener("$destroy", mockEvent); expect(mockWindow.onbeforeunload).toBeUndefined(); + expect(mockNavigationService.removeListener).toHaveBeenCalled(); }); }); } -); \ No newline at end of file +); diff --git a/platform/commonUI/edit/test/representers/EditRepresenterSpec.js b/platform/commonUI/edit/test/representers/EditRepresenterSpec.js index 8b0ef22a7d..f49547fc18 100644 --- a/platform/commonUI/edit/test/representers/EditRepresenterSpec.js +++ b/platform/commonUI/edit/test/representers/EditRepresenterSpec.js @@ -33,6 +33,8 @@ define( testRepresentation, mockDomainObject, mockPersistence, + mockStatusCapability, + mockCapabilities, representer; function mockPromise(value) { @@ -57,11 +59,20 @@ define( ]); mockPersistence = jasmine.createSpyObj("persistence", ["persist"]); + mockStatusCapability = + jasmine.createSpyObj("statusCapability", ["get", "listen"]); + + mockCapabilities = { + "persistence": mockPersistence, + "status": mockStatusCapability + }; mockDomainObject.getModel.andReturn({}); mockDomainObject.hasCapability.andReturn(true); mockDomainObject.useCapability.andReturn(true); - mockDomainObject.getCapability.andReturn(mockPersistence); + mockDomainObject.getCapability.andCallFake(function(capability){ + return mockCapabilities[capability]; + }); representer = new EditRepresenter(mockQ, mockLog, mockScope); representer.represent(testRepresentation, mockDomainObject); @@ -101,4 +112,4 @@ define( }); } -); \ No newline at end of file +); diff --git a/platform/commonUI/general/bundle.js b/platform/commonUI/general/bundle.js index 132bbd36a2..aeb05762b1 100644 --- a/platform/commonUI/general/bundle.js +++ b/platform/commonUI/general/bundle.js @@ -447,12 +447,8 @@ define([ ] }, { - "key": "inspector-region", + "key": "object-inspector", "templateUrl": "templates/object-inspector.html" - }, - { - "key": "inspector-browse", - "templateUrl": "templates/inspector-browse.html" } ], "controls": [ diff --git a/platform/commonUI/general/res/templates/inspector-browse.html b/platform/commonUI/general/res/templates/inspector-browse.html deleted file mode 100644 index 3a7a60f516..0000000000 --- a/platform/commonUI/general/res/templates/inspector-browse.html +++ /dev/null @@ -1,64 +0,0 @@ - - -
-
Inspection
- -
- -
diff --git a/platform/representation/src/gestures/DropGesture.js b/platform/representation/src/gestures/DropGesture.js index 225211c3bd..535d04e6d2 100644 --- a/platform/representation/src/gestures/DropGesture.js +++ b/platform/representation/src/gestures/DropGesture.js @@ -166,16 +166,12 @@ define( editableDomainObject = createVirtualPanel(domainObject, selectedObject); if (editableDomainObject) { editableDomainObject.getCapability('action').perform('edit'); - //navigationService.setNavigation(editableDomainObject); broadcastDrop(id, event); - //editableDomainObject.getCapability('status').set('editing', true); } } 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); editableDomainObject.getCapability('action').perform('edit'); } broadcastDrop(id, event);