UI-Ref : added example use of chained dialogs in topology overlay, illustrating how to get additional data from the server, and to allow user to make a sequence of selections.

Change-Id: Ice882b45fc9e378e134023fc40b81747a954d19f
diff --git a/uiref/src/main/resources/app/view/uiRefTopov/uiRefTopovDemo.js b/uiref/src/main/resources/app/view/uiRefTopov/uiRefTopovDemo.js
index 803ec59..839633f 100644
--- a/uiref/src/main/resources/app/view/uiRefTopov/uiRefTopovDemo.js
+++ b/uiref/src/main/resources/app/view/uiRefTopov/uiRefTopovDemo.js
@@ -10,14 +10,136 @@
     var $log, fs, flash, wss, tss, tds;
 
     // constants
-    var displayStart = 'uiRefTopovDisplayStart',
-        displayUpdate = 'uiRefTopovDisplayUpdate',
-        displayStop = 'uiRefTopovDisplayStop';
+    var pfx = 'uiRefTopov',
+        displayStart = pfx + 'DisplayStart',
+        displayUpdate = pfx + 'DisplayUpdate',
+        displayStop = pfx + 'DisplayStop',
+        portsReq = pfx + 'DevicePortsReq',
+        portsResp = pfx + 'DevicePortsResp',
+        portsOp = pfx + 'DevicePortFakeOp';
 
     // internal state
-    var currentMode = null;
+    var currentMode = null,
+        ctx = {                 // chained dialog context
+            device: null,
+            port: -1,
+            foo: false,
+            bar: false
+        },
+        handlers = {};
 
 
+    // Handle device ports response from server:
+    //   This will be invoked in response to a device selected and the
+    //   "chain" button pressed on the details dialog, once the response
+    //   comes back from the server.
+    // We are going to open a dialog and ask the user to select one
+    //   of the ports for the device...
+    handlers[portsResp] = function (data) {
+        $log.debug('hey! Port Data from the server!', data);
+        ctx.device = data.id;
+        
+        // invoked when the OK button is pressed on this dialog
+        function dOk() {
+            $log.debug('Dialog OK button pressed');
+            portOptionsDialog();
+        }
+
+        tds.openDialog()
+            .setTitle('Choose Port')
+            .addContent(createPortChoiceContent(data.ports))
+            .addCancel()
+            .addOkChained(dOk)      // NOTE: we use the "chained" version here
+            .bindKeys();
+    };
+
+    function createPortChoiceContent(ports) {
+        var content = tds.createDiv('port-list'),
+            form,
+            portSelect;
+
+        content.append('p').text('Select port of device ' + ctx.device);
+        form = content.append('form');
+        form.append('span').text('port number: ');
+
+        // onchange function for selection widget
+        function selectPort() {
+            ctx.port = this.options[this.selectedIndex].value;
+        }
+
+        portSelect = form.append('select').on('change', selectPort);
+        ports.forEach(function (p) {
+            portSelect.append('option')
+                .attr('value', p.id)
+                .text(p.id);
+        });
+        
+        ctx.port = -1;      // clear state from any previous invocations
+
+        return content;
+    }
+
+
+    // the function that is called if OK is pressed on our ports dialog
+    function portOptionsDialog() {
+
+        // invoked when the OK button is pressed on this dialog
+        function dOk() {
+            $log.debug('Port Options Dialog OK button pressed');
+            $log.debug('Sending event', portsOp, ctx);
+            wss.sendEvent(portsOp, ctx);
+        }
+
+        tds.openDialog()
+            .setTitle('Select Port Options')
+            .addContent(createPortOptionsContent())
+            .addCancel()
+            .addOk(dOk)     // NOTE: NOT the "chained" version!
+            .bindKeys();
+    }
+
+    function createPortOptionsContent() {
+        var content = tds.createDiv('port-opts'),
+            form;
+
+        // helper function to add a paragraph
+        function para(text) {
+            content.append('p').text(text);
+        }
+
+        para('Device ' + ctx.device);
+        para('Port ' + ctx.port);
+
+        form = content.append('form');
+
+        // helper function to add a checkbox to the form, which updates the
+        //  context when the user toggles the checked state of the box.
+        function cbox(name, val) {
+
+            // onchange function for checkbox widget
+            function onchange() {
+                ctx[val] = this.checked;
+            }
+
+            form.append('input').attr({
+                type: 'checkbox',
+                name: name,
+                value: val
+            }).on('change', onchange);
+
+            ctx[val] = false;   // clear state from any previous invocations
+
+            form.append('span').text(name);
+            form.append('br');
+        }
+
+        // add two checkboxes...
+        cbox('Foo', 'foo');
+        cbox('Bar', 'bar');
+
+        return content;
+    }
+
     // === ---------------------------
     // === Helper functions
 
