GUI -- User Prefs written as a service; persistent topo settings updated a bit; still WIP.

Change-Id: I6945dd9eb4b325a8f1637c44e2c4b271126b2bc4
diff --git a/web/gui/src/main/webapp/app/fw/util/prefs.js b/web/gui/src/main/webapp/app/fw/util/prefs.js
new file mode 100644
index 0000000..f940ca7
--- /dev/null
+++ b/web/gui/src/main/webapp/app/fw/util/prefs.js
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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 -- Util -- User Preference Service
+ */
+(function () {
+    'use strict';
+
+    // injected refs
+    var $log, $cookies;
+
+    // internal state
+    var cache = {};
+
+    // NOTE: in Angular 1.3.5, $cookies is just a simple object, and
+    //       cookie values are just strings. From the 1.3.5 docs:
+    //
+    //       "Only a simple Object is exposed and by adding or removing
+    //        properties to/from this object, new cookies are created/deleted
+    //        at the end of current $eval. The object's properties can only
+    //        be strings."
+    //
+    //       We may want to upgrade the version of Angular sometime soon
+    //        since later version support objects as cookie values.
+
+    // NOTE: prefs represented as simple name/value(number) pairs
+    //       => a temporary restriction while we are encoding into cookies
+    /*
+        {
+          foo: 1,
+          bar: 0,
+          goo: 2
+        }
+
+        stored as "foo:1,bar:0,goo:2"
+     */
+
+    // reads cookie with given name and returns an object rep of its value
+    // or null if no such cookie is set
+    function getPrefs(name) {
+        var cook = $cookies[name],
+            bits,
+            obj = {};
+
+        if (cook) {
+            bits = cook.split(',');
+            bits.forEach(function (value) {
+                var x = value.split(':');
+                obj[x[0]] = Number(x[1]);
+            });
+
+            // update the cache
+            cache[name] = obj;
+            return obj;
+        }
+        // perhaps we have a cached copy..
+        return cache[name];
+    }
+
+    function setPrefs(name, obj) {
+        var bits = [],
+            str;
+
+        angular.forEach(obj, function (value, key) {
+            bits.push(key + ':' + value);
+        });
+        str = bits.join(',');
+
+        // keep a cached copy of the object
+        cache[name] = obj;
+
+        // The angular way of doing this...
+        // $cookies[name] = str;
+        //  ...but it appears that this gets delayed, and doesn't 'stick' ??
+
+        // FORCE cookie to be set by writing directly to document.cookie...
+        document.cookie = name + '=' + encodeURIComponent(str);
+        $log.debug('<<>> Wrote cookie <'+name+'>:', str);
+    }
+
+    angular.module('onosUtil')
+    .factory('PrefsService', ['$log', '$cookies',
+        function (_$log_, _$cookies_) {
+            $log = _$log_;
+            $cookies = _$cookies_;
+
+            return {
+                getPrefs: getPrefs,
+                setPrefs: setPrefs
+            };
+        }]);
+
+}());
diff --git a/web/gui/src/main/webapp/app/view/topo/topo.js b/web/gui/src/main/webapp/app/view/topo/topo.js
index 5843b56..402ad86 100644
--- a/web/gui/src/main/webapp/app/view/topo/topo.js
+++ b/web/gui/src/main/webapp/app/view/topo/topo.js
@@ -29,7 +29,7 @@
     ];
 
     // references to injected services etc.
-    var $log, $cookies, fs, ks, zs, gs, ms, sus, flash, wss,
+    var $log, $cookies, fs, ks, zs, gs, ms, sus, flash, wss, ps,
         tes, tfs, tps, tis, tss, tls, tts, tos, fltr, ttbs;
 
     // DOM elements
