From 4190941bfb1037d12370e26b5c9fca8821626894 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Tue, 17 Feb 2015 10:01:27 -0800 Subject: [PATCH 1/8] [Views] Broadcast event on drop Broadcast an event when drag-drop domain object composition occurs, such that the view in question may utilize the drop coordinates (e.g. to position elements in a layout or fixed position view.) WTD-877. --- .../representation/src/gestures/DropGesture.js | 18 ++++++++++++++++++ .../src/gestures/GestureConstants.js | 6 +++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/platform/representation/src/gestures/DropGesture.js b/platform/representation/src/gestures/DropGesture.js index bcd49713fe..00faa83710 100644 --- a/platform/representation/src/gestures/DropGesture.js +++ b/platform/representation/src/gestures/DropGesture.js @@ -20,6 +20,19 @@ define( */ function DropGesture($q, element, domainObject) { + function broadcastDrop(id, event) { + // Find the relevant scope... + var scope = element && element.scope && element.scope(); + if (scope && scope.$broadcast) { + // ...and broadcast the event. This allows specific + // views to have post-drop behavior which depends on + // drop position. + scope.$broadcast( + GestureConstants.MCT_DROP_EVENT, + { id: id, dropEvent: event } + ); + } + } function doPersist() { var persistence = domainObject.getCapability("persistence"); @@ -60,6 +73,11 @@ define( } } )).then(function (result) { + // Broadcast the drop event if it was successful + if (result) { + broadcastDrop(id, event); + } + // If mutation was successful, persist the change return result && doPersist(); }); diff --git a/platform/representation/src/gestures/GestureConstants.js b/platform/representation/src/gestures/GestureConstants.js index ae35fa439f..126a7b3981 100644 --- a/platform/representation/src/gestures/GestureConstants.js +++ b/platform/representation/src/gestures/GestureConstants.js @@ -14,5 +14,9 @@ define({ * An estimate for the dimensions of a context menu, used for * positioning. */ - MCT_MENU_DIMENSIONS: [ 170, 200 ] + MCT_MENU_DIMENSIONS: [ 170, 200 ], + /** + * Identifier for drop events. + */ + MCT_DROP_EVENT: 'mctDrop' }); \ No newline at end of file From e158f2811ca1fd4600b6f21c0d5a315b81cfa1d7 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Tue, 17 Feb 2015 10:03:48 -0800 Subject: [PATCH 2/8] [Views] Wire in drop gesture Support drop gesture from fixed position view and from layout view, WTD-877. --- platform/features/layout/bundle.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/platform/features/layout/bundle.json b/platform/features/layout/bundle.json index 760cd2a686..0ce44d3016 100644 --- a/platform/features/layout/bundle.json +++ b/platform/features/layout/bundle.json @@ -9,7 +9,8 @@ "glyph": "L", "type": "layout", "templateUrl": "templates/layout.html", - "uses": [ "composition" ] + "uses": [ "composition" ], + "gestures": [ "drop" ] }, { "key": "fixed", @@ -17,7 +18,8 @@ "glyph": "3", "type": "telemetry.panel", "templateUrl": "templates/fixed.html", - "uses": [ "composition" ] + "uses": [ "composition" ], + "gestures": [ "drop" ] } ], "representations": [ From 01d953bf4580be461a38b75b980d2a92bd1ce622 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Tue, 17 Feb 2015 10:23:10 -0800 Subject: [PATCH 3/8] [Layout] Position frames on drop Position frames within a layout based on the position where they were dropped into the layout, WTD-877. --- .../features/layout/src/LayoutController.js | 20 +++++++++++++++++++ .../src/gestures/DropGesture.js | 13 ++++++++++-- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/platform/features/layout/src/LayoutController.js b/platform/features/layout/src/LayoutController.js index 70a5126d78..9843252afb 100644 --- a/platform/features/layout/src/LayoutController.js +++ b/platform/features/layout/src/LayoutController.js @@ -85,6 +85,26 @@ define( // Position panes when the model field changes $scope.$watch("model.composition", lookupPanels); + // Position panes where they are dropped + $scope.$on("mctDrop", function (e, id, position) { + // Make sure there is a "panels" field in the + // view configuration. + $scope.configuration.panels = + $scope.configuration.panels || {}; + // Store the position of this panel. + $scope.configuration.panels[id] = { + position: [ + Math.floor(position.x / gridSize[0]), + Math.floor(position.y / gridSize[1]) + ], + dimensions: DEFAULT_DIMENSIONS + }; + // Mark change as persistable + if ($scope.commit) { + $scope.commit("Dropped a frame."); + } + }); + return { /** * Get a style object for a frame with the specified domain diff --git a/platform/representation/src/gestures/DropGesture.js b/platform/representation/src/gestures/DropGesture.js index 00faa83710..985fe102d3 100644 --- a/platform/representation/src/gestures/DropGesture.js +++ b/platform/representation/src/gestures/DropGesture.js @@ -22,14 +22,23 @@ define( function DropGesture($q, element, domainObject) { function broadcastDrop(id, event) { // Find the relevant scope... - var scope = element && element.scope && element.scope(); + var scope = element && element.scope && element.scope(), + rect; if (scope && scope.$broadcast) { + // Get the representation's bounds, to convert + // drop position + rect = element[0].getBoundingClientRect(); + // ...and broadcast the event. This allows specific // views to have post-drop behavior which depends on // drop position. scope.$broadcast( GestureConstants.MCT_DROP_EVENT, - { id: id, dropEvent: event } + id, + { + x: event.pageX - rect.left, + y: event.pageY - rect.top + } ); } } From ef4ed506051cdf58a21aae80d8fe364f29b1b9be Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Tue, 17 Feb 2015 10:25:31 -0800 Subject: [PATCH 4/8] [Layout] Separate out handleDrop Separate drop-handling into a non-anonymous function in LayoutController to satisfy code style guidelines; WTD-877. --- platform/features/layout/src/LayoutController.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/platform/features/layout/src/LayoutController.js b/platform/features/layout/src/LayoutController.js index 9843252afb..6eaefbfe88 100644 --- a/platform/features/layout/src/LayoutController.js +++ b/platform/features/layout/src/LayoutController.js @@ -82,11 +82,8 @@ define( ids.forEach(populatePosition); } - // Position panes when the model field changes - $scope.$watch("model.composition", lookupPanels); - - // Position panes where they are dropped - $scope.$on("mctDrop", function (e, id, position) { + // Position a panel after a drop event + function handleDrop(e, id, position) { // Make sure there is a "panels" field in the // view configuration. $scope.configuration.panels = @@ -103,7 +100,13 @@ define( if ($scope.commit) { $scope.commit("Dropped a frame."); } - }); + } + + // Position panes when the model field changes + $scope.$watch("model.composition", lookupPanels); + + // Position panes where they are dropped + $scope.$on("mctDrop", handleDrop); return { /** From 136dddab86c20339bf6ff546ab6c6887644c8cfe Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Tue, 17 Feb 2015 10:27:35 -0800 Subject: [PATCH 5/8] [Fixed Position] Position elements where dropped Position elements where they are dropped in Fixed Position view, WTD-877 --- .../features/layout/src/FixedController.js | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/platform/features/layout/src/FixedController.js b/platform/features/layout/src/FixedController.js index 9dc59c1764..29fb15fafb 100644 --- a/platform/features/layout/src/FixedController.js +++ b/platform/features/layout/src/FixedController.js @@ -147,6 +147,26 @@ define( telemetrySubscriber.subscribe(domainObject, updateValues); } + // Position a panel after a drop event + function handleDrop(e, id, position) { + // Make sure there is a "elements" field in the + // view configuration. + $scope.configuration.elements = + $scope.configuration.elements || {}; + // Store the position of this element. + $scope.configuration.elements[id] = { + position: [ + Math.floor(position.x / gridSize[0]), + Math.floor(position.y / gridSize[1]) + ], + dimensions: DEFAULT_DIMENSIONS + }; + // Mark change as persistable + if ($scope.commit) { + $scope.commit("Dropped a frame."); + } + } + // Position panes when the model field changes $scope.$watch("model", lookupPanels); @@ -156,6 +176,9 @@ define( // Free up subscription on destroy $scope.$on("$destroy", releaseSubscription); + // Position panes where they are dropped + $scope.$on("mctDrop", handleDrop); + // Initialize styles (position etc.) for cells refreshCellStyles(); From 553101100b94a92b7727511f37e96ab7af54ae7f Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Tue, 17 Feb 2015 10:34:13 -0800 Subject: [PATCH 6/8] [Fixed Position] Refresh subscriptions Refresh subscriptions when composition changes, such as due to a drag-drop addition of an element to the view. WTD-877. --- .../features/layout/src/FixedController.js | 18 +++++++++++++----- .../layout/test/FixedControllerSpec.js | 6 +++--- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/platform/features/layout/src/FixedController.js b/platform/features/layout/src/FixedController.js index 29fb15fafb..928dd770e8 100644 --- a/platform/features/layout/src/FixedController.js +++ b/platform/features/layout/src/FixedController.js @@ -90,9 +90,9 @@ define( } // Compute panel positions based on the layout's object model - function lookupPanels(model) { - var configuration = $scope.configuration || {}, - ids = (model || {}).composition || []; + function lookupPanels(ids) { + var configuration = $scope.configuration || {}; + ids = ids || []; // Pull panel positions from configuration rawPositions = shallowCopy(configuration.elements || {}, ids); @@ -101,7 +101,7 @@ define( positions = {}; // Update width/height that we are tracking - gridSize = (model || {}).layoutGrid || DEFAULT_GRID_SIZE; + gridSize = ($scope.model || {}).layoutGrid || DEFAULT_GRID_SIZE; // Compute positions and add defaults where needed ids.forEach(populatePosition); @@ -147,6 +147,14 @@ define( telemetrySubscriber.subscribe(domainObject, updateValues); } + // Handle changes in the object's composition + function updateComposition(ids) { + // Populate panel positions + lookupPanels(ids); + // Resubscribe - objects in view have changed + subscribe($scope.domainObject); + } + // Position a panel after a drop event function handleDrop(e, id, position) { // Make sure there is a "elements" field in the @@ -168,7 +176,7 @@ define( } // Position panes when the model field changes - $scope.$watch("model", lookupPanels); + $scope.$watch("model.composition", updateComposition); // Subscribe to telemetry when an object is available $scope.$watch("domainObject", subscribe); diff --git a/platform/features/layout/test/FixedControllerSpec.js b/platform/features/layout/test/FixedControllerSpec.js index 0293905909..8b5990069c 100644 --- a/platform/features/layout/test/FixedControllerSpec.js +++ b/platform/features/layout/test/FixedControllerSpec.js @@ -113,7 +113,7 @@ define( it("configures view based on model", function () { mockScope.model = testModel; - findWatch("model")(mockScope.model); + findWatch("model.composition")(mockScope.model.composition); // Should have styles for all elements of composition expect(controller.getStyle('a')).toBeDefined(); expect(controller.getStyle('b')).toBeDefined(); @@ -126,7 +126,7 @@ define( mockScope.domainObject = mockDomainObject; mockScope.model = testModel; findWatch("domainObject")(mockDomainObject); - findWatch("model")(mockScope.model); + findWatch("model.composition")(mockScope.model.composition); // Invoke the subscription callback mockSubscriber.subscribe.mostRecentCall.args[1](); @@ -148,7 +148,7 @@ define( }; mockScope.model = testModel; - findWatch("model")(mockScope.model); + findWatch("model.composition")(mockScope.model.composition); // Set first bounds controller.setBounds(s1); From 156f05342160b3be80b5719eb01721c8b6a0d6b9 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Tue, 17 Feb 2015 10:50:02 -0800 Subject: [PATCH 7/8] [Fixed Position] Add specs for drop Add specs related to positioning of objects based on drop position, WTD-877. --- .../features/layout/src/FixedController.js | 4 ++ .../features/layout/src/LayoutController.js | 4 ++ .../layout/test/FixedControllerSpec.js | 40 ++++++++++++++++++- .../layout/test/LayoutControllerSpec.js | 32 +++++++++++++-- 4 files changed, 75 insertions(+), 5 deletions(-) diff --git a/platform/features/layout/src/FixedController.js b/platform/features/layout/src/FixedController.js index 928dd770e8..07f0f99731 100644 --- a/platform/features/layout/src/FixedController.js +++ b/platform/features/layout/src/FixedController.js @@ -157,6 +157,8 @@ define( // Position a panel after a drop event function handleDrop(e, id, position) { + // Ensure that configuration field is populated + $scope.configuration = $scope.configuration || {}; // Make sure there is a "elements" field in the // view configuration. $scope.configuration.elements = @@ -173,6 +175,8 @@ define( if ($scope.commit) { $scope.commit("Dropped a frame."); } + // Populate template-facing position for this id + populatePosition(id); } // Position panes when the model field changes diff --git a/platform/features/layout/src/LayoutController.js b/platform/features/layout/src/LayoutController.js index 6eaefbfe88..c8e057d0f8 100644 --- a/platform/features/layout/src/LayoutController.js +++ b/platform/features/layout/src/LayoutController.js @@ -84,6 +84,8 @@ define( // Position a panel after a drop event function handleDrop(e, id, position) { + // Ensure that configuration field is populated + $scope.configuration = $scope.configuration || {}; // Make sure there is a "panels" field in the // view configuration. $scope.configuration.panels = @@ -100,6 +102,8 @@ define( if ($scope.commit) { $scope.commit("Dropped a frame."); } + // Populate template-facing position for this id + populatePosition(id); } // Position panes when the model field changes diff --git a/platform/features/layout/test/FixedControllerSpec.js b/platform/features/layout/test/FixedControllerSpec.js index 8b5990069c..61c8773907 100644 --- a/platform/features/layout/test/FixedControllerSpec.js +++ b/platform/features/layout/test/FixedControllerSpec.js @@ -27,6 +27,17 @@ define( return watch; } + // As above, but for $on calls + function findOn(expr) { + var on; + mockScope.$on.calls.forEach(function (call) { + if (call.args[0] === expr) { + on = call.args[1]; + } + }); + return on; + } + function makeMockDomainObject(id) { var mockObject = jasmine.createSpyObj( 'domainObject-' + id, @@ -39,7 +50,7 @@ define( beforeEach(function () { mockScope = jasmine.createSpyObj( '$scope', - [ "$on", "$watch" ] + [ "$on", "$watch", "commit" ] ); mockSubscriber = jasmine.createSpyObj( 'telemetrySubscriber', @@ -157,6 +168,33 @@ define( controller.setBounds(s2); expect(controller.getCellStyles().length).toEqual(60); // 10 * 6 }); + + it("listens for drop events", function () { + // Layout should position panels according to + // where the user dropped them, so it needs to + // listen for drop events. + expect(mockScope.$on).toHaveBeenCalledWith( + 'mctDrop', + jasmine.any(Function) + ); + + // Verify precondition + expect(controller.getStyle('d')).not.toBeDefined(); + + // Notify that a drop occurred + testModel.composition.push('d'); + findOn('mctDrop')( + {}, + 'd', + { x: 300, y: 100 } + ); + expect(controller.getStyle('d')).toBeDefined(); + + // Should have triggered commit (provided by + // EditRepresenter) with some message. + expect(mockScope.commit) + .toHaveBeenCalledWith(jasmine.any(String)); + }); }); } ); \ No newline at end of file diff --git a/platform/features/layout/test/LayoutControllerSpec.js b/platform/features/layout/test/LayoutControllerSpec.js index f80b356d74..3096cac1a2 100644 --- a/platform/features/layout/test/LayoutControllerSpec.js +++ b/platform/features/layout/test/LayoutControllerSpec.js @@ -14,7 +14,7 @@ define( beforeEach(function () { mockScope = jasmine.createSpyObj( "$scope", - [ "$watch" ] + [ "$watch", "$on", "commit" ] ); testModel = { @@ -97,9 +97,6 @@ define( // Populate scope mockScope.$watch.mostRecentCall.args[1](testModel.composition); - // Add a commit method to scope - mockScope.commit = jasmine.createSpy("commit"); - // Do a drag controller.startDrag("b", [1, 1], [0, 0]); controller.continueDrag([100, 100]); @@ -110,6 +107,33 @@ define( expect(mockScope.commit) .toHaveBeenCalledWith(jasmine.any(String)); }); + + it("listens for drop events", function () { + // Layout should position panels according to + // where the user dropped them, so it needs to + // listen for drop events. + expect(mockScope.$on).toHaveBeenCalledWith( + 'mctDrop', + jasmine.any(Function) + ); + + // Verify precondition + expect(testConfiguration.panels.d).not.toBeDefined(); + + // Notify that a drop occurred + testModel.composition.push('d'); + mockScope.$on.mostRecentCall.args[1]( + {}, + 'd', + { x: 300, y: 100 } + ); + expect(testConfiguration.panels.d).toBeDefined(); + + // Should have triggered commit (provided by + // EditRepresenter) with some message. + expect(mockScope.commit) + .toHaveBeenCalledWith(jasmine.any(String)); + }); }); } ); \ No newline at end of file From 0510705f3a52d64f6e4e853d3464b7d1007cb920 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Tue, 17 Feb 2015 10:56:38 -0800 Subject: [PATCH 8/8] [Representation] Update docs Update documentation to reflect that an event is broadcast when drops occur. WTD-877. --- platform/representation/README.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/platform/representation/README.md b/platform/representation/README.md index 2408f645f8..cf4d2bee6b 100644 --- a/platform/representation/README.md +++ b/platform/representation/README.md @@ -1,4 +1,4 @@ -This bundle introduces the notion of "representations" to Open MCT Web, +This bundle introduces the notion of "representations" to Open MCT Web, primarily via an Angular directive, `mct-representation`. A representation is used to display domain objects as Angular templates. @@ -107,9 +107,14 @@ introduces three specific gestures as "built in" options, listed by key: drag-drop domain object composition. * `drop`: Representations with this gesture can serve as drop targets for drag-drop domain object composition. + * When a drop occurs, an `mctDrop` event will be broadcast with two + arguments (in addition to Angular's event object): The domain object + identifier for the dropped object, and the position (with `x` and `y` + properties in pixels) of the drop, relative to the top-left of the + representation which features the drop gesture. * `menu`: Representations with this gesture will provide a custom context menu (instead of the browser default). - * It should be noted that this gesture does _not_ define the appearance - or functionality of this menu; rather, it simply adds a - representation of key `context-menu` to the document at an appropriate - location. This representation will be supplied by the commonUI bundle. \ No newline at end of file + * It should be noted that this gesture does _not_ define the appearance + or functionality of this menu; rather, it simply adds a + representation of key `context-menu` to the document at an appropriate + location. This representation will be supplied by the commonUI bundle. \ No newline at end of file