GUI -- Topo Panels now dynamically adjust their height to fit in the whole window. Topo panel structure changed to have api for dynamic content to be added later.

Change-Id: I70b272ca740efdf5392d8896271baef4a7cf97b5
diff --git a/web/gui/src/main/webapp/app/view/topo/topo.css b/web/gui/src/main/webapp/app/view/topo/topo.css
index 9356c60..a618e5a 100644
--- a/web/gui/src/main/webapp/app/view/topo/topo.css
+++ b/web/gui/src/main/webapp/app/view/topo/topo.css
@@ -82,7 +82,7 @@
 
 #topo-p-detail {
     /* Base css from panel.css */
-    top: 320px;
+    top: 310px;
 }
 
 #topo-p-detail .actionBtns .actionBtn {
@@ -95,6 +95,10 @@
 
 /* --- general topo-panel styling --- */
 
+.topo-p div.body {
+    overflow-y: scroll;
+}
+
 .topo-p svg {
     display: inline-block;
     width: 42px;
@@ -119,7 +123,9 @@
 
 .topo-p h2 {
     position: absolute;
-    margin: 0 4px;
+    padding: 0 4px;
+    margin: 0;
+    word-wrap: break-word;
     top: 20px;
     left: 50px;
 }
@@ -131,7 +137,9 @@
 }
 
 .topo-p h3 {
-    margin: 0 4px;
+    padding: 0 4px;
+    margin: 0;
+    word-wrap: break-word;
     top: 20px;
     left: 50px;
 }
@@ -143,16 +151,19 @@
 }
 
 .topo-p p, table {
-    margin: 4px 4px;
+    padding: 4px;
+    margin: 0;
 }
 
+.topo-p td {
+    word-wrap: break-word;
+}
 .topo-p td.label {
     font-style: italic;
     padding-right: 12px;
     /* works for both light and dark themes ... */
     color: #777;
 }
-
 .topo-p td.value {
 }
 
diff --git a/web/gui/src/main/webapp/app/view/topo/topoPanel.js b/web/gui/src/main/webapp/app/view/topo/topoPanel.js
index bce9e8e..2e50499 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoPanel.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoPanel.js
@@ -33,14 +33,101 @@
             width: 260
         };
 
-    // panels
-    var summaryPanel,
-        detailPanel;
-
     // internal state
     var useDetails = true,      // should we show details if we have 'em?
         haveDetails = false;    // do we have details that we could show?
 
+    // panels
+    var summary, detail;
+
+    // === -----------------------------------------------------
+    // Panel API
+    function createTopoPanel(id, opts) {
+        var p = ps.createPanel(id, opts),
+            header, body, footer;
+        p.classed(pCls, true);
+
+        function hAppend(x) {
+            return header.append(x);
+        }
+
+        function bAppend(x) {
+            return body.append(x);
+        }
+
+        function fAppend(x) {
+            return footer.append(x);
+        }
+
+        function setup() {
+            p.empty();
+
+            p.append('div').classed('header', true);
+            p.append('div').classed('body', true);
+            p.append('div').classed('footer', true);
+
+            header = p.el().select('.header');
+            body = p.el().select('.body');
+            footer = p.el().select('.footer');
+        }
+
+        // fromTop is how many pixels from the top of the page the panel is
+        // max is the max height of the panel in pixels
+        //    only adjusts if the body content would be 10px or larger
+        function adjustHeight(fromTop, max) {
+            var totalPHeight, avSpace,
+                overflow = 0,
+                pdg = 30;
+
+            if (!fromTop) {
+                $log.warn('adjustHeight: height from top of page not given');
+                return null;
+            } else if (!body || !p) {
+                // if we have reached this function without setting a panel
+                // sometimes the d3 tick function calls detail panel's 'up'
+                // on reload when body isn't defined yet.
+                $log.warn('adjustHeight: panel is not defined');
+                return null;
+            }
+
+            p.el().style('height', null);
+            body.style('height', null);
+
+            totalPHeight = fromTop + p.height();
+            avSpace = fs.windowSize(pdg).height;
+
+            if (totalPHeight >= avSpace) {
+                overflow = totalPHeight - avSpace;
+            }
+
+            function _adjustBody(height) {
+                if (height < 10) {
+                    return false;
+                } else {
+                    body.style('height', height + 'px');
+                }
+                return true;
+            }
+
+            if (!_adjustBody(fs.noPxStyle(body, 'height') - overflow)) {
+                return;
+            }
+
+            if (max && p.height() > max) {
+                _adjustBody(fs.noPxStyle(body, 'height') - (p.height() - max));
+            }
+        }
+
+        return {
+            panel: p,
+            setup: setup,
+            appendHeader: hAppend,
+            appendBody: bAppend,
+            appendFooter: fAppend,
+            adjustHeight: adjustHeight
+        };
+    }
+
     // === -----------------------------------------------------
     // Utility functions
 
