diff --git a/platform/commonUI/edit/bundle.js b/platform/commonUI/edit/bundle.js index 695ca2aab3..053c2a3d83 100644 --- a/platform/commonUI/edit/bundle.js +++ b/platform/commonUI/edit/bundle.js @@ -37,6 +37,7 @@ define([ "./src/policies/EditableLinkPolicy", "./src/policies/EditableMovePolicy", "./src/policies/EditNavigationPolicy", + "./src/policies/EditContextualActionPolicy", "./src/representers/EditRepresenter", "./src/representers/EditToolbarRepresenter", "text!./res/templates/library.html", @@ -61,6 +62,7 @@ define([ EditableLinkPolicy, EditableMovePolicy, EditNavigationPolicy, + EditContextualActionPolicy, EditRepresenter, EditToolbarRepresenter, libraryTemplate, @@ -191,6 +193,11 @@ define([ "category": "action", "implementation": EditActionPolicy }, + { + "category": "action", + "implementation": EditContextualActionPolicy, + "depends": ["navigationService"] + }, { "category": "action", "implementation": EditableMovePolicy diff --git a/platform/commonUI/edit/src/policies/EditActionPolicy.js b/platform/commonUI/edit/src/policies/EditActionPolicy.js index 7f623ce07b..502577ade9 100644 --- a/platform/commonUI/edit/src/policies/EditActionPolicy.js +++ b/platform/commonUI/edit/src/policies/EditActionPolicy.js @@ -38,14 +38,6 @@ define( this.policyService = policyService; } - function applicableView(key){ - return ['plot', 'scrolling'].indexOf(key) >= 0; - } - - function editableType(key){ - return key === 'telemetry.panel'; - } - /** * Get a count of views which are not flagged as non-editable. * @private @@ -65,7 +57,8 @@ define( // A view is editable unless explicitly flagged as not (views || []).forEach(function (view) { - if (view.editable === true || (applicableView(view.key) && editableType(type.getKey()))) { + if (view.editable === true || + (view.key === 'plot' && type.getKey() === 'telemetry.panel')) { count++; } }); diff --git a/platform/commonUI/edit/src/policies/EditContextualActionPolicy.js b/platform/commonUI/edit/src/policies/EditContextualActionPolicy.js new file mode 100644 index 0000000000..9f98698439 --- /dev/null +++ b/platform/commonUI/edit/src/policies/EditContextualActionPolicy.js @@ -0,0 +1,59 @@ +/***************************************************************************** + * 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 the context menu is visible when + * objects are being edited + * @memberof platform/commonUI/edit + * @constructor + * @implements {Policy.} + */ + function EditContextualActionPolicy(navigationService) { + this.navigationService = navigationService; + this.blacklist = ["move", "copy", "link", "window", "follow"]; + } + + 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')){ + return false; + } else { + return this.blacklist.indexOf(actionMetadata.key) === -1; + } + } else { + return true; + } + }; + + return EditContextualActionPolicy; + } +); diff --git a/platform/commonUI/edit/test/policies/EditActionPolicySpec.js b/platform/commonUI/edit/test/policies/EditActionPolicySpec.js index 823c3d2737..e2cf51ab97 100644 --- a/platform/commonUI/edit/test/policies/EditActionPolicySpec.js +++ b/platform/commonUI/edit/test/policies/EditActionPolicySpec.js @@ -35,19 +35,43 @@ define( mockDomainObject, mockEditAction, mockPropertiesAction, + mockTypeCapability, + mockStatusCapability, + capabilities, + plotView, policy; beforeEach(function () { mockDomainObject = jasmine.createSpyObj( 'domainObject', - [ 'useCapability' ] + [ + 'useCapability', + 'hasCapability', + 'getCapability' + ] ); + mockStatusCapability = jasmine.createSpyObj('statusCapability', ['get']); + mockStatusCapability.get.andReturn(false); + mockTypeCapability = jasmine.createSpyObj('type', ['getKey']); + capabilities = { + 'status': mockStatusCapability, + 'type': mockTypeCapability + }; + mockEditAction = jasmine.createSpyObj('edit', ['getMetadata']); mockPropertiesAction = jasmine.createSpyObj('edit', ['getMetadata']); + mockDomainObject.getCapability.andCallFake(function(capability){ + return capabilities[capability]; + }); + mockDomainObject.hasCapability.andCallFake(function(capability){ + return !!capabilities[capability]; + }); + editableView = { editable: true }; nonEditableView = { editable: false }; undefinedView = { someKey: "some value" }; + plotView = { key: "plot", editable: false }; testViews = []; mockDomainObject.useCapability.andCallFake(function (c) { @@ -66,38 +90,53 @@ define( policy = new EditActionPolicy(); }); - //TODO: Disabled for NEM Beta - xit("allows the edit action when there are editable views", function () { + it("allows the edit action when there are editable views", function () { testViews = [ editableView ]; - expect(policy.allow(mockEditAction, testContext)).toBeTruthy(); - // No edit flag defined; should be treated as editable - testViews = [ undefinedView, undefinedView ]; - expect(policy.allow(mockEditAction, testContext)).toBeTruthy(); + expect(policy.allow(mockEditAction, testContext)).toBe(true); }); - //TODO: Disabled for NEM Beta - xit("allows the edit properties action when there are no editable views", function () { + it("allows the edit properties action when there are no editable views", function () { testViews = [ nonEditableView, nonEditableView ]; - expect(policy.allow(mockPropertiesAction, testContext)).toBeTruthy(); + expect(policy.allow(mockPropertiesAction, testContext)).toBe(true); }); - //TODO: Disabled for NEM Beta - xit("disallows the edit action when there are no editable views", function () { + it("disallows the edit action when there are no editable views", function () { testViews = [ nonEditableView, nonEditableView ]; - expect(policy.allow(mockEditAction, testContext)).toBeFalsy(); + expect(policy.allow(mockEditAction, testContext)).toBe(false); }); - //TODO: Disabled for NEM Beta - xit("disallows the edit properties action when there are" + + it("disallows the edit properties action when there are" + " editable views", function () { testViews = [ editableView ]; - expect(policy.allow(mockPropertiesAction, testContext)).toBeFalsy(); + expect(policy.allow(mockPropertiesAction, testContext)).toBe(false); }); + it("disallows the edit action when object is already being" + + " edited", function () { + testViews = [ editableView ]; + mockStatusCapability.get.andReturn(true); + expect(policy.allow(mockEditAction, testContext)).toBe(false); + }); + + it("allows editing of panels in plot view", function () { + testViews = [ plotView ]; + mockTypeCapability.getKey.andReturn('telemetry.panel'); + + expect(policy.allow(mockEditAction, testContext)).toBe(true); + }); + + it("disallows editing of plot view when object not a panel type", function () { + testViews = [ plotView ]; + mockTypeCapability.getKey.andReturn('something.else'); + + expect(policy.allow(mockEditAction, testContext)).toBe(false); + }); + + it("allows the edit properties outside of the 'view-control' category", function () { testViews = [ nonEditableView ]; testContext.category = "something-else"; - expect(policy.allow(mockPropertiesAction, testContext)).toBeTruthy(); + expect(policy.allow(mockPropertiesAction, testContext)).toBe(true); }); }); } diff --git a/platform/commonUI/edit/test/policies/EditContextualActionPolicySpec.js b/platform/commonUI/edit/test/policies/EditContextualActionPolicySpec.js new file mode 100644 index 0000000000..625cd13b18 --- /dev/null +++ b/platform/commonUI/edit/test/policies/EditContextualActionPolicySpec.js @@ -0,0 +1,76 @@ +/***************************************************************************** + * 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,xit,xdescribe*/ + +define( + ["../../src/policies/EditContextualActionPolicy"], + function (EditContextualActionPolicy) { + "use strict"; + + describe("The Edit contextual action policy", function () { + var policy, + navigationService, + mockAction, + context, + navigatedObject, + mockDomainObject, + metadata; + + beforeEach(function () { + navigatedObject = jasmine.createSpyObj("navigatedObject", ["hasCapability"]); + navigatedObject.hasCapability.andReturn(false); + + mockDomainObject = jasmine.createSpyObj("domainObject", ["hasCapability"]); + mockDomainObject.hasCapability.andReturn(false); + + navigationService = jasmine.createSpyObj("navigationService", ["getNavigation"]); + navigationService.getNavigation.andReturn(navigatedObject); + + metadata = {key: "move"}; + mockAction = jasmine.createSpyObj("action", ["getMetadata"]); + mockAction.getMetadata.andReturn(metadata); + + context = {domainObject: mockDomainObject}; + + policy = new EditContextualActionPolicy(navigationService); + }); + + it('Allows all actions when navigated object not in edit mode', function() { + expect(policy.allow(mockAction, context)).toBe(true); + }); + + it('Allows no actions when navigated object in edit mode, but' + + ' selected object not in edit mode ', function() { + navigatedObject.hasCapability.andReturn(true); + expect(policy.allow(mockAction, context)).toBe(false); + }); + + it('Disallows blacklisted actions when navigated object and' + + ' selected object in edit mode', function() { + navigatedObject.hasCapability.andReturn(true); + mockDomainObject.hasCapability.andReturn(true); + expect(policy.allow(mockAction, context)).toBe(false); + }); + + }); + } +); \ No newline at end of file diff --git a/platform/core/src/actions/ActionCapability.js b/platform/core/src/actions/ActionCapability.js index 7caf5c0ecc..2164969a05 100644 --- a/platform/core/src/actions/ActionCapability.js +++ b/platform/core/src/actions/ActionCapability.js @@ -28,7 +28,6 @@ define( [], function () { "use strict"; - var DISALLOWED_ACTIONS = ["copy", "window", "follow"]; /** * The ActionCapability allows applicable Actions to be retrieved and @@ -55,37 +54,22 @@ define( this.domainObject = domainObject; } - function isEditable(domainObject){ - return domainObject.getCapability('status').get('editing'); - } - - function hasEditableAncestor(domainObject){ - return domainObject.hasCapability('context') && - domainObject - .getCapability('context') - .getPath() - .some(function isEditable (ancestor){ - return ancestor.getCapability('status').get('editing'); - }); - } - /** - * Retrieve the actions applicable to the domain object in the given - * context. + * Perform an action. This will find and perform the + * first matching action available for the specified + * context or key. * * @param {ActionContext|string} context the context in which - * to assess the applicability of the available actions; this is - * passed along to the action service to match against available + * to perform the action; this is passed along to + * the action service to match against available * actions. The "domainObject" field will automatically * be populated with the domain object that exposed * this capability. If given as a string, this will * be taken as the "key" field to match against * specific actions. - * - * Additionally, this function will limit the actions - * available for an object in Edit Mode - * @returns {Array} The actions applicable to this domain - * object in the given context + * @returns {Promise} the result of the action that was + * performed, or undefined if no matching action + * was found. * @memberof platform/core.ActionCapability# */ ActionCapability.prototype.getActions = function (context) { @@ -94,19 +78,11 @@ define( // but additionally adds a domainObject field. var baseContext = typeof context === 'string' ? { key: context } : (context || {}), - actionContext = Object.create(baseContext), - actions; + actionContext = Object.create(baseContext); actionContext.domainObject = this.domainObject; - actions = this.actionService.getActions(actionContext) || []; - if (isEditable(this.domainObject) || hasEditableAncestor(this.domainObject)){ - return actions.filter(function(action){ - return DISALLOWED_ACTIONS.indexOf(action.getMetadata().key) === -1; - }); - } else { - return actions; - } + return this.actionService.getActions(actionContext); }; /** diff --git a/platform/core/test/actions/ActionCapabilitySpec.js b/platform/core/test/actions/ActionCapabilitySpec.js index feb1273721..ab3db012f1 100644 --- a/platform/core/test/actions/ActionCapabilitySpec.js +++ b/platform/core/test/actions/ActionCapabilitySpec.js @@ -19,8 +19,7 @@ * this source code distribution or the Licensing information page available * at runtime from the About dialog for additional information. *****************************************************************************/ -/*global define,Promise,describe,xdescribe,it,expect,beforeEach,waitsFor, - jasmine*/ +/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/ /** * ActionCapabilitySpec. Created by vwoeltje on 11/6/14. @@ -29,8 +28,8 @@ define( ["../../src/actions/ActionCapability"], function (ActionCapability) { "use strict"; - //TODO: Disabled for NEM beta - xdescribe("The action capability", function () { + + describe("The action capability", function () { var mockQ, mockAction, mockActionService, diff --git a/platform/representation/bundle.js b/platform/representation/bundle.js index a58ce4f34c..c257e972f0 100644 --- a/platform/representation/bundle.js +++ b/platform/representation/bundle.js @@ -99,9 +99,7 @@ define([ "implementation": ContextMenuGesture, "depends": [ "$timeout", - "$parse", - "agentService", - "navigationService" + "agentService" ] } ], diff --git a/platform/representation/src/gestures/ContextMenuGesture.js b/platform/representation/src/gestures/ContextMenuGesture.js index be2fb27e91..e7c0c7ba9f 100644 --- a/platform/representation/src/gestures/ContextMenuGesture.js +++ b/platform/representation/src/gestures/ContextMenuGesture.js @@ -41,32 +41,16 @@ define( * in the context menu will be performed * @implements {Gesture} */ - function ContextMenuGesture($timeout, $parse, agentService, navigationService, element, domainObject) { + function ContextMenuGesture($timeout, agentService, element, domainObject) { var isPressing, - longTouchTime = 500, - parameters = element && element.attr('parameters') && $parse(element.attr('parameters'))(); - - function suppressMenu() { - return parameters - && parameters.suppressMenuOnEdit - && navigationService.getNavigation() - && navigationService.getNavigation().hasCapability('editor'); - } + longTouchTime = 500; function showMenu(event) { - /** - * Some menu items should have the context menu action - * suppressed (eg. the navigation menu on the left) - */ - if (suppressMenu()){ - return; - } else { - domainObject.getCapability('action').perform({ - key: 'menu', - domainObject: domainObject, - event: event - }); - } + domainObject.getCapability('action').perform({ + key: 'menu', + domainObject: domainObject, + event: event + }); } // When context menu event occurs, show object actions instead diff --git a/platform/representation/test/gestures/ContextMenuGestureSpec.js b/platform/representation/test/gestures/ContextMenuGestureSpec.js index 1ed9d628ec..0e78729ef9 100644 --- a/platform/representation/test/gestures/ContextMenuGestureSpec.js +++ b/platform/representation/test/gestures/ContextMenuGestureSpec.js @@ -30,16 +30,14 @@ define( function (ContextMenuGesture) { "use strict"; - var JQLITE_FUNCTIONS = [ "on", "off", "find", "append", "remove", "attr" ], + var JQLITE_FUNCTIONS = [ "on", "off", "find", "append", "remove" ], DOMAIN_OBJECT_METHODS = [ "getId", "getModel", "getCapability", "hasCapability", "useCapability"]; describe("The 'context menu' gesture", function () { var mockTimeout, - mockParse, mockElement, mockAgentService, - mockNavigationService, mockDomainObject, mockEvent, mockTouchEvent, @@ -53,7 +51,6 @@ define( beforeEach(function () { mockTimeout = jasmine.createSpy("$timeout"); - mockParse = jasmine.createSpy("$parse"); mockElement = jasmine.createSpyObj("element", JQLITE_FUNCTIONS); mockAgentService = jasmine.createSpyObj("agentService", ["isMobile"]); mockDomainObject = jasmine.createSpyObj("domainObject", DOMAIN_OBJECT_METHODS); @@ -62,17 +59,14 @@ define( "action", [ "perform", "getActions" ] ); - mockActionContext = jasmine.createSpyObj( - "actionContext", - [ "" ] - ); mockActionContext = {domainObject: mockDomainObject, event: mockEvent}; mockDomainObject.getCapability.andReturn(mockContextMenuAction); mockContextMenuAction.perform.andReturn(jasmine.any(Function)); mockAgentService.isMobile.andReturn(false); - gesture = new ContextMenuGesture(mockTimeout, mockParse, mockAgentService, mockNavigationService, mockElement, mockDomainObject); + + gesture = new ContextMenuGesture(mockTimeout, mockAgentService, mockElement, mockDomainObject); // Capture the contextmenu callback fireGesture = mockElement.on.mostRecentCall.args[1]; @@ -108,7 +102,7 @@ define( mockAgentService.isMobile.andReturn(true); // Then create new (mobile) gesture - gesture = new ContextMenuGesture(mockTimeout, mockParse, mockAgentService, mockNavigationService, mockElement, mockDomainObject); + gesture = new ContextMenuGesture(mockTimeout, mockAgentService, mockElement, mockDomainObject); // Set calls for the touchstart and touchend gestures fireTouchStartGesture = mockElement.on.calls[1].args[1];