GUI -- Topology View all, packet, and optical layer radio buttons are now part of the topology view toolbar, instead of being in the masthead. Keystroke 'N' for cycle node layers added. Button glyph added. Fixed previously unseen broken unit tests for table.js.

Change-Id: I6e53bdc6cacbf41b990abd07d30fc99ef4c3b8c0
diff --git a/web/gui/src/main/webapp/app/view/topo/topo.css b/web/gui/src/main/webapp/app/view/topo/topo.css
index c45bbed..b629559 100644
--- a/web/gui/src/main/webapp/app/view/topo/topo.css
+++ b/web/gui/src/main/webapp/app/view/topo/topo.css
@@ -542,41 +542,3 @@
     fill: #eee;
 }
 
-
-
-
-/* radio buttons injected into masthead */
-
-#topo-radio-group span.radio {
-    font-size: 10pt;
-    margin: 4px 2px;
-    padding: 1px 6px;
-    -moz-border-radius: 3px;
-    border-radius: 3px;
-    cursor: pointer;
-}
-
-.light #topo-radio-group span.radio {
-    border: 1px dotted #222;
-    color: #eee;
-}
-.dark #topo-radio-group span.radio {
-    border: 1px dotted #bbb;
-    color: #888;
-}
-
-#topo-radio-group span.radio.active {
-    padding: 1px 6px;
-}
-
-.light #topo-radio-group span.radio.active {
-    background-color: #bbb;
-    border: 1px solid #eee;
-    color: #666;
-
-}
-.dark #topo-radio-group span.radio.active {
-    background-color: #222;
-    border: 1px solid #eee;
-    color: #78a;
-}
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 0579a98..a6624ca 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, fs, ks, zs, gs, ms, sus, flash, wss,
-        tes, tfs, tps, tis, tss, tls, tts, tos, ttbs;
+        tes, tfs, tps, tis, tss, tls, tts, tos, fltr, ttbs;
 
     // DOM elements
     var ovtopo, svg, defs, zoomLayer, mapG, forceG, noDevsLayer;
@@ -55,6 +55,7 @@
 
             //X: [toggleNodeLock, 'Lock / unlock node positions'],
             Z: [tos.toggleOblique, 'Toggle oblique view (Experimental)'],
+            N: [fltr.clickAction, 'Cycle node layers'],
             L: [tfs.cycleDeviceLabels, 'Cycle device labels'],
             U: [tfs.unpin, 'Unpin node (hover mouse over)'],
             R: [resetZoom, 'Reset pan / zoom'],
@@ -75,7 +76,7 @@
 
             _helpFormat: [
                 ['I', 'O', 'D', '-', 'H', 'M', 'P', 'dash', 'B' ],
-                ['X', 'Z', 'L', 'U', 'R', '-', 'dot'],
+                ['X', 'Z', 'N', 'L', 'U', 'R', '-', 'dot'],
                 ['V', 'rightArrow', 'leftArrow', 'W', 'A', 'F', '-', 'E' ]
             ]
         };
@@ -244,11 +245,12 @@
             'WebSocketService',
             'TopoEventService', 'TopoForceService', 'TopoPanelService',
             'TopoInstService', 'TopoSelectService', 'TopoLinkService',
