Implemented table building functions

Change-Id: Ie4003080b13725561df22de41ec85f8c3f31c794
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/tooltip.service.ts b/web/gui2/src/main/webapp/app/fw/widget/tooltip.service.ts
deleted file mode 100644
index 6b08779..0000000
--- a/web/gui2/src/main/webapp/app/fw/widget/tooltip.service.ts
+++ /dev/null
@@ -1,33 +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 { LogService } from '../../log.service';
-
-/**
- * ONOS GUI -- Widget -- Tooltip Service
- */
-@Injectable()
-export class TooltipService {
-
-  constructor(
-      private fs: FnService,
-      private log: LogService,
-  ) {
-    this.log.debug('TooltipService 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
   ]
 })