ONOS-2074 - GUI -- Refactor Application view to use directives. WIP.

Change-Id: If886b5af1313ef350e041dc9f9a21ba150edcd79
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/ApplicationResource.java b/web/gui/src/main/java/org/onosproject/ui/impl/ApplicationResource.java
index c4b4c88..398bfee 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/ApplicationResource.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/ApplicationResource.java
@@ -18,7 +18,6 @@
 import com.sun.jersey.multipart.FormDataParam;
 import org.onlab.rest.BaseResource;
 import org.onosproject.app.ApplicationAdminService;
-import org.onosproject.core.Application;
 
 import javax.ws.rs.Consumes;
 import javax.ws.rs.POST;
@@ -38,8 +37,8 @@
     @POST
     @Consumes(MediaType.MULTIPART_FORM_DATA)
     public Response upload(@FormDataParam("file") InputStream stream) throws IOException {
-        Application app = get(ApplicationAdminService.class).install(stream);
-        return Response.ok(app.toString()).build();
+        get(ApplicationAdminService.class).install(stream);
+        return Response.ok().build();
     }
 
 }
diff --git a/web/gui/src/main/webapp/app/fw/remote/rest.js b/web/gui/src/main/webapp/app/fw/remote/rest.js
index aa451f9..04b9fe0 100644
--- a/web/gui/src/main/webapp/app/fw/remote/rest.js
+++ b/web/gui/src/main/webapp/app/fw/remote/rest.js
@@ -24,9 +24,9 @@
 
     angular.module('onosRemote')
     .factory('RestService',
-        ['$log', '$http', 'UrlFnService',
+        ['$log', '$http', 'FnService', 'UrlFnService',
 
-        function (_$log_, $http, ufs) {
+        function (_$log_, $http, fs, ufs) {
             $log = _$log_;
 
             function get(url, callback, errorCb) {
@@ -37,7 +37,7 @@
                     callback(response.data);
                 }, function (response) {
                     // error
-                    var  emsg = 'Failed to retrieve JSON data: ' + fullUrl;
+                    var emsg = 'Failed to retrieve JSON data: ' + fullUrl;
                     $log.warn(emsg, response.status, response.data);
                     if (errorCb) {
                         errorCb(emsg);
@@ -45,8 +45,28 @@
                 });
             }
 
+            // TODO: test this
+            function post(url, data, callbacks) {
+                var fullUrl = ufs.rsUrl(url);
+                $http.post(fullUrl, data).then(function (response) {
+                    // success
+                    if (callbacks && fs.isF(callbacks.success)) {
+                        callbacks.success(response.data);
+                    }
+                }, function (response) {
+                    // error
+                    var msg = 'Problem with $http post request: ' + fullUrl;
+                    $log.warn(msg, response.status, response.data);
+
+                    if (callbacks && fs.isF(callbacks.error)) {
+                        callbacks.error(msg);
+                    }
+                });
+            }
+
             return {
-                get: get
+                get: get,
+                post: post
             };
         }]);
 }());
diff --git a/web/gui/src/main/webapp/app/fw/widget/tableBuilder.js b/web/gui/src/main/webapp/app/fw/widget/tableBuilder.js
index b3d759e..ba04b30 100644
--- a/web/gui/src/main/webapp/app/fw/widget/tableBuilder.js
+++ b/web/gui/src/main/webapp/app/fw/widget/tableBuilder.js
@@ -44,6 +44,7 @@
             req = o.tag + 'DataRequest',
             resp = o.tag + 'DataResponse',
             onSel = fs.isF(o.selCb),
+            onResp = fs.isF(o.respCb),
             promise;
 
         o.scope.tableData = [];
@@ -52,6 +53,7 @@
 
         function respCb(data) {
             o.scope.tableData = data[root];
+            onResp && onResp();
             o.scope.$apply();
         }
 
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 c2d61cc..2b84e83 100644
--- a/web/gui/src/main/webapp/app/view/app/app.css
+++ b/web/gui/src/main/webapp/app/view/app/app.css
@@ -31,3 +31,8 @@
     width: 24px;
     border: none;
 }