-            'TopoTrafficService', 'TopoObliqueService', 'TopoToolbarService',
+            'TopoTrafficService', 'TopoObliqueService', 'TopoFilterService',
+            'TopoToolbarService',
 
         function ($scope, _$log_, $loc, $timeout, _fs_, mast, _ks_, _zs_,
                   _gs_, _ms_, _sus_, _flash_, _wss_, _tes_, _tfs_, _tps_,
-                  _tis_, _tss_, _tls_, _tts_, _tos_, _ttbs_) {
+                  _tis_, _tss_, _tls_, _tts_, _tos_, _fltr_, _ttbs_) {
             var self = this,
                 projection,
                 dim,
@@ -281,6 +283,7 @@
             tls = _tls_;
             tts = _tts_;
             tos = _tos_;
+            fltr = _fltr_;
             ttbs = _ttbs_;
 
             self.notifyResize = function () {
diff --git a/web/gui/src/main/webapp/app/view/topo/topoFilter.js b/web/gui/src/main/webapp/app/view/topo/topoFilter.js
index 53a6302..1226664 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoFilter.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoFilter.js
@@ -35,86 +35,51 @@
 
     // which "layer" a particular item "belongs to"
     var layerLookup = {
-        host: {
-            endstation: 'pkt', // default, if host event does not define type
-            router:     'pkt',
-            bgpSpeaker: 'pkt'
-        },
-        device: {
-            switch: 'pkt',
-            roadm: 'opt'
-        },
-        link: {
-            hostLink: 'pkt',
-            direct: 'pkt',
-            indirect: '',
-            tunnel: '',
-            optical: 'opt'
-        }
-    };
-
-    var idPrefix = 'topo-rb-';
-
-    var dispatch = {
-            all: function () { suppressLayers(false); },
-            pkt: function () { showLayer('pkt'); },
-            opt: function () { showLayer('opt'); }
-        },
-        filterButtons = [
-            { text: 'All Layers', id: 'all' },
-            { text: 'Packet Only', id: 'pkt' },
-            { text: 'Optical Only', id: 'opt' }
-        ],
-        btnG,
-        btnDef = {},
-        targetDiv;
-
-
-    function injectButtons(div) {
-        targetDiv = div;
-
-        btnG = div.append('div').attr('id', 'topo-radio-group');
-
-        filterButtons.forEach(function (btn, i) {
-            var bid = btn.id,
-                txt = btn.text,
-                uid = idPrefix + bid,
-                button = btnG.append('span')
-                    .attr({
-                        id: uid,
-                        'class': 'radio'
-                    })
-                    .text(txt);
-            btnDef[uid] = btn;
-
-            if (i === 0) {
-                button.classed('active', true);
-                btnG.selected = bid;
+            host: {
+                endstation: 'pkt', // default, if host event does not define type
+                router:     'pkt',
+                bgpSpeaker: 'pkt'
+            },
+            device: {
+                switch: 'pkt',
+                roadm: 'opt'
+            },
+            link: {
+                hostLink: 'pkt',
+                direct: 'pkt',
+                indirect: '',
+                tunnel: '',
+                optical: 'opt'
             }
-        });
+        },
+        // order of layer cycling in button
+        dispatch = [
+            {
+                type: 'all',
+                action: function () { suppressLayers(false); },
+                msg: 'All Layers Shown'
+            },
+            {
+                type: 'pkt',
+                action: function () { showLayer('pkt'); },
+                msg: 'Packet Layer Shown'
+            },
+            {
+                type: 'opt',
+                action: function () { showLayer('opt'); },
+                msg: 'Optical Layer Shown'
+            }
+        ],
+        layer = 0;
 
-        btnG.selectAll('span')
-            .on('click', function () {
-               var button = d3.select(this),
-                   uid = button.attr('id'),
-                   btn = btnDef[uid],
-                   act = button.classed('active');
-
-                if (!act) {
-                    btnG.selectAll('span').classed('active', false);
-                    button.classed('active', true);
-                    btnG.selected = btn.id;
-                    clickAction(btn.id);
-                }
-            });
-    }
-
-    function clickAction(which) {
-        dispatch[which]();
+    function clickAction() {
+        layer = (layer + 1) % dispatch.length;
+        dispatch[layer].action();
+        flash.flash(dispatch[layer].msg);
     }
 
     function selected() {
-        return btnG ? btnG.selected : '';
+        return dispatch[layer].type;
     }
 
     function inLayer(d, layer) {
@@ -169,20 +134,12 @@
                 tps = _tps_;
                 tts = _tts_;
 
-                function initFilter(_api_, div) {
+                function initFilter(_api_) {
                     api = _api_;
-                    injectButtons(div);
-                }
-
-                function destroyFilter() {
-                    targetDiv.select('#topo-radio-group').remove();
-                    btnG = null;
-                    btnDef = {};
                 }
 
                 return {
                     initFilter: initFilter,
-                    destroyFilter: destroyFilter,
 
                     clickAction: clickAction,
                     selected: selected,
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 bbc697d..425a87f 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoForce.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoForce.js
@@ -816,7 +816,7 @@
                 tss.initSelect(mkSelectApi(uplink));
                 tts.initTraffic(mkTrafficApi(uplink));
                 tos.initOblique(mkObliqueApi(uplink, fltr));
-                fltr.initFilter(mkFilterApi(uplink), d3.select('#mast-right'));
+                fltr.initFilter(mkFilterApi(uplink));
                 tls.initLink(mkLinkApi(svg, uplink), td3);
 
                 settings = angular.extend({}, defaultSettings, opts);
@@ -855,7 +855,6 @@
                 force.stop();
 
                 tls.destroyLink();
-                fltr.destroyFilter();
                 tos.destroyOblique();
                 tts.destroyTraffic();
                 tss.destroySelect();
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 f9ff18b..eb7c006 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoToolbar.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoToolbar.js
@@ -44,6 +44,7 @@
 
         //X: { id: 'nodelock-tog', gid: 'lock', isel: false },
         Z: { id: 'oblique-tog', gid: 'oblique', isel: false },
+        N: { id: 'filters-btn', gid: 'filters' },
         L: { id: 'cycleLabels-btn', gid: 'cycleLabels' },
         R: { id: 'resetZoom-btn', gid: 'resetZoom' },
 
@@ -93,6 +94,7 @@
     function addSecondRow() {
         //addToggle('X');
         addToggle('Z');
+        addButton('N');
         addButton('L');
         addButton('R');
     }