GUI2 implementation of device/flow/port/group/meter/host/link/tunnel view

Review comments incorporated.

Change-Id: I45dd6570961cc3e0f4ffddb7acbf02cd7d860de5
diff --git a/web/gui2/src/main/webapp/app/fw/util/prefs.service.ts b/web/gui2/src/main/webapp/app/fw/util/prefs.service.ts
index f696125..991df7f 100644
--- a/web/gui2/src/main/webapp/app/fw/util/prefs.service.ts
+++ b/web/gui2/src/main/webapp/app/fw/util/prefs.service.ts
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 import { Injectable } from '@angular/core';
-import { FnService } from '../util/fn.service';
+import { FnService } from './fn.service';
 import { LogService } from '../../log.service';
 import { WebSocketService } from '../remote/websocket.service';
 
@@ -22,16 +22,96 @@
  * ONOS GUI -- Util -- User Preference Service
  */
 @Injectable({
-  providedIn: 'root',
+    providedIn: 'root',
 })
 export class PrefsService {
-
+    protected Prefs;
+    protected handlers: string[] = [];
+    cache: any;
+    listeners: any;
     constructor(
-        private fs: FnService,
-        private log: LogService,
-        private wss: WebSocketService
+        protected fs: FnService,
+        protected log: LogService,
+        protected wss: WebSocketService
     ) {
+        this.cache = {};
+        this.wss.bindHandlers(new Map<string, (data) => void>([
+            [this.Prefs, (data) => this.updatePrefs(data)]
+        ]));
+        this.handlers.push(this.Prefs);
+
         this.log.debug('PrefsService constructed');
     }
 
+    setPrefs(name: string, obj: any) {
+        // keep a cached copy of the object and send an update to server
+        this.cache[name] = obj;
+        this.wss.sendEvent('updatePrefReq', { key: name, value: obj });
+    }
+    updatePrefs(data: any) {
+        this.cache = data;
+        this.listeners.forEach(function (lsnr) { lsnr(); });
+    }
+
+    asNumbers(obj: any, keys?: any, not?: any) {
+        if (!obj) {
+            return null;
+        }
+
+        const skip = {};
+        if (not) {
+            keys.forEach(k => {
+                skip[k] = 1;
+            }
+            );
+        }
+
+        if (!keys || not) {
+            // do them all
+            Array.from(obj).forEach((v, k) => {
+                if (!not || !skip[k]) {
+                    obj[k] = Number(obj[k]);
+                }
+            });
+        } else {
+            // do the explicitly named keys
+            keys.forEach(k => {
+                obj[k] = Number(obj[k]);
+            });
+        }
+        return obj;
+    }
+
+    getPrefs(name: string, defaults: any, qparams?: string) {
+        const obj = Object.assign({}, defaults || {}, this.cache[name] || {});
+
+        // if query params are specified, they override...
+        if (this.fs.isO(qparams)) {
+            obj.forEach(k => {
+                if (qparams.hasOwnProperty(k)) {
+                    obj[k] = qparams[k];
+                }
+            });
+        }
+        return obj;
+    }
+
+    // merge preferences:
+    // The assumption here is that obj is a sparse object, and that the
+    //  defined keys should overwrite the corresponding values, but any
+    //  existing keys that are NOT explicitly defined here should be left
+    //  alone (not deleted).
+    mergePrefs(name: string, obj: any) {
+        const merged = this.cache[name] || {};
+        this.setPrefs(name, Object.assign(merged, obj));
+    }
+
+    addListener(listener: any) {
+        this.listeners.push(listener);
+    }
+
+    removeListener(listener: any) {
+        this.listeners = this.listeners.filter(function (obj) { return obj === listener; });
+    }
+
 }