@@ -48,6 +135,11 @@
         tbody.append('tr').append('td').attr('colspan', 2).append('hr');
     }
 
+    function addBtnFooter() {
+        detail.appendFooter('hr');
+        detail.appendFooter('div').classed('actionBtns', true);
+    }
+
     function addProp(tbody, label, value) {
         var tr = tbody.append('tr'),
             lab;
@@ -65,7 +157,7 @@
     }
 
     function listProps(tbody, data) {
-        data.propOrder.forEach(function(p) {
+        data.propOrder.forEach(function (p) {
             if (p === '-') {
                 addSep(tbody);
             } else {
@@ -74,23 +166,15 @@
         });
     }
 
-    function dpa(x) {
-        return detailPanel.append(x);
-    }
-
-    function spa(x) {
-        return summaryPanel.append(x);
-    }
-
     // === -----------------------------------------------------
     //  Functions for populating the summary panel
 
     function populateSummary(data) {
-        summaryPanel.empty();
+        summary.setup();
 
-        var svg = spa('svg'),
-            title = spa('h2'),
-            table = spa('table'),
+        var svg = summary.appendHeader('svg'),
+            title = summary.appendHeader('h2'),
+            table = summary.appendBody('table'),
             tbody = table.append('tbody');
 
         gs.addGlyph(svg, 'node', 40);
@@ -104,33 +188,31 @@
     //  Functions for populating the detail panel
 
     function displaySingle(data) {
-        detailPanel.empty();
+        detail.setup();
 
-        var svg = dpa('svg'),
-            title = dpa('h2'),
-            table = dpa('table'),
+        var svg = detail.appendHeader('svg'),
+            title = detail.appendHeader('h2'),
+            table = detail.appendBody('table'),
             tbody = table.append('tbody');
 
         gs.addGlyph(svg, (data.type || 'unknown'), 40);
         title.text(data.id);
         listProps(tbody, data);
-        dpa('hr');
-        dpa('div').classed('actionBtns', true);
+        addBtnFooter();
     }
 
     function displayMulti(ids) {
-        detailPanel.empty();
+        detail.setup();
 
-        var title = dpa('h3'),
-            table = dpa('table'),
+        var title = detail.appendHeader('h3'),
+            table = detail.appendBody('table'),
             tbody = table.append('tbody');
 
         title.text('Selected Nodes');
         ids.forEach(function (d, i) {
             addProp(tbody, i+1, d);
         });
-        dpa('hr');
-        dpa('div').classed('actionBtns', true);
+        addBtnFooter();
     }
 
     function addAction(o) {
@@ -177,11 +259,11 @@
         ];
 
     function displayLink(data) {
-        detailPanel.empty();
+        detail.setup();
 
-        var svg = dpa('svg'),
-            title = dpa('h2'),
-            table = dpa('table'),
+        var svg = detail.appendHeader('svg'),
+            title = detail.appendHeader('h2'),
+            table = detail.appendBody('table'),
             tbody = table.append('tbody'),
             edgeLink = data.type() === 'hostLink',
             order = edgeLink ? edgeOrder : coreOrder;
@@ -235,7 +317,7 @@
 
     function toggleSummary(x) {
         var kev = (x === 'keyev'),
-            on = kev ? !summaryPanel.isVisible() : !!x,
+            on = kev ? !summary.panel.isVisible() : !!x,
             verb = on ? 'Show' : 'Hide';
 
         if (on) {
@@ -253,29 +335,33 @@
     // === LOGIC For showing/hiding summary and detail panels...
 
     function showSummaryPanel() {
-        if (detailPanel.isVisible()) {
-            detailPanel.down(summaryPanel.show);
+        function _show() {
+            summary.panel.show();
+            summary.adjustHeight(64, 226);
+        }
+        if (detail.panel.isVisible()) {
+            detail.down(_show);
         } else {
-            summaryPanel.show();
+            _show();
         }
     }
 
     function hideSummaryPanel() {
         // instruct server to stop sending summary data
         wss.sendEvent("cancelSummary");
-        summaryPanel.hide(detailPanel.up);
+        summary.panel.hide(detail.up);
     }
 
     function showDetailPanel() {
-        if (summaryPanel.isVisible()) {
-            detailPanel.down(detailPanel.show);
+        if (summary.panel.isVisible()) {
+            detail.down(detail.panel.show);
         } else {
-            detailPanel.up(detailPanel.show);
+            detail.up(detail.panel.show);
         }
     }
 
     function hideDetailPanel() {
-        detailPanel.hide();
+        detail.panel.hide();
     }
 
     // ==========================
@@ -283,15 +369,15 @@
     function noop () {}
 
     function augmentDetailPanel() {
-        var dp = detailPanel;
-        dp.ypos = { up: 64, down: 320, current: 320};
+        var d = detail;
+        d.ypos = { up: 64, down: 310, current: 310};
 
-        dp._move = function (y, cb) {
+        d._move = function (y, cb) {
             var endCb = fs.isF(cb) || noop,
-                yp = dp.ypos;
+                yp = d.ypos;
             if (yp.current !== y) {
                 yp.current = y;
-                dp.el().transition().duration(300)
+                d.panel.el().transition().duration(300)
                     .each('end', endCb)
                     .style('top', yp.current + 'px');
             } else {
@@ -299,8 +385,15 @@
             }
         };
 
-        dp.down = function (cb) { dp._move(dp.ypos.down, cb); };
-        dp.up = function (cb) { dp._move(dp.ypos.up, cb); };
+        // d.up is being called on the tick function for some reason
+        d.down = function (cb) {
+            d._move(d.ypos.down, cb);
+            detail.adjustHeight(d.ypos.current);
+        };
+        d.up = function (cb) {
+            d._move(d.ypos.up, cb);
+            detail.adjustHeight(d.ypos.current);
+        };
     }
 
     function toggleUseDetailsFlag(x) {
@@ -324,11 +417,8 @@
     // ==========================
 
     function initPanels() {
-        summaryPanel = ps.createPanel(idSum, panelOpts);
-        detailPanel = ps.createPanel(idDet, panelOpts);
-
-        summaryPanel.classed(pCls, true);
-        detailPanel.classed(pCls, true);
+        summary = createTopoPanel(idSum, panelOpts);
+        detail = createTopoPanel(idDet, panelOpts);
 
         augmentDetailPanel();
     }
@@ -336,7 +426,7 @@
     function destroyPanels() {
         ps.destroyPanel(idSum);
         ps.destroyPanel(idDet);
-        summaryPanel = detailPanel = null;
+        summary.panel = detail.panel = null;
         haveDetails = false;
     }
 
@@ -359,6 +449,7 @@
             return {
                 initPanels: initPanels,
                 destroyPanels: destroyPanels,
+                createTopoPanel: createTopoPanel,
 
                 showSummary: showSummary,
                 toggleSummary: toggleSummary,
@@ -366,15 +457,15 @@
                 toggleUseDetailsFlag: toggleUseDetailsFlag,
                 displaySingle: displaySingle,
                 displayMulti: displayMulti,
-                addAction: addAction,
                 displayLink: displayLink,
                 displayNothing: displayNothing,
                 displaySomething: displaySomething,
+                addAction: addAction,
 
                 hideSummaryPanel: hideSummaryPanel,
 
-                detailVisible: function () { return detailPanel.isVisible(); },
-                summaryVisible: function () { return summaryPanel.isVisible(); }
+                detailVisible: function () { return detail.panel.isVisible(); },
+                summaryVisible: function () { return summary.panel.isVisible(); }
             };
         }]);
 }());