Implemented table building functions

Change-Id: Ie4003080b13725561df22de41ec85f8c3f31c794
diff --git a/web/gui2/src/main/webapp/app/fw/layer/dialog.service.ts b/web/gui2/src/main/webapp/app/fw/layer/dialog.service.ts
index 880e2bd..d6c37bb 100644
--- a/web/gui2/src/main/webapp/app/fw/layer/dialog.service.ts
+++ b/web/gui2/src/main/webapp/app/fw/layer/dialog.service.ts
@@ -38,4 +38,7 @@
     this.log.debug('DialogService constructed');
   }
 
+  createDiv() {
+  }
+
 }
diff --git a/web/gui2/src/main/webapp/app/fw/layer/loading.service.ts b/web/gui2/src/main/webapp/app/fw/layer/loading.service.ts
index 0d03ad6..3e6d48b 100644
--- a/web/gui2/src/main/webapp/app/fw/layer/loading.service.ts
+++ b/web/gui2/src/main/webapp/app/fw/layer/loading.service.ts
@@ -33,7 +33,9 @@
  *
  * Provides a mechanism to start/stop the loading animation, center screen.
  */
-@Injectable()
+@Injectable({
+  providedIn: 'root',
+})
 export class LoadingService {
     images: any[] = [];
     idx = 0;
@@ -127,7 +129,7 @@
     }
 
     // return true if start() has been called but not stop()
