ONOS-5579 Refactoring Details Panel

Added an Editable Textfield Component
Refactored Device View
Refactored Host View

Change-Id: I7ca423f6c198f8e09b20ed4e57e352de04b797e9
This commit is contained in:
Steven Burrows 2017-09-14 11:58:15 +01:00
parent a441074299
commit f50a1775a7
5 changed files with 408 additions and 247 deletions

View File

@ -0,0 +1,16 @@
/*
* Copyright 2017-present Open Networking Foundation
*
* 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.
*/

View File

@ -0,0 +1,178 @@
/*
* Copyright 2017-present Open Networking Foundation
*
* 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.
*/
(function () {
var ps, fs, mast, wss, is, EditableTextComponent;
var panel,
pStartY,
wSize,
wssHandlers = {},
options;
// Constants
var topPdg = 28,
defaultLabelWidth = 110,
defaultValueWidth = 80;
// Elements
var container,
top,
bottom;
function createDetailsPanel(name, _options) {
options = _options;
scope = options.scope;
panel = ps.createPanel(name, options);
calculatePositions();
panel.el().style({
position: 'absolute',
top: pStartY + 'px',
});
hide();
return panel;
}
function calculatePositions() {
pStartY = fs.noPxStyle(d3.select('.tabular-header'), 'height')
+ mast.mastHeight() + topPdg;
wSize = fs.windowSize(pStartY);
pHeight = wSize.height;
}
function hide() {
panel.hide();
}
function setResponse(name, callback) {
var additionalHandler = {};
additionalHandler[name] = callback;
wss.bindHandlers(additionalHandler);
wssHandlers = _.extend({}, wssHandlers, additionalHandler);
}
function addContainers() {
container = panel.append('div').classed('container', true);
top = container.append('div').classed('top', true);
bottom = container.append('div').classed('bottom', true);
}
function addCloseButton(onClose) {
var closeBtn = top.append('div').classed('close-btn', true);
is.loadEmbeddedIcon(closeBtn, 'close', 20);
closeBtn.on('click', onClose || function () {});
}
function addHeading(icon) {
top.append('div').classed('iconDiv ' + icon, true);
new EditableTextComponent(top.append('h2'), {
scope: options.scope,
nameChangeRequest: options.nameChangeRequest,
keyBindings: options.keyBindings,
});
}
function addTable(parent, className) {
return parent.append('div').classed(className, true).append('table');
}
function addProp(tbody, key, value) {
console.log(tbody);
var tr = tbody.append('tr');
function addCell(cls, txt, width) {
tr.append('td').attr('class', cls).attr('width', width).text(txt);
}
addCell('label', key + ' :', defaultLabelWidth);
addCell('value', value, defaultValueWidth);
}
function addPropsList(el, props) {
var tblDiv = el.append('div').classed('top-tables', true);
var left = addTable(tblDiv, 'left').append('tbody');
var right = addTable(tblDiv, 'right').append('tbody');
var keys = _.keys(props);
_.each(props, function (value, key) {
var index = keys.indexOf(key);
if (index < keys.length / 2) {
addProp(left, key, value);
} else {
addProp(right, key, value);
}
});
}
function empty() {
panel.empty();
}
function select(id) {
return panel.el().select(id);
}
function destroy() {
wss.unbindHandlers(wssHandlers);
}
angular.module('onosLayer')
.factory('DetailsPanelService', [
'PanelService', 'FnService', 'MastService', 'WebSocketService',
'IconService', 'EditableTextComponent',
function (_ps_, _fs_, _mast_, _wss_, _is_, _etc_) {
ps = _ps_;
fs = _fs_;
mast = _mast_;
wss = _wss_;
is = _is_;
EditableTextComponent = _etc_;
return {
create: createDetailsPanel,
setResponse: setResponse,
addContainers: addContainers,
addCloseButton: addCloseButton,
addHeading: addHeading,
addPropsList: addPropsList,
// Elements
container: function () { return container; },
top: function () { return top; },
bottom: function () { return bottom; },
select: select,
empty: empty,
hide: hide,
destroy: destroy,
};
},
]);
})();

View File

