[Fixed Position] Modify fixed position to use the Selection API

Change method name to shouldSelect() as requested by the reviewer.

Fix tests.

Fixes #1848
This commit is contained in:
Pegah Sarram
2017-12-07 14:59:12 -08:00
committed by Victor Woeltjen
parent 0c6786198a
commit 14894cf197
10 changed files with 248 additions and 139 deletions

View File

@@ -78,7 +78,8 @@ define(
return; return;
} }
var selectedObjectComposition = selection[0].context.oldItem.useCapability('composition'); var selected = selection[0].context.oldItem;
var selectedObjectComposition = selected && selected.useCapability('composition');
if (selectedObjectComposition) { if (selectedObjectComposition) {
selectedObjectComposition.then(function (composition) { selectedObjectComposition.then(function (composition) {

View File

@@ -44,11 +44,17 @@ define(
this.selectedObj = undefined; this.selectedObj = undefined;
openmct.selection.on('change', function (selection) { openmct.selection.on('change', function (selection) {
if (selection[0] && selection[0].context.toolbar) { var selected = selection[0];
this.select(selection[0].context.toolbar);
if (selected && selected.context.toolbar) {
this.select(selected.context.toolbar);
} else { } else {
this.deselect(); this.deselect();
} }
if (selected && selected.context.viewProxy) {
this.proxy(selected.context.viewProxy);
}
}.bind(this)); }.bind(this));
} }

View File

@@ -50,7 +50,11 @@ define(
view.show(container); view.show(container);
} else { } else {
self.providerView = false; self.providerView = false;
$scope.inspectorKey = selection[0].context.oldItem.getCapability("type").typeDef.inspector; var selectedItem = selection[0].context.oldItem;
if (selectedItem) {
$scope.inspectorKey = selectedItem.getCapability("type").typeDef.inspector;
}
} }
} }

View File

@@ -272,7 +272,8 @@ define([
"$scope", "$scope",
"$q", "$q",
"dialogService", "dialogService",
"openmct" "openmct",
"$element"
] ]
} }
], ],

View File

@@ -23,7 +23,7 @@
ng-controller="FixedController as controller"> ng-controller="FixedController as controller">
<!-- Background grid --> <!-- Background grid -->
<div class="l-grid-holder" ng-click="controller.clearSelection()"> <div class="l-grid-holder" ng-click="controller.bypassSelection($event)">
<div class="l-grid l-grid-x" <div class="l-grid l-grid-x"
ng-if="!controller.getGridSize()[0] < 3" ng-if="!controller.getGridSize()[0] < 3"
ng-style="{ 'background-size': controller.getGridSize() [0] + 'px 100%' }"></div> ng-style="{ 'background-size': controller.getGridSize() [0] + 'px 100%' }"></div>
@@ -35,35 +35,28 @@
<!-- Fixed position elements --> <!-- Fixed position elements -->
<div ng-repeat="element in controller.getElements()" <div ng-repeat="element in controller.getElements()"
class="l-fixed-position-item s-selectable s-moveable s-hover-border" class="l-fixed-position-item s-selectable s-moveable s-hover-border"
ng-class="{
's-not-selected': controller.selected() && !controller.selected(element),
's-selected': controller.selected(element)
}"
ng-style="element.style" ng-style="element.style"
ng-click="controller.select(element, $event)"> mct-selectable="controller.getContext(element)"
mct-init-select="controller.shouldSelect(element)">
<mct-include key="element.template" <mct-include key="element.template"
parameters="{ gridSize: controller.getGridSize() }" parameters="{ gridSize: controller.getGridSize() }"
ng-model="element"> ng-model="element">
</mct-include> </mct-include>
</div> </div>
<!-- Selection highlight, handles --> <!-- Selection highlight, handles -->
<span class="s-selected s-moveable" ng-if="controller.selected()"> <span class="s-selected s-moveable" ng-if="controller.isElementSelected()">
<div class="l-fixed-position-item t-edit-handle-holder" <div class="l-fixed-position-item t-edit-handle-holder"
mct-drag-down="controller.moveHandle().startDrag(controller.selected())" mct-drag-down="controller.moveHandle().startDrag()"
mct-drag="controller.moveHandle().continueDrag(delta)" mct-drag="controller.moveHandle().continueDrag(delta)"
mct-drag-up="controller.moveHandle().endDrag()" mct-drag-up="controller.endDrag()"
ng-style="controller.selected().style" ng-style="controller.getSelectedElementStyle()">
ng-click="$event.stopPropagation()">
</div> </div>
<div ng-repeat="handle in controller.handles()" <div ng-repeat="handle in controller.handles()"
class="l-fixed-position-item-handle edit-corner" class="l-fixed-position-item-handle edit-corner"
ng-style="handle.style()" ng-style="handle.style()"
mct-drag-down="handle.startDrag()" mct-drag-down="handle.startDrag()"
mct-drag="handle.continueDrag(delta)" mct-drag="handle.continueDrag(delta)"
mct-drag-up="handle.endDrag()" mct-drag-up="controller.endDrag(handle)">
ng-click="$event.stopPropagation()">
</div> </div>
</span> </span>
</div> </div>

