ONOS-2850 : Beginnings of Topology Programmable Dialog box --- WIP.

Change-Id: I7e08b3c5d97f409c470eeb97b0f988a14b6d495f
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 255ddbc..dda6d5c 100644
--- a/web/gui/src/main/webapp/app/view/topo/topo.css
+++ b/web/gui/src/main/webapp/app/view/topo/topo.css
@@ -96,6 +96,24 @@
     height: 30px;
 }
 
+/* --- Topo Dialog Panel --- */
+
+#topo-p-dialog .dialog-button {
+    display: inline-block;
+    cursor: pointer;
+    height: 20px;
+    padding: 2px 6px;
+    margin: 4px;
+    float: right;
+}
+
+.light #topo-p-dialog .dialog-button {
+    background-color: #fec;
+}
+.dark #topo-p-dialog .dialog-button {
+    background-color: #369;
+}
+
 /* --- general topo-panel styling --- */
 
 .topo-p div.header div.icon {
diff --git a/web/gui/src/main/webapp/app/view/topo/topoDialog.js b/web/gui/src/main/webapp/app/view/topo/topoDialog.js
new file mode 100644
index 0000000..e485804
--- /dev/null
+++ b/web/gui/src/main/webapp/app/view/topo/topoDialog.js
@@ -0,0 +1,182 @@
+/*
+ *  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 -- Topology Dialog Module.
+ Defines functions for manipulating a dialog box.
+ */
+
+(function () {
+    'use strict';
+
+    // injected refs
+    var $log, $window, $rootScope, fs, ps, bns;
+
+    // constants
+    var pCls = 'topo-p',
+        idDialog = 'topo-p-dialog',
+        panelOpts = {
+            width: 300
+        };
+
+    // internal state
+    var pApi, panel, dApi;
+
+    // TODO: ESC key invokes Cancel callback
+    // TODO: Enter invokes OK callback
+
+    // create the dialog; return its API
+    function createDialog() {
+        var header, body, footer,
+            p = ps.createPanel(idDialog, panelOpts);
+        p.classed(pCls, true);
+        panel = p;
+
+        function reset() {
+            p.empty();
+            p.append('div').classed('header', true);
+            p.append('div').classed('body', true);
+            p.append('div').classed('footer', true);
+
+            header = p.el().select('.header');
+            body = p.el().select('.body');
+            footer = p.el().select('.footer');
+        }
+
+        function hAppend(x) {
+            if (typeof x === 'string') {
+                return header.append(x);
+            }
+            header.node().appendChild(x.node());
+            return header;
+        }
+
+        function bAppend(x) {
+            if (typeof x === 'string') {
+                return body.append(x);
+            }
+            body.node().appendChild(x.node());
+            return body;
+        }
+
+        function fAppend(x) {
+            if (typeof x === 'string') {
+                return footer.append(x);
+            }
+            footer.node().appendChild(x.node());
+            return footer;
+        }
+
+        function destroy() {
+            ps.destroyPanel(idDialog);
+        }
+
+        return {
+            reset: reset,
+            appendHeader: hAppend,
+            appendBody: bAppend,
+            appendFooter: fAppend,
+            destroy: destroy
+        };
+    }
+
+    function makeButton(text, callback) {
+        var cb = fs.isF(callback);
+
+        function invoke() {
+            cb && cb();
+            panel.hide();
+        }
+        return createDiv('dialog-button')
+            .text(text)
+            .on('click', invoke);
+    }
+
+    function addContent(content) {
+        if (pApi) {
+            pApi.appendBody(content);
+        }
+        return dApi;
+    }
+
+    function addButton(text, cb) {
+        if (pApi) {
+            pApi.appendFooter(makeButton(text, cb));
+        }
+        return dApi;
+    }
+
+    // opens the dialog (creates if necessary)
+    function openDialog() {
+        $log.debug('Open DIALOG');
+        if (!pApi) {
+            pApi = createDialog();
+        }
+        pApi.reset();
+        pApi.appendHeader('h2').text('=dialog=');
+        panel.show();
+
+        // return the dialog object API
+        dApi = {
+            addContent: addContent,
+            addButton: addButton
+        };
+        return dApi;
+    }
+
+    // closes the dialog (destroying panel)
+    function closeDialog() {
+        $log.debug('Close DIALOG');
+        if (pApi) {
+            panel.hide();
+            pApi.destroy();
+            pApi = null;
+            dApi = null;
+        }
+    }
+
+    // creates a detached div, returning D3 selection
+    // optional CSS class may be provided
+    function createDiv(cls) {
+        var div = d3.select(document.createElement('div'));
+        if (cls) {
+            div.classed(cls, true);
+        }
+        return div;
+    }
+
+    // ==========================
+
+    angular.module('ovTopo')
+    .factory('TopoDialogService',
+        ['$log', '$window', '$rootScope', 'FnService', 'PanelService', 'ButtonService',
+
+        function (_$log_, _$window_, _$rootScope_,
+                  _fs_, _ps_, _bns_) {
+            $log = _$log_;
+            $window = _$window_;
+            $rootScope = _$rootScope_;
+            fs = _fs_;
+            ps = _ps_;
+            bns = _bns_;
+
+            return {
+                openDialog: openDialog,
+                closeDialog: closeDialog,
+                createDiv: createDiv
+            };
+        }]);
+}());
diff --git a/web/gui/src/main/webapp/app/view/topo/topoSelect.js b/web/gui/src/main/webapp/app/view/topo/topoSelect.js
index 483c4ba..4ad7690 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoSelect.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoSelect.js
@@ -240,6 +240,33 @@
         return cc;
     }
 
+    // returns a selection context, providing info about what is selected
+    function selectionContext() {
+        var devices = [],
+            hosts = [],
+            types = {};
+
+        angular.forEach(selections, function (d) {
+            var o = d.obj,
+                c = o.class;
+
+            if (c === 'device') {
+                devices.push(o.id);
+                types[o.id] = o.type;
+            }
+            if (c === 'host') {
+                hosts.push(o.id);
+                types[o.id] = o.type;
+            }
+        });
+
+        return {
+            devices: devices,
+            hosts: hosts,
+            types: types
+        };
+    }
+
     // === -----------------------------------------------------
     // === MODULE DEFINITION ===
 
@@ -280,7 +307,8 @@
                 selectOrder: function () { return selectOrder; },
                 somethingSelected: somethingSelected,
 
-                clickConsumed: clickConsumed
+                clickConsumed: clickConsumed,
+                selectionContext: selectionContext
             };
         }]);
 }());
diff --git a/web/gui/src/main/webapp/index.html b/web/gui/src/main/webapp/index.html
index 5df3c66..7e0d9b7 100644
--- a/web/gui/src/main/webapp/index.html
+++ b/web/gui/src/main/webapp/index.html
@@ -98,6 +98,7 @@
     <script src="app/view/topo/topo.js"></script>
     <script src="app/view/topo/topoD3.js"></script>
     <script src="app/view/topo/topoEvent.js"></script>
+    <script src="app/view/topo/topoDialog.js"></script>
     <script src="app/view/topo/topoFilter.js"></script>
     <script src="app/view/topo/topoForce.js"></script>
     <script src="app/view/topo/topoInst.js"></script>