@ -0,0 +1,119 @@
/*
* Copyright 2017-present Open Networking Foundation
*
* 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.
*/
(function () {
var ks, wss;
var EditableText = function (el, options) {
// constructor
this.el = el;
this.scope = options.scope;
this.options = options;
this.el.classed('editable clickable', true).on('click', this.onEdit.bind(this));
this.editingName = false;
};
EditableText.prototype = {
bindHandlers: function () {
ks.keyBindings({
'enter': this.save.bind(this),
'esc': [this.cancel.bind(this), 'Close the details panel']
});
},
unbindHandlers: function () {
ks.unbindKeys();
if (this.options.keyBindings) {
// Reset to original bindings before editable text
ks.keyBindings(this.options.keyBindings);
}
},
addTextField: function () {
return this.el.append('input').classed('name-input', true)
.attr('type', 'text')
.attr('value', this.scope.panelData.name)[0][0];
},
onEdit: function () {
if (!this.editingName) {
this.el.classed('editable clickable', false);
this.el.text('');
var el = this.addTextField();
el.focus();
el.select();
this.editingName = true;
this.bindHandlers();
ks.enableGlobalKeys(false);
}
},
exit: function (name) {
this.el.text(name);
this.el.classed('editable clickable', true);
this.editingName = false;
ks.enableGlobalKeys(true);
this.unbindHandlers();
},
cancel: function (a, b, ev) {
if (this.editingName) {
this.exit(this.scope.panelData.name);
return true;
}
return false;
},
save: function () {
var id = this.scope.panelData.id,
val,
newVal;
if (this.editingName) {
val = this.el.select('input').property('value').trim();
newVal = val || id;
this.exit(newVal);
this.scope.panelData.name = newVal;
wss.sendEvent(this.options.nameChangeRequest, { id: id, name: val });
}
},
};
angular.module('onosLayer')
.factory('EditableTextComponent', [
'KeyService', 'WebSocketService',
function (_ks_, _wss_) {
ks = _ks_;
wss = _wss_;
return EditableText;
},
]);
})();

View File

