GUI -- Refactored Button Service.

Change-Id: I194af8f44aae4d6905bbe564d3aaae6599d20e92
diff --git a/web/gui/src/main/webapp/app/fw/widget/button.js b/web/gui/src/main/webapp/app/fw/widget/button.js
index 14d329f..6ad0015 100644
--- a/web/gui/src/main/webapp/app/fw/widget/button.js
+++ b/web/gui/src/main/webapp/app/fw/widget/button.js
@@ -27,7 +27,15 @@
     var btnSize = 25,
         btnPadding = 4;
 
-    function noop() {}
+
+    // === Helper Functions
+
+    function divExists(div, msg) {
+        if (!div) {
+            $log.warn('div undefined (' + msg + ')');
+        }
+        return !!div;
+    }
 
     function createDiv(div, cls, id) {
         return div.append('div')
@@ -35,11 +43,22 @@
             .attr('id', id);
     }
 
+    function noop() {}
+
+    function buttonWidth() {
+        return btnSize + 2 * btnPadding;
+    }
+
+
+    // === BUTTON =================================================
+
+    // div is where to put the button (d3.selection of a DIV element)
+    // id should be globally unique
+    // gid is glyph ID (from Glyph Service)
+    // cb is callback function on click
+    // tooltip is text for tooltip
     function button(div, id, gid, cb, tooltip) {
-        if (!div) {
-            $log.warn('Button cannot append to div');
-            return null;
-        }
+        if (!divExists(div, 'button')) return null;
 
         var btnDiv = createDiv(div, 'button', id),
             cbFnc = fs.isF(cb) || noop;
@@ -50,16 +69,21 @@
 
         return {
             id: id,
-            click: cbFnc,
-            el: btnDiv
+            width: buttonWidth
         }
     }
 
+
+    // === TOGGLE BUTTON ==========================================
+
+    // div is where to put the button (d3.selection of a DIV element)
+    // id should be globally unique
+    // gid is glyph ID (from Glyph Service)
+    // initState is whether the toggle is on or not to begin
+    // cb is callback function on click
+    // tooltip is text for tooltip
     function toggle(div, id, gid, initState, cb, tooltip) {
-        if (!div) {
-            $log.warn('Toggle cannot append to div');
-            return null;
-        }
+        if (!divExists(div, 'toggle button')) return null;
 
         var sel = !!initState,
             togDiv = createDiv(div, 'toggleButton', id),
@@ -69,115 +93,143 @@
         togDiv.classed('selected', sel);
 
         function _toggle(b) {
-            if (b === undefined) {
-                sel = !sel;
-            } else {
-                sel = !!b;
-            }
-            cbFnc(sel);
+            sel = (b === undefined) ? !sel : !!b;
             togDiv.classed('selected', sel);
+            cbFnc(sel);
         }
 
         togDiv.on('click', _toggle);
 
         return {
             id: id,
-            el: togDiv,
+            width: buttonWidth,
             selected: function () { return sel; },
             toggle: _toggle
         }
     }
 