@@ -100,7 +100,7 @@
     function toggleInstances(x) {
         if (x === 'keyev') {
             tis.toggle();
-            updateCookieState('insts', tis.isVisible());
+            updatePrefsState('insts', tis.isVisible());
         } else if (x) {
             tis.show();
         } else {
@@ -112,7 +112,7 @@
     function toggleMap(x) {
         var on = (x === 'keyev') ? !sus.visible(mapG) : !!x;
         sus.visible(mapG, on);
-        updateCookieState('bg', on);
+        updatePrefsState('bg', on);
     }
 
     //  TODO: need wrapper functions for state changes needed in cookies
@@ -248,22 +248,9 @@
             .attr('opacity', b ? 1 : 0);
     }
 
-    // --- Config from Cookies -------------------------------------------
+    // --- User Preferemces ----------------------------------------------
 
-    // TODO: write a general purpose cookie service, rather than custom here
-
-    // NOTE: in Angular 1.3.5, $cookies is just a simple object, and
-    //       cookie values are just strings. From the 1.3.5 docs:
-    //
-    //       "Only a simple Object is exposed and by adding or removing
-    //        properties to/from this object, new cookies are created/deleted
-    //        at the end of current $eval. The object's properties can only
-    //        be strings."
-    //
-    //       We may want to upgrade the version of Angular sometime soon
-    //        since later version support objects as cookie values.
-
-    var defaultCookieState = {
+    var defaultPrefsState = {
         bg: 1,
         insts: 1,
         summary: 1,
@@ -271,54 +258,27 @@
         hosts: 0
     };
 
-    var cookieState = {};
+    var prefsState = {};
 
-    function writeCookieState() {
-        var bits = [],
-            str;
-        angular.forEach(cookieState, function (value, key) {
-            bits.push(key + ':' + value);
-        });
-        str = bits.join(',');
-
-        // The angular way of doing this...
-        // $cookies.topo_state = str;
-        //  ...but it appears that this gets delayed, and doesn't 'stick' ??
-
-        // FORCE cookie to be set by writing directly to document.cookie...
-        document.cookie = 'topo_state=' + encodeURIComponent(str);
-        $log.debug('<<>> Wrote cookie:', str);
+    function topoDefPrefs() {
+        return angular.extend({}, defaultPrefsState);
     }
 
-    function readCookieState() {
-        var cook = $cookies.topo_state || '',
-            bits;
-
-        if (!cook) {
-            cookieState = angular.extend({}, defaultCookieState);
-            writeCookieState(); // seed the pot
-
-        } else {
-            bits = cook.split(',');
-            bits.forEach(function (value) {
-                var x = value.split(':');
-                cookieState[x[0]] = Number(x[1]);
-            });
-        }
+    function updatePrefsState(what, b) {
+        prefsState[what] = b ? 1 : 0;
+        ps.setPrefs('topo_prefs', prefsState);
     }
 
-    function updateCookieState(what, b) {
-        cookieState[what] = b ? 1 : 0;
-        writeCookieState();
-    }
 
-    function restoreConfigFromCookies() {
-        readCookieState();
-        $log.debug('Cookie State:', cookieState);
+    function restoreConfigFromPrefs() {
+        // NOTE: toolbar will have set this for us..
+        prefsState = ps.getPrefs('topo_prefs');
 
-        toggleInstances(cookieState.insts);
-        tps.toggleSummary(cookieState.summary);
-        tps.toggleDetails(cookieState.detail);
+        $log.debug('TOPO---- Prefs State:', prefsState);
+
+        toggleInstances(prefsState.insts);
+        tps.toggleSummary(prefsState.summary);
+        tps.toggleDetails(prefsState.detail);
     }
 
 
@@ -328,15 +288,15 @@
         .controller('OvTopoCtrl', ['$scope', '$log', '$location', '$timeout',
             '$cookies', 'FnService', 'MastService', 'KeyService', 'ZoomService',
             'GlyphService', 'MapService', 'SvgUtilService', 'FlashService',
-            'WebSocketService',
+            'WebSocketService', 'PrefsService',
             'TopoEventService', 'TopoForceService', 'TopoPanelService',
             'TopoInstService', 'TopoSelectService', 'TopoLinkService',
             'TopoTrafficService', 'TopoObliqueService', 'TopoFilterService',
             'TopoToolbarService',
 
         function ($scope, _$log_, $loc, $timeout, _$cookies_, _fs_, mast, _ks_,
-                  _zs_, _gs_, _ms_, _sus_, _flash_, _wss_, _tes_, _tfs_, _tps_,
-                  _tis_, _tss_, _tls_, _tts_, _tos_, _fltr_, _ttbs_) {
+                  _zs_, _gs_, _ms_, _sus_, _flash_, _wss_, _ps_, _tes_, _tfs_,
+                  _tps_, _tis_, _tss_, _tls_, _tts_, _tos_, _fltr_, _ttbs_) {
             var self = this,
                 projection,
                 dim,
@@ -359,6 +319,7 @@
             sus = _sus_;
             flash = _flash_;
             wss = _wss_;
+            ps = _ps_;
             tes = _tes_;
             tfs = _tfs_;
             // TODO: consider funnelling actions through TopoForceService...
@@ -403,7 +364,7 @@
                 function (proj) {
                     projection = proj;
                     $log.debug('** We installed the projection: ', proj);
-                    toggleMap(cookieState.bg);
+                    toggleMap(prefsState.bg);
                 }
             );
 
@@ -414,7 +375,7 @@
             tes.start();
 
             // temporary solution for persisting user settings
-            restoreConfigFromCookies();
+            restoreConfigFromPrefs();
 
             $log.log('OvTopoCtrl has been created');
         }]);
diff --git a/web/gui/src/main/webapp/app/view/topo/topoToolbar.js b/web/gui/src/main/webapp/app/view/topo/topoToolbar.js
index eb7c006..8a1895b 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoToolbar.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoToolbar.js
@@ -23,13 +23,14 @@
     'use strict';
 
     // injected references
-    var $log, tbs, api;
+    var $log, tbs, ps, api;
 
     // internal state
     var toolbar, keyData;
 
     // constants
-    var name = 'topo-tbar';
+    var name = 'topo-tbar',
+        cooktag = 'topo_prefs';
 
     // key to button mapping data
     var k2b = {
@@ -58,8 +59,46 @@
         E: { id: 'eqMaster-btn', gid: 'eqMaster' }
     };
 
+    // initial toggle state: default settings and tag to key mapping
+    var defaultPrefsState = {
+            bg: 1,
+            insts: 1,
+            summary: 1,
+            detail: 1,
+            hosts: 0
+        },
+        prefsMap = {
+            bg: 'B',
+            insts: 'I',
+            summary: 'O',
+            details: 'D',
+            hosts: 'H'
+        };
+
     function init(_api_) {
         api = _api_;
+
+        // retrieve initial toggle button settings from user prefs
+        setInitToggleState();
+    }
+
+    function topoDefPrefs() {
+        return angular.extend({}, defaultPrefsState);
+    }
+
+    function setInitToggleState() {
+        var state = ps.getPrefs(cooktag);
+        $log.debug('TOOLBAR---- read prefs state:', state);
+
+        if (!state) {
+            state = topoDefPrefs();
+            ps.setPrefs(cooktag, state);
+            $log.debug('TOOLBAR---- Set default prefs state:', state);
+        }
+
+        angular.forEach(prefsMap, function (v, k) {
+            k2b[v].isel = !!state[k];
+        });
     }
 
     function initKeyData() {
@@ -142,11 +181,13 @@
     }
 
     angular.module('ovTopo')
-        .factory('TopoToolbarService', ['$log', 'ToolbarService',
+        .factory('TopoToolbarService',
+        ['$log', 'ToolbarService', 'PrefsService',
 
-        function (_$log_, _tbs_) {
+        function (_$log_, _tbs_, _ps_) {
             $log = _$log_;
             tbs = _tbs_;
+            ps = _ps_;
 
             return {
                 init: init,
diff --git a/web/gui/src/main/webapp/index.html b/web/gui/src/main/webapp/index.html
index 06e0336..3a335a7 100644
--- a/web/gui/src/main/webapp/index.html
+++ b/web/gui/src/main/webapp/index.html
@@ -45,6 +45,7 @@
     <script src="app/fw/util/random.js"></script>
     <script src="app/fw/util/theme.js"></script>
     <script src="app/fw/util/keys.js"></script>
+    <script src="app/fw/util/prefs.js"></script>
 
     <script src="app/fw/mast/mast.js"></script>
     <script src="app/fw/nav/nav.js"></script>
diff --git a/web/gui/src/main/webapp/tests/app/fw/util/prefs-spec.js b/web/gui/src/main/webapp/tests/app/fw/util/prefs-spec.js
new file mode 100644
index 0000000..2b8a15c
--- /dev/null
+++ b/web/gui/src/main/webapp/tests/app/fw/util/prefs-spec.js
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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 -- Util -- User Preference Service - Unit Tests
+ */
+describe('factory: fw/util/prefs.js', function() {
+    var $cookies, ps, fs;
+
+    beforeEach(module('onosUtil'));
+
+    var mockCookies = {
+        foo: 'bar'
+    };
+
+    beforeEach(function () {
+        module(function ($provide) {
+            $provide.value('$cookies', mockCookies);
+        });
+    });
+
+    beforeEach(inject(function (PrefsService, FnService, _$cookies_) {
+        ps = PrefsService;
+        fs = FnService;
+        $cookies = _$cookies_;
+    }));
+
+    it('should define PrefsService', function () {
+        expect(ps).toBeDefined();
+    });
+
+    it('should define api functions', function () {
+        expect(fs.areFunctions(ps, [
+            'getPrefs', 'setPrefs'
+        ])).toBe(true);
+    });
+
+    // === Tests for getPrefs()
+    // TODO unit tests for getPrefs()
+
+    // === Tests for setPrefs()
+    // TODO unit tests for setPrefs()
+
+});
diff --git a/web/gui/src/main/webapp/tests/karma.conf.js b/web/gui/src/main/webapp/tests/karma.conf.js
index afd40ec..bd572c1 100644
--- a/web/gui/src/main/webapp/tests/karma.conf.js
+++ b/web/gui/src/main/webapp/tests/karma.conf.js
@@ -19,6 +19,7 @@
         '../tp/angular.js',
         '../tp/angular-mocks.js',
         '../tp/angular-route.js',
+        '../tp/angular-cookies.js',
         '../tp/d3.js',
         '../tp/topojson.v1.min.js',