@ -22,17 +22,14 @@
'use strict'; 'use strict';
// injected refs // injected refs
var $log, $scope, $loc, fs, mast, ps, wss, is, ns, ks; var $log, $scope, $loc, fs, mast, ps, wss, is, ns, ks, dps;
// internal state // internal state
var detailsPanel, var detailsPanel,
pStartY, pStartY,
pHeight, pHeight,
top, top,
bottom,
iconDiv,
wSize, wSize,
editingName = false,
device; device;
// constants // constants
@ -40,19 +37,12 @@
ctnrPdg = 24, ctnrPdg = 24,
scrollSize = 17, scrollSize = 17,
portsTblPdg = 50, portsTblPdg = 50,
defaultLabelWidth = 110,
defaultValueWidth = 80,
pName = 'device-details-panel', pName = 'device-details-panel',
detailsReq = 'deviceDetailsRequest', detailsReq = 'deviceDetailsRequest',
detailsResp = 'deviceDetailsResponse', detailsResp = 'deviceDetailsResponse',
nameChangeReq = 'deviceNameChangeRequest', nameChangeReq = 'deviceNameChangeRequest',
nameChangeResp = 'deviceNameChangeResponse', nameChangeResp = 'deviceNameChangeResponse',
friendlyProps = [
'URI', 'Type', 'Master ID', 'Chassis ID',
'Vendor', 'H/W Version', 'S/W Version', 'Protocol', 'Serial #',
'Pipeconf',
],
portCols = [ portCols = [
'enabled', 'id', 'speed', 'type', 'elinks_dest', 'name', 'enabled', 'id', 'speed', 'type', 'elinks_dest', 'name',
], ],
@ -60,6 +50,11 @@
'Enabled', 'ID', 'Speed', 'Type', 'Egress Links', 'Name', 'Enabled', 'ID', 'Speed', 'Type', 'Egress Links', 'Name',
]; ];
var keyBindings = {
esc: [closePanel, 'Close the details panel'],
_helpFormat: ['esc'],
};
function closePanel() { function closePanel() {
if (detailsPanel.isVisible()) { if (detailsPanel.isVisible()) {
$scope.selId = null; $scope.selId = null;
@ -69,119 +64,44 @@
return false; return false;
} }
function addCloseBtn(div) {
is.loadEmbeddedIcon(div, 'close', 20);
div.on('click', closePanel);
}
function exitEditMode(nameH2, name) {
nameH2.text(name);
nameH2.classed('editable clickable', true);
editingName = false;
ks.enableGlobalKeys(true);
}
function editNameSave() {
var nameH2 = top.select('h2'),
id = $scope.panelData.id,
val,
newVal;
if (editingName) {
val = nameH2.select('input').property('value').trim();
newVal = val || id;
exitEditMode(nameH2, newVal);
$scope.panelData.name = newVal;
wss.sendEvent(nameChangeReq, { id: id, name: val });
}
}
function editNameCancel() {
if (editingName) {
exitEditMode(top.select('h2'), $scope.panelData.name);
return true;
}
return false;
}
function editName() {
var nameH2 = top.select('h2'),
tf, el;
if (!editingName) {
nameH2.classed('editable clickable', false);
nameH2.text('');
tf = nameH2.append('input').classed('name-input', true)
.attr('type', 'text')
.attr('value', $scope.panelData.name);
el = tf[0][0];
el.focus();
el.select();
editingName = true;
ks.enableGlobalKeys(false);
}
}
function handleEscape() {
return editNameCancel() || closePanel();
}
function setUpPanel() { function setUpPanel() {
var container, closeBtn, tblDiv; // var container, closeBtn, tblDiv;
detailsPanel.empty();
container = detailsPanel.append('div').classed('container', true); dps.empty();
dps.addContainers();
dps.addCloseButton(closePanel);
top = container.append('div').classed('top', true); var top = dps.top();
closeBtn = top.append('div').classed('close-btn', true); var bottom = dps.bottom();
addCloseBtn(closeBtn);
iconDiv = top.append('div').classed('dev-icon', true);
top.append('h2').classed('editable clickable', true).on('click', editName);
tblDiv = top.append('div').classed('top-tables', true); dps.addHeading('dev-icon');
tblDiv.append('div').classed('left', true).append('table'); top.append('div').classed('top-content', true);
tblDiv.append('div').classed('right', true).append('table');
top.append('hr'); top.append('hr');
bottom = container.append('div').classed('bottom', true);
bottom.append('h2').classed('ports-title', true).text('Ports'); bottom.append('h2').classed('ports-title', true).text('Ports');
bottom.append('table'); bottom.append('table');
} }
function addProp(tbody, index, value) { function friendlyPropsList(details) {
var tr = tbody.append('tr'); return {
'URI': device.id,
function addCell(cls, txt, width) { 'Type': device.type,
tr.append('td').attr('class', cls).attr('width', width).text(txt); 'Master ID': details['masterid'],
} 'Chassis ID': details['chassid'],
addCell('label', friendlyProps[index] + ' :', defaultLabelWidth); 'Vendor': device.mfr,
addCell('value', value, defaultValueWidth); 'H/W Version': device.hw,
'S/W Version': device.sw,
'Protocol': details['protocol'],
'Serial #': device.serial,
'Pipeconf': details['pipeconf'],
};
} }
function populateTop(tblDiv, details) { function populateTop(tblDiv, details) {
var leftTbl = tblDiv.select('.left') is.loadEmbeddedIcon(dps.select('.iconDiv'), details._iconid_type, 40);
.select('table') dps.top().select('h2').text(details.name);
.append('tbody'), dps.addPropsList(tblDiv, friendlyPropsList(details));
rightTbl = tblDiv.select('.right')
.select('table')
.append('tbody');
is.loadEmbeddedIcon(iconDiv, details._iconid_type, 40);
top.select('h2').text(details.name);
// === demonstrate use of JsonCodec object see ONOS-5976
addProp(leftTbl, 0, device.id);
addProp(leftTbl, 1, device.type);
addProp(leftTbl, 2, details['masterid']);
addProp(leftTbl, 3, details['chassid']);
addProp(leftTbl, 4, device.mfr);
addProp(rightTbl, 5, device.hw);
addProp(rightTbl, 6, device.sw);
addProp(rightTbl, 7, details['protocol']);
addProp(rightTbl, 8, device.serial);
addProp(rightTbl, 9, details['pipeconf']);
} }
function addPortRow(tbody, port) { function addPortRow(tbody, port) {
@ -193,6 +113,7 @@
} }
function populateBottom(table, ports) { function populateBottom(table, ports) {
var theader = table.append('thead').append('tr'), var theader = table.append('thead').append('tr'),
tbody = table.append('tbody'), tbody = table.append('tbody'),
tbWidth, tbHeight; tbWidth, tbHeight;
@ -200,16 +121,15 @@
friendlyPortCols.forEach(function (col) { friendlyPortCols.forEach(function (col) {
theader.append('th').text(col); theader.append('th').text(col);
}); });
ports.forEach(function (port) { ports.forEach(function (port) {
addPortRow(tbody, port); addPortRow(tbody, port);
}); });
tbWidth = fs.noPxStyle(tbody, 'width') + scrollSize; tbWidth = fs.noPxStyle(tbody, 'width') + scrollSize;
tbHeight = pHeight tbHeight = pHeight
- (fs.noPxStyle(detailsPanel.el() - (fs.noPxStyle(detailsPanel.el().select('.top'), 'height')
.select('.top'), 'height') + fs.noPxStyle(detailsPanel.el().select('.ports-title'), 'height')
+ fs.noPxStyle(detailsPanel.el()
.select('.ports-title'), 'height')
+ portsTblPdg); + portsTblPdg);
table.style({ table.style({
@ -223,15 +143,14 @@
} }
function populateDetails(details) { function populateDetails(details) {
var topTbs, btmTbl, ports; var btmTbl, ports;
setUpPanel(); setUpPanel();
topTbs = top.select('.top-tables'); btmTbl = dps.bottom().select('table');
btmTbl = bottom.select('table');
ports = details.ports; ports = details.ports;
populateTop(topTbs, details); populateTop(dps.select('.top-content'), details);
populateBottom(btmTbl, ports); populateBottom(btmTbl, ports);
detailsPanel.height(pHeight); detailsPanel.height(pHeight);
@ -250,18 +169,19 @@
} }
} }
function createDetailsPane() { function createDetailsPanel() {
detailsPanel = ps.createPanel(pName, { detailsPanel = dps.create(pName, {
width: wSize.width, width: wSize.width,
margin: 0, margin: 0,
hideMargin: 0, hideMargin: 0,
scope: $scope,
keyBindings: keyBindings,
nameChangeRequest: nameChangeReq,
}); });
detailsPanel.el().style({
position: 'absolute', dps.setResponse(detailsResp, respDetailsCb);
top: pStartY + 'px',
});
$scope.hidePanel = function () { detailsPanel.hide(); }; $scope.hidePanel = function () { detailsPanel.hide(); };
detailsPanel.hide();
} }
// Sample functions for detail panel creation // Sample functions for detail panel creation
@ -283,10 +203,10 @@
['$log', '$scope', '$location', 'TableBuilderService', ['$log', '$scope', '$location', 'TableBuilderService',
'TableDetailService', 'FnService', 'TableDetailService', 'FnService',
'MastService', 'PanelService', 'WebSocketService', 'IconService', 'MastService', 'PanelService', 'WebSocketService', 'IconService',
'NavService', 'KeyService', 'NavService', 'KeyService', 'DetailsPanelService',
function (_$log_, _$scope_, _$location_, function (_$log_, _$scope_, _$location_,
tbs, tds, _fs_, _mast_, _ps_, _wss_, _is_, _ns_, _ks_) { tbs, tds, _fs_, _mast_, _ps_, _wss_, _is_, _ns_, _ks_, _dps_) {
var params, var params,
handlers = {}; handlers = {};
@ -300,6 +220,7 @@
is = _is_; is = _is_;
ns = _ns_; ns = _ns_;
ks = _ks_; ks = _ks_;
dps = _dps_;
params = $loc.search(); params = $loc.search();
@ -310,7 +231,7 @@
$scope.meterTip = 'Show meter view for selected device'; $scope.meterTip = 'Show meter view for selected device';
// details panel handlers // details panel handlers
handlers[detailsResp] = respDetailsCb; // handlers[detailsResp] = respDetailsCb;
handlers[nameChangeResp] = respNameCb; handlers[nameChangeResp] = respNameCb;
wss.bindHandlers(handlers); wss.bindHandlers(handlers);
@ -352,6 +273,7 @@
}; };
$scope.$on('$destroy', function () { $scope.$on('$destroy', function () {
dps.destroy();
wss.unbindHandlers(handlers); wss.unbindHandlers(handlers);
}); });
@ -373,7 +295,7 @@
function initPanel() { function initPanel() {
heightCalc(); heightCalc();
createDetailsPane(); createDetailsPanel();
} }
// Safari has a bug where it renders the fixed-layout table wrong // Safari has a bug where it renders the fixed-layout table wrong
@ -384,11 +306,8 @@
initPanel(); initPanel();
} }
// create key bindings to handle panel // create key bindings to handle panel
ks.keyBindings({ ks.keyBindings(keyBindings);
enter: editNameSave,
esc: [handleEscape, 'Close the details panel'],
_helpFormat: ['esc'],
});
ks.gestureNotes([ ks.gestureNotes([
['click', 'Select a row to show device details'], ['click', 'Select a row to show device details'],
['scroll down', 'See more devices'], ['scroll down', 'See more devices'],

View File

@ -22,16 +22,14 @@
'use strict'; 'use strict';
// injected refs // injected refs
var $log, $scope, $loc, fs, mast, ps, wss, is, ns, ks; var $log, $scope, $loc, fs, mast, ps, wss, is, ns, ks, dps;
// internal state // internal state
var detailsPanel, var detailsPanel,
pStartY, pStartY,
pHeight, pHeight,
top, top,
iconDiv, wSize;
wSize,
editingName = false;
// constants // constants
var topPdg = 28, var topPdg = 28,
@ -41,13 +39,10 @@
nameChangeReq = 'hostNameChangeRequest', nameChangeReq = 'hostNameChangeRequest',
nameChangeResp = 'hostNameChangeResponse'; nameChangeResp = 'hostNameChangeResponse';
var propOrder = [ var keyBindings = {
'id', 'ip', 'mac', 'vlan', 'configured', 'location', esc: [closePanel, 'Close the details panel'],
], _helpFormat: ['esc'],
friendlyProps = [ };
'Host ID', 'IP Address', 'MAC Address', 'VLAN',
'Configured', 'Location',
];
function closePanel() { function closePanel() {
if (detailsPanel.isVisible()) { if (detailsPanel.isVisible()) {
@ -58,105 +53,41 @@
return false; return false;
} }
function addCloseBtn(div) {
is.loadEmbeddedIcon(div, 'close', 20);
div.on('click', closePanel);
}
function exitEditMode(nameH2, name) {
nameH2.text(name);
nameH2.classed('editable clickable', true);
editingName = false;
ks.enableGlobalKeys(true);
}
function editNameSave() {
var nameH2 = top.select('h2'),
id = $scope.panelData.id,
ip = $scope.panelData.ip,
val,
newVal;
if (editingName) {
val = nameH2.select('input').property('value').trim();
newVal = val || ip;
exitEditMode(nameH2, newVal);
$scope.panelData.name = newVal;
wss.sendEvent(nameChangeReq, { id: id, name: val });
}
}
function editNameCancel() {
if (editingName) {
exitEditMode(top.select('h2'), $scope.panelData.name);
return true;
}
return false;
}
function editName() {
var nameH2 = top.select('h2'),
tf, el;
if (!editingName) {
nameH2.classed('editable clickable', false);
nameH2.text('');
tf = nameH2.append('input').classed('name-input', true)
.attr('type', 'text')
.attr('value', $scope.panelData.name);
el = tf[0][0];
el.focus();
el.select();
editingName = true;
ks.enableGlobalKeys(false);
}
}
function handleEscape() {
return editNameCancel() || closePanel();
}
function setUpPanel() { function setUpPanel() {
var container, closeBtn;
detailsPanel.empty();
container = detailsPanel.append('div').classed('container', true); dps.empty();
dps.addContainers();
dps.addCloseButton(closePanel);
top = container.append('div').classed('top', true); var top = dps.top();
closeBtn = top.append('div').classed('close-btn', true);
addCloseBtn(closeBtn); dps.addHeading('host-icon');
iconDiv = top.append('div').classed('host-icon', true); top.append('div').classed('top-content', true);
top.append('h2').classed('editable clickable', true).on('click', editName);
top.append('div').classed('top-tables', true);
top.append('hr'); top.append('hr');
} }
function addProp(tbody, index, value) { function friendlyPropsList(details) {
var tr = tbody.append('tr'); return {
'Host ID': details.id,
function addCell(cls, txt) { 'IP Address': details.ip[0],
tr.append('td').attr('class', cls).text(txt); 'MAC Address': details.mac,
} 'VLAN': details.vlan,
addCell('label', friendlyProps[index] + ' :'); 'Configured': details.configured,
addCell('value', value); 'Location': details.location,
};
} }
function populateTop(details) { function populateTop(tblDiv, details) {
var tab = top.select('.top-tables').append('table').append('tbody'); is.loadEmbeddedIcon(dps.select('.iconDiv'), details._iconid_type, 40);
dps.top().select('h2').text(details.name);
is.loadEmbeddedIcon(iconDiv, details._iconid_type, 40); dps.addPropsList(tblDiv, friendlyPropsList(details));
top.select('h2').text(details.name);
propOrder.forEach(function (prop, i) {
addProp(tab, i, details[prop]);
});
} }
function populateDetails(details) { function populateDetails(details) {
setUpPanel(); setUpPanel();
populateTop(details); populateTop(dps.select('.top-content'), details);
detailsPanel.height(pHeight); detailsPanel.height(pHeight);
// configure width based on content.. for now hardcoded // configure width based on content.. for now hardcoded
detailsPanel.width(400); detailsPanel.width(400);
@ -174,18 +105,19 @@
} }
} }
function createDetailsPane() { function createDetailsPanel() {
detailsPanel = ps.createPanel(pName, { detailsPanel = dps.create(pName, {
width: wSize.width, width: wSize.width,
margin: 0, margin: 0,
hideMargin: 0, hideMargin: 0,
scope: $scope,
keyBindings: keyBindings,
nameChangeRequest: nameChangeReq,
}); });
detailsPanel.el().style({
position: 'absolute', dps.setResponse(detailsResp, respDetailsCb);
top: pStartY + 'px',
});
$scope.hidePanel = function () { detailsPanel.hide(); }; $scope.hidePanel = function () { detailsPanel.hide(); };
detailsPanel.hide();
} }
@ -196,12 +128,12 @@
'$location', '$location',
'TableBuilderService', 'TableBuilderService',
'FnService', 'MastService', 'PanelService', 'WebSocketService', 'FnService', 'MastService', 'PanelService', 'WebSocketService',
'IconService', 'NavService', 'KeyService', 'IconService', 'NavService', 'KeyService', 'DetailsPanelService',
function (_$log_, _$scope_, _$location_, function (_$log_, _$scope_, _$location_,
tbs, tbs,
_fs_, _mast_, _ps_, _wss_, _fs_, _mast_, _ps_, _wss_,
_is_, _ns_, _ks_) { _is_, _ns_, _ks_, _dps_) {
var params, var params,
handlers = {}; handlers = {};
@ -216,13 +148,13 @@
is = _is_; is = _is_;
ns = _ns_; ns = _ns_;
ks = _ks_; ks = _ks_;
dps = _dps_;
params = $loc.search(); params = $loc.search();
$scope.panelData = {}; $scope.panelData = {};
// details panel handlers // details panel handlers
handlers[detailsResp] = respDetailsCb;
handlers[nameChangeResp] = respNameCb; handlers[nameChangeResp] = respNameCb;
wss.bindHandlers(handlers); wss.bindHandlers(handlers);
@ -254,6 +186,7 @@
}; };
$scope.$on('$destroy', function () { $scope.$on('$destroy', function () {
dps.destroy();
wss.unbindHandlers(handlers); wss.unbindHandlers(handlers);
}); });
@ -261,7 +194,7 @@
}]) }])
.directive('hostDetailsPanel', .directive('hostDetailsPanel',
['$rootScope', '$window', '$timeout', 'KeyService', ['$rootScope', '$window', '$timeout', 'KeyService', 'DetailsPanelService',
function ($rootScope, $window, $timeout, ks) { function ($rootScope, $window, $timeout, ks) {
return function (scope) { return function (scope) {
var unbindWatch; var unbindWatch;
@ -275,7 +208,7 @@
function initPanel() { function initPanel() {
heightCalc(); heightCalc();
createDetailsPane(); createDetailsPanel();
} }
// Safari has a bug where it renders the fixed-layout table wrong // Safari has a bug where it renders the fixed-layout table wrong
@ -286,11 +219,7 @@
initPanel(); initPanel();
} }
// create key bindings to handle panel // create key bindings to handle panel
ks.keyBindings({ ks.keyBindings(keyBindings);
enter: editNameSave,
esc: [handleEscape, 'Close the details panel'],
_helpFormat: ['esc'],
});
ks.gestureNotes([ ks.gestureNotes([
['click', 'Select a row to show device details'], ['click', 'Select a row to show device details'],
['scroll down', 'See more devices'], ['scroll down', 'See more devices'],