Topo2: Multi Details panel

Change-Id: Iab16aa38e5271a59da57d00fb4cb308036f86e2c
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Collection.js b/web/gui/src/main/webapp/app/view/topo2/topo2Collection.js
index 36f4264..11953e1 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Collection.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Collection.js
@@ -77,6 +77,9 @@
 
             return this;
         },
+        filter: function (comparator) {
+            return _.filter(this.models, comparator);
+        },
         _reset: function () {
             this._byId = [];
             this.models = [];
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 d6bc085..19d5554 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Device.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Device.js
@@ -60,6 +60,13 @@
                     events: {
                         'click': 'onClick'
                     },
+                    onChange: function () {
+
+                        // Update class names when the model changes
+                        if (this.el) {
+                            this.el.attr('class', this.svgClassName());
+                        }
+                    },
                     nodeType: 'device',
                     icon: function () {
                         var type = this.get('type');
@@ -71,27 +78,35 @@
 
                         if (ev.shiftKey) {
                             // TODO: Multi-Select Details Panel
+                            this.set('selected', true);
                         } else {
 
-                            var selected = this.get('selected'),
-                                id = this.get('id'),
-                                nodeType = this.get('nodeType');
-
+                            var s = Boolean(this.get('selected'));
+                            // Clear all selected Items
                             _.each(this.collection.models, function (m) {
                                 m.set('selected', false);
-                                m.el.attr('class', m.svgClassName());
                             });
 
-                            if (selected) {
-                                this.set('selected', false);
-                                detailsPanel.hide();
-                            } else {
-                                this.set('selected', true);
+                            this.set('selected', !s);
+                        }
+
+                        var selected = this.collection.filter(function (m) {
+                            return m.get('selected');
+                        });
+
+                        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);
                             }
-
-                            this.el.attr('class', this.svgClassName());
+                        } else {
+                            detailsPanel.hide();
                         }
                     },
                     onExit: function () {
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2DeviceDetailsPanel.js b/web/gui/src/main/webapp/app/view/topo2/topo2DeviceDetailsPanel.js
index 9673600..50b43d3 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2DeviceDetailsPanel.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2DeviceDetailsPanel.js
@@ -136,6 +136,7 @@
                     cb: function () { ns.navTo(path, { devId: devId }); }
                 });
             }
+            // TODO: Implement Overlay service
             // else if (btn = _getButtonDef(id, data)) {
             //     addAction(btn);
             // }
@@ -152,26 +153,30 @@
             title = detailsPanel.appendToHeader('h2')
                 .classed('clickable', true),
             table = detailsPanel.appendToBody('table'),
-            tbody = table.append('tbody'),
-            navFn;
+            tbody = table.append('tbody');
 
         gs.addGlyph(svg, (data.type || 'unknown'), 26);
         title.text(data.title);
 
-        // // only add navigation when displaying a device
-        // if (isDevice[data.type]) {
-        //     navFn = function () {
-        //         ns.navTo(devPath, { devId: data.id });
-        //     };
-        //
-        //     svg.on('click', navFn);
-        //     title.on('click', navFn);
-        // }
-
         listProps(tbody, data);
         addBtnFooter();
     }
 
+    function renderMulti(nodes) {
+        detailsPanel.emptyRegions();
+
+        var title = detailsPanel.appendToHeader('h3'),
+            table = detailsPanel.appendToBody('table'),
+            tbody = table.append('tbody');
+
+        title.text('Selected Items');
+        nodes.forEach(function (n, i) {
+            addProp(tbody, i + 1, n.get('id'));
+        });
+
+        // addBtnFooter();
+        show();
+    }
 
     function bindHandlers() {
         wss.bindHandlers(handlerMap);
@@ -226,6 +231,7 @@
             return {
                 init: init,
                 updateDetails: updateDetails,
+                showMulti: renderMulti,
 
                 toggle: toggle,
                 show: show,