+
+#ov-app form#inputFileForm,
+#ov-app input#uploadFile {
+    display: none;
+}
diff --git a/web/gui/src/main/webapp/app/view/app/app.html b/web/gui/src/main/webapp/app/view/app/app.html
index 3b78853..0243b5a 100644
--- a/web/gui/src/main/webapp/app/view/app/app.html
+++ b/web/gui/src/main/webapp/app/view/app/app.html
@@ -7,31 +7,28 @@
                  icon icon-size="36" icon-id="refresh"
                  ng-click="toggleRefresh()"></div>
             <div class="separator"></div>
-            <div id="app-install"
-                 icon icon-size="36" icon-id="plus"
-                 class="active">
+
+            <form id="inputFileForm">
+                <input id="uploadFile"
+                       type="file" size="50" accept=".oar"
+                       file-model="appFile">
+            </form>
+            <div icon icon-size="36" icon-id="plus"
+                 class="active" trigger-form>
             </div>
-            <div id="app-activate"
-                 icon icon-size="36" icon-id="play"
+            <div icon icon-size="36" icon-id="play"
+                 ng-click="appAction('activate')"
                  ng-class="{active: ctrlBtnState.installed}">
             </div>
-            <div id="app-deactivate"
-                 icon icon-size="36" icon-id="stop"
+            <div icon icon-size="36" icon-id="stop"
+                 ng-click="appAction('deactivate')"
                  ng-class="{active: ctrlBtnState.active}">
             </div>
-            <div id="app-uninstall"
-                 icon icon-size="36" icon-id="garbage"
+            <div icon icon-size="36" icon-id="garbage"
+                 ng-click="appAction('uninstall')"
                  ng-class="{active: ctrlBtnState.selection}">
             </div>
         </div>
