ONOS-2325 - GUI -- Table View rows now flash yellow when their information updates. Minor device details panel bug fix.
Change-Id: I78eb0f90af00ce4484255d7e9e0c3c8a10a0eda7
diff --git a/web/gui/src/main/webapp/app/fw/util/fn.js b/web/gui/src/main/webapp/app/fw/util/fn.js
index 3e2fb29..ad441cd 100644
--- a/web/gui/src/main/webapp/app/fw/util/fn.js
+++ b/web/gui/src/main/webapp/app/fw/util/fn.js
@@ -172,6 +172,32 @@
return true;
}
+ // returns true if the two objects have all the same properties
+ function sameObjProps(obj1, obj2) {
+ var key;
+ for (key in obj1) {
+ if (obj1.hasOwnProperty(key)) {
+ if (!(obj1[key] === obj2[key])) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ // returns true if the array contains the object
+ // does NOT use strict object reference equality,
+ // instead checks each property individually for equality
+ function containsObj(arr, obj) {
+ var i;
+ for (i = 0; i < arr.length; i++) {
+ if (sameObjProps(arr[i], obj)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
// return the given string with the first character capitalized.
function cap(s) {
return s.toLowerCase().replace(/^[a-z]/, function (m) {
@@ -227,6 +253,8 @@
inArray: inArray,
removeFromArray: removeFromArray,
isEmptyObject: isEmptyObject,
+ sameObjProps: sameObjProps,
+ containsObj: containsObj,
cap: cap,
noPx: noPx,
noPxStyle: noPxStyle,
diff --git a/web/gui/src/main/webapp/app/fw/widget/table.css b/web/gui/src/main/webapp/app/fw/widget/table.css
index ae5ef03..846ce9e 100644
--- a/web/gui/src/main/webapp/app/fw/widget/table.css
+++ b/web/gui/src/main/webapp/app/fw/widget/table.css
@@ -63,6 +63,17 @@
background-color: #304860;
}
+/* highlighting */
+div.summary-list tr {
+ transition: background-color 500ms;
+}
+.light div.summary-list tr.data-change {
+ background-color: #FDFFDC;
+}
+.dark div.summary-list tr.data-change {
+ background-color: #5A5600;
+}
+
div.summary-list td {
padding: 6px;
text-align: left;
diff --git a/web/gui/src/main/webapp/app/fw/widget/table.js b/web/gui/src/main/webapp/app/fw/widget/table.js
index 21baaa3..0b3191b 100644
--- a/web/gui/src/main/webapp/app/fw/widget/table.js
+++ b/web/gui/src/main/webapp/app/fw/widget/table.js
@@ -26,6 +26,7 @@
// constants
var tableIconTdSize = 33,
pdg = 22,
+ flashTime = 2000,
colWidth = 'col-width',
tableIcon = 'table-icon',
asc = 'asc',
@@ -208,7 +209,46 @@
scope.$on('$destroy', function () {
resetSort();
});
- }
+ };
+ }])
+
+ .directive('onosFlashChanges', ['$log', '$parse', '$timeout',
+ function ($log, $parse, $timeout) {
+ return function (scope, element, attrs) {
+ var rowData = $parse(attrs.row)(scope),
+ id = attrs.rowId,
+ tr = d3.select(element[0]),
+ multiRows = d3.selectAll('.multi-row'),
+ promise;
+
+ scope.$watchCollection('changedData', function (newData) {
+ angular.forEach(newData, function (item) {
+ function classMultiRows(b) {
+ if (!multiRows.empty()) {
+ multiRows.each(function () {
+ d3.select(this).classed('data-change', b);
+ });
+ }
+ }
+
+ if (rowData[id] === item[id]) {
+ tr.classed('data-change', true);
+ classMultiRows(true);
+
+ promise = $timeout(function () {
+ tr.classed('data-change', false);
+ classMultiRows(false);
+ }, flashTime);
+ }
+
+ });
+ });
+ scope.$on('$destroy', function () {
+ if (promise) {
+ $timeout.cancel(promise);
+ }
+ });
+ };
}]);
}());
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 1196dee..7a38feb 100644
--- a/web/gui/src/main/webapp/app/fw/widget/tableBuilder.js
+++ b/web/gui/src/main/webapp/app/fw/widget/tableBuilder.js
@@ -30,7 +30,8 @@
// {
// scope: $scope, <- controller scope
// tag: 'device', <- table identifier
- // selCb: selCb <- row selection callback (optional)
+ // selCb: selCb, <- row selection callback (optional)
+ // respCb: respCb, <- websocket response callback (optional)
// query: params <- query parameters in URL (optional)
// }
// Note: selCb() is passed the row data model of the selected row,
@@ -45,31 +46,54 @@
resp = o.tag + 'DataResponse',
onSel = fs.isF(o.selCb),
onResp = fs.isF(o.respCb),
+ oldTableData = [],
promise;
o.scope.tableData = [];
+ o.scope.changedData = [];
o.scope.sortParams = {};
o.scope.autoRefresh = true;
o.scope.autoRefreshTip = 'Toggle auto refresh';
+ // === websocket functions --------------------
+ // response
function respCb(data) {
o.scope.tableData = data[root];
onResp && onResp();
o.scope.$apply();
- }
+ // checks if data changed for row flashing
+ if (!angular.equals(o.scope.tableData, oldTableData)) {
+ o.scope.changedData = [];
+ // only flash the row if the data already exists
+ if (oldTableData.length) {
+ angular.forEach(o.scope.tableData, function (item) {
+ if (!fs.containsObj(oldTableData, item)) {
+ o.scope.changedData.push(item);
+ }
+ });
+ }
+ angular.copy(o.scope.tableData, oldTableData);
+ }
+ }
+ handlers[resp] = respCb;
+ wss.bindHandlers(handlers);
+
+ // request
function sortCb(params) {
var p = angular.extend({}, params, o.query);
wss.sendEvent(req, p);
}
o.scope.sortCallback = sortCb;
+ // === selecting a row functions ----------------
function selCb($event, selRow) {
o.scope.selId = (o.scope.selId === selRow.id) ? null : selRow.id;
onSel && onSel($event, selRow);
}
o.scope.selectCallback = selCb;
+ // === autoRefresh functions ------------------
function startRefresh() {
promise = $interval(function () {
if (fs.debugOn('widget')) {
@@ -92,10 +116,7 @@
}
o.scope.toggleRefresh = toggleRefresh;
- handlers[resp] = respCb;
- wss.bindHandlers(handlers);
-
- // Cleanup on destroyed scope
+ // === Cleanup on destroyed scope -----------------
o.scope.$on('$destroy', function () {
wss.unbindHandlers(handlers);
stopRefresh();