Topo2: Topo2SelectService now maintains de/selecting nodes and displaying the details panel
Change-Id: I29d2476d8615263d79304636df6ca1664e7dc76b
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Device.js b/web/gui/src/main/webapp/app/view/topo2/topo2Device.js
index 823387c..fadcd93 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Device.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Device.js
@@ -47,19 +47,23 @@
angular.module('ovTopo2')
.factory('Topo2DeviceService',
- ['Topo2Collection', 'Topo2NodeModel', 'Topo2DeviceDetailsPanel',
- function (_c_, _nm_, detailsPanel) {
+ ['Topo2Collection', 'Topo2NodeModel', 'Topo2DeviceDetailsPanel', 'Topo2SelectService',
+ function (_c_, _nm_, detailsPanel, t2ss) {
Collection = _c_;
Model = _nm_.extend({
+
+ nodeType: 'device',
+ multiSelectEnabled: true,
+ events: {
+ 'click': 'onClick'
+ },
+
initialize: function () {
this.super = this.constructor.__super__;
this.super.initialize.apply(this, arguments);
},
- events: {
- 'click': 'onClick'
- },
onChange: function (change) {
if (this.el) {
this.el.attr('class', this.svgClassName());
@@ -67,30 +71,15 @@
rect.style('fill', this.devGlyphColor());
}
},
- nodeType: 'device',
icon: function () {
var type = this.get('type');
return remappedDeviceTypes[type] || type || 'unknown';
},
- onClick: function () {
-
- if (d3.event.defaultPrevented) return;
- var selected = this.select(d3.event);
-
- if (_.isArray(selected) && selected.length > 0) {
- if (selected.length === 1) {
- var model = selected[0],
- id = model.get('id'),
- nodeType = model.get('nodeType');
- detailsPanel.updateDetails(id, nodeType);
- detailsPanel.show();
- } else {
- // Multi Panel
- detailsPanel.showMulti(selected);
- }
- } else {
- detailsPanel.hide();
- }
+ showDetails: function () {
+ var id = this.get('id'),
+ nodeType = this.get('nodeType');
+ detailsPanel.updateDetails(id, nodeType);
+ detailsPanel.show();
},
onExit: function () {
var node = this.el;
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Force.js b/web/gui/src/main/webapp/app/view/topo2/topo2Force.js
index e1e392b..fc3e5b739 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Force.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Force.js
@@ -46,8 +46,9 @@
t2bgs.region = t2rs;
t2ls.init(svg, uplink, dim, zoomer, opts);
t2bcs.addLayout(t2ls);
- t2rs.layout = t2ls;
t2ss.init(svg, zoomer);
+ t2ss.region = t2rs;
+ t2rs.layout = t2ls;
navToBookmarkedRegion($loc.search().regionId);
}
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Host.js b/web/gui/src/main/webapp/app/view/topo2/topo2Host.js
index 787819b..5688dc5 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Host.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Host.js
@@ -54,30 +54,25 @@
Collection = _c_;
Model = NodeModel.extend({
+
+ nodeType: 'host',
+ events: {
+ 'click': 'onClick'
+ },
+
initialize: function () {
this.super = this.constructor.__super__;
this.super.initialize.apply(this, arguments);
},
- events: {
- 'click': 'onClick'
- },
onChange: function () {
// Update class names when the model changes
if (this.el) {
this.el.attr('class', this.svgClassName());
}
},
- onClick: function () {
- if (d3.event.defaultPrevented) return;
- var selected = this.select(d3.select);
-
- if (selected.length > 0) {
- t2hds.displayPanel(this);
- } else {
- t2hds.hide();
- }
+ showDetails: function() {
+ t2hds.displayPanel(this);
},
- nodeType: 'host',
icon: function () {
var type = this.get('type');
return remappedDeviceTypes[type] || type || 'm_endstation';
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Layout.js b/web/gui/src/main/webapp/app/view/topo2/topo2Layout.js
index 4e21040..473bd3a 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Layout.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Layout.js
@@ -158,7 +158,7 @@
.data(regionNodes, function (d) { return d.get('id'); });
this.drag = sus.createDragBehavior(this.force,
- t2ss.selectObject,
+ function () {}, // click event is no longer handled in the drag service
this.atDragEnd,
this.dragEnabled.bind(this),
clickEnabled
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Link.js b/web/gui/src/main/webapp/app/view/topo2/topo2Link.js
index 7c674c3..e94ed6c 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Link.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Link.js
@@ -217,22 +217,14 @@
});
},
select: function () {
-
- // TODO: if single selection clear selected devices, hosts, sub-regions
- var s = Boolean(this.get('selected'));
- // Clear all selected Items
- _.each(this.collection.models, function (m) {
- m.set('selected', false);
- });
-
- this.set('selected', !s);
- this.showDetails();
-
+ this.set({ 'selected': true });
return this.getSelected();
},
deselect: function () {
- this.set('selected', false);
- this.set('enhanced', false);
+ this.set({
+ 'selected': false,
+ 'enhanced': false
+ });
},
showDetails: function () {
var selected = this.getSelected();
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2NodeModel.js b/web/gui/src/main/webapp/app/view/topo2/topo2NodeModel.js
index 9d46494..fa3a345 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2NodeModel.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2NodeModel.js
@@ -49,8 +49,9 @@
'Topo2Model', 'FnService', 'Topo2PrefsService',
'SvgUtilService', 'IconService', 'ThemeService',
'Topo2MapConfigService', 'Topo2ZoomService', 'Topo2NodePositionService',
+ 'Topo2SelectService',
function (Model, _fn_, _ps_, _sus_, _is_, _ts_,
- _t2mcs_, zoomService, _t2nps_) {
+ _t2mcs_, zoomService, _t2nps_, t2ss) {
ts = _ts_;
fn = _fn_;
@@ -69,29 +70,7 @@
};
},
select: function () {
- var ev = d3.event;
-
- // TODO: if single selection clear selected devices, hosts, sub-regions
-
- if (ev.shiftKey) {
- // TODO: Multi-Select Details Panel
- this.set('selected', true);
- } else {
-
- var s = Boolean(this.get('selected'));
- // Clear all selected Items
- _.each(this.collection.models, function (m) {
- m.set('selected', false);
- });
-
- this.set('selected', !s);
- }
-
- var selected = this.collection.filter(function (m) {
- return m.get('selected');
- });
-
- return selected;
+ this.set('selected', true);
},
index: function () {
@@ -125,6 +104,12 @@
mouseoutHandler: function () {
this.set('hovered', false);
},
+ onClick: function () {
+ if (d3.event.defaultPrevented) return;
+
+ d3.event.preventDefault();
+ t2ss.selectObject(this, this.multiSelectEnabled);
+ },
fix: function (fixed) {
this.set({ fixed: fixed });
this.fixed = fixed;
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2PeerRegion.js b/web/gui/src/main/webapp/app/view/topo2/topo2PeerRegion.js
index 2ae8e64..64dc954 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2PeerRegion.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2PeerRegion.js
@@ -48,34 +48,30 @@
Collection = _c_;
Model = NodeModel.extend({
- initialize: function () {
- this.super = this.constructor.__super__;
- this.super.initialize.apply(this, arguments);
- },
+
+ nodeType: 'peer-region',
events: {
'dblclick': 'navigateToRegion',
'click': 'onClick'
},
+
+ initialize: function () {
+ this.super = this.constructor.__super__;
+ this.super.initialize.apply(this, arguments);
+ },
onChange: function () {
// Update class names when the model changes
if (this.el) {
this.el.attr('class', this.svgClassName());
}
},
- nodeType: 'peer-region',
+ showDetails: function () {
+ t2srp.displayPanel(this);
+ },
icon: function () {
var type = this.get('type');
return remappedDeviceTypes[type] || type || 'm_cloud';
},
- onClick: function () {
- var selected = this.select(d3.event);
-
- if (selected.length > 0) {
- t2srp.displayPanel(this);
- } else {
- t2srp.hide();
- }
- },
navigateToRegion: function () {
if (d3.event.defaultPrevented) return;
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Region.js b/web/gui/src/main/webapp/app/view/topo2/topo2Region.js
index 67b2a7c..d79cea9 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Region.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Region.js
@@ -148,6 +148,7 @@
return false;
},
deselectLink: function () {
+ console.log('remove link')
var selected = _.filter(this.regionLinks(), function (link) {
return link.get('selected', true);
});
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Select.js b/web/gui/src/main/webapp/app/view/topo2/topo2Select.js
index 242d55a0..e172e0d 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Select.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Select.js
@@ -21,43 +21,30 @@
(function () {
'use strict';
- var t2rs, t2zs;
+ var t2zs, t2ddp;
// internal state
- var consumeClick,
+ var instance,
+ consumeClick,
zoomer,
previousNearestLink; // previous link to mouse position
- function init(svg) {
- zoomer = t2zs.getZoomer();
- svg.on('mousemove', mouseMoveHandler);
- svg.on('click', mouseClickHandler);
- }
-
- function selectObject(obj) {}
-
- function clickConsumed(x) {
- var cc = consumeClick;
- consumeClick = Boolean(x);
- return cc;
- }
-
function mouseClickHandler() {
+ if (d3.event.defaultPrevented) return;
if (!d3.event.shiftKey) {
- t2rs.deselectLink();
+ this.clearSelection();
}
- if (!clickConsumed()) {
+ if (!this.clickConsumed()) {
if (previousNearestLink) {
- previousNearestLink.select();
+ this.selectObject(previousNearestLink, true);
}
}
-
}
// Select Links
- function mouseMoveHandler() {
+ function mouseMoveHandler(ev) {
var mp = getLogicalMousePosition(this),
link = computeNearestLink(mp);
@@ -124,8 +111,8 @@
var links = [];
- if (t2rs.model.get('links')) {
- links = (t2rs.backgroundRendered) ? t2rs.regionLinks() : [];
+ if (instance.region.model.get('links')) {
+ links = instance.region.regionLinks();
}
if (links.length) {
@@ -159,19 +146,76 @@
return nearest;
}
+ var SelectionService = function () {
+ instance = this;
+ this.selectedNodes = [];
+ };
+
+ SelectionService.prototype = {
+ init: function () {
+ zoomer = t2zs.getZoomer();
+
+ var svg = d3.select('#topo2');
+ svg.on('mousemove', mouseMoveHandler);
+ svg.on('click', mouseClickHandler.bind(this));
+ },
+ updateDetails: function () {
+
+ var nodeCount = this.selectedNodes.length;
+
+ if (nodeCount === 1) {
+ this.selectedNodes[0].showDetails();
+ } else if (nodeCount > 1) {
+ t2ddp.showMulti(this.selectedNodes);
+ } else {
+ t2ddp.hide();
+ }
+ },
+ selectObject: function (node, multiSelectEnabled) {
+
+ var event = d3.event;
+
+ if (multiSelectEnabled && !event.shiftKey || !multiSelectEnabled) {
+ this.clearSelection();
+ }
+
+ var nodeIndex = _.indexOf(this.selectedNodes, node);
+
+ if (nodeIndex < 0) {
+ this.selectedNodes.push(node);
+ node.select();
+ } else {
+ this.removeNode(node, nodeIndex);
+ }
+
+ this.updateDetails();
+ },
+ removeNode: function (node, index) {
+ this.selectedNodes.splice(index, 1);
+ node.deselect();
+ },
+ clearSelection: function () {
+ _.each(this.selectedNodes, function (node) {
+ node.deselect();
+ });
+
+ this.selectedNodes = [];
+ this.updateDetails();
+ },
+ clickConsumed: function (x) {
+ var cc = consumeClick;
+ consumeClick = Boolean(x);
+ return cc;
+ }
+ };
+
angular.module('ovTopo2')
.factory('Topo2SelectService', [
- 'Topo2RegionService', 'Topo2ZoomService',
- function (_t2rs_, _t2zs_) {
-
- t2rs = _t2rs_;
+ 'Topo2ZoomService', 'Topo2DeviceDetailsPanel',
+ function (_t2zs_, _t2ddp_) {
t2zs = _t2zs_;
-
- return {
- init: init,
- selectObject: selectObject,
- clickConsumed: clickConsumed
- };
+ t2ddp = _t2ddp_;
+ return instance || new SelectionService();
}
]);
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2SubRegion.js b/web/gui/src/main/webapp/app/view/topo2/topo2SubRegion.js
index 02c8b73..4696050 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2SubRegion.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2SubRegion.js
@@ -48,36 +48,30 @@
Collection = _c_;
Model = NodeModel.extend({
- initialize: function () {
- this.super = this.constructor.__super__;
- this.super.initialize.apply(this, arguments);
- },
+
+ nodeType: 'sub-region',
events: {
'dblclick': 'navigateToRegion',
'click': 'onClick'
},
+
+ initialize: function () {
+ this.super = this.constructor.__super__;
+ this.super.initialize.apply(this, arguments);
+ },
onChange: function () {
// Update class names when the model changes
if (this.el) {
this.el.attr('class', this.svgClassName());
}
},
- nodeType: 'sub-region',
+ showDetails: function () {
+ t2srp.displayPanel(this);
+ },
icon: function () {
var type = this.get('type');
return remappedDeviceTypes[type] || type || 'm_cloud';
},
- onClick: function () {
- if (d3.event.defaultPrevented) return;
-
- var selected = this.select(d3.event);
-
- if (selected.length > 0) {
- t2srp.displayPanel(this);
- } else {
- t2srp.hide();
- }
- },
navigateToRegion: function () {
if (d3.event.defaultPrevented) return;