Bri Prebilic Cole
Committed by Gerrit Code Review

ONOS-1783 - GUI -- Refresh buttons for tabular views added. Minor table.js refactor.

Change-Id: Iee6c65fa8477b367e40a556c3c820ca454601a5f
......@@ -24,3 +24,63 @@
padding-top: 20px;
padding-bottom: 20px;
}
/* Tabular view upper right control buttons */
div.ctrl-btns {
display: inline-block;
float: right;
height: 44px;
margin-right: 24px;
margin-top: 7px;
}
div.ctrl-btns div {
display: inline-block;
padding: 4px;
cursor: pointer;
}
/* Inactive */
.light .ctrl-btns div g.icon rect,
.light .ctrl-btns div:hover g.icon rect {
fill: #eee;
}
.dark .ctrl-btns div g.icon rect,
.dark .ctrl-btns div:hover g.icon rect {
fill: #222;
}
.light .ctrl-btns div g.icon use {
fill: #ddd;
}
.dark .ctrl-btns div g.icon use {
fill: #333;
}
/* Active hover */
.light .ctrl-btns div.active:hover g.icon rect {
fill: #800;
}
.dark .ctrl-btns div.active:hover g.icon rect {
fill: #CE5650;
}
/* Active */
.light .ctrl-btns div.active g.icon use {
fill: #fff;
}
.dark .ctrl-btns div.active g.icon use {
fill: #eee;
}
.light .ctrl-btns div.active g.icon rect {
fill: #bbb;
}
.dark .ctrl-btns div.active g.icon rect {
fill: #444;
}
......
......@@ -37,6 +37,8 @@
play: 'play',
stop: 'stop',
crown: 'crown',
upArrow: 'triangleUp',
downArrow: 'triangleDown',
......@@ -191,7 +193,7 @@
return g;
}
function createSortIcon() {
function sortIcons() {
function sortAsc(div) {
div.style('display', 'inline-block');
loadEmbeddedIcon(div, 'upArrow', 10);
......@@ -236,7 +238,7 @@
addDeviceIcon: addDeviceIcon,
addHostIcon: addHostIcon,
iconConfig: function () { return config; },
createSortIcon: createSortIcon
sortIcons: sortIcons
};
}]);
......
......@@ -27,11 +27,15 @@
var tableIconTdSize = 33,
pdg = 12,
colWidth = 'col-width',
tableIcon = 'table-icon';
tableIcon = 'table-icon',
asc = 'asc',
desc = 'desc',
none = 'none';
// internal state
var currCol = {},
prevCol = {};
prevCol = {},
sortIconAPI;
// Functions for creating a fixed header on a table (Angular Directive)
......@@ -115,48 +119,49 @@
setTableHeight(th, tb);
}
// Functions for sorting table rows by header and choosing appropriate icon
// Functions for sorting table rows by header
function updateSortingIcons(thElem, api) {
var div;
function updateSortDirection(thElem) {
sortIconAPI.sortNone(thElem.select('div'));
currCol.div = thElem.append('div');
currCol.colId = thElem.attr('colId');
if (currCol.colId === prevCol.colId) {
(currCol.icon === 'downArrow') ?
currCol.icon = 'upArrow' :
currCol.icon = 'downArrow';
prevCol.icon = currCol.icon;
} else {
currCol.icon = 'upArrow';
prevCol.icon = 'tableColSortNone';
}
div = thElem.select('div');
api.sortNone(div);
div = thElem.append('div');
if (currCol.icon === 'upArrow') {
api.sortAsc(div);
(currCol.dir === desc) ? currCol.dir = asc : currCol.dir = desc;
prevCol.dir = currCol.dir;
} else {
api.sortDesc(div);
currCol.dir = asc;
prevCol.dir = none;
}
(currCol.dir === asc) ?
sortIconAPI.sortAsc(currCol.div) : sortIconAPI.sortDesc(currCol.div);
if (prevCol.colId !== undefined &&
prevCol.icon === 'tableColSortNone') {
api.sortNone(prevCol.elem.select('div'));
if (prevCol.colId && prevCol.dir === none) {
sortIconAPI.sortNone(prevCol.div);
}
prevCol.colId = currCol.colId;
prevCol.elem = thElem;
prevCol.div = currCol.div;
}
function sortRequestParams() {
return {
sortCol: currCol.colId,
sortDir: (currCol.icon === 'upArrow' ? 'asc' : 'desc')
sortDir: currCol.dir
};
}
function resetSortIcons() {
if (currCol.div) {
sortIconAPI.sortNone(currCol.div);
}
if (prevCol.div) {
sortIconAPI.sortNone(prevCol.div);
}
currCol = {};
prevCol = {};
}
angular.module('onosWidget')
.directive('onosFixedHeader', ['$window', 'FnService', 'MastService',
function (_$window_, _fs_, _mast_) {
......@@ -210,8 +215,8 @@
link: function (scope, element) {
$log = _$log_;
is = _is_;
var table = d3.select(element[0]),
sortIconAPI = is.createSortIcon();
var table = d3.select(element[0]);
sortIconAPI = is.sortIcons();
// when a header is clicked, change its icon tag
// and get sorting order to send to the server.
......@@ -219,7 +224,7 @@
var thElem = d3.select(this);
if (thElem.attr('sortable') === '') {
updateSortingIcons(thElem, sortIconAPI);
updateSortDirection(thElem);
scope.ctrlCallback({
requestParams: sortRequestParams()
});
......@@ -227,6 +232,16 @@
});
}
};
}])
.factory('TableService', ['$log', 'IconService',
function ($log, is) {
sortIconAPI = is.sortIcons();
return {
resetSortIcons: resetSortIcons
};
}]);
}());
......
......@@ -23,59 +23,11 @@
}
#ov-app div.ctrl-btns {
display:inline-block;
float: right;
width: 200px;
height: 44px;
margin-right: 24px;
margin-top: 7px;
width: 290px;
}
div.ctrl-btns div {
display: inline-block;
padding: 4px;
cursor: pointer;
}
/* Inactive */
.light .ctrl-btns div g.icon rect,
.light .ctrl-btns div:hover g.icon rect {
fill: #eee;
}
.dark .ctrl-btns div g.icon rect,
.dark .ctrl-btns div:hover g.icon rect {
fill: #222;
}
.light .ctrl-btns div g.icon use {
fill: #ddd;
}
.dark .ctrl-btns div g.icon use {
fill: #333;
}
/* Active hover */
.light .ctrl-btns div.active:hover g.icon rect {
fill: #800;
#ov-app div.ctrl-btns div.separator {
cursor: auto;
width: 24px;
border: none;
}
.dark .ctrl-btns div.active:hover g.icon rect {
fill: #CE5650;
}
/* Active */
.light .ctrl-btns div.active g.icon use {
fill: #fff;
}
.dark .ctrl-btns div.active g.icon use {
fill: #eee;
}
.light .ctrl-btns div.active g.icon rect {
fill: #bbb;
}
.dark .ctrl-btns div.active g.icon rect {
fill: #444;
}
......
......@@ -3,6 +3,10 @@
<div class="tabular-header">
<h2>Applications ({{ctrl.tableData.length}} total)</h2>
<div class="ctrl-btns">
<div class="refresh active"
icon icon-size="36" icon-id="crown"
ng-click="refresh()"></div>
<div class="separator"></div>
<div id="app-install" icon icon-size="36" icon-id="plus" class="active"></div>
<div id="app-activate" icon icon-size="36" icon-id="play"></div>
<div id="app-deactivate" icon icon-size="36" icon-id="stop"></div>
......
......@@ -25,9 +25,9 @@
angular.module('ovApp', [])
.controller('OvAppCtrl',
['$log', '$scope', 'TableBuilderService', 'WebSocketService',
['$log', '$scope', 'TableService', 'TableBuilderService', 'WebSocketService',
function ($log, $scope, tbs, wss) {
function ($log, $scope, ts, tbs, wss) {
function selCb($event, row) {
selRow = angular.element($event.currentTarget);
selection = row;
......@@ -45,6 +45,12 @@
document.getElementById('file').dispatchEvent(evt);
});
$scope.refresh = function () {
$log.debug('Refreshing application page');
ts.resetSortIcons();
$scope.sortCallback();
};
function appAction(action) {
if (selection) {
$log.debug('Initiating uninstall of', selection);
......
......@@ -18,5 +18,10 @@
ONOS GUI -- Cluster View -- CSS file
*/
#ov-cluster td {
#ov-cluster h2 {
display: inline-block;
}
#ov-cluster div.ctrl-btns {
width: 45px;
}
\ No newline at end of file
......
......@@ -18,6 +18,11 @@
<div id="ov-cluster">
<div class="tabular-header">
<h2>Cluster Nodes ({{ctrl.tableData.length}} total)</h2>
<div class="ctrl-btns">
<div class="refresh active"
icon icon-size="36" icon-id="crown"
ng-click="refresh()"></div>
</div>
</div>
<table class="summary-list"
......
......@@ -23,15 +23,21 @@
angular.module('ovCluster', [])
.controller('OvClusterCtrl',
['$log', '$scope', 'TableBuilderService',
['$log', '$scope', 'TableService', 'TableBuilderService',
function ($log, $scope, tbs) {
function ($log, $scope, ts, tbs) {
tbs.buildTable({
self: this,
scope: $scope,
tag: 'cluster'
});
$scope.refresh = function () {
$log.debug('Refreshing cluster nodes page');
ts.resetSortIcons();
$scope.sortCallback();
};
$log.log('OvClusterCtrl has been created');
}]);
}());
......
......@@ -18,6 +18,14 @@
ONOS GUI -- Device View -- CSS file
*/
#ov-device h2 {
display: inline-block;
}
#ov-device div.ctrl-btns {
width: 45px;
}
/* More in generic panel.css */
#device-details-panel.floatpanel {
......
......@@ -2,6 +2,11 @@
<div id="ov-device">
<div class="tabular-header">
<h2>Devices ({{ctrl.tableData.length}} total)</h2>
<div class="ctrl-btns">
<div class="refresh active"
icon icon-size="36" icon-id="crown"
ng-click="refresh()"></div>
</div>
</div>
<table class="summary-list"
......
......@@ -203,12 +203,12 @@
angular.module('ovDevice', [])
.controller('OvDeviceCtrl',
['$log', '$scope', 'TableBuilderService', 'FnService',
['$log', '$scope', 'TableService', 'TableBuilderService', 'FnService',
'MastService', 'PanelService', 'WebSocketService', 'IconService',
'ButtonService', 'NavService', 'TooltipService',
function (_$log_, _$scope_,
tbs, _fs_, _mast_, _ps_, _wss_, _is_, _bns_, _ns_, _ttip_) {
ts, tbs, _fs_, _mast_, _ps_, _wss_, _is_, _bns_, _ns_, _ttip_) {
$log = _$log_;
$scope = _$scope_;
fs = _fs_;
......@@ -243,6 +243,12 @@
tag: 'device',
selCb: selCb
});
$scope.refresh = function () {
$log.debug('Refreshing devices page');
ts.resetSortIcons();
$scope.sortCallback();
};
createDetailsPane();
// details panel handlers
......
......@@ -18,6 +18,14 @@
ONOS GUI -- Flow View -- CSS file
*/
#ov-flow h2 {
display: inline-block;
}
#ov-flow div.ctrl-btns {
width: 45px;
}
.light #ov-flow tr:nth-child(6n + 2),
.light #ov-flow tr:nth-child(6n + 3),
.light #ov-flow tr:nth-child(6n + 4) {
......
......@@ -2,9 +2,14 @@
<div id="ov-flow">
<div class="tabular-header">
<h2>
Flows for Device {{ctrl.devId || "none"}}
Flows for Device {{ctrl.devId || "(No device selected)"}}
({{ctrl.tableData.length}} total)
</h2>
<div class="ctrl-btns">
<div class="refresh active"
icon icon-size="36" icon-id="crown"
ng-click="refresh()"></div>
</div>
</div>
<table class="summary-list"
......
......@@ -22,19 +22,21 @@
'use strict';
// injected references
var $log, $scope, $location, fs, tbs;
var $log, $scope, $location, fs, ts, tbs;
angular.module('ovFlow', [])
.controller('OvFlowCtrl',
['$log', '$scope', '$location', 'FnService', 'TableBuilderService',
['$log', '$scope', '$location',
'FnService', 'TableService', 'TableBuilderService',
function (_$log_, _$scope_, _$location_, _fs_, _tbs_) {
function (_$log_, _$scope_, _$location_, _fs_, _ts_, _tbs_) {
var self = this,
params;
$log = _$log_;
$scope = _$scope_;
$location = _$location_;
fs = _fs_;
ts = _ts_;
tbs = _tbs_;
params = $location.search();
......@@ -49,6 +51,12 @@
query: params
});
$scope.refresh = function () {
$log.debug('Refreshing flows page');
ts.resetSortIcons();
$scope.sortCallback();
};
$log.log('OvFlowCtrl has been created');
}]);
}());
......
......@@ -18,5 +18,10 @@
ONOS GUI -- Host View -- CSS file
*/
#ov-host td {
#ov-host h2 {
display: inline-block;
}
#ov-host div.ctrl-btns {
width: 45px;
}
\ No newline at end of file
......
......@@ -2,6 +2,11 @@
<div id="ov-host">
<div class="tabular-header">
<h2>Hosts ({{ctrl.tableData.length}} total)</h2>
<div class="ctrl-btns">
<div class="refresh active"
icon icon-size="36" icon-id="crown"
ng-click="refresh()"></div>
</div>
</div>
<table class="summary-list"
......
......@@ -23,15 +23,21 @@
angular.module('ovHost', [])
.controller('OvHostCtrl',
['$log', '$scope', 'TableBuilderService',
['$log', '$scope', 'TableService', 'TableBuilderService',
function ($log, $scope, tbs) {
function ($log, $scope, ts, tbs) {
tbs.buildTable({
self: this,
scope: $scope,
tag: 'host'
});
$scope.refresh = function () {
$log.debug('Refreshing hosts page');
ts.resetSortIcons();
$scope.sortCallback();
};
$log.log('OvHostCtrl has been created');
}]);
}());
......
......@@ -18,6 +18,14 @@
ONOS GUI -- Intent View -- CSS file
*/
#ov-intent h2 {
display: inline-block;
}
#ov-intent div.ctrl-btns {
width: 45px;
}
.light #ov-intent tr:nth-child(6n + 2),
.light #ov-intent tr:nth-child(6n + 3),
.light #ov-intent tr:nth-child(6n + 4) {
......
......@@ -18,6 +18,11 @@
<div id="ov-intent">
<div class="tabular-header">
<h2>Intents ({{ctrl.tableData.length}} total)</h2>
<div class="ctrl-btns">
<div class="refresh active"
icon icon-size="36" icon-id="crown"
ng-click="refresh()"></div>
</div>
</div>
<table class="summary-list"
onos-fixed-header
......
......@@ -23,15 +23,21 @@
angular.module('ovIntent', [])
.controller('OvIntentCtrl',
['$log', '$scope', 'TableBuilderService',
['$log', '$scope', 'TableService', 'TableBuilderService',
function ($log, $scope, tbs) {
function ($log, $scope, ts, tbs) {
tbs.buildTable({
self: this,
scope: $scope,
tag: 'intent'
});
$scope.refresh = function () {
$log.debug('Refreshing intents page');
ts.resetSortIcons();
$scope.sortCallback();
};
$log.log('OvIntentCtrl has been created');
}]);
}());
......
......@@ -18,5 +18,10 @@
ONOS GUI -- Link View -- CSS file
*/
#ov-link td {
#ov-link h2 {
display: inline-block;
}
#ov-link div.ctrl-btns {
width: 45px;
}
\ No newline at end of file
......
......@@ -18,6 +18,11 @@
<div id="ov-link">
<div class="tabular-header">
<h2>Links ({{ctrl.tableData.length}} total)</h2>
<div class="ctrl-btns">
<div class="refresh active"
icon icon-size="36" icon-id="crown"
ng-click="refresh()"></div>
</div>
</div>
<table class="summary-list"
......
......@@ -23,15 +23,21 @@
angular.module('ovLink', [])
.controller('OvLinkCtrl',
['$log', '$scope', 'TableBuilderService',
['$log', '$scope', 'TableService', 'TableBuilderService',
function ($log, $scope, tbs) {
function ($log, $scope, ts, tbs) {
tbs.buildTable({
self: this,
scope: $scope,
tag: 'link'
});
$scope.refresh = function () {
$log.debug('Refreshing links page');
ts.resetSortIcons();
$scope.sortCallback();
};
$log.log('OvLinkCtrl has been created');
}]);
}());
......
......@@ -19,7 +19,7 @@
*/
describe('factory: fw/widget/table.js', function () {
var $log, $compile, $rootScope,
fs, mast, is,
fs, ts, mast, is,
scope, compiled,
table, thead, tbody, mockHeader,
mockH2Height = 20,
......@@ -99,11 +99,12 @@ describe('factory: fw/widget/table.js', function () {
});
beforeEach(inject(function (_$log_, _$compile_, _$rootScope_,
FnService, MastService, IconService) {
FnService, TableService, MastService, IconService) {
$log = _$log_;
$compile = _$compile_;
$rootScope = _$rootScope_;
fs = FnService;
ts = TableService;
mast = MastService;
is = IconService;
}));
......@@ -127,6 +128,16 @@ describe('factory: fw/widget/table.js', function () {
mockHeader.remove();
});
it('should define TableBuilderService', function () {
expect(ts).toBeDefined();
});
it('should define api functions', function () {
expect(fs.areFunctions(ts, [
'resetSortIcons'
])).toBeTruthy();
});
function compileTable() {
compiled = $compile(table);
compiled(scope);
......@@ -204,26 +215,22 @@ describe('factory: fw/widget/table.js', function () {
thElems[1].click();
currentTh = angular.element(thElems[1]);
div = currentTh.find('div');
expect(div.html()).toBe('<svg class="embeddedIcon" ' +
'width="10" height="10" viewBox="0 0 50 50">' +
'<g class="icon upArrow">' +
'<rect width="50" height="50" rx="5"></rect>' +
'<use width="50" height="50" class="glyph" ' +
'xmlns:xlink="http://www.w3.org/1999/xlink" ' +
'xlink:href="#triangleUp">' +
'</use>' +
'</g></svg>');
expect(div.html()).toBe(
'<svg class="embeddedIcon" width="10" height="10" viewBox="0 0 ' +
'50 50"><g class="icon upArrow"><rect width="50" height="50" ' +
'rx="5"></rect><use width="50" height="50" class="glyph" xmlns:' +
'xlink="http://www.w3.org/1999/xlink" xlink:href="#triangleUp">' +
'</use></g></svg>'
);
thElems[1].click();
div = currentTh.find('div');
expect(div.html()).toBe('<svg class="embeddedIcon" ' +
'width="10" height="10" viewBox="0 0 50 50">' +
'<g class="icon downArrow">' +
'<rect width="50" height="50" rx="5"></rect>' +
'<use width="50" height="50" class="glyph" ' +
'xmlns:xlink="http://www.w3.org/1999/xlink" ' +
'xlink:href="#triangleDown">' +
'</use>' +
'</g></svg>');
expect(div.html()).toBe(
'<svg class="embeddedIcon" width="10" height="10" viewBox="0 0 ' +
'50 50"><g class="icon downArrow"><rect width="50" height="50" ' +
'rx="5"></rect><use width="50" height="50" class="glyph" xmlns:' +
'xlink="http://www.w3.org/1999/xlink" xlink:href="#triangleDown">' +
'</use></g></svg>'
);
thElems[2].click();
div = currentTh.children();
......@@ -233,15 +240,13 @@ describe('factory: fw/widget/table.js', function () {
// the new element should have the ascending icon
currentTh = angular.element(thElems[2]);
div = currentTh.children();
expect(div.html()).toBe('<svg class="embeddedIcon" ' +
'width="10" height="10" viewBox="0 0 50 50">' +
'<g class="icon upArrow">' +
'<rect width="50" height="50" rx="5"></rect>' +
'<use width="50" height="50" class="glyph" ' +
'xmlns:xlink="http://www.w3.org/1999/xlink" ' +
'xlink:href="#triangleUp">' +
'</use>' +
'</g></svg>');
expect(div.html()).toBe(
'<svg class="embeddedIcon" width="10" height="10" viewBox="0 0 ' +
'50 50"><g class="icon upArrow"><rect width="50" height="50" ' +
'rx="5"></rect><use width="50" height="50" class="glyph" xmlns:' +
'xlink="http://www.w3.org/1999/xlink" xlink:href="#triangleUp">' +
'</use></g></svg>'
);
}
it('should affirm that onos-fixed-header is working', function () {
......@@ -264,8 +269,7 @@ describe('factory: fw/widget/table.js', function () {
compileTable();
verifyGivenTags('onos-sortable-header');
// ctrlCallback functionality is tested in device-spec
// only checking that it has been called correctly in the directive
scope.sortCallback = jasmine.createSpy('sortCallback');
thElems = thead.find('th');
......@@ -273,4 +277,8 @@ describe('factory: fw/widget/table.js', function () {
verifyIcons(thElems);
});
// Note: testing resetSortIcons isn't feasible because due to the nature of
// directive compilation: they are jQuery elements, not d3 elements,
// so the function doesn't work.
});
......