-
-        <form id="app-form" method="POST" action="rs/applications/upload"
-              target="app-form-response" enctype="multipart/form-data" style="display:none">
-            <input type="file" name="file" id="file" size="50" accept=".oar">
-            <button type="submit" id="app-upload">Upload</button>
-        </form>
-        <iframe id="app-form-response" name="app-form-response"
-                src="" width="0" height="0" style="visibility:hidden;display:none"></iframe>
     </div>
 
     <div class="summary-list" onos-fixed-header>
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 29cc9b6..1918f3a 100644
--- a/web/gui/src/main/webapp/app/view/app/app.js
+++ b/web/gui/src/main/webapp/app/view/app/app.js
@@ -21,74 +21,115 @@
 (function () {
     'use strict';
 
-    var selectionObj;
+    // constants
+    var INSTALLED = 'INSTALLED',
+        ACTIVE = 'ACTIVE',
+        APP_MGMENT_REQ = 'appManagementRequest',
+        FILE_UPLOAD_URL = 'applications/upload';
 
     angular.module('ovApp', [])
     .controller('OvAppCtrl',
-        ['$log', '$scope', 'FnService', 'TableBuilderService', 'WebSocketService',
+        ['$log', '$scope', '$http',
+        'FnService', 'TableBuilderService', 'WebSocketService', 'UrlFnService',
 
-    function ($log, $scope, fs, tbs, wss) {
+    function ($log, $scope, $http, fs, tbs, wss, ufs) {
+        var refreshCtrls;
         $scope.ctrlBtnState = {};
-        // TODO: clean up view
-        // all DOM manipulation (adding styles, getting elements and doing stuff
-        //     with them) should be done in directives
 
         function selCb($event, row) {
+            // selId comes from tableBuilder
             $scope.ctrlBtnState.selection = !!$scope.selId;
-            selectionObj = row;
             $log.debug('Got a click on:', row);
 
-            if ($scope.ctrlBtnState.selection) {
-                $scope.ctrlBtnState.installed = row.state === 'INSTALLED';
-                $scope.ctrlBtnState.active = row.state === 'ACTIVE';
-            } else {
-                $scope.ctrlBtnState.installed = false;
-                $scope.ctrlBtnState.active = false;
-            }
+            refreshCtrls = function () {
+                if ($scope.ctrlBtnState.selection) {
+                    $scope.ctrlBtnState.installed = row.state === INSTALLED;
+                    $scope.ctrlBtnState.active = row.state === ACTIVE;
+                } else {
+                    $scope.ctrlBtnState.installed = false;
+                    $scope.ctrlBtnState.active = false;
+                }
+            };
+
+            refreshCtrls();
+        }
+
+        function respCb() {
+            refreshCtrls && refreshCtrls();
         }
 
         tbs.buildTable({
             scope: $scope,
             tag: 'app',
-            selCb: selCb
+            selCb: selCb,
+            respCb: respCb
         });
 
-        // TODO: use d3 click events -- move to directive
-        d3.select('#app-install').on('click', function () {
-            $log.debug('Initiating install');
-            var evt = document.createEvent("HTMLEvents");
-            evt.initEvent("click", true, true);
-            document.getElementById('file').dispatchEvent(evt);
-        });
-
-        // TODO: use d3 to select elements -- move to directive
-        document.getElementById('app-form-response').onload = function () {
-            document.getElementById('app-form').reset();
-            $scope.$apply();
-            //$scope.sortCallback($scope.sortParams);
+        $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
+                });
+            }
         };
 
-        function appAction(action) {
-            if ($scope.ctrlBtnState.selection) {
-                $log.debug('Initiating ' + action + ' of', selectionObj);
-                wss.sendEvent('appManagementRequest', {action: action, name: selectionObj.id});
+        $scope.$on('FileChanged', function () {
+            var formData = new FormData();
+            if ($scope.appFile) {
+                formData.append('file', $scope.appFile);
+                $http.post(ufs.rsUrl(FILE_UPLOAD_URL), formData, {
+                    transformRequest: angular.identity,
+                    headers: {
+                        'Content-Type': undefined
+                    }
+                })
+                    // TODO: look for finally function to combine lines
+                    // TODO: reexamine reset input value
+                    .success(function () {
+                        $scope.sortCallback($scope.sortParams);
+                        document.getElementById('inputFileForm').reset();
+                    })
+                    .error(function () {
+                        $scope.sortCallback($scope.sortParams);
+                    });
             }
-        }
-
-        // TODO: use d3 to select elements -- move to directive
-        d3.select('#file').on('change', function () {
-            var file = document.getElementById('file').value.replace('C:\\fakepath\\', '');
-            $log.info('Handling file', file);
-            var evt = document.createEvent("HTMLEvents");
-            evt.initEvent("click", true, true);
-            document.getElementById('app-upload').dispatchEvent(evt);
         });
 
-        // TODO: move to directive
-        d3.select('#app-uninstall').on('click', function () { appAction('uninstall'); });
-        d3.select('#app-activate').on('click', function () { appAction('activate'); });
-        d3.select('#app-deactivate').on('click', function () { appAction('deactivate'); });
-
         $log.log('OvAppCtrl has been created');
+    }])
+
+    // triggers the input form to appear when button is clicked
+    .directive('triggerForm', function () {
+        return {
+            restrict: 'A',
+            link: function (scope, elem) {
+                elem.bind('click', function () {
+                    document.getElementById('uploadFile')
+                        .dispatchEvent(new Event('click'));
+                });
+            }
+        };
+    })
+
+    // binds the model file to the scope in scope.appFile
+    // sends upload request to the server
+    .directive('fileModel', ['$parse',
+            function ($parse) {
+        return {
+            restrict: 'A',
+            link: function (scope, elem, attrs) {
+                var model = $parse(attrs.fileModel),
+                    modelSetter = model.assign;
+
+                elem.bind('change', function () {
+                    scope.$apply(function () {
+                        modelSetter(scope, elem[0].files[0]);
+                    });
+                    scope.$emit('FileChanged');
+                });
+            }
+        };
     }]);
 }());
diff --git a/web/gui/src/main/webapp/app/view/port/port.html b/web/gui/src/main/webapp/app/view/port/port.html
index 5683ca2..3e93eb3 100644
--- a/web/gui/src/main/webapp/app/view/port/port.html
+++ b/web/gui/src/main/webapp/app/view/port/port.html
@@ -18,8 +18,8 @@
 <div id="ov-port">
     <div class="tabular-header">
         <h2>
-            Ports for Device {{devId || "(No device selected)"}}
-            ({{tableData.length}} total)
+            Port Statistics for Device {{devId || "(No device selected)"}}
+            ({{tableData.length}} Ports total)
         </h2>
         <div class="ctrl-btns">
             <div class="refresh" ng-class="{active: autoRefresh}"