View File

@@ -47,7 +47,7 @@ define(
* @constructor * @constructor
* @param {Scope} $scope the controller's Angular scope * @param {Scope} $scope the controller's Angular scope
*/ */
function FixedController($scope, $q, dialogService, openmct) { function FixedController($scope, $q, dialogService, openmct, $element) {
this.names = {}; // Cache names by ID this.names = {}; // Cache names by ID
this.values = {}; // Cache values by ID this.values = {}; // Cache values by ID
this.elementProxiesById = {}; this.elementProxiesById = {};
@@ -55,9 +55,11 @@ define(
this.telemetryObjects = []; this.telemetryObjects = [];
this.subscriptions = []; this.subscriptions = [];
this.openmct = openmct; this.openmct = openmct;
this.$element = $element;
this.$scope = $scope; this.$scope = $scope;
this.gridSize = $scope.domainObject && $scope.domainObject.getModel().layoutGrid; this.gridSize = $scope.domainObject && $scope.domainObject.getModel().layoutGrid;
this.fixedViewSelectable = false;
var self = this; var self = this;
[ [
@@ -87,9 +89,8 @@ define(
// Update the style for a selected element // Update the style for a selected element
function updateSelectionStyle() { function updateSelectionStyle() {
var element = self.selection && self.selection.get(); if (self.selectedElementProxy) {
if (element) { self.selectedElementProxy.style = convertPosition(self.selectedElementProxy);
element.style = convertPosition(element);
} }
} }
@@ -136,25 +137,19 @@ define(
// Decorate elements in the current configuration // Decorate elements in the current configuration
function refreshElements() { function refreshElements() {
// Cache selection; we are instantiating new proxies var elements = (($scope.configuration || {}).elements || []);
// so we may want to restore this.
var selected = self.selection && self.selection.get(),
elements = (($scope.configuration || {}).elements || []),
index = -1; // Start with a 'not-found' value
// Find the selection in the new array
if (selected !== undefined) {
index = elements.indexOf(selected.element);
}
// Create the new proxies... // Create the new proxies...
self.elementProxies = elements.map(makeProxyElement); self.elementProxies = elements.map(makeProxyElement);
// Clear old selection, and restore if appropriate // If selection is not in array, select parent.
if (self.selection) { // Otherwise, set the element to select after refresh.
self.selection.deselect(); if (self.selectedElementProxy) {
if (index > -1) { var index = elements.indexOf(self.selectedElementProxy.element);
self.select(self.elementProxies[index]); if (index === -1) {
self.$element[0].click();
} else if (!self.elementToSelectAfterRefresh) {
self.elementToSelectAfterRefresh = self.elementProxies[index].element;
} }
} }
@@ -224,12 +219,12 @@ define(
$scope.configuration.elements || []; $scope.configuration.elements || [];
// Store the position of this element. // Store the position of this element.
$scope.configuration.elements.push(element); $scope.configuration.elements.push(element);
self.elementToSelectAfterRefresh = element;
// Refresh displayed elements // Refresh displayed elements
refreshElements(); refreshElements();
// Select the newly-added element
self.select(
self.elementProxies[self.elementProxies.length - 1]
);
// Mark change as persistable // Mark change as persistable
if ($scope.commit) { if ($scope.commit) {
$scope.commit("Dropped an element."); $scope.commit("Dropped an element.");
@@ -263,21 +258,36 @@ define(
self.getTelemetry($scope.domainObject); self.getTelemetry($scope.domainObject);
} }
// Sets the selectable object in response to the selection change event.
function setSelection(selectable) {
var selection = selectable[0];
if (!selection) {
return;
}
if (selection.context.elementProxy) {
self.selectedElementProxy = selection.context.elementProxy;
self.mvHandle = self.generateDragHandle(self.selectedElementProxy);
self.resizeHandles = self.generateDragHandles(self.selectedElementProxy);
} else {
// Make fixed view selectable if it's not already.
if (!self.fixedViewSelectable) {
self.fixedViewSelectable = true;
selection.context.viewProxy = new FixedProxy(addElement, $q, dialogService);
self.openmct.selection.select(selection);
}
self.resizeHandles = [];
self.mvHandle = undefined;
self.selectedElementProxy = undefined;
}
}
this.elementProxies = []; this.elementProxies = [];
this.generateDragHandle = generateDragHandle; this.generateDragHandle = generateDragHandle;
this.generateDragHandles = generateDragHandles; this.generateDragHandles = generateDragHandles;
this.updateSelectionStyle = updateSelectionStyle;
// Track current selection state
$scope.$watch("selection", function (selection) {
this.selection = selection;
// Expose the view's selection proxy
if (this.selection) {
this.selection.proxy(
new FixedProxy(addElement, $q, dialogService)
);
}
}.bind(this));
// Detect changes to grid size // Detect changes to grid size
$scope.$watch("model.layoutGrid", updateElementPositions); $scope.$watch("model.layoutGrid", updateElementPositions);
@@ -298,10 +308,13 @@ define(
$scope.$on("$destroy", function () { $scope.$on("$destroy", function () {
self.unsubscribe(); self.unsubscribe();
self.openmct.time.off("bounds", updateDisplayBounds); self.openmct.time.off("bounds", updateDisplayBounds);
self.openmct.selection.off("change", setSelection);
}); });
// Respond to external bounds changes // Respond to external bounds changes
this.openmct.time.on("bounds", updateDisplayBounds); this.openmct.time.on("bounds", updateDisplayBounds);
this.openmct.selection.on('change', setSelection);
this.$element.on('click', this.bypassSelection.bind(this));
} }
/** /**
@@ -492,42 +505,56 @@ define(
}; };
/** /**
* Check if the element is currently selected, or (if no * Checks if the element should be selected or not.
* argument is supplied) get the currently selected element. *
* @returns {boolean} true if selected * @param elementProxy the element to check
* @returns {boolean} true if the element should be selected.
*/ */
FixedController.prototype.selected = function (element) { FixedController.prototype.shouldSelect = function (elementProxy) {
var selection = this.selection; if (elementProxy.element === this.elementToSelectAfterRefresh) {
return selection && ((arguments.length > 0) ? delete this.elementToSelectAfterRefresh;
selection.selected(element) : selection.get()); return true;
}; } else {
return false;
/**
* Set the active user selection in this view.
* @param element the element to select
*/
FixedController.prototype.select = function select(element, event) {
if (event) {
event.stopPropagation();
}
if (this.selection) {
// Update selection...
this.selection.select(element);
// ...as well as move, resize handles
this.mvHandle = this.generateDragHandle(element);
this.resizeHandles = this.generateDragHandles(element);
} }
}; };
/** /**
* Clear the current user selection. * Checks if an element is currently selected.
*
* @returns {boolean} true if an element is selected.
*/ */
FixedController.prototype.clearSelection = function () { FixedController.prototype.isElementSelected = function () {
if (this.selection) { return (this.selectedElementProxy) ? true : false;
this.selection.deselect(); };
this.resizeHandles = [];
this.mvHandle = undefined; /**
* Gets the style for the selected element.
*
* @returns {string} element style
*/
FixedController.prototype.getSelectedElementStyle = function () {
return (this.selectedElementProxy) ? this.selectedElementProxy.style : undefined;
};
/**
* Gets the selected element.
*
* @returns the selected element
*/
FixedController.prototype.getSelectedElement = function () {
return this.selectedElementProxy;
};
/**
* Prevents the event from bubbling up if drag is in progress.
*/
FixedController.prototype.bypassSelection = function ($event) {
if (this.dragInProgress) {
if ($event) {
$event.stopPropagation();
}
return;
} }
}; };
@@ -548,6 +575,38 @@ define(
return this.mvHandle; return this.mvHandle;
}; };
/**
* Gets the selection context.
*
* @param elementProxy the element proxy
* @returns {object} the context object which includes elementProxy and toolbar
*/
FixedController.prototype.getContext = function (elementProxy) {
return {
elementProxy: elementProxy,
toolbar: elementProxy
};
};
/**
* End drag.
*
* @param handle the resize handle
*/
FixedController.prototype.endDrag = function (handle) {
this.dragInProgress = true;
setTimeout(function () {
this.dragInProgress = false;
}.bind(this), 0);
if (handle) {
handle.endDrag();
} else {
this.moveHandle().endDrag();
}
};
return FixedController; return FixedController;
} }
); );

View File

@@ -65,7 +65,7 @@ define(
* Start a drag gesture. This should be called when a drag * Start a drag gesture. This should be called when a drag
* begins to track initial state. * begins to track initial state.
*/ */
FixedDragHandle.prototype.startDrag = function startDrag() { FixedDragHandle.prototype.startDrag = function () {
// Cache initial x/y positions // Cache initial x/y positions
this.dragging = { this.dragging = {
x: this.elementHandle.x(), x: this.elementHandle.x(),

View File

@@ -55,8 +55,8 @@ define(
* @param element the fixed position element, as stored in its * @param element the fixed position element, as stored in its
* configuration * configuration
* @param index the element's index within its array * @param index the element's index within its array
* @param {number[]} gridSize the current layout grid size in [x,y] from
* @param {Array} elements the full array of elements * @param {Array} elements the full array of elements
* @param {number[]} gridSize the current layout grid size in [x,y] from
*/ */
function ElementProxy(element, index, elements, gridSize) { function ElementProxy(element, index, elements, gridSize) {
/** /**

View File

@@ -21,8 +21,14 @@
*****************************************************************************/ *****************************************************************************/
define( define(
["../src/FixedController"], [
function (FixedController) { "../src/FixedController",
"zepto"
],
function (
FixedController,
$
) {
describe("The Fixed Position controller", function () { describe("The Fixed Position controller", function () {
var mockScope, var mockScope,
@@ -46,6 +52,9 @@ define(
mockMetadata, mockMetadata,
mockTimeSystem, mockTimeSystem,
mockLimitEvaluator, mockLimitEvaluator,
mockSelection,
$element = [],
selectable = [],
controller; controller;
// Utility function; find a watch for a given expression // Utility function; find a watch for a given expression
@@ -180,17 +189,30 @@ define(
mockScope.model = testModel; mockScope.model = testModel;
mockScope.configuration = testConfiguration; mockScope.configuration = testConfiguration;
mockScope.selection = jasmine.createSpyObj(
'selection', selectable[0] = {
['select', 'get', 'selected', 'deselect', 'proxy'] context: {
); oldItem: mockDomainObject
}
};
mockSelection = jasmine.createSpyObj("selection", [
'select',
'on',
'off',
'get'
]);
mockSelection.get.andCallThrough();
mockOpenMCT = { mockOpenMCT = {
time: mockConductor, time: mockConductor,
telemetry: mockTelemetryAPI, telemetry: mockTelemetryAPI,
composition: mockCompositionAPI composition: mockCompositionAPI,
selection: mockSelection
}; };
$element = $('<div></div>');
spyOn($element[0], 'click');
mockMetadata = jasmine.createSpyObj('mockMetadata', [ mockMetadata = jasmine.createSpyObj('mockMetadata', [
'valuesForHints', 'valuesForHints',
'value', 'value',
@@ -226,11 +248,11 @@ define(
mockScope, mockScope,
mockQ, mockQ,
mockDialogService, mockDialogService,
mockOpenMCT mockOpenMCT,
$element
); );
findWatch("model.layoutGrid")(testModel.layoutGrid); findWatch("model.layoutGrid")(testModel.layoutGrid);
findWatch("selection")(mockScope.selection);
}); });
it("subscribes when a domain object is available", function () { it("subscribes when a domain object is available", function () {
@@ -306,41 +328,41 @@ define(
}); });
it("allows elements to be selected", function () { it("allows elements to be selected", function () {
var elements;
testModel.modified = 1; testModel.modified = 1;
findWatch("model.modified")(testModel.modified); findWatch("model.modified")(testModel.modified);
elements = controller.getElements(); selectable[0].context.elementProxy = controller.getElements()[1];
controller.select(elements[1]); mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
expect(mockScope.selection.select)
.toHaveBeenCalledWith(elements[1]); expect(controller.isElementSelected()).toBe(true);
}); });
it("allows selection retrieval", function () { it("allows selection retrieval", function () {
// selected with no arguments should give the current
// selection
var elements; var elements;
testModel.modified = 1; testModel.modified = 1;
findWatch("model.modified")(testModel.modified); findWatch("model.modified")(testModel.modified);
elements = controller.getElements(); elements = controller.getElements();
controller.select(elements[1]); selectable[0].context.elementProxy = elements[1];
mockScope.selection.get.andReturn(elements[1]); mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
expect(controller.selected()).toEqual(elements[1]);
expect(controller.getSelectedElement()).toEqual(elements[1]);
}); });
it("allows selections to be cleared", function () { it("selects the parent view when selected element is removed", function () {
var elements;
testModel.modified = 1; testModel.modified = 1;
findWatch("model.modified")(testModel.modified); findWatch("model.modified")(testModel.modified);
elements = controller.getElements(); var elements = controller.getElements();
controller.select(elements[1]); selectable[0].context.elementProxy = elements[1];
controller.clearSelection(); mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
expect(controller.selected(elements[1])).toBeFalsy();
elements[1].remove();
testModel.modified = 2;
findWatch("model.modified")(testModel.modified);
expect($element[0].click).toHaveBeenCalled();
}); });
it("retains selections during refresh", function () { it("retains selections during refresh", function () {
@@ -352,23 +374,21 @@ define(
findWatch("model.modified")(testModel.modified); findWatch("model.modified")(testModel.modified);
elements = controller.getElements(); elements = controller.getElements();
controller.select(elements[1]); selectable[0].context.elementProxy = elements[1];
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
// Verify precondition expect(controller.getSelectedElement()).toEqual(elements[1]);
expect(mockScope.selection.select.calls.length).toEqual(1);
// Mimic selection behavior
mockScope.selection.get.andReturn(elements[1]);
elements[2].remove(); elements[2].remove();
testModel.modified = 2; testModel.modified = 2;
findWatch("model.modified")(testModel.modified); findWatch("model.modified")(testModel.modified);
elements = controller.getElements(); elements = controller.getElements();
// Verify removal, as test assumes this // Verify removal, as test assumes this
expect(elements.length).toEqual(2); expect(elements.length).toEqual(2);
expect(mockScope.selection.select.calls.length).toEqual(2); expect(controller.shouldSelect(elements[1])).toBe(true);
}); });
it("Displays received values for telemetry elements", function () { it("Displays received values for telemetry elements", function () {
@@ -505,21 +525,25 @@ define(
}); });
it("exposes a view-level selection proxy", function () { it("exposes a view-level selection proxy", function () {
expect(mockScope.selection.proxy).toHaveBeenCalledWith( mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
jasmine.any(Object) var selection = mockOpenMCT.selection.select.mostRecentCall.args[0];
);
expect(mockOpenMCT.selection.select).toHaveBeenCalled();
expect(selection.context.viewProxy).toBeDefined();
}); });
it("exposes drag handles", function () { it("exposes drag handles", function () {
var handles; var handles;
// Select something so that drag handles are expected
testModel.modified = 1; testModel.modified = 1;
findWatch("model.modified")(testModel.modified); findWatch("model.modified")(testModel.modified);
controller.select(controller.getElements()[1]);
selectable[0].context.elementProxy = controller.getElements()[1];
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
// Should have a non-empty array of handles // Should have a non-empty array of handles
handles = controller.handles(); handles = controller.handles();
expect(handles).toEqual(jasmine.any(Array)); expect(handles).toEqual(jasmine.any(Array));
expect(handles.length).not.toEqual(0); expect(handles.length).not.toEqual(0);
@@ -532,15 +556,14 @@ define(
}); });
it("exposes a move handle", function () { it("exposes a move handle", function () {
var handle;
// Select something so that drag handles are expected
testModel.modified = 1; testModel.modified = 1;
findWatch("model.modified")(testModel.modified); findWatch("model.modified")(testModel.modified);
controller.select(controller.getElements()[1]);
selectable[0].context.elementProxy = controller.getElements()[1];
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
// Should have a move handle // Should have a move handle
handle = controller.moveHandle(); var handle = controller.moveHandle();
// And it should have start/continue/end drag methods // And it should have start/continue/end drag methods
expect(handle.startDrag).toEqual(jasmine.any(Function)); expect(handle.startDrag).toEqual(jasmine.any(Function));
@@ -551,26 +574,40 @@ define(
it("updates selection style during drag", function () { it("updates selection style during drag", function () {
var oldStyle; var oldStyle;
// Select something so that drag handles are expected
testModel.modified = 1; testModel.modified = 1;
findWatch("model.modified")(testModel.modified); findWatch("model.modified")(testModel.modified);
controller.select(controller.getElements()[1]);
mockScope.selection.get.andReturn(controller.getElements()[1]); selectable[0].context.elementProxy = controller.getElements()[1];
mockOpenMCT.selection.on.mostRecentCall.args[1](selectable);
// Get style // Get style
oldStyle = controller.selected().style; oldStyle = controller.getSelectedElementStyle();
// Start a drag gesture // Start a drag gesture
controller.moveHandle().startDrag(); controller.moveHandle().startDrag();
// Haven't moved yet; style shouldn't have updated yet // Haven't moved yet; style shouldn't have updated yet
expect(controller.selected().style).toEqual(oldStyle); expect(controller.getSelectedElementStyle()).toEqual(oldStyle);
// Drag a little // Drag a little
controller.moveHandle().continueDrag([1000, 100]); controller.moveHandle().continueDrag([1000, 100]);
// Style should have been updated // Style should have been updated
expect(controller.selected().style).not.toEqual(oldStyle); expect(controller.getSelectedElementStyle()).not.toEqual(oldStyle);
});
it("cleans up slection on scope destroy", function () {
expect(mockScope.$on).toHaveBeenCalledWith(
'$destroy',
jasmine.any(Function)
);
mockScope.$on.mostRecentCall.args[1]();
expect(mockOpenMCT.selection.off).toHaveBeenCalledWith(
'change',
jasmine.any(Function)
);
}); });
describe("on display bounds changes", function () { describe("on display bounds changes", function () {
@@ -702,6 +739,14 @@ define(
expect(controller.getElements()[0].cssClass).toEqual("alarm-a"); expect(controller.getElements()[0].cssClass).toEqual("alarm-a");
}); });
}); });
it("listens for selection change events", function () {
expect(mockOpenMCT.selection.on).toHaveBeenCalledWith(
'change',
jasmine.any(Function)
);
});
}); });
}); });
} }

View File

@@ -115,7 +115,7 @@ define(['EventEmitter'], function (EventEmitter) {
element.addEventListener('click', selectCapture); element.addEventListener('click', selectCapture);
if (select) { if (select) {
this.select(selectable); element.click();
} }
return function () { return function () {