ONOS-1842 - GUI -- Tables have better resizing behavior -- column headers stay above their columns and table cell text is constrained to one size.

Change-Id: I89ca7d25d46d895c78c41b8250ce40408fbaba85
diff --git a/web/gui/src/main/webapp/app/fw/widget/table.css b/web/gui/src/main/webapp/app/fw/widget/table.css
index d7db019..ff222e7 100644
--- a/web/gui/src/main/webapp/app/fw/widget/table.css
+++ b/web/gui/src/main/webapp/app/fw/widget/table.css
@@ -16,68 +16,80 @@
 
 /* ------ for summary-list tables ------ */
 
-table.summary-list {
-    margin: 0 20px 16px 12px;
+div.summary-list {
+    margin: 0 20px 16px 10px;
     font-size: 10pt;
     border-spacing: 0;
 }
 
-table.summary-list td.nodata {
+div.summary-list table {
+    border-collapse: collapse;
+    table-layout: fixed;
+    empty-cells: show;
+    margin: 0;
+}
+
+div.summary-list div.table-body {
+    overflow-y: scroll;
+}
+
+div.summary-list tr.no-data td {
     text-align: center;
     font-style: italic;
 }
 
-.light table.summary-list tr:nth-child(even) {
+.light div.summary-list tr:nth-child(even) {
     background-color: #ddd;
 }
-.light table.summary-list tr:nth-child(odd) {
+.light div.summary-list tr:nth-child(odd) {
     background-color: #eee;
 }
-.dark table.summary-list tr:nth-child(even) {
+.dark div.summary-list tr:nth-child(even) {
     background-color: #333;
 }
-.dark table.summary-list tr:nth-child(odd) {
+.dark div.summary-list tr:nth-child(odd) {
     background-color: #444;
 }
 
-.light table.summary-list tr.selected {
+.light div.summary-list tr.selected {
     background-color: deepskyblue;
 }
 
-.dark table.summary-list tr.selected {
+.dark div.summary-list tr.selected {
     background-color: #304860;
 }
 
-table.summary-list td,
-table.summary-list th {
+div.summary-list td {
     padding: 6px;
     text-align: left;
+    word-wrap: break-word;
 }
 
-table.summary-list th {
+div.summary-list .table-header td {
     letter-spacing: 0.02em;
     cursor: pointer;
+    font-weight: bold;
 }
-table.summary-list th:first-child {
+div.summary-list .table-header td:first-child {
     border-radius: 8px 0 0 0;
 }
-table.summary-list th:last-child {
+div.summary-list .table-header td:last-child {
     border-radius: 0 8px 0 0;
 }
 
-.light table.summary-list th {
+.light div.summary-list .table-header td {
     background-color: #bbb;
 }
-.dark table.summary-list th {
+.dark div.summary-list .table-header td {
     background-color: #222;
     color: #ccc;
 }
 
 /* rows are selectable */
-table.summary-list td {
+div.summary-list .table-body td {
     cursor: pointer;
 }
 
-.dark table.summary-list td {
+.dark div.summary-list td {
     color: #ccc;
 }
diff --git a/web/gui/src/main/webapp/app/fw/widget/table.js b/web/gui/src/main/webapp/app/fw/widget/table.js
index d9fddda..caacab4 100644
--- a/web/gui/src/main/webapp/app/fw/widget/table.js
+++ b/web/gui/src/main/webapp/app/fw/widget/table.js
@@ -37,86 +37,68 @@
         prevCol = {},
         sortIconAPI;
 
-    // Functions for creating a fixed header on a table (Angular Directive)
+    // Functions for creating a scrolling table body with fixed table header
 
-    function setElemWidth(elem, size) {
-        elem.style('width', size + 'px')
+    function _width(elem, width) {
+        elem.style('width', width);
     }
 
-    function setColWidth(th, td, size) {
-        setElemWidth(th, size);
-        setElemWidth(td, size);
+    function defaultSize(table, width) {
+        var thead = table.select('.table-header').select('table'),
+            tbody = table.select('.table-body').select('table'),
+            wpx = width + 'px';
+        _width(thead, wpx);
+        _width(tbody, wpx);
     }
 
-    // count number of headers of
-    //   - assigned width,
-    //   - icon width,
-    //   - and default width
-    // assumes assigned width is not given to icons
-    // returns the width of all columns that are not icons
-    // or have an assigned width
-    function getDefaultWidth(headers) {
-        var winWidth = fs.windowSize().width,
-            iconCols = 0,
-            regCols = 0,
-            cstmColWidth = 0;
+    function adjustTable(table, width, height) {
+        var thead = table.select('.table-header').select('table'),
+            tbodyDiv = table.select('.table-body'),
+            tbody = tbodyDiv.select('table'),
+            cstmWidths = {};
 
-        headers.each(function (d, i) {
-            var thElement = d3.select(this),
-                cstmWidth = thElement.attr(colWidth);
+        function findCstmWidths() {
+            var headers = thead.selectAll('td');
 
-            if (cstmWidth) {
-                cstmColWidth += fs.noPx(cstmWidth);
-            } else if (thElement.classed(tableIcon)) {
-                iconCols += 1;
-            } else {
-                regCols += 1;
-            }
-        });
+            headers.each(function (d, i) {
+                var h = d3.select(this),
+                    index = i.toString();
+                if (h.classed(tableIcon)) {
+                    cstmWidths[index] = tableIconTdSize + 'px';
+                }
+                if (h.attr(colWidth)) {
+                    cstmWidths[index] = h.attr(colWidth);
+                }
+            });
+            $log.debug('Headers with custom widths: ', cstmWidths);
+        }
 
-        return Math.floor((winWidth - cstmColWidth -
-                            (iconCols * tableIconTdSize)) / regCols);
-    }
+        function setTdWidths(elem) {
+            var tds = elem.selectAll('tr:not(.ignore-width)').selectAll('td');
+            _width(elem, width + 'px');
 
-    function setTableWidth(t) {
-        var tHeaders = t.selectAll('th'),
-            defaultColWidth = getDefaultWidth(tHeaders);
+            tds.each(function (d, i) {
+                var td = d3.select(this),
+                    index = i.toString();
+                if (cstmWidths.hasOwnProperty(index)) {
+                    _width(td, cstmWidths[index]);
+                }
+            });
+        }
 
-        tHeaders.each(function (d, i) {
-            var thElement = d3.select(this),
-                tr = t.select('tr:nth-of-type(2)'),
-                tdElement = tr.select('td:nth-of-type(' + (i + 1) + ')'),
-                custWidth = thElement.attr(colWidth);
+        function setHeight(body) {
+            var h = height - (mast.mastHeight() +
+                fs.noPxStyle(d3.select('.tabular-header'), 'height') +
+                fs.noPxStyle(thead, 'height') + pdg);
+            body.style('height', h + 'px');
+        }
 
-            if (custWidth) {
-                setColWidth(thElement, tdElement, fs.noPx(custWidth));
-            } else if (thElement.classed(tableIcon)) {
-                setColWidth(thElement, tdElement, tableIconTdSize);
-            } else {
-                setColWidth(thElement, tdElement, defaultColWidth);
-            }
-        });
-    }
+        findCstmWidths();
+        setTdWidths(thead);
+        setTdWidths(tbody);
+        setHeight(tbodyDiv);
 
-    // get the size of the window and then subtract the extra space at the top
-    // to get the height of the table
-    function setTableHeight(thead, tbody) {
-        var ttlHgt = fs.noPxStyle(d3.select('.tabular-header'), 'height'),
-            thHgt = fs.noPxStyle(thead, 'height'),
-            totalHgt = ttlHgt + thHgt + pdg,
-            tbleHgt = fs.windowSize(mast.mastHeight() + totalHgt).height;
-
-        thead.style('display', 'block');
-        tbody.style({
-            display: 'block',
-            height: tbleHgt + 'px',
-            overflow: 'auto'
-        });
-    }
-
-    function fixTable(t, th, tb) {
-        setTableWidth(t);
-        setTableHeight(th, tb);
+        cstmWidths = {};
     }
 
     // Functions for sorting table rows by header
@@ -163,40 +145,42 @@
     }
 
     angular.module('onosWidget')
-        .directive('onosFixedHeader', ['$window', 'FnService', 'MastService',
-            function (_$window_, _fs_, _mast_) {
+        .directive('onosFixedHeader', ['$log','$window',
+            'FnService', 'MastService',
+
+            function (_$log_, _$window_, _fs_, _mast_) {
             return function (scope, element) {
+                $log = _$log_;
                 $window = _$window_;
                 fs = _fs_;
                 mast = _mast_;
 
                 var w = angular.element($window),
                     table = d3.select(element[0]),
-                    thead = table.select('thead'),
-                    tbody = table.select('tbody'),
                     canAdjust = false;
 
                 scope.$watch(function () {
                     return {
-                        h: window.innerHeight,
-                        w: window.innerWidth
+                        h: $window.innerHeight,
+                        w: $window.innerWidth
                     };
-                }, function (newVal) {
-                    var wsz = fs.windowSize(0, 30);
-                    scope.windowHeight = newVal.h;
-                    scope.windowWidth = newVal.w;
+                }, function () {
+                    var wsz = fs.windowSize(0, 30),
+                        wWidth = wsz.width,
+                        wHeight = wsz.height;
 
-                    // default table size in case no data elements
-                    table.style('width', wsz.width + 'px');
+                    if (!scope.tableData.length) {
+                        defaultSize(table, wWidth);
+                    }
 
                     scope.$on('LastElement', function () {
                         // only adjust the table once it's completely loaded
-                        fixTable(table, thead, tbody);
+                        adjustTable(table, wWidth, wHeight);
                         canAdjust = true;
                     });
 
                     if (canAdjust) {
-                        fixTable(table, thead, tbody);
+                        adjustTable(table, wWidth, wHeight);
                     }
                 }, true);
 
@@ -215,16 +199,16 @@
                 link: function (scope, element) {
                     $log = _$log_;
                     is = _is_;
-                    var table = d3.select(element[0]);
+                    var header = d3.select(element[0]);
                         sortIconAPI = is.sortIcons();
 
-                    // when a header is clicked, change its icon tag
+                    // when a header is clicked, change its sort direction
                     // and get sorting order to send to the server.
-                    table.selectAll('th').on('click', function () {
-                        var thElem = d3.select(this);
+                    header.selectAll('td').on('click', function () {
+                        var col = d3.select(this);
 
-                        if (thElem.attr('sortable') === '') {
-                            updateSortDirection(thElem);
+                        if (col.attr('sortable') === '') {
+                            updateSortDirection(col);
                             scope.ctrlCallback({
                                 requestParams: sortRequestParams()
                             });
@@ -234,9 +218,9 @@
             };
         }])
 
-        .factory('TableService', ['$log', 'IconService',
+        .factory('TableService', ['IconService',
 
-            function ($log, is) {
+            function (is) {
                 sortIconAPI = is.sortIcons();
 
                 return {
diff --git a/web/gui/src/main/webapp/app/fw/widget/tableBuilder.js b/web/gui/src/main/webapp/app/fw/widget/tableBuilder.js
index 1343b8f..3a97732 100644
--- a/web/gui/src/main/webapp/app/fw/widget/tableBuilder.js
+++ b/web/gui/src/main/webapp/app/fw/widget/tableBuilder.js
@@ -25,7 +25,6 @@
 
     // example params to buildTable:
     // {
-    //    self: this,        <- controller object
     //    scope: $scope,     <- controller scope
     //    tag: 'device',     <- table identifier
     //    selCb: selCb       <- row selection callback (optional)
@@ -43,10 +42,10 @@
             resp = o.tag + 'DataResponse',
             onSel = fs.isF(o.selCb);
 
-        o.self.tableData = [];
+        o.scope.tableData = [];
 
         function respCb(data) {
-            o.self.tableData = data[root];
+            o.scope.tableData = data[root];
             o.scope.$apply();
         }
 
diff --git a/web/gui/src/main/webapp/app/view/app/app.html b/web/gui/src/main/webapp/app/view/app/app.html
index bb5f4ca..c57da76 100644
--- a/web/gui/src/main/webapp/app/view/app/app.html
+++ b/web/gui/src/main/webapp/app/view/app/app.html
@@ -1,7 +1,7 @@
 <!-- app partial HTML -->
 <div id="ov-app">
     <div class="tabular-header">
-        <h2>Applications ({{ctrl.tableData.length}} total)</h2>
+        <h2>Applications ({{tableData.length}} total)</h2>
         <div class="ctrl-btns">
             <div class="refresh active"
                  icon icon-size="36" icon-id="refresh"
@@ -19,39 +19,44 @@
         </form>
     </div>
 
-    <table class="summary-list"
-           onos-fixed-header
-           onos-sortable-header
-           sort-callback="sortCallback(requestParams)">
-        <thead>
-            <tr>
-                <th colId="state" class="table-icon" sortable></th>
-                <th colId="id" sortable>App ID </th>
-                <th colId="version" sortable>Version </th>
-                <th colId="origin" sortable>Origin </th>
-                <th colId="desc" col-width="640px">Description </th>
-            </tr>
-        </thead>
+    <div class="summary-list" onos-fixed-header>
 
-        <tbody>
-            <tr ng-hide="ctrl.tableData.length">
-                <td class="nodata" colspan="5">
-                    No Applications found
-                </td>
-            </tr>
+        <div class="table-header"
+             onos-sortable-header sort-callback="sortCallback(requestParams)">
+            <table>
+                <tr>
+                    <td colId="state" class="table-icon" sortable></td>
+                    <td colId="id" sortable>App ID </td>
+                    <td colId="version" sortable>Version </td>
+                    <td colId="origin" sortable>Origin </td>
+                    <td colId="desc" col-width="475px">Description </td>
+                </tr>
+            </table>
+        </div>
 
-            <tr ng-repeat="app in ctrl.tableData"
-                ng-click="selectCallback($event, app)"
-                ng-class="{selected: app === sel}"
-                ng-repeat-done>
-                <td class="table-icon">
-                    <div icon icon-id="{{app._iconid_state}}"></div>
-                </td>
-                <td>{{app.id}}</td>
-                <td>{{app.version}}</td>
-                <td>{{app.origin}}</td>
-                <td>{{app.desc}}</td>
-            </tr>
-        </tbody>
-    </table>
+        <div class="table-body">
+            <table>
+                <tr ng-hide="tableData.length" class="no-data ignore-width">
+                    <td colspan="5">
+                        No Applications found
+                    </td>
+                </tr>
+
+                <tr ng-repeat="app in tableData"
+                    ng-click="selectCallback($event, app)"
+                    ng-class="{selected: app === sel}"
+                    ng-repeat-done>
+                    <td class="table-icon">
+                        <div icon icon-id="{{app._iconid_state}}"></div>
+                    </td>
+                    <td>{{app.id}}</td>
+                    <td>{{app.version}}</td>
+                    <td>{{app.origin}}</td>
+                    <td>{{app.desc}}</td>
+                </tr>
+            </table>
+        </div>
+
+    </div>
+
 </div>
diff --git a/web/gui/src/main/webapp/app/view/app/app.js b/web/gui/src/main/webapp/app/view/app/app.js
index d0b561b..674407b 100644
--- a/web/gui/src/main/webapp/app/view/app/app.js
+++ b/web/gui/src/main/webapp/app/view/app/app.js
@@ -71,7 +71,6 @@
         d3.select('#app-deactivate').on('click', function () { appAction('deactivate'); });
 
         tbs.buildTable({
-            self: this,
             scope: $scope,
             tag: 'app',
             selCb: selCb
diff --git a/web/gui/src/main/webapp/app/view/cluster/cluster.html b/web/gui/src/main/webapp/app/view/cluster/cluster.html
index 2c58bc0..727974e 100644
--- a/web/gui/src/main/webapp/app/view/cluster/cluster.html
+++ b/web/gui/src/main/webapp/app/view/cluster/cluster.html
@@ -17,7 +17,7 @@
 <!-- Cluster partial HTML -->
 <div id="ov-cluster">
     <div class="tabular-header">
-        <h2>Cluster Nodes ({{ctrl.tableData.length}} total)</h2>
+        <h2>Cluster Nodes ({{tableData.length}} total)</h2>
         <div class="ctrl-btns">
             <div class="refresh active"
                  icon icon-size="36" icon-id="refresh"
@@ -25,37 +25,42 @@
         </div>
     </div>
 
-    <table class="summary-list"
-           onos-fixed-header
-           onos-sortable-header
-           sort-callback="sortCallback(requestParams)">
-        <thead>
-        <tr>
-            <th colId="_iconid_state" class="table-icon" sortable></th>
-            <th colId="id" sortable>ID </th>
-            <th colId="ip" sortable>IP Address </th>
-            <th colId="tcp" sortable>TCP Port </th>
-            <th colId="updated" sortable>Last Updated </th>
-        </tr>
-        </thead>
+    <div class="summary-list" onos-fixed-header>
 
-        <tbody>
-        <tr ng-hide="ctrl.tableData.length">
-            <td class="nodata" colspan="5">
-                No Cluster Nodes found
-            </td>
-        </tr>
+        <div class="table-header"
+             onos-sortable-header sort-callback="sortCallback(requestParams)">
+            <table>
+                <tr>
+                    <td colId="_iconid_state" class="table-icon" sortable></td>
+                    <td colId="id" sortable>ID </td>
+                    <td colId="ip" sortable>IP Address </td>
+                    <td colId="tcp" sortable>TCP Port </td>
+                    <td colId="updated" sortable>Last Updated </td>
+                </tr>
+            </table>
+        </div>
 
-        <tr ng-repeat="node in ctrl.tableData"
-            ng-repeat-done>
-            <td class="table-icon">
-                <div icon icon-id="{{node._iconid_state}}"></div>
-            </td>
-            <td>{{node.id}}</td>
-            <td>{{node.ip}}</td>
-            <td>{{node.tcp}}</td>
-            <td>{{node.updated}}</td>
-        </tr>
-        </tbody>
-    </table>
+        <div class="table-body">
+            <table>
+                <tr ng-hide="tableData.length" class="no-data ignore-width">
+                    <td colspan="5">
+                        No Cluster Nodes found
+                    </td>
+                </tr>
+
+                <tr ng-repeat="node in tableData"
+                    ng-repeat-done>
+                    <td class="table-icon">
+                        <div icon icon-id="{{node._iconid_state}}"></div>
+                    </td>
+                    <td>{{node.id}}</td>
+                    <td>{{node.ip}}</td>
+                    <td>{{node.tcp}}</td>
+                    <td>{{node.updated}}</td>
+                </tr>
+            </table>
+        </div>
+
+    </div>
+
 </div>
diff --git a/web/gui/src/main/webapp/app/view/cluster/cluster.js b/web/gui/src/main/webapp/app/view/cluster/cluster.js
index d88c03c..25e5bd2 100644
--- a/web/gui/src/main/webapp/app/view/cluster/cluster.js
+++ b/web/gui/src/main/webapp/app/view/cluster/cluster.js
@@ -27,7 +27,6 @@
 
             function ($log, $scope, ts, tbs) {
                 tbs.buildTable({
-                    self: this,
                     scope: $scope,
                     tag: 'cluster'
                 });
diff --git a/web/gui/src/main/webapp/app/view/device/device.html b/web/gui/src/main/webapp/app/view/device/device.html
index ba1cb4b..0c202e0 100644
--- a/web/gui/src/main/webapp/app/view/device/device.html
+++ b/web/gui/src/main/webapp/app/view/device/device.html
@@ -1,7 +1,7 @@
 <!-- Device partial HTML -->
 <div id="ov-device">
     <div class="tabular-header">
-        <h2>Devices ({{ctrl.tableData.length}} total)</h2>
+        <h2>Devices ({{tableData.length}} total)</h2>
         <div class="ctrl-btns">
             <div class="refresh active"
                  icon icon-size="36" icon-id="refresh"
@@ -9,49 +9,54 @@
         </div>
     </div>
 
-    <table class="summary-list"
-           onos-fixed-header
-           onos-sortable-header
-           sort-callback="sortCallback(requestParams)">
-        <thead>
-            <tr>
-                <th colId="available" class="table-icon" sortable></th>
-                <th colId="type" class="table-icon" sortable></th>
-                <th colId="id" sortable>Device ID </th>
-                <th colId="masterid" sortable>Master Instance </th>
-                <th colId="num_ports" sortable>Ports </th>
-                <th colId="mfr" sortable>Vendor </th>
-                <th colId="hw" sortable>H/W Version </th>
-                <th colId="sw" sortable>S/W Version </th>
-                <th colId="protocol" sortable>Protocol </th>
-            </tr>
-        </thead>
+    <div class="summary-list" onos-fixed-header>
 
-        <tbody>
-            <tr ng-hide="ctrl.tableData.length">
-                <td class="nodata" colspan="9">
-                    No Devices found
-                </td>
-            </tr>
+        <div class="table-header"
+             onos-sortable-header sort-callback="sortCallback(requestParams)">
+            <table>
+                <tr>
+                    <td colId="available" class="table-icon" sortable></td>
+                    <td colId="type" class="table-icon" sortable></td>
+                    <td colId="id" sortable>Device ID </td>
+                    <td colId="masterid" sortable>Master Instance </td>
+                    <td colId="num_ports" sortable>Ports </td>
+                    <td colId="mfr" sortable>Vendor </td>
+                    <td colId="hw" sortable>H/W Version </td>
+                    <td colId="sw" sortable>S/W Version </td>
+                    <td colId="protocol" sortable>Protocol </td>
+                </tr>
+            </table>
+        </div>
 
-            <tr ng-repeat="dev in ctrl.tableData"
-                ng-click="selectCallback($event, dev)"
-                ng-class="{selected: dev === sel}"
-                ng-repeat-done>
-                <td class="table-icon">
-                    <div icon icon-id="{{dev._iconid_available}}"></div>
-                </td>
-                <td class="table-icon">
-                    <div icon icon-id="{{dev._iconid_type}}"></div>
-                </td>
-                <td>{{dev.id}}</td>
-                <td>{{dev.masterid}}</td>
-                <td>{{dev.num_ports}}</td>
-                <td>{{dev.mfr}}</td>
-                <td>{{dev.hw}}</td>
-                <td>{{dev.sw}}</td>
-                <td>{{dev.protocol}}</td>
-            </tr>
-        </tbody>
-    </table>
+        <div class="table-body">
+            <table>
+                <tr ng-hide="tableData.length" class="no-data ignore-width">
+                    <td colspan="9">
+                        No Devices found
+                    </td>
+                </tr>
+
+                <tr ng-repeat="dev in tableData"
+                    ng-click="selectCallback($event, dev)"
+                    ng-class="{selected: dev === sel}"
+                    ng-repeat-done>
+                    <td class="table-icon">
+                        <div icon icon-id="{{dev._iconid_available}}"></div>
+                    </td>
+                    <td class="table-icon">
+                        <div icon icon-id="{{dev._iconid_type}}"></div>
+                    </td>
+                    <td>{{dev.id}}</td>
+                    <td>{{dev.masterid}}</td>
+                    <td>{{dev.num_ports}}</td>
+                    <td>{{dev.mfr}}</td>
+                    <td>{{dev.hw}}</td>
+                    <td>{{dev.sw}}</td>
+                    <td>{{dev.protocol}}</td>
+                </tr>
+            </table>
+        </div>
+
+    </div>
+
 </div>
diff --git a/web/gui/src/main/webapp/app/view/device/device.js b/web/gui/src/main/webapp/app/view/device/device.js
index 38953d7..cd0799b 100644
--- a/web/gui/src/main/webapp/app/view/device/device.js
+++ b/web/gui/src/main/webapp/app/view/device/device.js
@@ -25,8 +25,7 @@
     var $log, $scope, fs, mast, ps, wss, is, bns, ns, ttip;
 
     // internal state
-    var self,
-        detailsPanel,
+    var detailsPanel,
         pStartY, pHeight,
         top, bottom, iconDiv,
         wSize, selRow;
@@ -183,8 +182,8 @@
     }
 
     function respDetailsCb(data) {
-        self.panelData = data.details;
-        populateDetails(self.panelData);
+        $scope.panelData = data.details;
+        populateDetails($scope.panelData);
         detailsPanel.show();
     }
 
@@ -219,9 +218,8 @@
             bns = _bns_;
             ns = _ns_;
             ttip = _ttip_;
-            self = this;
             var handlers = {};
-            self.panelData = [];
+            $scope.panelData = [];
             pStartY = fs.noPxStyle(d3.select('.tabular-header'), 'height')
                                             + mast.mastHeight() + topPdg;
             wSize = fs.windowSize(pStartY);
@@ -238,7 +236,6 @@
             }
 
             tbs.buildTable({
-                self: self,
                 scope: $scope,
                 tag: 'device',
                 selCb: selCb
diff --git a/web/gui/src/main/webapp/app/view/flow/flow.css b/web/gui/src/main/webapp/app/view/flow/flow.css
index ecbd217..3e5cb2c 100644
--- a/web/gui/src/main/webapp/app/view/flow/flow.css
+++ b/web/gui/src/main/webapp/app/view/flow/flow.css
@@ -50,4 +50,5 @@
 #ov-flow td.selector,
 #ov-flow td.treatment {
     padding-left: 36px;
+    opacity: 0.65;
 }
\ No newline at end of file
diff --git a/web/gui/src/main/webapp/app/view/flow/flow.html b/web/gui/src/main/webapp/app/view/flow/flow.html
index d2278ab..33fd548 100644
--- a/web/gui/src/main/webapp/app/view/flow/flow.html
+++ b/web/gui/src/main/webapp/app/view/flow/flow.html
@@ -2,8 +2,8 @@
 <div id="ov-flow">
     <div class="tabular-header">
         <h2>
-            Flows for Device {{ctrl.devId || "(No device selected)"}}
-            ({{ctrl.tableData.length}} total)
+            Flows for Device {{devId || "(No device selected)"}}
+            ({{tableData.length}} total)
         </h2>
         <div class="ctrl-btns">
             <div class="refresh active"
@@ -12,46 +12,52 @@
         </div>
     </div>
 
-    <table class="summary-list"
-           onos-fixed-header
-           onos-sortable-header
-           sort-callback="sortCallback(requestParams)">
-        <thead>
-        <tr>
-            <th colId="id" sortable>Flow ID </th>
-            <th colId="appId" sortable>App ID </th>
-            <th colId="groupId" sortable>Group ID </th>
-            <th colId="tableId" sortable>Table ID </th>
-            <th colId="priority" sortable>Priority </th>
-            <th colId="timeout" sortable>Timeout </th>
-            <th colId="permanent" sortable>Permanent </th>
-            <th colId="state" sortable>State </th>
-        </tr>
-        </thead>
+    <div class="summary-list" onos-fixed-header>
 
-        <tbody>
-        <tr ng-hide="ctrl.tableData.length">
-            <td class="nodata" colspan="8">
-                No Flows found
-            </td>
-        </tr>
+        <div class="table-header"
+             onos-sortable-header sort-callback="sortCallback(requestParams)">
+            <table>
+                <tr>
+                    <td colId="id" col-width="180px" sortable>Flow ID </td>
+                    <td colId="appId" sortable>App ID </td>
+                    <td colId="groupId" sortable>Group ID </td>
+                    <td colId="tableId" sortable>Table ID </td>
+                    <td colId="priority" sortable>Priority </td>
+                    <td colId="timeout" sortable>Timeout </td>
+                    <td colId="permanent" sortable>Permanent </td>
+                    <td colId="state" sortable>State </td>
+                </tr>
+            </table>
+        </div>
 
-        <tr ng-repeat-start="flow in ctrl.tableData">
-            <td>{{flow.id}}</td>
-            <td>{{flow.appId}}</td>
-            <td>{{flow.groupId}}</td>
-            <td>{{flow.tableId}}</td>
-            <td>{{flow.priority}}</td>
-            <td>{{flow.timeout}}</td>
-            <td>{{flow.permanent}}</td>
-            <td>{{flow.state}}</td>
-        </tr>
-        <tr>
-            <td class="selector" colspan="8">{{flow.selector}}</td>
-        </tr>
-        <tr ng-repeat-end ng-repeat-done>
-            <td class="treatment" colspan="8">{{flow.treatment}}</td>
-        </tr>
-        </tbody>
-    </table>
+        <div class="table-body">
+            <table>
+                <tr ng-hide="tableData.length" class="no-data ignore-width">
+                    <td colspan="8">
+                        No Flows found
+                    </td>
+                </tr>
+
+                <tr ng-repeat-start="flow in tableData">
+                    <td>{{flow.id}}</td>
+                    <td>{{flow.appId}}</td>
+                    <td>{{flow.groupId}}</td>
+                    <td>{{flow.tableId}}</td>
+                    <td>{{flow.priority}}</td>
+                    <td>{{flow.timeout}}</td>
+                    <td>{{flow.permanent}}</td>
+                    <td>{{flow.state}}</td>
+                </tr>
+                <tr class="ignore-width">
+                    <td class="selector" colspan="8">{{flow.selector}}</td>
+                </tr>
+                <tr class="ignore-width"
+                    ng-repeat-end ng-repeat-done>
+                    <td class="treatment" colspan="8">{{flow.treatment}}</td>
+                </tr>
+            </table>
+        </div>
+
+    </div>
+
 </div>
diff --git a/web/gui/src/main/webapp/app/view/flow/flow.js b/web/gui/src/main/webapp/app/view/flow/flow.js
index bcd471b..c75ed87 100644
--- a/web/gui/src/main/webapp/app/view/flow/flow.js
+++ b/web/gui/src/main/webapp/app/view/flow/flow.js
@@ -30,8 +30,7 @@
             'FnService', 'TableService', 'TableBuilderService',
 
         function (_$log_, _$scope_, _$location_, _fs_, _ts_, _tbs_) {
-            var self = this,
-                params;
+            var params;
             $log = _$log_;
             $scope = _$scope_;
             $location = _$location_;
@@ -41,11 +40,10 @@
 
             params = $location.search();
             if (params.hasOwnProperty('devId')) {
-                self.devId = params['devId'];
+                $scope.devId = params['devId'];
             }
 
             tbs.buildTable({
-                self: self,
                 scope: $scope,
                 tag: 'flow',
                 query: params
diff --git a/web/gui/src/main/webapp/app/view/host/host.html b/web/gui/src/main/webapp/app/view/host/host.html
index 9f70432..a4f8c4f 100644
--- a/web/gui/src/main/webapp/app/view/host/host.html
+++ b/web/gui/src/main/webapp/app/view/host/host.html
@@ -1,7 +1,7 @@
 <!-- Host partial HTML -->
 <div id="ov-host">
     <div class="tabular-header">
-        <h2>Hosts ({{ctrl.tableData.length}} total)</h2>
+        <h2>Hosts ({{tableData.length}} total)</h2>
         <div class="ctrl-btns">
             <div class="refresh active"
                  icon icon-size="36" icon-id="refresh"
@@ -9,39 +9,44 @@
         </div>
     </div>
 
-    <table class="summary-list"
-           onos-fixed-header
-           onos-sortable-header
-           sort-callback="sortCallback(requestParams)">
-        <thead>
-            <tr>
-                <th colId="type" class="table-icon" sortable></th>
-                <th colId="id" sortable>Host ID </th>
-                <th colId="mac" sortable>MAC Address </th>
-                <th colId="vlan" sortable>VLAN ID </th>
-                <th colId="ips" sortable>IP Addresses </th>
-                <th colId="location" sortable>Location </th>
-            </tr>
-        </thead>
+    <div class="summary-list" onos-fixed-header>
 
-        <tbody>
-            <tr ng-hide="ctrl.tableData.length">
-                <td class="nodata" colspan="6">
-                    No Hosts found
-                </td>
-            </tr>
+        <div class="table-header"
+             onos-sortable-header sort-callback="sortCallback(requestParams)">
+            <table>
+                <tr>
+                    <td colId="type" class="table-icon" sortable></td>
+                    <td colId="id" sortable>Host ID </td>
+                    <td colId="mac" sortable>MAC Address </td>
+                    <td colId="vlan" sortable>VLAN ID </td>
+                    <td colId="ips" sortable>IP Addresses </td>
+                    <td colId="location" sortable>Location </td>
+                </tr>
+            </table>
+        </div>
 
-            <tr ng-repeat="host in ctrl.tableData"
-                ng-repeat-done>
-                <td class="table-icon">
-                    <div icon icon-id="{{host._iconid_type}}"></div>
-                </td>
-                <td>{{host.id}}</td>
-                <td>{{host.mac}}</td>
-                <td>{{host.vlan}}</td>
-                <td>{{host.ips}}</td>
-                <td>{{host.location}}</td>
-            </tr>
-        </tbody>
-    </table>
+        <div class="table-body">
+            <table>
+                <tr ng-hide="tableData.length" class="no-data ignore-width">
+                    <td colspan="6">
+                        No Hosts found
+                    </td>
+                </tr>
+
+                <tr ng-repeat="host in tableData"
+                    ng-repeat-done>
+                    <td class="table-icon">
+                        <div icon icon-id="{{host._iconid_type}}"></div>
+                    </td>
+                    <td>{{host.id}}</td>
+                    <td>{{host.mac}}</td>
+                    <td>{{host.vlan}}</td>
+                    <td>{{host.ips}}</td>
+                    <td>{{host.location}}</td>
+                </tr>
+            </table>
+        </div>
+
+    </div>
+
 </div>
diff --git a/web/gui/src/main/webapp/app/view/host/host.js b/web/gui/src/main/webapp/app/view/host/host.js
index a3e43aa..e82c7da 100644
--- a/web/gui/src/main/webapp/app/view/host/host.js
+++ b/web/gui/src/main/webapp/app/view/host/host.js
@@ -27,7 +27,6 @@
 
         function ($log, $scope, ts, tbs) {
             tbs.buildTable({
-                self: this,
                 scope: $scope,
                 tag: 'host'
             });
@@ -37,7 +36,7 @@
                 ts.resetSortIcons();
                 $scope.sortCallback();
             };
-            
+
             $log.log('OvHostCtrl has been created');
         }]);
 }());
diff --git a/web/gui/src/main/webapp/app/view/intent/intent.css b/web/gui/src/main/webapp/app/view/intent/intent.css
index 4f9ea8a..49bfc18 100644
--- a/web/gui/src/main/webapp/app/view/intent/intent.css
+++ b/web/gui/src/main/webapp/app/view/intent/intent.css
@@ -50,4 +50,5 @@
 #ov-intent td.resources,
 #ov-intent td.details {
     padding-left: 36px;
+    opacity: 0.65;
 }
diff --git a/web/gui/src/main/webapp/app/view/intent/intent.html b/web/gui/src/main/webapp/app/view/intent/intent.html
index 2b737f4..c33df85 100644
--- a/web/gui/src/main/webapp/app/view/intent/intent.html
+++ b/web/gui/src/main/webapp/app/view/intent/intent.html
@@ -17,45 +17,51 @@
 <!-- Intent partial HTML -->
 <div id="ov-intent">
     <div class="tabular-header">
-        <h2>Intents ({{ctrl.tableData.length}} total)</h2>
+        <h2>Intents ({{tableData.length}} total)</h2>
         <div class="ctrl-btns">
             <div class="refresh active"
                  icon icon-size="36" icon-id="refresh"
                  ng-click="refresh()"></div>
         </div>
     </div>
-    <table class="summary-list"
-           onos-fixed-header
-           onos-sortable-header
-           sort-callback="sortCallback(requestParams)">
-        <thead>
-        <tr>
-            <th colId="appId" sortable>Application ID </th>
-            <th colId="key" sortable>Key </th>
-            <th colId="type" sortable>Type </th>
-            <th colId="priority" sortable>Priority </th>
-        </tr>
-        </thead>
 
-        <tbody>
-            <tr ng-hide="ctrl.tableData.length">
-                <td class="nodata" colspan="4">
-                    No Intents found
-                </td>
-            </tr>
+    <div class="summary-list" onos-fixed-header>
 
-            <tr ng-repeat-start="intent in ctrl.tableData">
-                <td>{{intent.appId}}</td>
-                <td>{{intent.key}}</td>
-                <td>{{intent.type}}</td>
-                <td>{{intent.priority}}</td>
-            </tr>
-            <tr>
-                <td class="resources" colspan="4">{{intent.resources}}</td>
-            </tr>
-            <tr ng-repeat-end ng-repeat-done>
-                <td class="details" colspan="4">{{intent.details}}</td>
-            </tr>
-        </tbody>
-    </table>
+        <div class="table-header"
+             onos-sortable-header sort-callback="sortCallback(requestParams)">
+            <table>
+                <tr>
+                    <td colId="appId" sortable>Application ID </td>
+                    <td colId="key" sortable>Key </td>
+                    <td colId="type" sortable>Type </td>
+                    <td colId="priority" sortable>Priority </td>
+                </tr>
+            </table>
+        </div>
+
+        <div class="table-body">
+            <table>
+                <tr ng-hide="tableData.length" class="no-data ignore-width">
+                    <td colspan="4">
+                        No Intents found
+                    </td>
+                </tr>
+
+                <tr ng-repeat-start="intent in tableData">
+                    <td>{{intent.appId}}</td>
+                    <td>{{intent.key}}</td>
+                    <td>{{intent.type}}</td>
+                    <td>{{intent.priority}}</td>
+                </tr>
+                <tr>
+                    <td class="resources" colspan="4">{{intent.resources}}</td>
+                </tr>
+                <tr ng-repeat-end ng-repeat-done>
+                    <td class="details" colspan="4">{{intent.details}}</td>
+                </tr>
+            </table>
+        </div>
+
+    </div>
+
 </div>
diff --git a/web/gui/src/main/webapp/app/view/intent/intent.js b/web/gui/src/main/webapp/app/view/intent/intent.js
index 4e23aec..e74e927 100644
--- a/web/gui/src/main/webapp/app/view/intent/intent.js
+++ b/web/gui/src/main/webapp/app/view/intent/intent.js
@@ -27,7 +27,6 @@
 
             function ($log, $scope, ts, tbs) {
                 tbs.buildTable({
-                    self: this,
                     scope: $scope,
                     tag: 'intent'
                 });
diff --git a/web/gui/src/main/webapp/app/view/link/link.html b/web/gui/src/main/webapp/app/view/link/link.html
index 29121c9..ab20b63 100644
--- a/web/gui/src/main/webapp/app/view/link/link.html
+++ b/web/gui/src/main/webapp/app/view/link/link.html
@@ -17,7 +17,7 @@
 <!-- Link partial HTML -->
 <div id="ov-link">
     <div class="tabular-header">
-        <h2>Links ({{ctrl.tableData.length}} total)</h2>
+        <h2>Links ({{tableData.length}} total)</h2>
         <div class="ctrl-btns">
             <div class="refresh active"
                  icon icon-size="36" icon-id="refresh"
@@ -25,39 +25,44 @@
         </div>
     </div>
 
-    <table class="summary-list"
-           onos-fixed-header
-           onos-sortable-header
-           sort-callback="sortCallback(requestParams)">
-        <thead>
-            <tr>
-                <th colId="_iconid_state" class="table-icon" sortable></th>
-                <th colId="one" sortable>Port 1 </th>
-                <th colId="two" sortable>Port 2 </th>
-                <th colId="type" sortable>Type </th>
-                <th colId="direction" sortable>Direction </th>
-                <th colId="durable" sortable>Durable </th>
-            </tr>
-        </thead>
+    <div class="summary-list" onos-fixed-header>
 
-        <tbody>
-            <tr ng-hide="ctrl.tableData.length">
-                <td class="nodata" colspan="6">
-                    No Links found
-                </td>
-            </tr>
+        <div class="table-header"
+             onos-sortable-header sort-callback="sortCallback(requestParams)">
+            <table>
+                <tr>
+                    <td colId="_iconid_state" class="table-icon" sortable></td>
+                    <td colId="one" sortable>Port 1 </td>
+                    <td colId="two" sortable>Port 2 </td>
+                    <td colId="type" sortable>Type </td>
+                    <td colId="direction" sortable>Direction </td>
+                    <td colId="durable" sortable>Durable </td>
+                </tr>
+            </table>
+        </div>
 
-            <tr ng-repeat="link in ctrl.tableData"
-                ng-repeat-done>
-                <td class="table-icon">
-                    <div icon icon-id="{{link._iconid_state}}"></div>
-                </td>
-                <td>{{link.one}}</td>
-                <td>{{link.two}}</td>
-                <td>{{link.type}}</td>
-                <td>{{link.direction}}</td>
-                <td>{{link.durable}}</td>
-            </tr>
-        </tbody>
-    </table>
+        <div class="table-body">
+            <table>
+                <tr ng-hide="tableData.length" class="no-data ignore-width">
+                    <td colspan="6">
+                        No Links found
+                    </td>
+                </tr>
+
+                <tr ng-repeat="link in tableData"
+                    ng-repeat-done>
+                    <td class="table-icon">
+                        <div icon icon-id="{{link._iconid_state}}"></div>
+                    </td>
+                    <td>{{link.one}}</td>
+                    <td>{{link.two}}</td>
+                    <td>{{link.type}}</td>
+                    <td>{{link.direction}}</td>
+                    <td>{{link.durable}}</td>
+                </tr>
+            </table>
+        </div>
+
+    </div>
+
 </div>
diff --git a/web/gui/src/main/webapp/app/view/link/link.js b/web/gui/src/main/webapp/app/view/link/link.js
index bc69430..743e90d 100644
--- a/web/gui/src/main/webapp/app/view/link/link.js
+++ b/web/gui/src/main/webapp/app/view/link/link.js
@@ -27,7 +27,6 @@
 
         function ($log, $scope, ts, tbs) {
             tbs.buildTable({
-                self: this,
                 scope: $scope,
                 tag: 'link'
             });
@@ -37,7 +36,7 @@
                 ts.resetSortIcons();
                 $scope.sortCallback();
             };
-            
+
             $log.log('OvLinkCtrl has been created');
         }]);
 }());
diff --git a/web/gui/src/main/webapp/tests/app/fw/widget/table-spec.js b/web/gui/src/main/webapp/tests/app/fw/widget/table-spec.js
index 693f42d..6df87b3 100644
--- a/web/gui/src/main/webapp/tests/app/fw/widget/table-spec.js
+++ b/web/gui/src/main/webapp/tests/app/fw/widget/table-spec.js
@@ -20,71 +20,59 @@
 describe('factory: fw/widget/table.js', function () {
     var $log, $compile, $rootScope,
         fs, ts, mast, is,
-        scope, compiled,
-        table, thead, tbody, mockHeader,
-        mockH2Height = 20,
-        mockMastHeight = 20,
-        mockHeaderHeight = mockH2Height + mockMastHeight,
-        tableIconTdSize = 100,
-        numTestElems = 4;
+        scope,
+        containerDiv,
+        headerDiv, bodyDiv,
+        header, body,
+        mockHeader,
+        mockHeaderHeight = 40;
 
     var onosFixedHeaderTags =
-            '<table onos-fixed-header>' +
-            '<thead style="height:27px;">' +
-            '<tr>' +
-            '<th></th>' +
-            '<th>Device ID </th>' +
-            '<th col-width="100px">H/W Version </th>' +
-            '<th>S/W Version </th>' +
-            '</tr>' +
-            '</thead>' +
-            '<tbody>' +
-            '<tr>' +
-            '<td colspan="4">' +
-                'No Devices found' +
-            '</td>' +
-            '</tr>' +
-            '<tr>' +
-            '<td class="table-icon">' +
-                '<div icon icon-id="{{dev._iconid_available}}">' +
-                '</div>' +
-            '</td>' +
-            '<td>Some ID</td>' +
-            '<td>Some HW</td>' +
-            '<td>Some Software</td>' +
-            '</tr>' +
-            '</tbody>' +
-            '</table>',
+            '<div class="summary-list" onos-fixed-header>' +
+            '<div class="table-header">' +
+                '<table>' +
+                    '<tr>' +
+                        '<td colId="type" class="table-icon"></td>' +
+                        '<td colId="id">Host ID </td>' +
+                        '<td colId="mac" sortable>MAC Address </td>' +
+                        '<td colId="location" col-width="110px">Location </td>' +
+                    '</tr>' +
+                '</table>' +
+            '</div>' +
+
+            '<div class="table-body">' +
+                '<table>' +
+                    '<tr class="ignore-width">' +
+                        '<td class="not-picked"></td>' +
+                    '</tr>' +
+                    '<tr>' +
+                        '<td class="table-icon">Some Icon</td>' +
+                        '<td>Some ID</td>' +
+                        '<td>Some MAC Address</td>' +
+                        '<td>Some Location</td>' +
+                    '</tr>' +
+                '</table>' +
+            '</div>' +
+            '</div>',
 
         onosSortableHeaderTags =
-            '<table onos-sortable-header ' +
+            '<div onos-sortable-header ' +
             'sort-callback="sortCallback(requestParams)">' +
-            '<thead>' +
-            '<tr>' +
-            '<th colId="available"></th>' +
-            '<th colId="id" sortable>Device ID </th>' +
-            '<th colId="hw" sortable>H/W Version </th>' +
-            '<th colId="sw" sortable>S/W Version </th>' +
-            '</tr>' +
-            '</thead>' +
-            '<tbody>' +
-            '<tr>' +
-            '<td>' +
-                '<div icon icon-id="{{dev._iconid_available}}">' +
-                '</div>' +
-            '</td>' +
-            '<td>Some ID</td>' +
-            '<td>Some HW</td>' +
-            '<td>Some Software</td>' +
-            '</tr>' +
-            '</tbody>' +
-            '</table>';
+                '<table>' +
+                    '<tr>' +
+                        '<td colId="type"></td>' +
+                        '<td colId="id" sortable>Host ID </td>' +
+                        '<td colId="mac" sortable>MAC Address </td>' +
+                        '<td colId="location" sortable>Location </td>' +
+                    '</tr>' +
+                '</table>' +
+            '</div>';
 
     beforeEach(module('onosWidget', 'onosUtil', 'onosMast', 'onosSvg'));
 
     var mockWindow = {
-        innerWidth: 400,
-        innerHeight: 200,
+        innerWidth: 600,
+        innerHeight: 400,
         navigator: {
             userAgent: 'defaultUA'
         },
@@ -111,23 +99,44 @@
 
     beforeEach(function () {
         scope = $rootScope.$new();
+        scope.tableData = [];
     });
 
+    // Note: dummy header so that d3 doesn't trip up.
+    //       $compile has to be used on the directive tag element, so it can't
+    //       be included in the tag strings declared above.
     beforeEach(function () {
         mockHeader = d3.select('body')
             .append('h2')
             .classed('tabular-header', true)
-            .style('height', mockHeaderHeight + 'px')
-            .html('Some Header');
+            .style({
+                height: mockHeaderHeight + 'px',
+                margin: 0,
+                padding: 0
+            })
+            .text('Some Header');
     });
 
     afterEach(function () {
-        table = null;
-        thead = null;
-        tbody = null;
+        containerDiv = undefined;
+        headerDiv = undefined;
+        bodyDiv = undefined;
+        header = undefined;
+        body = undefined;
         mockHeader.remove();
     });
 
+    function populateTableData() {
+        scope.tableData = [
+            {
+                type: 'endstation',
+                id: '1234',
+                mac: '00:00:03',
+                location: 'USA'
+            }
+        ];
+    }
+
     it('should define TableBuilderService', function () {
         expect(ts).toBeDefined();
     });
@@ -138,83 +147,119 @@
         ])).toBeTruthy();
     });
 
-    function compileTable() {
-        compiled = $compile(table);
+    function compile(elem) {
+        var compiled = $compile(elem);
         compiled(scope);
         scope.$digest();
     }
 
-    function verifyGivenTags(dirName) {
-        expect(table).toBeDefined();
-        expect(table.attr(dirName)).toBe('');
+    function selectTables() {
+        expect(containerDiv.find('div').length).toBe(2);
 
-        thead = table.find('thead');
-        expect(thead).toBeDefined();
-        tbody = table.find('tbody');
-        expect(tbody).toBeDefined();
+        headerDiv = angular.element(containerDiv[0].querySelector('.table-header'));
+        expect(headerDiv.length).toBe(1);
+
+        bodyDiv = angular.element(containerDiv[0].querySelector('.table-body'));
+        expect(bodyDiv.length).toBe(1);
+
+        header = headerDiv.find('table');
+        expect(header.length).toBe(1);
+
+        body = bodyDiv.find('table');
+        expect(body.length).toBe(1);
     }
 
-    function verifyCssDisplay() {
-        var padding = 21, // bottom table constant 12 + mastPadding(?) 9
-            tableHeight = fs.windowSize(mockHeaderHeight).height -
-                (fs.noPx(table.find('thead').css('height')) + padding);
+    function verifyGivenTags(dirName, div) {
+        expect(div).toBeDefined();
+        expect(div.attr(dirName)).toBe('');
+    }
 
-        expect(thead.css('display')).toBe('block');
-        expect(tbody.css('display')).toBe('block');
-        expect(tbody.css('height')).toBe(tableHeight + 'px');
-        expect(tbody.css('overflow')).toBe('auto');
+    function verifyDefaultSize() {
+        expect(header.css('width')).toBe('570px');
+        expect(body.css('width')).toBe('570px');
+    }
 
-        // TODO: investigate why math for calculating the height works better
-        // in the browser window (thead height is 0 in this test?)
+    function verifyHeight() {
+        var padding = 12,
+            mastHeight = 36,
+            tableHeight = (mockWindow.innerHeight - mockHeaderHeight) -
+                (fs.noPx(headerDiv.css('height')) + mastHeight + padding);
+
+        expect(bodyDiv.css('height')).toBe(tableHeight + 'px');
     }
 
     function verifyColWidth() {
-        var winWidth = fs.windowSize().width,
-            colWidth, thElems, tr, tdElem;
+        var hdrs = header.find('td'),
+            cols = body.find('td');
 
-        colWidth = Math.floor(winWidth / numTestElems);
+        expect(angular.element(hdrs[0]).css('width')).toBe('33px');
+        expect(angular.element(hdrs[3]).css('width')).toBe('110px');
 
-        thElems = thead.find('th');
+        expect(angular.element(cols[1]).css('width')).toBe('33px');
+        expect(angular.element(cols[4]).css('width')).toBe('110px');
+    }
 
-        angular.forEach(thElems, function (thElem, i) {
-            thElem = angular.element(thElems[i]);
-            tr = angular.element(tbody.find('tr').eq(1));
-            tdElem = angular.element(tr.find('td').eq(i));
-            var custWidth = thElem.attr('col-width');
+    function verifyCallbacks(h) {
+        expect(scope.sortCallback).not.toHaveBeenCalled();
 
-            if (custWidth) {
-                expect(thElem.css('width')).toBe(custWidth);
-                expect(tdElem.css('width')).toBe(custWidth);
-            } else if (tdElem.attr('class') === 'table-icon') {
-                expect(thElem.css('width')).toBe(tableIconTdSize + 'px');
-                expect(tdElem.css('width')).toBe(tableIconTdSize + 'px');
-            } else {
-                expect(thElem.css('width')).toBe(colWidth + 'px');
-                expect(tdElem.css('width')).toBe(colWidth + 'px');
-            }
+        h[0].click();
+        expect(scope.sortCallback).not.toHaveBeenCalled();
+
+        h[1].click();
+        expect(scope.sortCallback).toHaveBeenCalledWith({
+            sortCol: 'id',
+            sortDir: 'asc'
+        });
+        h[1].click();
+        expect(scope.sortCallback).toHaveBeenCalledWith({
+            sortCol: 'id',
+            sortDir: 'desc'
+        });
+        h[1].click();
+        expect(scope.sortCallback).toHaveBeenCalledWith({
+            sortCol: 'id',
+            sortDir: 'asc'
+        });
+
+        h[2].click();
+        expect(scope.sortCallback).toHaveBeenCalledWith({
+            sortCol: 'mac',
+            sortDir: 'asc'
+        });
+        h[2].click();
+        expect(scope.sortCallback).toHaveBeenCalledWith({
+            sortCol: 'mac',
+            sortDir: 'desc'
+        });
+        h[2].click();
+        expect(scope.sortCallback).toHaveBeenCalledWith({
+            sortCol: 'mac',
+            sortDir: 'asc'
+        });
+
+        h[3].click();
+        expect(scope.sortCallback).toHaveBeenCalledWith({
+            sortCol: 'location',
+            sortDir: 'asc'
+        });
+        h[3].click();
+        expect(scope.sortCallback).toHaveBeenCalledWith({
+            sortCol: 'location',
+            sortDir: 'desc'
+        });
+        h[3].click();
+        expect(scope.sortCallback).toHaveBeenCalledWith({
+            sortCol: 'location',
+            sortDir: 'asc'
         });
     }
 
-    function verifyCallbacks(thElems) {
-        expect(scope.sortCallback).not.toHaveBeenCalled();
+    function verifyIcons(h) {
+        var currH, div;
 
-        // first test header has no 'sortable' attr
-        thElems[0].click();
-        expect(scope.sortCallback).not.toHaveBeenCalled();
-
-        // the other headers have 'sortable'
-        for(var i = 1; i < numTestElems; i += 1) {
-            thElems[i].click();
-            expect(scope.sortCallback).toHaveBeenCalled();
-        }
-    }
-
-    function verifyIcons(thElems) {
-        var currentTh, div;
-        // make sure it has the correct icon after clicking
-        thElems[1].click();
-        currentTh = angular.element(thElems[1]);
-        div = currentTh.find('div');
+        h[1].click();
+        currH = angular.element(h[1]);
+        div = currH.find('div');
         expect(div.html()).toBe(
             '<svg class="embeddedIcon" width="10" height="10" viewBox="0 0 ' +
             '50 50"><g class="icon upArrow"><rect width="50" height="50" ' +
@@ -222,8 +267,8 @@
             'xlink="http://www.w3.org/1999/xlink" xlink:href="#triangleUp">' +
             '</use></g></svg>'
         );
-        thElems[1].click();
-        div = currentTh.find('div');
+        h[1].click();
+        div = currH.find('div');
         expect(div.html()).toBe(
             '<svg class="embeddedIcon" width="10" height="10" viewBox="0 0 ' +
             '50 50"><g class="icon downArrow"><rect width="50" height="50" ' +
@@ -232,14 +277,14 @@
             '</use></g></svg>'
         );
 
-        thElems[2].click();
-        div = currentTh.children();
+        h[2].click();
+        div = currH.children();
         // clicked on a new element, so the previous icon should have been deleted
         expect(div.html()).toBeFalsy();
 
         // the new element should have the ascending icon
-        currentTh = angular.element(thElems[2]);
-        div = currentTh.children();
+        currH = angular.element(h[2]);
+        div = currH.children();
         expect(div.html()).toBe(
             '<svg class="embeddedIcon" width="10" height="10" viewBox="0 0 ' +
             '50 50"><g class="icon upArrow"><rect width="50" height="50" ' +
@@ -250,31 +295,42 @@
     }
 
     it('should affirm that onos-fixed-header is working', function () {
-        table = angular.element(onosFixedHeaderTags);
+        containerDiv = angular.element(onosFixedHeaderTags);
 
-        compileTable();
-        verifyGivenTags('onos-fixed-header');
+        compile(containerDiv);
 
-        // table will not be fixed unless it receives the 'LastElement' event
+        verifyGivenTags('onos-fixed-header', containerDiv);
+        selectTables();
+        verifyDefaultSize();
+
+        populateTableData();
+
         scope.$emit('LastElement');
         scope.$digest();
 
-        verifyCssDisplay();
+        verifyHeight();
+        verifyColWidth();
+
+        mockWindow.innerHeight = 300;
+        scope.$digest();
+        verifyHeight();
+
+        mockWindow.innerWidth = 500;
+        scope.$digest();
         verifyColWidth();
     });
 
     it('should affirm that onos-sortable-header is working', function () {
-        var thElems;
-        table = angular.element(onosSortableHeaderTags);
+        headerDiv = angular.element(onosSortableHeaderTags);
 
-        compileTable();
-        verifyGivenTags('onos-sortable-header');
+        compile(headerDiv);
+        verifyGivenTags('onos-sortable-header', headerDiv);
 
         scope.sortCallback = jasmine.createSpy('sortCallback');
 
-        thElems = thead.find('th');
-        verifyCallbacks(thElems);
-        verifyIcons(thElems);
+        header = headerDiv.find('td');
+        verifyCallbacks(header);
+        verifyIcons(header);
     });
 
     // Note: testing resetSortIcons isn't feasible because due to the nature of
diff --git a/web/gui/src/main/webapp/tests/app/fw/widget/tableBuilder-spec.js b/web/gui/src/main/webapp/tests/app/fw/widget/tableBuilder-spec.js
index f631617..3353805 100644
--- a/web/gui/src/main/webapp/tests/app/fw/widget/tableBuilder-spec.js
+++ b/web/gui/src/main/webapp/tests/app/fw/widget/tableBuilder-spec.js
@@ -48,7 +48,6 @@
 
     beforeEach(function () {
         mockObj = {
-            self: {},
             scope: $rootScope.$new(),
             tag: 'foo',
             selCb: mockSelCb
@@ -78,10 +77,10 @@
     });
 
     it('should set tableData', function () {
-        expect(mockObj.self.tableData).not.toBeDefined();
+        expect(mockObj.scope.tableData).not.toBeDefined();
         tbs.buildTable(mockObj);
-        expect(fs.isA(mockObj.self.tableData)).toBeTruthy();
-        expect(mockObj.self.tableData.length).toBe(0);
+        expect(fs.isA(mockObj.scope.tableData)).toBeTruthy();
+        expect(mockObj.scope.tableData.length).toBe(0);
     });
 
     it('should unbind handlers on destroyed scope', function () {