@@ -40,6 +162,7 @@
     function createDialogContent(devs) {
         var content = tds.createDiv('my-content-class'),
             items;
+        
         content.append('p').text('Do something to these devices?');
         items = content.append('div');
         devs.forEach(function (d) {
@@ -48,25 +171,25 @@
         return content;
     }
 
-    function dCancel() {
-        $log.debug('Dialog CANCEL button pressed');
-    }
 
-    function dOk() {
-        $log.debug('Dialog OK button pressed');
-    }
-
-    function createListContent() {
-        var content = tds.createDiv('my-list-class'),
-            items;
-        // TODO: figure out best way to inject selectable list
-        content.append('p').text('(Selectable list to show here...)');
+    function createCustomContent() {
+        var content = tds.createDiv('my-div-class');
+        content.append('p').text('(Some content goes here...)');
         return content;
     }
 
     // === ---------------------------
     // === Main API functions
 
+    function overlayActive(active) {
+        if (active) {
+            wss.bindHandlers(handlers);
+        } else {
+            stopDisplay();
+            wss.unbindHandlers(handlers);
+        }
+    }
+
     function startDisplay(mode) {
         if (currentMode === mode) {
             $log.debug('(in mode', mode, 'already)');
@@ -93,31 +216,59 @@
         return false;
     }
 
-    // this example dialog invoked from the details panel, when one or more
-    //  devices have been selected
-    function deviceDialog() {
-        var ctx = tss.selectionContext();
+    // this example dialog is invoked from the details panel, when one or more
+    //  devices have been selected, and the "banner" button is pressed.
+    function simpleDialog() {
+        var sctx = tss.selectionContext();
 
-        $log.debug('device dialog invoked with context:', ctx);
+        $log.debug('SIMPLE: device dialog invoked with context:', sctx);
+
+        function dCancel() {
+            $log.debug('Dialog CANCEL button pressed');
+        }
+
+        function dOk() {
+            $log.debug('Dialog OK button pressed');
+        }
 
         // only if at least one device was selected
-        if (ctx.devices.length) {
+        if (sctx.devices.length) {
             tds.openDialog()
                 .setTitle('Process Devices')
-                .addContent(createDialogContent(ctx.devices))
+                .addContent(createDialogContent(sctx.devices))
                 .addCancel(dCancel)     // 'esc' key bound to 'Cancel' button
                 .addOk(dOk)             // 'enter' key bound to 'OK' button
                 .bindKeys();
         }
     }
 
+    // this example dialog is invoked from the details panel, when a single 
+    // device has been selected and the "chain" button is pressed.
+    function chainedDialogs() {
+        var sctx = tss.selectionContext();
+
+        $log.debug('CHAINED: device dialog invoked with context:', sctx);
+
+        // only if exactly one device was selected...
+        if (sctx.devices.length === 1) {
+            // send a request for port information about the device to server
+            wss.sendEvent(portsReq, {
+                id: sctx.devices[0]
+            });
+        }
+    }
+
     // this example dialog invoked from the toolbar
     function listDialog() {
         $log.debug('list dialog invoked');
 
+        function dOk() {
+            $log.debug('Dialog Gotcha button pressed');
+        }
+
         tds.openDialog()
             .setTitle('A list of stuff')
-            .addContent(createListContent())
+            .addContent(createCustomContent())
             .addOk(dOk, 'Gotcha')  // custom text for "OK" button
             .bindKeys();
     }
@@ -127,24 +278,27 @@
 
     angular.module('ovUiRefTopov', [])
         .factory('UiRefTopovDemoService',
-        ['$log', 'FnService', 'FlashService', 'WebSocketService',
-            'TopoSelectService', 'TopoDialogService',
+            ['$log', 'FnService', 'FlashService', 'WebSocketService',
+                'TopoSelectService', 'TopoDialogService',
 
-            function (_$log_, _fs_, _flash_, _wss_, _tss_, _tds_) {
-                $log = _$log_;
-                fs = _fs_;
-                flash = _flash_;
-                wss = _wss_;
-                tss = _tss_;
-                tds = _tds_;
+                function (_$log_, _fs_, _flash_, _wss_, _tss_, _tds_) {
+                    $log = _$log_;
+                    fs = _fs_;
+                    flash = _flash_;
+                    wss = _wss_;
+                    tss = _tss_;
+                    tds = _tds_;
 
-                return {
-                    startDisplay: startDisplay,
-                    updateDisplay: updateDisplay,
-                    stopDisplay: stopDisplay,
+                    return {
+                        overlayActive: overlayActive,
 
-                    deviceDialog: deviceDialog,
-                    listDialog: listDialog
-                };
-            }]);
+                        startDisplay: startDisplay,
+                        updateDisplay: updateDisplay,
+                        stopDisplay: stopDisplay,
+
+                        chainedDialogs: chainedDialogs,
+                        simpleDialog: simpleDialog,
+                        listDialog: listDialog
+                    };
+                }]);
 }());
diff --git a/uiref/src/main/resources/app/view/uiRefTopov/uiRefTopovOverlay.js b/uiref/src/main/resources/app/view/uiRefTopov/uiRefTopovOverlay.js
index bf9e484..3a91674 100644
--- a/uiref/src/main/resources/app/view/uiRefTopov/uiRefTopovOverlay.js
+++ b/uiref/src/main/resources/app/view/uiRefTopov/uiRefTopovOverlay.js
@@ -53,31 +53,31 @@
 
         activate: function () {
             $log.debug("UI Ref topology overlay ACTIVATED");
+            demo.overlayActive(true);
         },
         deactivate: function () {
-            demo.stopDisplay();
+            demo.overlayActive(false);
             $log.debug("UI Ref topology overlay DEACTIVATED");
         },
 
-        // detail panel button definitions
+        // Detail panel button definitions
+        // NOTE: the callbacks needs to be wrapped in anonymous functions
+        //       to defer the dereferencing of 'demo' to after injection
+        //       of the business logic service API.
         buttons: {
-            foo: {
-                gid: 'chain',
-                tt: 'A FOO action',
-                cb: function() {
-                    demo.deviceDialog();
-                }
-            },
-            bar: {
+            simpleDialog: {
                 gid: '*banner',
-                tt: 'A BAR action',
-                cb: function (data) {
-                    $log.debug('BAR action invoked with data:', data);
-                }
+                tt: 'Simple dialog example',
+                cb: function() { demo.simpleDialog(); }
+            },
+            chainDialog: {
+                gid: 'chain',
+                tt: 'Chained dialogs example',
+                cb: function () { demo.chainedDialogs(); }
             }
         },
 
-        // Key bindings for traffic overlay buttons
+        // Key bindings for topology overlay buttons
         // NOTE: fully qual. button ID is derived from overlay-id and key-name
         keyBindings: {
             0: {
@@ -95,20 +95,20 @@
                 tt: 'Start Link Mode',
                 gid: 'chain'
             },
-            G: {
+            A: {
                 cb: function () { demo.listDialog(); },
-                tt: 'Uses the G key',
+                tt: 'List Dialog',
                 gid: 'crown'
             },
 
+            // defines the order in which the buttons appear on the toolbar
             _keyOrder: [
-                '0', 'V', 'F', 'G'
+                '0', 'V', 'F', 'A'
             ]
         },
 
         hooks: {
-            // hook for handling escape key
-            // Must return true to consume ESC, false otherwise.
+            // hook for handling escape key...
             escape: function () {
                 // Must return true to consume ESC, false otherwise.
                 return demo.stopDisplay();
@@ -116,16 +116,24 @@
 
             // hooks for when the selection changes...
             empty: function () {
+                // selection changed to the empty set
                 selectionCallback('empty');
             },
             single: function (data) {
+                // selection changed to a single node
                 selectionCallback('single', data);
+                // NOTE: the detail buttons to show on the dialog are included
+                //       in the detail data response from the server
             },
             multi: function (selectOrder) {
+                // selection changed to more than one node
                 selectionCallback('multi', selectOrder);
-                tov.addDetailButton('foo');
-                tov.addDetailButton('bar');
+                // NOTE: we have to manually add detail button(s) for a 
+                //       multi-selection
+                tov.addDetailButton('simpleDialog');
             },
+            
+            // hooks for mouse movement over nodes (devices/hosts)...
             mouseover: function (m) {
                 // m has id, class, and type properties
                 $log.debug('mouseover:', m);
@@ -138,6 +146,8 @@
         }
     };
 
+    // just an example callback to log the selection to the console.
+    // usually you would do something more useful.
     function selectionCallback(x, d) {
         $log.debug('Selection callback', x, d);
     }