ONOS-2849: Refactored topo dialog to general DialogService. Implemented confirmation dialog in App view.

Change-Id: Ib20e98253b2d13f7d7debef2dea5a530b61ced99
diff --git a/web/gui/src/main/webapp/app/fw/layer/dialog.css b/web/gui/src/main/webapp/app/fw/layer/dialog.css
new file mode 100644
index 0000000..2c04992
--- /dev/null
+++ b/web/gui/src/main/webapp/app/fw/layer/dialog.css
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2016 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 -- Dialog Service -- CSS file
+ */
+
+.dialog h2 {
+    padding: 0 4px;
+    margin: 0;
+    word-wrap: break-word;
+    display: inline-block;
+    width: 210px;
+    vertical-align: middle;
+}
+.light .dialog h2 {
+    color: black;
+}
+.dark .dialog h2 {
+    color: #ddd;
+}
+
+.dialog .dialog-button {
+    display: inline-block;
+    cursor: pointer;
+    height: 20px;
+    padding: 2px 6px;
+    margin: 4px;
+    float: right;
+}
+
+.light .dialog .dialog-button {
+    background-color: #fec;
+}
+.dark .dialog .dialog-button {
+    background-color: #369;
+}
diff --git a/web/gui/src/main/webapp/app/fw/layer/dialog.js b/web/gui/src/main/webapp/app/fw/layer/dialog.js
new file mode 100644
index 0000000..f0a6b65
--- /dev/null
+++ b/web/gui/src/main/webapp/app/fw/layer/dialog.js
@@ -0,0 +1,195 @@
+/*
+ *  Copyright 2016 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 -- Layer -- Dialog Service
+
+ Builds on the panel service to provide dialog functionality.
+ */
+(function () {
+    'use strict';
+
+    // injected refs
+    var $log, $window, fs, ps, bns;
+
+    // configuration
+    var defaultSettings = {
+            width: 300,
+            edge: 'left'
+        };
+
+    // internal state
+    var pApi, panel, dApi;
+
+    // TODO: ONOS-3741
+    // TODO: ESC key invokes Cancel callback
+    // TODO: Enter invokes OK callback
+
+    // create the dialog; return its API
+    function createDialog(id, opts) {
+        var header, body, footer,
+            settings = angular.extend({}, defaultSettings, opts),
+            p = ps.createPanel(id, settings),
+            cls = opts && opts.cssCls;
+
+        p.classed('dialog', true);
+        if (cls) {
+            p.classed(cls, 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(id);
+        }
+
+        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 setTitle(title) {
+        if (pApi) {
+            pApi.appendHeader('h2').text(title);
+        }
+        return dApi;
+    }
+
+    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(id, opts) {
+        $log.debug('Open DIALOG', id, opts);
+        if (!pApi) {
+            pApi = createDialog(id, opts);
+        }
+        pApi.reset();
+        panel.show();
+
+        // return the dialog object API
+        dApi = {
+            setTitle: setTitle,
+            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('onosLayer')
+    .factory('DialogService',
+        ['$log', '$window', 'FnService', 'PanelService', 'ButtonService',
+
+        // TODO: for now, $window is not used, but we should provide an option
+            // to center the dialog on the window.
+
+        function (_$log_, _$window_, _fs_, _ps_, _bns_) {
+            $log = _$log_;
+            $window = _$window_;
+            fs = _fs_;
+            ps = _ps_;
+            bns = _bns_;
+
+            return {
+                openDialog: openDialog,
+                closeDialog: closeDialog,
+                createDiv: createDiv
+            };
+        }]);
+
+}());
diff --git a/web/gui/src/main/webapp/app/view/app/app.css b/web/gui/src/main/webapp/app/view/app/app.css
index 9413369..528c622 100644
--- a/web/gui/src/main/webapp/app/view/app/app.css
+++ b/web/gui/src/main/webapp/app/view/app/app.css
@@ -30,3 +30,8 @@
 #ov-app input#uploadFile {
     display: none;
 }
+
+#app-dialog p {
+    color: darkred;
+    font-size: 12pt;
+}
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 7cc081c..3b27c38 100644
--- a/web/gui/src/main/webapp/app/view/app/app.js
+++ b/web/gui/src/main/webapp/app/view/app/app.js
@@ -24,16 +24,20 @@
     // constants
     var INSTALLED = 'INSTALLED',
         ACTIVE = 'ACTIVE',
-        APP_MGMENT_REQ = 'appManagementRequest',
-        FILE_UPLOAD_URL = 'applications/upload';
+        appMgmtReq = 'appManagementRequest',
+        fileUploadUrl = 'applications/upload',
+        dialogId = 'app-dialog',
+        dialogOpts = {
+            edge: 'right'
+        };
 
     angular.module('ovApp', [])
     .controller('OvAppCtrl',
         ['$log', '$scope', '$http',
         'FnService', 'TableBuilderService', 'WebSocketService', 'UrlFnService',
-        'KeyService',
+        'KeyService', 'DialogService',
 
-    function ($log, $scope, $http, fs, tbs, wss, ufs, ks) {
+    function ($log, $scope, $http, fs, tbs, wss, ufs, ks, ds) {
         $scope.ctrlBtnState = {};
         $scope.uploadTip = 'Upload an application (.oar file)';
         $scope.activateTip = 'Activate selected application';
@@ -77,15 +81,41 @@
             ['scroll down', 'See more apps']
         ]);
 
+
+        function createConfirmationText(action, sid) {
+            var content = ds.createDiv();
+            content.append('p').text(action + ' ' + sid);
+            return content;
+        }
+
+        function confirmAction(action) {
+            var sid = $scope.selId,
+                spar = $scope.sortParams;
+
+            function dOk() {
+                $log.debug('Initiating', action, 'of', sid);
+                wss.sendEvent(appMgmtReq, {
+                    action: action,
+                    name: sid,
+                    sortCol: spar.sortCol,
+                    sortDir: spar.sortDir
+                });
+            }
+
+            function dCancel() {
+                $log.debug('Canceling', action, 'of', sid);
+            }
+
+            ds.openDialog(dialogId, dialogOpts)
+                .setTitle('Confirm Action')
+                .addContent(createConfirmationText(action, sid))
+                .addButton('OK', dOk)
+                .addButton('Cancel', dCancel);
+        }
+
         $scope.appAction = function (action) {
             if ($scope.ctrlBtnState.selection) {
-                $log.debug('Initiating ' + action + ' of ' + $scope.selId);
-                wss.sendEvent(APP_MGMENT_REQ, {
-                    action: action,
-                    name: $scope.selId,
-                    sortCol: $scope.sortParams.sortCol,
-                    sortDir: $scope.sortParams.sortDir
-                });
+                confirmAction(action);
             }
         };
 
@@ -93,7 +123,7 @@
             var formData = new FormData();
             if ($scope.appFile) {
                 formData.append('file', $scope.appFile);
-                $http.post(ufs.rsUrl(FILE_UPLOAD_URL), formData, {
+                $http.post(ufs.rsUrl(fileUploadUrl), formData, {
                     transformRequest: angular.identity,
                     headers: {
                         'Content-Type': undefined
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 0319b9b..69f500f 100644
--- a/web/gui/src/main/webapp/app/view/topo/topo.css
+++ b/web/gui/src/main/webapp/app/view/topo/topo.css
@@ -96,23 +96,6 @@
     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 --- */
 
diff --git a/web/gui/src/main/webapp/app/view/topo/topoDialog.js b/web/gui/src/main/webapp/app/view/topo/topoDialog.js
index 0c47511..c5e89dd 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoDialog.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoDialog.js
@@ -16,175 +16,29 @@
 
 /*
  ONOS GUI -- Topology Dialog Module.
- Defines functions for manipulating a dialog box.
+ Creates a dialog box for the topology view.
  */
 
 (function () {
     'use strict';
 
-    // injected refs
-    var $log, $window, $rootScope, fs, ps, bns;
-
     // constants
-    var pCls = 'topo-p dialog',
-        idDialog = 'topo-p-dialog',
-        panelOpts = {
-            width: 300,
-            edge: 'left'
+    var idDialog = 'topo-p-dialog',
+        opts = {
+            cssCls: 'topo-p'
         };
 
-    // 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 setTitle(title) {
-        if (pApi) {
-            pApi.appendHeader('h2').text(title);
-        }
-        return dApi;
-    }
-
-    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();
-        panel.show();
-
-        // return the dialog object API
-        dApi = {
-            setTitle: setTitle,
-            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',
+        ['DialogService',
 
-        function (_$log_, _$window_, _$rootScope_,
-                  _fs_, _ps_, _bns_) {
-            $log = _$log_;
-            $window = _$window_;
-            $rootScope = _$rootScope_;
-            fs = _fs_;
-            ps = _ps_;
-            bns = _bns_;
-
+        function (ds) {
             return {
-                openDialog: openDialog,
-                closeDialog: closeDialog,
-                createDiv: createDiv
+                openDialog: function () { return ds.openDialog(idDialog, opts); },
+                closeDialog: ds.closeDialog,
+                createDiv: ds.createDiv
             };
         }]);
 }());
diff --git a/web/gui/src/main/webapp/index.html b/web/gui/src/main/webapp/index.html
index 8cf75e0..adead69 100644
--- a/web/gui/src/main/webapp/index.html
+++ b/web/gui/src/main/webapp/index.html
@@ -73,6 +73,7 @@
 
     <script src="app/fw/layer/layer.js"></script>
     <script src="app/fw/layer/panel.js"></script>
+    <script src="app/fw/layer/dialog.js"></script>
     <script src="app/fw/layer/flash.js"></script>
     <script src="app/fw/layer/quickhelp.js"></script>
     <script src="app/fw/layer/veil.js"></script>
@@ -86,6 +87,7 @@
     <link rel="stylesheet" href="app/fw/svg/glyph.css">
     <link rel="stylesheet" href="app/fw/svg/icon.css">
     <link rel="stylesheet" href="app/fw/layer/panel.css">
+    <link rel="stylesheet" href="app/fw/layer/dialog.css">
     <link rel="stylesheet" href="app/fw/layer/flash.css">
     <link rel="stylesheet" href="app/fw/layer/quickhelp.css">
     <link rel="stylesheet" href="app/fw/layer/veil.css">