+
+    // === RADIO BUTTON SET =======================================
+
+
+    // div is where to put the button (d3.selection of a DIV element)
+    // id should be globally unique
+    // rset is an array of button descriptors of the following form:
+    //     {
+    //       gid: glyphId,
+    //       tooltip: tooltipText,
+    //       cb:  callbackFunction
+    //     }
     function radioSet(div, id, rset) {
-        if (!div) {
-            $log.warn('Radio buttons cannot append to div');
+        if (!divExists(div, 'radio button set')) return null;
+
+        if (!fs.isA(rset) || !rset.length) {
+            $log.warn('invalid array (radio button set)');
             return null;
         }
-        if (!fs.isA(rset)) {
-            $log.warn('Radio button set is not an array');
-            return null;
-        }
-        if (rset.length === 0) {
-            $log.warn('Cannot create radio button set from empty array');
-            return null;
-        }
-        var rDiv = div.append('div').classed('radioSet', true),
+
+        var rDiv = createDiv(div, 'radioSet', id),
             rads = [],
-            sel;
+            idxByKey = {},
+            currIdx = 0;
 
-        function _selected(s) {
-            var curr = d3.select(this),
-                currId = curr.attr('id'),
-                selIndex = _getIndex(),
-                currIndex = _getIndex(currId);
+        function rsetWidth() {
+            return ((btnSize + btnPadding) * rads.length) + btnPadding;
+        }
 
-            // I have it going by id's because I couldn't think of a way
-            // to get the radio button's index from the div element
-                // We could look at the end of the radio button id for its number
-                // but I didn't know how to get the end of the string's number
-            if (!s) {
-                if (sel !== currId) {
-                    rads[selIndex].el.classed('selected', false);
-                    curr.classed('selected', true);
-                    rads[currIndex].cb();
-                    sel = currId;
-                }
-            } else {
-                if (!rads[s].el.classed('selected')) {
-                    rads[selIndex].el.classed('selected', false);
-                    rads[s].el.classed('selected', true);
-                    rads[s].cb();
-                    sel = rads[s].id;
-                }
+        function rbclick() {
+            var id = d3.select(this).attr('id'),
+                m = /^.*-(\d+)$/.exec(id),
+                idx = Number(m[1]);
+
+            if (idx !== currIdx) {
+                rads[currIdx].el.classed('selected', false);
+                currIdx = idx;
+                rads[currIdx].el.classed('selected', true);
+                invokeCurrent();
             }
         }
 
-        // given the id, will get the index of element
-        // without the id, will get the index of sel
-        function _getIndex(id) {
-            if (!id) {
-                for (var i = 0; i < rads.length; i++) {
-                    if (rads[i].id === sel) { return i; }
-                }
-            } else {
-                for (var j = 0; j < rads.length; j++) {
-                    if (rads[j].id === id) { return j; }
-                }
-            }
-        }
+         // {
+         //     gid: gid,
+         //     tooltip: ...,       (optional)
+         //     key: ...,           (optional)
+         //     cb: cb
+         //     id: ...             (added by us)
+         //     index: ...          (added by us)
+         // }
 
         rset.forEach(function (btn, index) {
-            var rid = {id: id + '-' + index},
-                rbtn = angular.extend({}, btn, rid),
-                istate = (index === 0),
-                rBtnDiv = createDiv(rDiv, 'radioButton', rbtn.id);
 
-            if (istate) { rBtnDiv.classed('selected', true); }
-            is.loadIcon(rBtnDiv, rbtn.gid, btnSize, true);
-            rbtn.el = rBtnDiv;
-            rbtn.cb = fs.isF(rbtn.cb) || noop;
+            if (!fs.isO(btn)) {
+                $log.warn('radio button descriptor at index ' + index +
+                            ' not an object');
+                return;
+            }
 
-            rBtnDiv.on('click', _selected);
+            var rid = id + '-' + index,
+                initSel = (index === 0),
+                rbdiv = createDiv(rDiv, 'radioButton', rid);
 
-            rads.push(rbtn);
+            rbdiv.classed('selected', initSel);
+            rbdiv.on('click', rbclick);
+            is.loadIcon(rbdiv, btn.gid, btnSize, true);
+            angular.extend(btn, {
+                el: rbdiv,
+                id: rid,
+                cb: fs.isF(btn.cb) || noop,
+                index: index
+            });
+
+            if (btn.key) {
+                idxByKey[btn.key] = index;
+            }
+
+            rads.push(btn);
         });
-        sel = rads[0].id;
-        rads[0].cb();
+
+
+        function invokeCurrent() {
+            var curr = rads[currIdx];
+            curr.cb(curr.index, curr.key);
+        }
+
+        function selected(x) {
+            var curr = rads[currIdx],
+                idx;
+
+            if (x === undefined) {
+                return curr.key || curr.index;
+            } else {
+                idx = idxByKey[x];
+                if (idx === undefined) {
+                    $log.warn('no radio button with key "' + x + '"');
+                } else {
+                    selectedIndex(idx);
+                }
+            }
+        }
+
+        function selectedIndex(x) {
+            if (x === undefined) {
+                return currIdx;
+            } else {
+                if (x >= 0 && x < rads.length) {
+                    currIdx = x;
+                    invokeCurrent();
+                } else {
+                    $log.warn('invalid radio button index', x);
+                }
+            }
+        }
 
         return {
-            rads: rads,
-            width: (((btnSize + btnPadding) * rads.length) + btnPadding),
-            selected: function (i) {
-                if (i === undefined) { _getIndex(); }
-                else { _selected(i); }
-            }
+            width: rsetWidth,
+            selected: selected,
+            selectedIndex: selectedIndex
         }
     }
 
-    function width(s) {
-        if (s) { btnSize = s; }
-        return btnSize;
-    }
 
     angular.module('onosWidget')
     .factory('ButtonService',
@@ -191,8 +243,7 @@
             return {
                 button: button,
                 toggle: toggle,
-                radioSet: radioSet,
-                width: width
+                radioSet: radioSet
             };
         }]);
 
diff --git a/web/gui/src/main/webapp/app/fw/widget/toolbar.js b/web/gui/src/main/webapp/app/fw/widget/toolbar.js
index 71b25a6..79feeed 100644
--- a/web/gui/src/main/webapp/app/fw/widget/toolbar.js
+++ b/web/gui/src/main/webapp/app/fw/widget/toolbar.js
@@ -88,7 +88,7 @@
         if (!validId(btnId, 'addButton')) { return null; }
         ids.push(btnId);
         button =  bns.button(tbarDiv, btnId, gid, cb, tooltip);
-        if (button) { addToWidth(bns.width()); }
+        if (button) { addToWidth(button.width()); }
         displayTools();
         return button;
     }
@@ -99,7 +99,7 @@
         if (!validId(togId, 'addToggle')) { return null; }
         ids.push(togId);
         toggle =  bns.toggle(tbarDiv, togId, gid, initState, cb, tooltip);
-        if (toggle) { addToWidth(bns.width()); }
+        if (toggle) { addToWidth(toggle.width()); }
         displayTools();
         return toggle;
     }
@@ -110,7 +110,7 @@
         if (!validId(radId, 'addRadioSet')) { return null; }
         ids.push(radId);
         radios = bns.radioSet(tbarDiv, radId, rset);
-        if (radios) { addToWidth(radios.width); }
+        if (radios) { addToWidth(radios.width()); }
         displayTools();
         return radios;
     }