mirror of
https://github.com/nasa/openmct.git
synced 2025-06-21 08:39:59 +00:00
Merge branch 'master' into open671
Conflicts: main.js platform/commonUI/edit/src/policies/EditableMovePolicy.js platform/commonUI/general/src/directives/MCTTree.js platform/commonUI/general/src/ui/ToggleView.js platform/core/src/actions/ActionCapability.js platform/core/test/models/CachingModelDecoratorSpec.js platform/core/test/services/InstantiateSpec.js platform/features/events/bundle.js platform/features/events/src/DomainColumn.js platform/features/events/src/EventListController.js platform/features/events/src/EventListPopulator.js platform/features/events/src/RangeColumn.js platform/features/events/src/directives/MCTDataTable.js platform/features/events/src/policies/MessagesViewPolicy.js platform/features/events/test/DomainColumnSpec.js platform/features/events/test/EventListControllerSpec.js platform/features/events/test/EventListPopulatorSpec.js platform/features/events/test/RangeColumnSpec.js platform/features/events/test/policies/MessagesViewPolicySpec.js platform/features/rtevents/bundle.js platform/features/rtevents/src/DomainColumn.js platform/features/rtevents/src/RTEventListController.js platform/features/rtevents/src/RangeColumn.js platform/features/rtevents/src/directives/MCTRTDataTable.js platform/features/rtevents/src/policies/RTMessagesViewPolicy.js platform/features/rtevents/test/DomainColumnSpec.js platform/features/rtevents/test/RTEventListControllerSpec.js platform/features/rtevents/test/RangeColumnSpec.js platform/features/rtevents/test/policies/RTMessagesViewPolicySpec.js platform/features/rtscrolling/bundle.js platform/features/rtscrolling/src/DomainColumn.js platform/features/rtscrolling/src/NameColumn.js platform/features/rtscrolling/src/RTScrollingListController.js platform/features/rtscrolling/src/RangeColumn.js platform/features/scrolling/src/DomainColumn.js platform/features/scrolling/src/RangeColumn.js platform/features/scrolling/src/ScrollingListController.js platform/features/scrolling/src/ScrollingListPopulator.js platform/features/scrolling/test/DomainColumnSpec.js platform/features/scrolling/test/RangeColumnSpec.js platform/features/scrolling/test/ScrollingListControllerSpec.js platform/features/scrolling/test/ScrollingListPopulatorSpec.js platform/features/table/src/directives/MCTTable.js platform/features/table/test/controllers/TelemetryTableControllerSpec.js platform/representation/src/gestures/DropGesture.js platform/telemetry/src/TelemetryFormatter.js test-main.js
This commit is contained in:
@ -3,6 +3,15 @@ define(
|
||||
[],
|
||||
function () {
|
||||
|
||||
/**
|
||||
* A controller for the MCTTable directive. Populates scope with
|
||||
* data used for populating, sorting, and filtering
|
||||
* tables.
|
||||
* @param $scope
|
||||
* @param $timeout
|
||||
* @param element
|
||||
* @constructor
|
||||
*/
|
||||
function MCTTableController($scope, $timeout, element) {
|
||||
var self = this;
|
||||
|
||||
@ -11,6 +20,9 @@ define(
|
||||
this.$timeout = $timeout;
|
||||
this.maxDisplayRows = 50;
|
||||
|
||||
this.scrollable = element.find('div');
|
||||
this.scrollable.on('scroll', this.onScroll.bind(this));
|
||||
|
||||
$scope.visibleRows = [];
|
||||
$scope.overrideRowPositioning = false;
|
||||
|
||||
@ -31,8 +43,6 @@ define(
|
||||
|
||||
setDefaults($scope);
|
||||
|
||||
element.find('div').on('scroll', this.onScroll.bind(this));
|
||||
|
||||
$scope.toggleSort = function (key) {
|
||||
if (!$scope.enableSort) {
|
||||
return;
|
||||
@ -49,22 +59,96 @@ define(
|
||||
self.updateRows($scope.rows);
|
||||
};
|
||||
|
||||
/*
|
||||
* Define watches to listen for changes to headers and rows.
|
||||
*/
|
||||
$scope.$watchCollection('filters', function () {
|
||||
self.updateRows(self.$scope.rows);
|
||||
self.updateRows($scope.rows);
|
||||
});
|
||||
$scope.$watchCollection('headers', this.updateHeaders.bind(this));
|
||||
$scope.$watchCollection('rows', this.updateRows.bind(this));
|
||||
$scope.$watch('headers', this.updateHeaders.bind(this));
|
||||
$scope.$watch('rows', this.updateRows.bind(this));
|
||||
|
||||
/*
|
||||
* Listen for rows added individually (eg. for real-time tables)
|
||||
*/
|
||||
$scope.$on('add:row', this.newRow.bind(this));
|
||||
$scope.$on('remove:row', this.removeRow.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* On scroll, calculate which rows indexes are visible and
|
||||
* ensure that an equal number of rows are preloaded for
|
||||
* scrolling in either direction.
|
||||
* If auto-scroll is enabled, this function will scroll to the
|
||||
* bottom of the page
|
||||
* @private
|
||||
*/
|
||||
MCTTableController.prototype.scrollToBottom = function () {
|
||||
var self = this;
|
||||
|
||||
//Use timeout to defer execution until next digest when any
|
||||
// pending UI changes have completed, eg. a new row in the table.
|
||||
if (this.$scope.autoScroll) {
|
||||
this.$timeout(function (){
|
||||
self.scrollable[0].scrollTop = self.scrollable[0].scrollHeight;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles a row add event. Rows can be added as needed using the
|
||||
* `addRow` broadcast event.
|
||||
* @private
|
||||
*/
|
||||
MCTTableController.prototype.newRow = function (event, rowIndex) {
|
||||
var row = this.$scope.rows[rowIndex];
|
||||
//Add row to the filtered, sorted list of all rows
|
||||
if (this.filterRows([row]).length > 0) {
|
||||
this.insertSorted(this.$scope.displayRows, row);
|
||||
}
|
||||
|
||||
this.$timeout(this.setElementSizes.bind(this))
|
||||
.then(this.scrollToBottom.bind(this));
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles a row add event. Rows can be added as needed using the
|
||||
* `addRow` broadcast event.
|
||||
* @private
|
||||
*/
|
||||
MCTTableController.prototype.removeRow = function (event, rowIndex) {
|
||||
var row = this.$scope.rows[rowIndex],
|
||||
// Do a sequential search here. Only way of finding row is by
|
||||
// object equality, so array is in effect unsorted.
|
||||
indexInDisplayRows = this.$scope.displayRows.indexOf(row);
|
||||
if (indexInDisplayRows != -1) {
|
||||
this.$scope.displayRows.splice(indexInDisplayRows, 1);
|
||||
this.setVisibleRows();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
MCTTableController.prototype.onScroll = function (event) {
|
||||
//If user scrolls away from bottom, disable auto-scroll.
|
||||
// Auto-scroll will be re-enabled if user scrolls to bottom again.
|
||||
if (this.scrollable[0].scrollTop <
|
||||
(this.scrollable[0].scrollHeight - this.scrollable[0].offsetHeight)) {
|
||||
this.$scope.autoScroll = false;
|
||||
} else {
|
||||
this.$scope.autoScroll = true;
|
||||
}
|
||||
this.setVisibleRows();
|
||||
this.$scope.$digest();
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets visible rows based on array
|
||||
* content and current scroll state.
|
||||
*/
|
||||
MCTTableController.prototype.setVisibleRows = function () {
|
||||
var self = this,
|
||||
topScroll = event.target.scrollTop,
|
||||
bottomScroll = topScroll + event.target.offsetHeight,
|
||||
target = this.scrollable[0],
|
||||
topScroll = target.scrollTop,
|
||||
bottomScroll = topScroll + target.offsetHeight,
|
||||
firstVisible,
|
||||
lastVisible,
|
||||
totalVisible,
|
||||
@ -72,42 +156,59 @@ define(
|
||||
start,
|
||||
end;
|
||||
|
||||
//No need to scroll
|
||||
if (this.$scope.displayRows.length < this.maxDisplayRows) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (topScroll < this.$scope.headerHeight) {
|
||||
firstVisible = 0;
|
||||
//Check whether need to resynchronize visible with display
|
||||
// rows (if data added)
|
||||
if (this.$scope.visibleRows.length !=
|
||||
this.$scope.displayRows.length){
|
||||
start = 0;
|
||||
end = this.$scope.displayRows.length;
|
||||
} else {
|
||||
//Data is in sync, and no need to calculate scroll,
|
||||
// so do nothing.
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
firstVisible = Math.floor(
|
||||
(topScroll - this.$scope.headerHeight) / this.$scope.rowHeight
|
||||
//rows has exceeded display maximum, so may be necessary to
|
||||
// scroll
|
||||
if (topScroll < this.$scope.headerHeight) {
|
||||
firstVisible = 0;
|
||||
} else {
|
||||
firstVisible = Math.floor(
|
||||
(topScroll - this.$scope.headerHeight) /
|
||||
this.$scope.rowHeight
|
||||
);
|
||||
}
|
||||
lastVisible = Math.ceil(
|
||||
(bottomScroll - this.$scope.headerHeight) /
|
||||
this.$scope.rowHeight
|
||||
);
|
||||
|
||||
totalVisible = lastVisible - firstVisible;
|
||||
numberOffscreen = this.maxDisplayRows - totalVisible;
|
||||
start = firstVisible - Math.floor(numberOffscreen / 2);
|
||||
end = lastVisible + Math.ceil(numberOffscreen / 2);
|
||||
|
||||
if (start < 0) {
|
||||
start = 0;
|
||||
end = Math.min(this.maxDisplayRows,
|
||||
this.$scope.displayRows.length);
|
||||
} else if (end >= this.$scope.displayRows.length) {
|
||||
end = this.$scope.displayRows.length;
|
||||
start = end - this.maxDisplayRows + 1;
|
||||
}
|
||||
if (this.$scope.visibleRows[0] &&
|
||||
this.$scope.visibleRows[0].rowIndex === start &&
|
||||
this.$scope.visibleRows[this.$scope.visibleRows.length - 1]
|
||||
.rowIndex === end) {
|
||||
|
||||
return; // don't update if no changes are required.
|
||||
}
|
||||
}
|
||||
lastVisible = Math.ceil(
|
||||
(bottomScroll - this.$scope.headerHeight) / this.$scope.rowHeight
|
||||
);
|
||||
|
||||
totalVisible = lastVisible - firstVisible;
|
||||
numberOffscreen = this.maxDisplayRows - totalVisible;
|
||||
start = firstVisible - Math.floor(numberOffscreen / 2);
|
||||
end = lastVisible + Math.ceil(numberOffscreen / 2);
|
||||
|
||||
if (start < 0) {
|
||||
start = 0;
|
||||
end = this.$scope.visibleRows.length - 1;
|
||||
} else if (end >= this.$scope.displayRows.length) {
|
||||
end = this.$scope.displayRows.length - 1;
|
||||
start = end - this.maxDisplayRows + 1;
|
||||
}
|
||||
if (this.$scope.visibleRows[0].rowIndex === start &&
|
||||
this.$scope.visibleRows[this.$scope.visibleRows.length-1]
|
||||
.rowIndex === end) {
|
||||
|
||||
return; // don't update if no changes are required.
|
||||
}
|
||||
|
||||
//Set visible rows from display rows, based on calculated offset.
|
||||
this.$scope.visibleRows = this.$scope.displayRows.slice(start, end)
|
||||
.map(function(row, i) {
|
||||
.map(function (row, i) {
|
||||
return {
|
||||
rowIndex: start + i,
|
||||
offsetY: ((start + i) * self.$scope.rowHeight) +
|
||||
@ -115,8 +216,6 @@ define(
|
||||
contents: row
|
||||
};
|
||||
});
|
||||
|
||||
this.$scope.$digest();
|
||||
};
|
||||
|
||||
/**
|
||||
@ -134,10 +233,11 @@ define(
|
||||
this.$scope.filters = {};
|
||||
}
|
||||
// Reset column sort information unless the new headers
|
||||
// contain the column current sorted on.
|
||||
if (this.$scope.enableSort && newHeaders.indexOf(this.$scope.sortColumn) === -1) {
|
||||
this.$scope.sortColumn = undefined;
|
||||
this.$scope.sortDirection = undefined;
|
||||
// contain the column currently sorted on.
|
||||
if (this.$scope.enableSort &&
|
||||
newHeaders.indexOf(this.$scope.sortColumn) === -1) {
|
||||
this.$scope.sortColumn = undefined;
|
||||
this.$scope.sortDirection = undefined;
|
||||
}
|
||||
this.updateRows(this.$scope.rows);
|
||||
};
|
||||
@ -153,80 +253,142 @@ define(
|
||||
firstRow = tbody.find('tr'),
|
||||
column = firstRow.find('td'),
|
||||
headerHeight = thead.prop('offsetHeight'),
|
||||
//row height is hard-coded for now.
|
||||
rowHeight = 20,
|
||||
overallHeight = headerHeight + (rowHeight * (this.$scope.displayRows ? this.$scope.displayRows.length - 1 : 0));
|
||||
columnWidth,
|
||||
tableWidth = 0,
|
||||
overallHeight = headerHeight + (rowHeight *
|
||||
(this.$scope.displayRows ? this.$scope.displayRows.length - 1 : 0));
|
||||
|
||||
this.$scope.columnWidths = [];
|
||||
|
||||
while (column.length) {
|
||||
columnWidth = column.prop('offsetWidth');
|
||||
this.$scope.columnWidths.push(column.prop('offsetWidth'));
|
||||
tableWidth += columnWidth;
|
||||
column = column.next();
|
||||
}
|
||||
this.$scope.headerHeight = headerHeight;
|
||||
this.$scope.rowHeight = rowHeight;
|
||||
this.$scope.totalHeight = overallHeight;
|
||||
this.setVisibleRows();
|
||||
|
||||
this.$scope.visibleRows = this.$scope.displayRows.slice(0, this.maxDisplayRows).map(function(row, i) {
|
||||
return {
|
||||
rowIndex: i,
|
||||
offsetY: (i * self.$scope.rowHeight) + self.$scope.headerHeight,
|
||||
contents: row
|
||||
};
|
||||
});
|
||||
if (tableWidth > 0) {
|
||||
this.$scope.totalWidth = tableWidth + 'px';
|
||||
} else {
|
||||
this.$scope.totalWidth = 'none';
|
||||
}
|
||||
|
||||
this.$scope.overrideRowPositioning = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
MCTTableController.prototype.insertSorted = function (array, element) {
|
||||
var index = -1,
|
||||
self = this,
|
||||
sortKey = this.$scope.sortColumn;
|
||||
|
||||
function binarySearch(searchArray, searchElement, min, max){
|
||||
var sampleAt = Math.floor((max - min) / 2) + min,
|
||||
valA,
|
||||
valB;
|
||||
if (max < min) {
|
||||
return min; // Element is not in array, min gives direction
|
||||
}
|
||||
|
||||
valA = isNaN(searchElement[sortKey].text) ?
|
||||
searchElement[sortKey].text :
|
||||
parseFloat(searchElement[sortKey].text);
|
||||
valB = isNaN(searchArray[sampleAt][sortKey].text) ?
|
||||
searchArray[sampleAt][sortKey].text :
|
||||
parseFloat(searchArray[sampleAt][sortKey].text);
|
||||
|
||||
switch(self.sortComparator(valA, valB)) {
|
||||
case -1:
|
||||
return binarySearch(searchArray, searchElement, min,
|
||||
sampleAt - 1);
|
||||
case 0 :
|
||||
return sampleAt;
|
||||
case 1 :
|
||||
return binarySearch(searchArray, searchElement,
|
||||
sampleAt + 1, max);
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.$scope.sortColumn || !this.$scope.sortDirection) {
|
||||
//No sorting applied, push it on the end.
|
||||
index = array.length;
|
||||
} else {
|
||||
//Sort is enabled, perform binary search to find insertion point
|
||||
index = binarySearch(array, element, 0, array.length - 1);
|
||||
}
|
||||
if (index === -1){
|
||||
array.unshift(element);
|
||||
} else if (index === array.length){
|
||||
array.push(element);
|
||||
} else {
|
||||
array.splice(index, 0, element);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Compare two variables, returning a number that represents
|
||||
* which is larger. Similar to the default array sort
|
||||
* comparator, but does not coerce all values to string before
|
||||
* conversion. Strings are lowercased before comparison.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
MCTTableController.prototype.sortComparator = function (a, b) {
|
||||
var result = 0,
|
||||
sortDirectionMultiplier;
|
||||
|
||||
if (typeof a === "string" && typeof b === "string") {
|
||||
a = a.toLowerCase();
|
||||
b = b.toLowerCase();
|
||||
}
|
||||
|
||||
if (a < b) {
|
||||
result = -1;
|
||||
}
|
||||
if (a > b) {
|
||||
result = 1;
|
||||
}
|
||||
|
||||
if (this.$scope.sortDirection === 'asc') {
|
||||
sortDirectionMultiplier = 1;
|
||||
} else if (this.$scope.sortDirection === 'desc') {
|
||||
sortDirectionMultiplier = -1;
|
||||
}
|
||||
|
||||
return result * sortDirectionMultiplier;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a new array which is a result of applying the sort
|
||||
* criteria defined in $scope.
|
||||
*
|
||||
* Does not modify the array that was passed in.
|
||||
*/
|
||||
MCTTableController.prototype.sortRows = function(rowsToSort) {
|
||||
/**
|
||||
* Compare two variables, returning a number that represents
|
||||
* which is larger. Similar to the default array sort
|
||||
* comparator, but does not coerce all values to string before
|
||||
* conversion. Strings are lowercased before comparison.
|
||||
*/
|
||||
function genericComparator(a, b) {
|
||||
if (typeof a === "string" && typeof b === "string") {
|
||||
a = a.toLowerCase();
|
||||
b = b.toLowerCase();
|
||||
}
|
||||
|
||||
if (a < b) {
|
||||
return -1;
|
||||
}
|
||||
if (a > b) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
MCTTableController.prototype.sortRows = function (rowsToSort) {
|
||||
var self = this,
|
||||
sortKey = this.$scope.sortColumn;
|
||||
|
||||
if (!this.$scope.sortColumn || !this.$scope.sortDirection) {
|
||||
return rowsToSort;
|
||||
}
|
||||
var sortKey = this.$scope.sortColumn,
|
||||
sortDirectionMultiplier;
|
||||
|
||||
if (this.$scope.sortDirection === 'asc') {
|
||||
sortDirectionMultiplier = 1;
|
||||
} else if (this.$scope.sortDirection === 'desc') {
|
||||
sortDirectionMultiplier = -1;
|
||||
}
|
||||
|
||||
return rowsToSort.slice(0).sort(function(a, b) {
|
||||
return rowsToSort.sort(function (a, b) {
|
||||
//If the values to compare can be compared as
|
||||
// numbers, do so. String comparison of number
|
||||
// values can cause inconsistencies
|
||||
var valA = isNaN(a[sortKey].text) ? a[sortKey].text : parseFloat(a[sortKey].text),
|
||||
valB = isNaN(b[sortKey].text) ? b[sortKey].text : parseFloat(b[sortKey].text);
|
||||
var valA = isNaN(a[sortKey].text) ? a[sortKey].text :
|
||||
parseFloat(a[sortKey].text),
|
||||
valB = isNaN(b[sortKey].text) ? b[sortKey].text :
|
||||
parseFloat(b[sortKey].text);
|
||||
|
||||
return genericComparator(valA, valB) *
|
||||
sortDirectionMultiplier;
|
||||
return self.sortComparator(valA, valB);
|
||||
});
|
||||
};
|
||||
|
||||
@ -236,7 +398,7 @@ define(
|
||||
* pre-calculate optimal column sizes without having to render
|
||||
* every row.
|
||||
*/
|
||||
MCTTableController.prototype.findLargestRow = function(rows) {
|
||||
MCTTableController.prototype.findLargestRow = function (rows) {
|
||||
var largestRow = rows.reduce(function (largestRow, row) {
|
||||
Object.keys(row).forEach(function (key) {
|
||||
var currentColumn = row[key].text,
|
||||
@ -257,10 +419,11 @@ define(
|
||||
return largestRow;
|
||||
}, JSON.parse(JSON.stringify(rows[0] || {})));
|
||||
|
||||
largestRow = JSON.parse(JSON.stringify(largestRow));
|
||||
|
||||
// Pad with characters to accomodate variable-width fonts,
|
||||
// and remove characters that would allow word-wrapping.
|
||||
largestRow = JSON.parse(JSON.stringify(largestRow));
|
||||
Object.keys(largestRow).forEach(function(key) {
|
||||
Object.keys(largestRow).forEach(function (key) {
|
||||
var padCharacters,
|
||||
i;
|
||||
|
||||
@ -275,8 +438,15 @@ define(
|
||||
return largestRow;
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculates the widest row in the table, pads that row, and adds
|
||||
* it to the table. Allows the table to size itself, then uses this
|
||||
* as basis for column dimensions.
|
||||
* @private
|
||||
*/
|
||||
MCTTableController.prototype.resize = function (){
|
||||
var largestRow = this.findLargestRow(this.$scope.displayRows);
|
||||
var largestRow = this.findLargestRow(this.$scope.displayRows),
|
||||
self = this;
|
||||
this.$scope.visibleRows = [
|
||||
{
|
||||
rowIndex: 0,
|
||||
@ -285,7 +455,28 @@ define(
|
||||
}
|
||||
];
|
||||
|
||||
this.$timeout(this.setElementSizes.bind(this));
|
||||
//Wait a timeout to allow digest of previous change to visible
|
||||
// rows to happen.
|
||||
this.$timeout(function () {
|
||||
//Remove temporary padding row used for setting column widths
|
||||
self.$scope.visibleRows = [];
|
||||
self.setElementSizes();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @priate
|
||||
*/
|
||||
MCTTableController.prototype.filterAndSort = function (rows) {
|
||||
var displayRows = rows;
|
||||
if (this.$scope.enableFilter) {
|
||||
displayRows = this.filterRows(displayRows);
|
||||
}
|
||||
|
||||
if (this.$scope.enableSort) {
|
||||
displayRows = this.sortRows(displayRows.slice(0));
|
||||
}
|
||||
this.$scope.displayRows = displayRows;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -293,29 +484,27 @@ define(
|
||||
* will be sorted before display.
|
||||
*/
|
||||
MCTTableController.prototype.updateRows = function (newRows) {
|
||||
var displayRows = newRows;
|
||||
//Reset visible rows because new row data available.
|
||||
this.$scope.visibleRows = [];
|
||||
|
||||
this.$scope.overrideRowPositioning = false;
|
||||
|
||||
//Nothing to show because no columns visible
|
||||
if (!this.$scope.displayHeaders) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.$scope.enableFilter) {
|
||||
displayRows = this.filterRows(displayRows);
|
||||
}
|
||||
|
||||
if (this.$scope.enableSort) {
|
||||
displayRows = this.sortRows(displayRows);
|
||||
}
|
||||
this.$scope.displayRows = displayRows;
|
||||
this.filterAndSort(newRows || []);
|
||||
this.resize();
|
||||
};
|
||||
|
||||
/**
|
||||
* Filter rows.
|
||||
* Applies user defined filters to rows. These filters are based on
|
||||
* the text entered in the search areas in each column.
|
||||
* @param rowsToFilter {Object[]} The rows to apply filters to
|
||||
* @returns {Object[]} A filtered copy of the supplied rows
|
||||
*/
|
||||
MCTTableController.prototype.filterRows = function(rowsToFilter) {
|
||||
MCTTableController.prototype.filterRows = function (rowsToFilter) {
|
||||
var filters = {},
|
||||
self = this;
|
||||
|
||||
@ -323,7 +512,7 @@ define(
|
||||
* Returns true if row matches all filters.
|
||||
*/
|
||||
function matchRow(filters, row) {
|
||||
return Object.keys(filters).every(function(key) {
|
||||
return Object.keys(filters).every(function (key) {
|
||||
if (!row[key]) {
|
||||
return false;
|
||||
}
|
||||
@ -336,7 +525,7 @@ define(
|
||||
return rowsToFilter;
|
||||
}
|
||||
|
||||
Object.keys(this.$scope.filters).forEach(function(key) {
|
||||
Object.keys(this.$scope.filters).forEach(function (key) {
|
||||
if (!self.$scope.filters[key]) {
|
||||
return;
|
||||
}
|
||||
|
@ -0,0 +1,123 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web is 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.
|
||||
*
|
||||
* Open MCT Web includes source code licensed under additional open source
|
||||
* licenses. See the Open Source Licenses file (LICENSES.md) included with
|
||||
* this source code distribution or the Licensing information page available
|
||||
* at runtime from the About dialog for additional information.
|
||||
*****************************************************************************/
|
||||
/*global define*/
|
||||
|
||||
define(
|
||||
[
|
||||
'./TelemetryTableController'
|
||||
],
|
||||
function (TableController) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Extends TelemetryTableController and adds real-time streaming
|
||||
* support.
|
||||
* @memberof platform/features/table
|
||||
* @param $scope
|
||||
* @param telemetryHandler
|
||||
* @param telemetryFormatter
|
||||
* @constructor
|
||||
*/
|
||||
function RTTelemetryTableController($scope, telemetryHandler, telemetryFormatter) {
|
||||
TableController.call(this, $scope, telemetryHandler, telemetryFormatter);
|
||||
|
||||
$scope.autoScroll = false;
|
||||
this.maxRows = 100000;
|
||||
|
||||
/*
|
||||
* Determine if auto-scroll should be enabled. Is enabled
|
||||
* automatically when telemetry type is string
|
||||
*/
|
||||
function hasStringTelemetry(domainObject) {
|
||||
var telemetry = domainObject &&
|
||||
domainObject.getCapability('telemetry'),
|
||||
metadata = telemetry ? telemetry.getMetadata() : {},
|
||||
ranges = metadata.ranges || [];
|
||||
|
||||
return ranges.some(function (range) {
|
||||
return range.format === 'string';
|
||||
});
|
||||
}
|
||||
$scope.$watch('domainObject', function (domainObject) {
|
||||
//When a domain object becomes available, check whether the
|
||||
// view should auto-scroll to the bottom.
|
||||
if (domainObject && hasStringTelemetry(domainObject)){
|
||||
$scope.autoScroll = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
RTTelemetryTableController.prototype = Object.create(TableController.prototype);
|
||||
|
||||
/**
|
||||
Override the subscribe function defined on the parent controller in
|
||||
order to handle realtime telemetry instead of historical.
|
||||
*/
|
||||
RTTelemetryTableController.prototype.subscribe = function () {
|
||||
var self = this;
|
||||
self.$scope.rows = undefined;
|
||||
(this.subscriptions || []).forEach(function (unsubscribe){
|
||||
unsubscribe();
|
||||
});
|
||||
|
||||
if (this.handle) {
|
||||
this.handle.unsubscribe();
|
||||
}
|
||||
|
||||
function updateData(){
|
||||
var datum,
|
||||
row;
|
||||
self.handle.getTelemetryObjects().forEach(function (telemetryObject){
|
||||
datum = self.handle.getDatum(telemetryObject);
|
||||
if (datum) {
|
||||
row = self.table.getRowValues(telemetryObject, datum);
|
||||
if (!self.$scope.rows){
|
||||
self.$scope.rows = [row];
|
||||
self.$scope.$digest();
|
||||
} else {
|
||||
self.$scope.rows.push(row);
|
||||
|
||||
if (self.$scope.rows.length > self.maxRows) {
|
||||
self.$scope.$broadcast('remove:row', 0);
|
||||
self.$scope.rows.shift();
|
||||
}
|
||||
|
||||
self.$scope.$broadcast('add:row',
|
||||
self.$scope.rows.length - 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
this.handle = this.$scope.domainObject && this.telemetryHandler.handle(
|
||||
this.$scope.domainObject,
|
||||
updateData,
|
||||
true // Lossless
|
||||
);
|
||||
|
||||
this.setup();
|
||||
};
|
||||
|
||||
return RTTelemetryTableController;
|
||||
}
|
||||
);
|
@ -56,9 +56,9 @@ define(
|
||||
self.populateForm(model);
|
||||
});
|
||||
|
||||
$scope.$watchCollection('configuration.table.columns', function(columns){
|
||||
$scope.$watchCollection('configuration.table.columns', function (columns){
|
||||
if (columns){
|
||||
self.domainObject.useCapability('mutation', function(model) {
|
||||
self.domainObject.useCapability('mutation', function (model) {
|
||||
model.configuration.table.columns = columns;
|
||||
});
|
||||
self.domainObject.getCapability('persistence').persist();
|
||||
|
@ -52,10 +52,11 @@ define(
|
||||
this.handle = undefined;
|
||||
//this.pending = false;
|
||||
this.telemetryHandler = telemetryHandler;
|
||||
this.table = new TableConfiguration($scope.domainObject, telemetryFormatter);
|
||||
this.table = new TableConfiguration($scope.domainObject,
|
||||
telemetryFormatter);
|
||||
this.changeListeners = [];
|
||||
|
||||
$scope.rows = [];
|
||||
$scope.rows = undefined;
|
||||
|
||||
// Subscribe to telemetry when a domain object becomes available
|
||||
this.$scope.$watch('domainObject', function(domainObject){
|
||||
@ -71,21 +72,24 @@ define(
|
||||
this.$scope.$on("$destroy", this.destroy.bind(this));
|
||||
}
|
||||
|
||||
TelemetryTableController.prototype.registerChangeListeners = function() {
|
||||
//Defer registration of change listeners until domain object is
|
||||
// available in order to avoid race conditions
|
||||
|
||||
/**
|
||||
* Defer registration of change listeners until domain object is
|
||||
* available in order to avoid race conditions
|
||||
* @private
|
||||
*/
|
||||
TelemetryTableController.prototype.registerChangeListeners = function () {
|
||||
this.changeListeners.forEach(function (listener) {
|
||||
return listener && listener();
|
||||
});
|
||||
this.changeListeners = [];
|
||||
// When composition changes, re-subscribe to the various
|
||||
// telemetry subscriptions
|
||||
this.changeListeners.push(this.$scope.$watchCollection('domainObject.getModel().composition', this.subscribe.bind(this)));
|
||||
this.changeListeners.push(this.$scope.$watchCollection(
|
||||
'domainObject.getModel().composition', this.subscribe.bind(this)));
|
||||
|
||||
//Change of bounds in time conductor
|
||||
this.changeListeners.push(this.$scope.$on('telemetry:display:bounds', this.subscribe.bind(this)));
|
||||
|
||||
this.changeListeners.push(this.$scope.$on('telemetry:display:bounds',
|
||||
this.subscribe.bind(this)));
|
||||
};
|
||||
|
||||
/**
|
||||
@ -99,16 +103,17 @@ define(
|
||||
};
|
||||
|
||||
/**
|
||||
Create a new subscription. This is called when
|
||||
Create a new subscription. This can be overridden by children to
|
||||
change default behaviour (which is to retrieve historical telemetry
|
||||
only).
|
||||
*/
|
||||
TelemetryTableController.prototype.subscribe = function() {
|
||||
TelemetryTableController.prototype.subscribe = function () {
|
||||
var self = this;
|
||||
|
||||
if (this.handle) {
|
||||
this.handle.unsubscribe();
|
||||
}
|
||||
|
||||
this.$scope.rows = [];
|
||||
|
||||
//Noop because not supporting realtime data right now
|
||||
function noop(){
|
||||
}
|
||||
@ -119,25 +124,37 @@ define(
|
||||
true // Lossless
|
||||
);
|
||||
|
||||
this.handle.request({}, this.addHistoricalData.bind(this));
|
||||
this.handle.request({}).then(this.addHistoricalData.bind(this));
|
||||
|
||||
this.setup();
|
||||
};
|
||||
|
||||
/**
|
||||
* Add any historical data available
|
||||
* Populates historical data on scope when it becomes available
|
||||
* @private
|
||||
*/
|
||||
TelemetryTableController.prototype.addHistoricalData = function(domainObject, series) {
|
||||
var i;
|
||||
for (i=0; i < series.getPointCount(); i++) {
|
||||
this.updateRows(domainObject, this.handle.makeDatum(domainObject, series, i));
|
||||
}
|
||||
TelemetryTableController.prototype.addHistoricalData = function () {
|
||||
var rowData = [],
|
||||
self = this;
|
||||
|
||||
this.handle.getTelemetryObjects().forEach(function (telemetryObject){
|
||||
var series = self.handle.getSeries(telemetryObject) || {},
|
||||
pointCount = series.getPointCount ? series.getPointCount() : 0,
|
||||
i = 0;
|
||||
|
||||
for (; i < pointCount; i++) {
|
||||
rowData.push(self.table.getRowValues(telemetryObject,
|
||||
self.handle.makeDatum(telemetryObject, series, i)));
|
||||
}
|
||||
});
|
||||
|
||||
this.$scope.rows = rowData;
|
||||
};
|
||||
|
||||
/**
|
||||
* Setup table columns based on domain object metadata
|
||||
*/
|
||||
TelemetryTableController.prototype.setup = function() {
|
||||
TelemetryTableController.prototype.setup = function () {
|
||||
var handle = this.handle,
|
||||
table = this.table,
|
||||
self = this;
|
||||
@ -159,7 +176,7 @@ define(
|
||||
};
|
||||
|
||||
/**
|
||||
* Add data to rows
|
||||
* @private
|
||||
* @param object The object for which data is available (table may
|
||||
* be composed of multiple objects)
|
||||
* @param datum The data received from the telemetry source
|
||||
@ -171,6 +188,7 @@ define(
|
||||
/**
|
||||
* When column configuration changes, update the visible headers
|
||||
* accordingly.
|
||||
* @private
|
||||
*/
|
||||
TelemetryTableController.prototype.filterColumns = function (columnConfig) {
|
||||
if (!columnConfig){
|
||||
@ -178,7 +196,7 @@ define(
|
||||
this.table.saveColumnConfiguration(columnConfig);
|
||||
}
|
||||
//Populate headers with visible columns (determined by configuration)
|
||||
this.$scope.headers = Object.keys(columnConfig).filter(function(column) {
|
||||
this.$scope.headers = Object.keys(columnConfig).filter(function (column) {
|
||||
return columnConfig[column];
|
||||
});
|
||||
};
|
||||
|
Reference in New Issue
Block a user