ONOS-2876 -- GUI - first cut at client-side code for device friendly name setting (still WIP).

Change-Id: I531a2f1fef698cb72a5529f732bb2a0a97e2acc4
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/DeviceViewMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/DeviceViewMessageHandler.java
index fb83cdd..45c42b6 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/DeviceViewMessageHandler.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/DeviceViewMessageHandler.java
@@ -153,6 +153,10 @@
             ObjectNode data = MAPPER.createObjectNode();
 
             data.put(ID, deviceId.toString());
+
+            // TODO: get friendly name from the device
+            data.put(NAME, deviceId.toString());
+
             data.put(TYPE, capitalizeFully(device.type().toString()));
             data.put(TYPE_IID, getTypeIconId(device));
             data.put(MFR, device.manufacturer());
diff --git a/web/gui/src/main/webapp/app/view/device/device.css b/web/gui/src/main/webapp/app/view/device/device.css
index fc08f68..a2ff2a1 100644
--- a/web/gui/src/main/webapp/app/view/device/device.css
+++ b/web/gui/src/main/webapp/app/view/device/device.css
@@ -75,6 +75,30 @@
     margin: 8px 0;
 }
 
+#device-details-panel .name-div {
+    height: 20px;
+    padding: 8px 0 0 8px;
+}
+
+#device-details-panel .name-div span {
+    display: inline-block;
+}
+
+#device-details-panel .name-div .label {
+    font-style: italic;
+    padding-right: 12px;
+    /* works for both light and dark themes ... */
+    color: #777;
+}
+
+#device-details-panel .name-div .value {
+}
+
+#device-details-panel .editable {
+    cursor: pointer;
+    border-bottom: 1px dashed darkgreen;
+}
+
 #device-details-panel .top div.left {
     float: left;
     padding: 0 18px 0 0;
diff --git a/web/gui/src/main/webapp/app/view/device/device.js b/web/gui/src/main/webapp/app/view/device/device.js
index 7a2dc4f..76c0d03 100644
--- a/web/gui/src/main/webapp/app/view/device/device.js
+++ b/web/gui/src/main/webapp/app/view/device/device.js
@@ -26,9 +26,13 @@
 
     // internal state
     var detailsPanel,
-        pStartY, pHeight,
-        top, bottom, iconDiv,
-        wSize;
+        pStartY,
+        pHeight,
+        top,
+        bottom,
+        iconDiv,
+        wSize,
+        editingName = false;
 
     // constants
     var topPdg = 13,
@@ -59,7 +63,9 @@
         if (detailsPanel.isVisible()) {
             $scope.selId = null;
             detailsPanel.hide();
+            return true;
         }
+        return false;
     }
 
     function addCloseBtn(div) {
@@ -68,6 +74,72 @@
         div.on('click', closePanel);
     }
 
+    function getNameSpan() {
+        return top.select('.name-div').select('.value');
+    }
+
+    function nameValid(name) {
+        // TODO: guard against empty strings etc.
+        return true;
+    }
+
+    function editNameSave() {
+        var span = getNameSpan(),
+            newVal;
+        if (editingName) {
+            newVal = span.select('input').property('value');
+
+            $log.debug("TODO: Saving name change... to '" + newVal + "'");
+            if (!nameValid(newVal)) {
+                return editNameCancel();
+            }
+
+            // TODO: send event to server to set friendly name for device
+            $scope.panelData.name = newVal;
+
+            span.html(newVal);
+            span.classed('editable', true);
+            editingName = false;
+        }
+    }
+
+    function editNameCancel() {
+        var span = getNameSpan();
+        if (editingName) {
+            $log.debug("Canceling name change...");
+            span.html($scope.panelData.name);
+            span.classed('editable', true);
+            editingName = false;
+            return true;
+        }
+        return false;
+    }
+
+    function editName() {
+        $log.log('editName() .. editing=' + editingName);
+        var span = getNameSpan();
+        if (!editingName) {
+            editingName = true;
+            span.classed('editable', false);
+            span.html('');
+            span.append('input').classed('name-input', true)
+                .attr('type', 'text')
+                .attr('value', $scope.panelData.name);
+        }
+    }
+
+    function handleEscape() {
+        return editNameCancel() || closePanel();
+    }
+
+    function setUpEditableName(top) {
+        var div = top.append('div').classed('name-div', true);
+
+        div.append('span').classed('label', true);
+        div.append('span').classed('value editable', true)
+            .on('click', editName);
+    }
+
     function setUpPanel() {
         var container, closeBtn, tblDiv;
         detailsPanel.empty();
@@ -80,6 +152,8 @@
         iconDiv = top.append('div').classed('dev-icon', true);
         top.append('h2');
 
+        setUpEditableName(top);
+
         tblDiv = top.append('div').classed('top-tables', true);
         tblDiv.append('div').classed('left', true).append('table');
         tblDiv.append('div').classed('right', true).append('table');
@@ -156,14 +230,23 @@
         detailsPanel.width(tbWidth + ctnrPdg);
     }
 
+    function populateName(div, name) {
+        var lab = div.select('.label'),
+            val = div.select('.value');
+        lab.html('Friendly Name:');
+        val.html(name);
+    }
+
     function populateDetails(details) {
-        var topTbs, btmTbl, ports;
+        var nameDiv, topTbs, btmTbl, ports;
         setUpPanel();
 
+        nameDiv = top.select('.name-div');
         topTbs = top.select('.top-tables');
         btmTbl = bottom.select('table');
         ports = details.ports;
 
+        populateName(nameDiv, details.name);
         populateTop(topTbs, details);
         populateBottom(btmTbl, ports);
 
@@ -278,7 +361,8 @@
             }
             // create key bindings to handle panel
             ks.keyBindings({
-                esc: [closePanel, 'Close the details panel'],
+                enter: editNameSave,
+                esc: [handleEscape, 'Close the details panel'],
                 _helpFormat: ['esc']
             });
             ks.gestureNotes([