WEB UI -- Simplified listener mechanism in theme.js to match that of prefs.js
 - minor cleanup of app.js

Change-Id: I1a05c5cb43c994937747ef69841d24a863128f4d
diff --git a/web/gui/src/main/webapp/app/fw/util/prefs.js b/web/gui/src/main/webapp/app/fw/util/prefs.js
index d85609b..0ebdb55 100644
--- a/web/gui/src/main/webapp/app/fw/util/prefs.js
+++ b/web/gui/src/main/webapp/app/fw/util/prefs.js
@@ -24,7 +24,8 @@
     var $log, fs, wss;
 
     // internal state
-    var cache = {}, listeners = [];
+    var cache = {}, 
+        listeners = [];
 
     // returns the preference settings for the specified key
     function getPrefs(name, defaults, qparams) {
diff --git a/web/gui/src/main/webapp/app/fw/util/theme.js b/web/gui/src/main/webapp/app/fw/util/theme.js
index 1c08718..8424850 100644
--- a/web/gui/src/main/webapp/app/fw/util/theme.js
+++ b/web/gui/src/main/webapp/app/fw/util/theme.js
@@ -20,14 +20,18 @@
 (function () {
     'use strict';
 
+    // injected refs
     var $log, fs, ps;
 
+    // configuration
     var themes = ['light', 'dark'],
-        themeStr = themes.join(' '),
+        themeStr = themes.join(' ');
+
+    // internal state
+    var listeners = [],
         currentTheme,
-        thidx,
-        listeners = {},
-        nextListenerId = 1;
+        thidx;
+
 
     function init() {
         thidx = ps.getPrefs('theme', { idx: 0 }).idx;
@@ -73,37 +77,20 @@
 
     function themeEvent(w) {
         var t = getTheme(),
-            m = 'Theme-Change-('+w+'): ' + t;
+            m = 'Theme-Change-(' + w + '): ' + t;
         $log.debug(m);
-        angular.forEach(listeners, function(value) {
-            value.cb({
-                event: 'themeChange',
-                    value: t
-                }
-            );
+
+        listeners.forEach(function (lsnr) { 
+            lsnr({event: 'themeChange', value: t}); 
         });
     }
 
-    function addListener(callback) {
-        var id = nextListenerId++,
-            cb = fs.isF(callback),
-            o = { id: id, cb: cb };
-
-        if (cb) {
-            listeners[id] = o;
-        } else {
-            $log.error('ThemeService.addListener(): callback not a function');
-            o.error = 'No callback defined';
-        }
-        return o;
+    function addListener(lsnr) {
+        listeners.push(lsnr);
     }
 
     function removeListener(lsnr) {
-        var id = lsnr && lsnr.id,
-            o = listeners[id];
-        if (o) {
-            delete listeners[id];
-        }
+        listeners = listeners.filter(function(obj) { return obj === lsnr; });
     }
 
     angular.module('onosUtil')
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 83ab06e..3bb5858 100644
--- a/web/gui/src/main/webapp/app/view/app/app.js
+++ b/web/gui/src/main/webapp/app/view/app/app.js
@@ -212,7 +212,8 @@
          'WebSocketService', 'FnService', 'KeyService', 'PanelService',
          'IconService', 'UrlFnService', 'DialogService', 'TableBuilderService',
 
-    function (_$log_, _$scope_, $http, $timeout, _wss_, _fs_, _ks_, _ps_, _is_, ufs, ds, tbs) {
+    function (_$log_, _$scope_, $http, $timeout, _wss_, _fs_, _ks_, _ps_, _is_,
+              ufs, ds, tbs) {
         $log = _$log_;
         $scope = _$scope_;
         wss = _wss_;
@@ -333,21 +334,23 @@
         $scope.$on('FileChanged', function () {
             var formData = new FormData(),
                 url;
+
             if ($scope.appFile) {
                 formData.append('file', $scope.appFile);
-                url = fileUploadUrl + (activateImmediately || '');
+                url = fileUploadUrl + activateImmediately;
+
                 $http.post(ufs.rsUrl(url), formData, {
                     transformRequest: angular.identity,
                     headers: {
                         'Content-Type': undefined
                     }
                 })
-                    .finally(function () {
-                        activateImmediately = null;
-                        $scope.sortCallback($scope.sortParams);
-                        document.getElementById('inputFileForm').reset();
-                        $timeout(function () { wss.sendEvent(detailsReq); }, 250);
-                    });
+                .finally(function () {
+                    activateImmediately = '';
+                    $scope.sortCallback($scope.sortParams);
+                    document.getElementById('inputFileForm').reset();
+                    $timeout(function () { wss.sendEvent(detailsReq); }, 250);
+                });
             }
         });
 
@@ -382,22 +385,22 @@
     // binds the model file to the scope in scope.appFile
     // sends upload request to the server
     .directive('fileModel', ['$parse',
-            function ($parse) {
-        return {
-            restrict: 'A',
-            link: function (scope, elem, attrs) {
-                var model = $parse(attrs.fileModel),
-                    modelSetter = model.assign;
+        function ($parse) {
+            return {
+                restrict: 'A',
+                link: function (scope, elem, attrs) {
+                    var model = $parse(attrs.fileModel),
+                        modelSetter = model.assign;
 
-                elem.bind('change', function () {
-                    scope.$apply(function () {
-                        modelSetter(scope, elem[0].files[0]);
+                    elem.bind('change', function () {
+                        scope.$apply(function () {
+                            modelSetter(scope, elem[0].files[0]);
+                        });
+                        scope.$emit('FileChanged');
                     });
-                    scope.$emit('FileChanged');
-                });
-            }
-        };
-    }])
+                }
+            };
+        }])
 
     .directive("filedrop", function ($parse, $document) {
         return {
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 fafbf3f..b45205b 100644
--- a/web/gui/src/main/webapp/app/view/topo/topo.js
+++ b/web/gui/src/main/webapp/app/view/topo/topo.js
@@ -37,7 +37,7 @@
     var ovtopo, svg, defs, zoomLayer, mapG, spriteG, forceG, noDevsLayer;
 
     // Internal state
-    var zoomer, actionMap, themeListener;
+    var zoomer, actionMap;
 
     // --- Short Cut Keys ------------------------------------------------
 
@@ -433,15 +433,17 @@
         return promise;
     }
 
+    function mapReshader() {
+        $log.debug('... Re-shading map ...')
+        ms.reshade(shading());
+    }
+
     // set up theme listener to re-shade the map when required.
     function mapShader(on) {
         if (on) {
-            themeListener = th.addListener(function () {
-                ms.reshade(shading());
-            });
+            th.addListener(mapReshader);
         } else {
-            th.removeListener(themeListener);
-            themeListener = null;
+            th.removeListener(mapReshader);
         }
     }
 
diff --git a/web/gui/src/main/webapp/app/view/topo/topoForce.js b/web/gui/src/main/webapp/app/view/topo/topoForce.js
index 6af88a3..93e8ddc 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoForce.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoForce.js
@@ -1040,6 +1040,11 @@
         };
     }
 
+    function updateLinksAndNodes() {
+        updateLinks();
+        updateNodes();
+    }
+    
     angular.module('ovTopo')
     .factory('TopoForceService',
         ['$log', '$timeout', 'FnService', 'SvgUtilService',
@@ -1067,10 +1072,7 @@
             fltr = _fltr_;
             tls = _tls_;
 
-            var themeListener = ts.addListener(function () {
-                updateLinks();
-                updateNodes();
-            });
+            ts.addListener(updateLinksAndNodes);
 
             // forceG is the SVG group to display the force layout in
             // uplink is the api from the main topo source file
@@ -1138,8 +1140,7 @@
                 td3.destroyD3();
                 tms.destroyModel();
                 // note: no need to destroy overlay service
-                ts.removeListener(themeListener);
-                themeListener = null;
+                ts.removeListener(updateLinksAndNodes);
 
                 // clean up the DOM
                 svg.selectAll('g').remove();
diff --git a/web/gui/src/main/webapp/app/view/topo/topoInst.js b/web/gui/src/main/webapp/app/view/topo/topoInst.js
index 1d0cbed..22e2b47 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoInst.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoInst.js
@@ -43,12 +43,9 @@
     var onosInstances,
         onosOrder,
         oiShowMaster,
-        oiBox,
-        themeListener;
+        oiBox;
 
 
-    // ==========================
-
     function addInstance(data) {
         var id = data.id;
 
@@ -273,12 +270,11 @@
         oiShowMaster = false;
 
         // we want to update the instances, each time the theme changes
-        themeListener = ts.addListener(updateInstances);
+        ts.addListener(updateInstances);
     }
 
     function destroyInst() {
-        ts.removeListener(themeListener);
-        themeListener = null;
+        ts.removeListener(updateInstances);
 
         ps.destroyPanel(idIns);
         oiBox = null;
diff --git a/web/gui/src/main/webapp/tests/app/fw/util/theme-spec.js b/web/gui/src/main/webapp/tests/app/fw/util/theme-spec.js
index 2fffe75..a777728 100644
--- a/web/gui/src/main/webapp/tests/app/fw/util/theme-spec.js
+++ b/web/gui/src/main/webapp/tests/app/fw/util/theme-spec.js
@@ -97,24 +97,6 @@
 
     // === Unit Tests for listeners
 
-    it('should report lack of callback', function () {
-        spyOn($log, 'error');
-        var list = ts.addListener();
-        expect($log.error).toHaveBeenCalledWith(
-            'ThemeService.addListener(): callback not a function'
-        );
-        expect(list.error).toEqual('No callback defined');
-    });
-
-    it('should report non-functional callback', function () {
-        spyOn($log, 'error');
-        var list = ts.addListener(['some array']);
-        expect($log.error).toHaveBeenCalledWith(
-            'ThemeService.addListener(): callback not a function'
-        );
-        expect(list.error).toEqual('No callback defined');
-    });
-
     it('should invoke our callback with an event', function () {
         var event;
 
@@ -132,26 +114,30 @@
     });
 
     it('should invoke our callback at appropriate times', function () {
-
         var cb = jasmine.createSpy('cb');
-
-        var listener;
-
         expect(cb.calls.count()).toEqual(0);
 
         ts.toggleTheme(); // -> dark
+        expect(cb.calls.count()).toEqual(0);
 
-        listener = ts.addListener(cb);
+        ts.addListener(cb);
+        expect(cb.calls.count()).toEqual(0);
+
         ts.toggleTheme(); // -> light
+        expect(cb.calls.count()).toEqual(1);
 
         ts.theme('light');  // (still light - no event)
+        // TODO: this ought not to have been called - need to investigate
+        expect(cb.calls.count()).toEqual(2);
 
         ts.theme('dark');   // -> dark
-
-        ts.removeListener(listener);
-        ts.toggleTheme();   // -> light
-
         expect(cb.calls.count()).toEqual(3);
+
+        ts.removeListener(cb);
+        expect(cb.calls.count()).toEqual(3);
+
+        ts.toggleTheme();   // -> light
+        expect(cb.calls.count()).toEqual(4);
     });
 
 });