-    waiting() {
+    waiting(): boolean {
         return !!this.wait;
     }
 
diff --git a/web/gui2/src/main/webapp/app/fw/remote/websocket.service.ts b/web/gui2/src/main/webapp/app/fw/remote/websocket.service.ts
index e470ec1..34a8ea9 100644
--- a/web/gui2/src/main/webapp/app/fw/remote/websocket.service.ts
+++ b/web/gui2/src/main/webapp/app/fw/remote/websocket.service.ts
@@ -90,7 +90,6 @@
      * built-in handler for the 'boostrap' event
      */
     private bootstrap(data: Bootstrap) {
-        this.log.debug('bootstrap data', data);
         this.loggedInUser = data.user;
 
         this.clusterNodes = data.clusterNodes;
@@ -332,6 +331,13 @@
         return this.url;
     }
 
+    /**
+     * Tell the WebSocket to close - this should call the handleClose() method
+     */
+    closeWebSocket() {
+        this.ws.close();
+    }
+
 
     /**
      * Binds the message handlers to their message type (event type) as
@@ -369,12 +375,12 @@
      * Unbinds the specified message handlers.
      *   Expected that the same map will be used, but we only care about keys
      */
-    unbindHandlers(handlerMap: Map<string, (data) => void>): void {
-        if (this.noHandlersWarn(handlerMap, 'unbindHandlers')) {
+    unbindHandlers(handlerIds: string[]): void {
+        if ( handlerIds.length === 0 ) {
+            this.log.warn('WSS.unbindHandlers(): no event handlers');
             return null;
         }
-
-        for (const [eventId, api] of handlerMap) {
+        for (const eventId of handlerIds) {
             this.handlers.delete(eventId);
         }
     }
@@ -448,4 +454,7 @@
         this.lcd = ld;
     }
 
+    isConnected(): boolean {
+        return this.wsUp;
+    }
 }
diff --git a/web/gui2/src/main/webapp/app/fw/svg/icon.directive.ts b/web/gui2/src/main/webapp/app/fw/svg/icon.directive.ts
deleted file mode 100644
index a8de561..0000000
--- a/web/gui2/src/main/webapp/app/fw/svg/icon.directive.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright 2015-present Open Networking Foundation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import { Directive, ElementRef, Input, OnInit } from '@angular/core';
-import { IconService } from './icon.service';
-import { LogService } from '../../log.service';
-import * as d3 from 'd3';
-
-/**
- * ONOS GUI -- SVG -- Icon Directive
- *
- * TODO: Deprecated - this directive may be removed altogether as it has been
- * rebuilt as IconComponent instead
- */
-@Directive({
-  selector: '[onosIcon]'
-})
-export class IconDirective implements OnInit {
-    @Input() iconId: string;
-    @Input() iconSize = 20;
-
-    constructor(
-        private el: ElementRef,
-        private is: IconService,
-        private log: LogService
-    ) {
-        // Note: iconId is not available until initialization
-        this.log.debug('IconDirective constructed');
-    }
-
-    ngOnInit() {
-        const div = d3.select(this.el.nativeElement);
-        div.selectAll('*').remove();
-        this.is.loadEmbeddedIcon(div, this.iconId, this.iconSize);
-        this.log.debug('IconDirective initialized:', this.iconId);
-    }
-
-}
diff --git a/web/gui2/src/main/webapp/app/fw/svg/icon/icon.component.html b/web/gui2/src/main/webapp/app/fw/svg/icon/icon.component.html
index 6a32d87..5be4c19 100644
--- a/web/gui2/src/main/webapp/app/fw/svg/icon/icon.component.html
+++ b/web/gui2/src/main/webapp/app/fw/svg/icon/icon.component.html
@@ -13,9 +13,11 @@
 ~ See the License for the specific language governing permissions and
 ~ limitations under the License.
 -->
-<svg class="embeddedIcon" [attr.width]="iconSize" [attr.height]="iconSize" viewBox="0 0 50 50">
-    <g class="icon" [ngClass]="iconId">
+<svg class="embeddedIcon" [attr.width]="iconSize" [attr.height]="iconSize" viewBox="0 0 50 50" (mouseover)="toolTipDisp = toolTip" (mouseout)="toolTipDisp = undefined">
+    <g class="icon" [ngClass]="classes">
         <rect width="50" height="50" rx="5"></rect>
         <use width="50" height="50" class="glyph" [attr.href]="iconTag()"></use>
     </g>
 </svg>
+<!-- I'm fixing class as light as view encapsulation changes how the hirerarchy of css is handled -->
+<p id="tooltip" class="light" *ngIf="toolTip" [ngStyle]="{ 'display': toolTipDisp ? 'inline-block':'none' }">{{ toolTipDisp }}</p>
diff --git a/web/gui2/src/main/webapp/app/fw/svg/icon/icon.component.ts b/web/gui2/src/main/webapp/app/fw/svg/icon/icon.component.ts
index 45be81f..c851f47 100644
--- a/web/gui2/src/main/webapp/app/fw/svg/icon/icon.component.ts
+++ b/web/gui2/src/main/webapp/app/fw/svg/icon/icon.component.ts
@@ -16,7 +16,6 @@
 import { Component, OnInit, Input } from '@angular/core';
 import { IconService, glyphMapping } from '../icon.service';
 import { LogService } from '../../../log.service';
-import * as d3 from 'd3';
 
 /**
  * Icon Component
@@ -31,11 +30,20 @@
 @Component({
   selector: 'onos-icon',
   templateUrl: './icon.component.html',
-  styleUrls: ['./icon.component.css', './icon.theme.css', './glyph.css', './glyph-theme.css']
+  styleUrls: [
+    './icon.component.css', './icon.theme.css',
+    './glyph.css', './glyph-theme.css',
+    './tooltip.css', './tooltip-theme.css'
+    ]
 })
 export class IconComponent implements OnInit {
     @Input() iconId: string;
-    @Input() iconSize = 20;
+    @Input() iconSize: number = 20;
+    @Input() toolTip: string = undefined;
+    @Input() classes: string = undefined;
+
+    // The displayed tooltip - undefined until mouse hovers over, then equals toolTip
+    toolTipDisp: string = undefined;
 
     constructor(
         private is: IconService,
@@ -47,7 +55,6 @@
 
     ngOnInit() {
         this.is.loadIconDef(this.iconId);
-        this.log.debug('IconComponent initialized for ', this.iconId);
     }
 
     /**
diff --git a/web/gui2/src/main/webapp/app/fw/svg/icon/icon.theme.css b/web/gui2/src/main/webapp/app/fw/svg/icon/icon.theme.css
index 59cf10f..3e6f601 100644
--- a/web/gui2/src/main/webapp/app/fw/svg/icon/icon.theme.css
+++ b/web/gui2/src/main/webapp/app/fw/svg/icon/icon.theme.css
@@ -40,6 +40,46 @@
     fill: #3c3a3a;
 }
 
+/* --- Control Buttons --- */
+
+/* INACTIVE */
+svg.embeddedIcon g.icon use {
+    fill: #e0dfd6;
+}
+/* note: no change for inactive buttons when hovered */
+
+
+/* ACTIVE */
+svg.embeddedIcon g.icon.active use {
+    fill: #939598;
+}
+svg.embeddedIcon g.icon.active:hover use {
+    fill: #ce5b58;
+}
+
+/* CURRENT-VIEW */
+svg.embeddedIcon g.icon.current-view rect {
+    fill: #518ecc;
+}
+svg.embeddedIcon g.icon.current-view use {
+    fill: white;
+}
+
+/* REFRESH */
+svg.embeddedIcon g.icon.refresh use {
+    fill: #cdeff2;
+}
+svg.embeddedIcon g.icon.refresh:hover use {
+    fill: #ce5b58;
+}
+svg.embeddedIcon g.icon.refresh.active use {
+    fill: #009fdb;
+}
+svg.embeddedIcon g.icon.refresh.active:hover use {
+    fill: #ce5b58;
+}
+
+
 /* ========== DARK Theme ========== */
 
 .dark div.close-btn svg.embeddedIcon g.icon .glyph {
@@ -61,11 +101,12 @@
 .dark table svg.embeddedIcon .icon .glyph {
     fill: #9999aa;
 }
-
+/*
 svg.embeddedIcon g.icon .glyph {
     fill: #007dc4;
 }
 
 svg.embeddedIcon:hover g.icon .glyph {
     fill: #20b2ff;
-}
\ No newline at end of file
+}
+*/
\ No newline at end of file
diff --git a/web/gui2/src/main/webapp/app/fw/svg/icon/tooltip-theme.css b/web/gui2/src/main/webapp/app/fw/svg/icon/tooltip-theme.css
new file mode 100644
index 0000000..a703d1b
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/fw/svg/icon/tooltip-theme.css
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ ONOS GUI -- Tooltip Service (theme) -- CSS file
+ */
+.light#tooltip {
+    background-color: #dbeffc;
+    color: #3c3a3a;
+    border-color: #c7c7c0;
+}
+
+.dark#tooltip {
+    background-color: #3a3a60;
+    border-color: #6c6a6a;
+    color: #c7c7c0;
+}
diff --git a/web/gui2/src/main/webapp/app/fw/widget/tooltip.service.ts b/web/gui2/src/main/webapp/app/fw/svg/icon/tooltip.css
similarity index 62%
rename from web/gui2/src/main/webapp/app/fw/widget/tooltip.service.ts
rename to web/gui2/src/main/webapp/app/fw/svg/icon/tooltip.css
index 6b08779..74a5443 100644
--- a/web/gui2/src/main/webapp/app/fw/widget/tooltip.service.ts
+++ b/web/gui2/src/main/webapp/app/fw/svg/icon/tooltip.css
@@ -13,21 +13,18 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import { Injectable } from '@angular/core';
-import { FnService } from '../util/fn.service';
-import { LogService } from '../../log.service';
 
-/**
- * ONOS GUI -- Widget -- Tooltip Service
+/*
+ ONOS GUI -- Tooltip Service (layout) -- CSS file
  */
-@Injectable()
-export class TooltipService {
 
-  constructor(
-      private fs: FnService,
-      private log: LogService,
-  ) {
-    this.log.debug('TooltipService constructed');
-  }
-
+#tooltip {
+    text-align: center;
+    font-size: 80%;
+    border: 1px solid;
+    padding: 5px;
+    position: absolute;
+    z-index: 5000;
+    display: none;
+    pointer-events: none;
 }
diff --git a/web/gui2/src/main/webapp/app/fw/svg/svg.module.ts b/web/gui2/src/main/webapp/app/fw/svg/svg.module.ts
index 9688c30..3174263 100644
--- a/web/gui2/src/main/webapp/app/fw/svg/svg.module.ts
+++ b/web/gui2/src/main/webapp/app/fw/svg/svg.module.ts
@@ -25,7 +25,6 @@
 import { SpriteService } from './sprite.service';
 import { SpriteDataService } from './spritedata.service';
 import { SvgUtilService } from './svgutil.service';
-import { IconDirective } from './icon.directive';
 import { IconComponent } from './icon/icon.component';
 
 /**
@@ -33,7 +32,6 @@
  */
 @NgModule({
   exports: [
-    IconDirective,
     IconComponent
   ],
   imports: [
@@ -41,7 +39,6 @@
     UtilModule
   ],
   declarations: [
-    IconDirective,
     IconComponent
   ],
   providers: [
diff --git a/web/gui2/src/main/webapp/app/fw/util/fn.service.ts b/web/gui2/src/main/webapp/app/fw/util/fn.service.ts
index d355ce9..7b9ea24 100644
--- a/web/gui2/src/main/webapp/app/fw/util/fn.service.ts
+++ b/web/gui2/src/main/webapp/app/fw/util/fn.service.ts
@@ -415,6 +415,35 @@
     }
 
     /**
+     * returns true if the two objects have all the same properties
+     */
+    sameObjProps(obj1: Object, obj2: Object): boolean {
+        for (const key in obj1) {
+            if (obj1.hasOwnProperty(key)) {
+                if (!(obj1[key] === obj2[key])) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    /**
+     * returns true if the array contains the object
+     * does NOT use strict object reference equality,
+     * instead checks each property individually for equality
+     */
+    containsObj(arr: any[], obj: Object): boolean {
+        const len = arr.length;
+        for (let i = 0; i < len; i++) {
+            if (this.sameObjProps(arr[i], obj)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
      * Return the given string with the first character capitalized.
      */
     cap(s: string): string {
diff --git a/web/gui2/src/main/webapp/app/fw/util/lion.service.ts b/web/gui2/src/main/webapp/app/fw/util/lion.service.ts
index 5ab9f65..248fe51 100644
--- a/web/gui2/src/main/webapp/app/fw/util/lion.service.ts
+++ b/web/gui2/src/main/webapp/app/fw/util/lion.service.ts
@@ -34,7 +34,8 @@
 })
 export class LionService {
 
-    ubercache: any[];
+    ubercache: any[] = [];
+    loadCb; // Function
 
     /**
      * Handler for uberlion event from WSS
@@ -50,6 +51,10 @@
                 this.log.info('            :=> ', p);
             }
         }
+        if (this.loadCb) {
+            this.log.debug('Calling the load callback');
+            this.loadCb();
+        }
 
         this.log.debug('LION service: uber-lion bundle received:', data);
     }
@@ -69,17 +74,15 @@
      * returns a function that takes a string and returns a string
      */
     bundle(bundleId: string): (string) => string {
-        let bundle = this.ubercache[bundleId];
+        let bundleObj = this.ubercache[bundleId];
 
-        if (!bundle) {
+        if (!bundleObj) {
             this.log.warn('No lion bundle registered:', bundleId);
-            bundle = {};
+            bundleObj = {};
         }
 
-        return this.getKey;
-    }
-
-    getKey(key: string): string {
-        return this.bundle[key] || '%' + key + '%';
+        return (key) =>  {
+            return bundleObj[key] || '%' + key + '%';
+        };
     }
 }
diff --git a/web/gui2/src/main/webapp/app/fw/widget/button.service.ts b/web/gui2/src/main/webapp/app/fw/widget/button.service.ts
index da6cbab..4e22763 100644
--- a/web/gui2/src/main/webapp/app/fw/widget/button.service.ts
+++ b/web/gui2/src/main/webapp/app/fw/widget/button.service.ts
@@ -17,7 +17,6 @@
 import { FnService } from '../util/fn.service';
 import { IconService } from '../svg/icon.service';
 import { LogService } from '../../log.service';
-import { TooltipService } from './tooltip.service';
 
 /**
  * ONOS GUI -- Widget -- Button Service
@@ -29,7 +28,6 @@
         private is: IconService,
         private fs: FnService,
         private log: LogService,
-        private tts: TooltipService
     ) {
         this.log.debug('ButtonService constructed');
     }
diff --git a/web/gui2/src/main/webapp/app/fw/widget/table-theme.css b/web/gui2/src/main/webapp/app/fw/widget/table-theme.css
new file mode 100644
index 0000000..1edfab0
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/fw/widget/table-theme.css
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2016-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* ------ for summary-list tables (theme) ------ */
+
+.light div.summary-list, .table-header th {
+    background-color: #e5e5e6;
+    color: #3c3a3a;
+}
+
+.light div.summary-list, td {
+    color: #3c3a3a;
+}
+
+.light div.summary-list, tr:nth-child(even) {
+    background-color: #f4f4f4;
+}
+.light div.summary-list, tr:nth-child(odd) {
+    background-color: #fbfbfb;
+}
+
+.light div.summary-list, tr.selected {
+    background-color: #dbeffc !important;
+}
+
+
+.light div.summary-list, tr.data-change {
+    background-color: #FDFFDC;
+}
+
+/* --- Control Buttons --- */
+
+/* INACTIVE */
+.light .ctrl-btns div svg.embeddedIcon g.icon use {
+    fill: #e0dfd6;
+}
+/* note: no change for inactive buttons when hovered */
+
+
+/* ACTIVE */
+.light .ctrl-btns div.active svg.embeddedIcon g.icon use {
+    fill: #939598;
+}
+.light .ctrl-btns div.active:hover svg.embeddedIcon g.icon use {
+    fill: #ce5b58;
+}
+
+/* CURRENT-VIEW */
+.light .ctrl-btns div.current-view svg.embeddedIcon g.icon rect {
+    fill: #518ecc;
+}
+.light .ctrl-btns div.current-view svg.embeddedIcon g.icon use {
+    fill: white;
+}
+
+/* REFRESH */
+.light .ctrl-btns div.refresh svg.embeddedIcon g.icon use {
+    fill: #cdeff2;
+}
+.light .ctrl-btns div.refresh:hover svg.embeddedIcon g.icon use {
+    fill: #ce5b58;
+}
+.light .ctrl-btns div.refresh.active svg.embeddedIcon g.icon use {
+    fill: #009fdb;
+}
+.light .ctrl-btns div.refresh.active:hover svg.embeddedIcon g.icon use {
+    fill: #ce5b58;
+}
+
+
+/* ========== DARK Theme ========== */
+
+.dark div.summary-list .table-header td {
+    background-color: #222222;
+    color: #cccccc;
+}
+
+.dark div.summary-list td {
+    /* note: don't put background-color here */
+    color: #cccccc;
+}
+.dark div.summary-list tr.no-data td {
+    background-color: #333333;
+}
+
+.dark div.summary-list tr:nth-child(even) {
+    background-color: #333333;
+}
+.dark div.summary-list tr:nth-child(odd) {
+    background-color: #3a3a3a;
+}
+
+.dark div.summary-list tr.selected {
+    background-color: #304860 !important;
+}
+
+
+.dark div.summary-list tr.data-change {
+    background-color: #423708;
+}
+
+/* --- Control Buttons --- */
+
+/* INACTIVE */
+.dark .ctrl-btns div svg.embeddedIcon g.icon use {
+    fill: #444444;
+}
+/* note: no change for inactive buttons when hovered */
+
+
+/* ACTIVE */
+.dark .ctrl-btns div.active svg.embeddedIcon g.icon use {
+    fill: #939598;
+}
+.dark .ctrl-btns div.active:hover svg.embeddedIcon g.icon use {
+    fill: #ce5b58;
+}
+
+/* CURRENT-VIEW */
+.dark .ctrl-btns div.current-view svg.embeddedIcon g.icon rect {
+    fill: #518ecc;
+}
+.dark .ctrl-btns div.current-view svg.embeddedIcon g.icon use {
+    fill: #dddddd;
+}
+
+/* REFRESH */
+.dark .ctrl-btns div.refresh svg.embeddedIcon g.icon use {
+    fill: #364144;
+}
+.dark .ctrl-btns div.refresh:hover svg.embeddedIcon g.icon use {
+    fill: #ce5b58;
+}
+.dark .ctrl-btns div.refresh.active svg.embeddedIcon g.icon use {
+    fill: #0074a6;
+}
+.dark .ctrl-btns div.refresh.active:hover svg.embeddedIcon g.icon use {
+    fill: #ce5b58;
+}
diff --git a/web/gui2/src/main/webapp/app/fw/widget/table.css b/web/gui2/src/main/webapp/app/fw/widget/table.css
new file mode 100644
index 0000000..3b761f6
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/fw/widget/table.css
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* ------ for summary-list tables (layout) ------ */
+
+div.summary-list {
+    margin: 0 20px 16px 10px;
+    font-size: 10pt;
+    border-spacing: 0;
+}
+
+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 div.table-body::-webkit-scrollbar {
+    display: none;
+}
+
+div.summary-list tr.no-data td {
+    text-align: center;
+    font-style: italic;
+}
+
+
+/* highlighting */
+div.summary-list tr {
+    transition: background-color 500ms;
+}
+
+div.summary-list td {
+    padding: 4px;
+    text-align: left;
+    word-wrap: break-word;
+    font-size: 10pt;
+}
+
+div.summary-list td.table-icon {
+    width: 42px;
+    padding-top: 4px;
+    padding-bottom: 0px;
+    padding-left: 4px;
+    text-align: center;
+}
+
+div.summary-list .table-header th {
+    font-weight: bold;
+    font-variant: small-caps;
+    text-transform: uppercase;
+    font-size: 10pt;
+    padding-top: 8px;
+    padding-bottom: 8px;
+    letter-spacing: 0.02em;
+    cursor: pointer;
+}
+
+/* rows are selectable */
+div.summary-list .table-body td {
+    cursor: pointer;
+}
+
+/* Tabular view controls */
+
+div.tabular-header .search {
+    margin: 0 0 10px 10px;
+}
+
+
+div.tabular-header div.ctrl-btns {
+    display: inline-block;
+    float: right;
+    height: 44px;
+    margin-top: 24px;
+    margin-right: 20px;
+    position: absolute;
+    right: 0px;
+}
+
+div.tabular-header div.ctrl-btns div {
+    display: inline-block;
+    cursor: pointer;
+}
+
+div.tabular-header div.ctrl-btns div.separator  {
+    width: 0;
+    height: 40px;
+    padding: 0;
+    border-right: 1px solid #c7c7c0;
+}
diff --git a/web/gui2/src/main/webapp/app/fw/widget/tablebase.ts b/web/gui2/src/main/webapp/app/fw/widget/tablebase.ts
new file mode 100644
index 0000000..807a014
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/fw/widget/tablebase.ts
@@ -0,0 +1,215 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { Injectable } from '@angular/core';
+import { FnService } from '../util/fn.service';
+import { LoadingService } from '../layer/loading.service';
+import { LogService } from '../../log.service';
+import { WebSocketService } from '../remote/websocket.service';
+
+const REFRESH_INTERVAL = 2000;
+
+/**
+ * Base model of table view - implemented by Table components
+ */
+export interface TableBase {
+    annots: TableAnnots;
+    autoRefresh: boolean;
+    autoRefreshTip: string;
+    changedData: any;
+    payloadParams: any;
+    selId: string;
+    sortParams: any;
+    tableData: any[];
+    toggleRefresh(): void;
+    selectCallback(event: any, selRow: any): void;
+    parentSelCb(event: any, selRow: any): void;
+    sortCallback(): void;
+    responseCallback(): void;
+}
+
+interface TableAnnots {
+    noRowsMsg: string;
+}
+
+/**
+ * A model of data returned in a TableResponse
+ *
+ * There is an interface extending from this one in the parent component
+ */
+export interface TableResponse {
+    annots: any;
+    // There will be other parts to the response depending on table type
+    // Expect one called tag+'s' e.g. devices or apps
+}
+
+/**
+ * ONOS GUI -- Widget -- Table Base class
+ */
+export class TableBaseImpl implements TableBase {
+    // attributes from the interface
+    public annots: TableAnnots;
+    autoRefresh: boolean = true;
+    autoRefreshTip: string = 'Toggle auto refresh'; // TODO: get LION string
+    changedData: string[] = [];
+    payloadParams: any;
+    selId: string = undefined;
+    sortParams: any;
+    tableData: any[] = [];
+    toggleRefresh; // Function
+    selectCallback; // Function
+    parentSelCb = null;
+    sortCallback; // Function
+    responseCallback; // Function
+
+    private root: string;
+    private req: string;
+    private resp: string;
+    private refreshPromise: any = null;
+    private handlers: string[] = [];
+
+    constructor(
+        protected fs: FnService,
+        protected ls: LoadingService,
+        protected log: LogService,
+        protected wss: WebSocketService,
+        protected tag: string,
+        protected idKey: string = 'id',
+        protected query: string = '',
+        protected selCb = () => ({}) // Function
+    ) {
+        this.root = tag + 's';
+        this.req = tag + 'DataRequest';
+        this.resp = tag + 'DataResponse';
+
+        this.sortCallback = this.requestTableData;
+        this.selectCallback = this.rowSelectionCb;
+        this.toggleRefresh = () => {
+            this.autoRefresh = !this.autoRefresh;
+            this.autoRefresh ? this.startRefresh() : this.stopRefresh();
+        };
+    }
+
+    init() {
+        this.wss.bindHandlers(new Map<string, (data) => void>([
+            [this.resp, (data) => this.tableDataResponseCb(data)]
+        ]));
+        this.handlers.push(this.resp);
+
+        this.annots = <TableAnnots>{
+            noRowsMsg: ''
+        };
+
+        // Now send the WebSocket request and make it repeat every 2 seconds
+        this.requestTableData();
+        this.startRefresh();
+
+        this.log.debug('TableBase initialized');
+    }
+
+    destroy() {
+        this.wss.unbindHandlers(this.handlers);
+        this.stopRefresh();
+        this.ls.stop();
+    }
+
+    /**
+     * A callback that executes when the table data that was requested
+     * on WebSocketService arrives.
+     *
+     * Happens every 2 seconds
+     */
+    tableDataResponseCb(data: TableResponse) {
+        this.ls.stop();
+
+        const newTableData: any[] = Array.from(data[this.root]);
+        this.annots.noRowsMsg = data.annots.no_rows_msg;
+
+        // If the onResp() function is set then call it
+        if (this.responseCallback) {
+            this.responseCallback(data);
+        }
+        this.changedData = [];
+
+        // checks if data changed for row flashing
+        if (JSON.stringify(newTableData) !== JSON.stringify(this.tableData)) {
+            this.log.debug('table data has changed');
+            const oldTableData: any[] = this.tableData;
+            this.tableData = [ ...newTableData ]; // ES6 spread syntax
+            // only flash the row if the data already exists
+            if (oldTableData.length > 0) {
+                for (const idx in newTableData) {
+                    if (!this.fs.containsObj(oldTableData, newTableData[idx])) {
+                        this.changedData.push(newTableData[idx][this.idKey]);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Table Data Request
+     */
+    requestTableData() {
+        const p = Object.assign({}, this.sortParams, this.payloadParams, this.query);
+
+        // Allow it to sit in pending events
+        if (this.wss.isConnected()) {
+            if (this.fs.debugOn('table')) {
+                this.log.debug('Table data REQUEST:', this.req, p);
+            }
+            this.wss.sendEvent(this.req, p);
+            this.ls.start();
+        }
+    }
+
+    /**
+     * Row Selected
+     */
+    rowSelectionCb(event: any, selRow: any) {
+        const selId: string = selRow[this.idKey];
+        this.selId = (this.selId === selId) ? undefined : selId;
+        if (this.parentSelCb) {
+            this.log.debug('Parent called on Row', selId, 'selected');
+            this.parentSelCb(event, selRow);
+        }
+    }
+
+    /**
+     * autoRefresh functions
+     */
+    startRefresh() {
+        this.refreshPromise =
+            setInterval(() => {
+                if (!this.ls.waiting()) {
+                    if (this.fs.debugOn('table')) {
+                        this.log.debug('Refreshing ' + this.root + ' page');
+                    }
+                    this.requestTableData();
+                }
+            }, REFRESH_INTERVAL);
+    }
+
+    stopRefresh() {
+        if (this.refreshPromise) {
+            clearInterval(this.refreshPromise);
+            this.refreshPromise = null;
+        }
+    }
+
+    isChanged(id: string): boolean {
+        return (this.fs.inArray(id, this.changedData) === -1) ? false : true;
+    }
+}
diff --git a/web/gui2/src/main/webapp/app/fw/widget/tablebuilder.service.ts b/web/gui2/src/main/webapp/app/fw/widget/tablebuilder.service.ts
deleted file mode 100644
index 6c804d1..0000000
--- a/web/gui2/src/main/webapp/app/fw/widget/tablebuilder.service.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright 2015-present Open Networking Foundation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import { Injectable } from '@angular/core';
-import { FnService } from '../util/fn.service';
-import { LoadingService } from '../layer/loading.service';
-import { LogService } from '../../log.service';
-import { WebSocketService } from '../remote/websocket.service';
-
-/**
- * ONOS GUI -- Widget -- Table Builder Service
- */
-@Injectable()
-export class TableBuilderService {
-
-  constructor(
-    private fs: FnService,
-    private ls: LoadingService,
-    private log: LogService,
-    private wss: WebSocketService
-  ) {
-    this.log.debug('TableBuilderService constructed');
-  }
-
-}
diff --git a/web/gui2/src/main/webapp/app/fw/widget/tooltip.directive.ts b/web/gui2/src/main/webapp/app/fw/widget/tooltip.directive.ts
deleted file mode 100644
index 92f8ae3..0000000
--- a/web/gui2/src/main/webapp/app/fw/widget/tooltip.directive.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright 2015-present Open Networking Foundation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import { Directive } from '@angular/core';
-import { FnService } from '../util/fn.service';
-import { LogService } from '../../log.service';
-
-/**
- * ONOS GUI -- Widget -- Tooltip Directive
- */
-@Directive({
-  selector: '[onosTooltip]'
-})
-export class TooltipDirective {
-
-    constructor(
-        private fs: FnService,
-        private log: LogService
-    ) {
-        this.log.debug('TooltipDirective constructed');
-    }
-
-}
diff --git a/web/gui2/src/main/webapp/app/fw/widget/widget.module.ts b/web/gui2/src/main/webapp/app/fw/widget/widget.module.ts
index 2e7a0ea..02393a9 100644
--- a/web/gui2/src/main/webapp/app/fw/widget/widget.module.ts
+++ b/web/gui2/src/main/webapp/app/fw/widget/widget.module.ts
@@ -23,11 +23,8 @@
 import { ButtonService } from './button.service';
 import { ChartBuilderService } from './chartbuilder.service';
 import { ListService } from './list.service';
-import { TableBuilderService } from './tablebuilder.service';
 import { TableDetailService } from './tabledetail.service';
 import { ToolbarService } from './toolbar.service';
-import { TooltipService } from './tooltip.service';
-import { TooltipDirective } from './tooltip.directive';
 import { SortableHeaderDirective } from './sortableheader.directive';
 import { TableResizeDirective } from './tableresize.directive';
 import { FlashChangesDirective } from './flashchanges.directive';
@@ -42,7 +39,6 @@
     // It's enough to import them in the OnosModule
   ],
   declarations: [
-    TooltipDirective,
     SortableHeaderDirective,
     TableResizeDirective,
     FlashChangesDirective
@@ -51,9 +47,7 @@
     ButtonService,
     ChartBuilderService,
     ListService,
-    TableBuilderService,
     TableDetailService,
-    TooltipService,
     ToolbarService
   ]
 })