mirror of
https://github.com/nasa/openmct.git
synced 2025-06-19 07:38:15 +00:00
[Table] #670 Added new view type 'table'
This commit is contained in:
64
platform/features/table/src/DomainColumn.js
Normal file
64
platform/features/table/src/DomainColumn.js
Normal file
@ -0,0 +1,64 @@
|
||||
/*****************************************************************************
|
||||
* 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,moment*/
|
||||
|
||||
/**
|
||||
* Module defining DomainColumn.
|
||||
*/
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* A column which will report telemetry domain values
|
||||
* (typically, timestamps.) Used by the ScrollingListController.
|
||||
*
|
||||
* @memberof platform/features/table
|
||||
* @constructor
|
||||
* @param domainMetadata an object with the machine- and human-
|
||||
* readable names for this domain (in `key` and `name`
|
||||
* fields, respectively.)
|
||||
* @param {TelemetryFormatter} telemetryFormatter the telemetry
|
||||
* formatting service, for making values human-readable.
|
||||
*/
|
||||
function DomainColumn(domainMetadata, telemetryFormatter) {
|
||||
this.domainMetadata = domainMetadata;
|
||||
this.telemetryFormatter = telemetryFormatter;
|
||||
}
|
||||
|
||||
DomainColumn.prototype.getTitle = function () {
|
||||
return this.domainMetadata.name;
|
||||
};
|
||||
|
||||
DomainColumn.prototype.getValue = function (domainObject, datum) {
|
||||
return {
|
||||
text: this.telemetryFormatter.formatDomainValue(
|
||||
datum[this.domainMetadata.key],
|
||||
this.domainMetadata.format
|
||||
)
|
||||
};
|
||||
};
|
||||
|
||||
return DomainColumn;
|
||||
}
|
||||
);
|
54
platform/features/table/src/NameColumn.js
Normal file
54
platform/features/table/src/NameColumn.js
Normal file
@ -0,0 +1,54 @@
|
||||
/*****************************************************************************
|
||||
* 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,Promise*/
|
||||
|
||||
/**
|
||||
* Module defining NameColumn. Created by vwoeltje on 11/18/14.
|
||||
*/
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* A column which will report the name of the domain object
|
||||
* which exposed specific telemetry values.
|
||||
*
|
||||
* @memberof platform/features/table
|
||||
* @constructor
|
||||
*/
|
||||
function NameColumn() {
|
||||
}
|
||||
|
||||
NameColumn.prototype.getTitle = function () {
|
||||
return "Name";
|
||||
};
|
||||
|
||||
NameColumn.prototype.getValue = function (domainObject) {
|
||||
return {
|
||||
text: domainObject.getModel().name
|
||||
};
|
||||
};
|
||||
|
||||
return NameColumn;
|
||||
}
|
||||
);
|
67
platform/features/table/src/RangeColumn.js
Normal file
67
platform/features/table/src/RangeColumn.js
Normal file
@ -0,0 +1,67 @@
|
||||
/*****************************************************************************
|
||||
* 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,Promise*/
|
||||
|
||||
/**
|
||||
* Module defining DomainColumn. Created by vwoeltje on 11/18/14.
|
||||
*/
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* A column which will report telemetry range values
|
||||
* (typically, measurements.) Used by the ScrollingListController.
|
||||
*
|
||||
* @memberof platform/features/table
|
||||
* @constructor
|
||||
* @param rangeMetadata an object with the machine- and human-
|
||||
* readable names for this range (in `key` and `name`
|
||||
* fields, respectively.)
|
||||
* @param {TelemetryFormatter} telemetryFormatter the telemetry
|
||||
* formatting service, for making values human-readable.
|
||||
*/
|
||||
function RangeColumn(rangeMetadata, telemetryFormatter) {
|
||||
this.rangeMetadata = rangeMetadata;
|
||||
this.telemetryFormatter = telemetryFormatter;
|
||||
}
|
||||
|
||||
RangeColumn.prototype.getTitle = function () {
|
||||
return this.rangeMetadata.name;
|
||||
};
|
||||
|
||||
RangeColumn.prototype.getValue = function (domainObject, datum) {
|
||||
var range = this.rangeMetadata.key,
|
||||
limit = domainObject.getCapability('limit'),
|
||||
value = isNaN(datum[range]) ? datum[range] : parseFloat(datum[range]),
|
||||
alarm = limit && limit.evaluate(datum, range);
|
||||
|
||||
return {
|
||||
cssClass: alarm && alarm.cssClass,
|
||||
text: typeof(value) === 'undefined' ? undefined : this.telemetryFormatter.formatRangeValue(value)
|
||||
};
|
||||
};
|
||||
|
||||
return RangeColumn;
|
||||
}
|
||||
);
|
163
platform/features/table/src/Table.js
Normal file
163
platform/features/table/src/Table.js
Normal file
@ -0,0 +1,163 @@
|
||||
/*****************************************************************************
|
||||
* 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,moment*/
|
||||
|
||||
define(
|
||||
[
|
||||
'./DomainColumn',
|
||||
'./RangeColumn',
|
||||
'./NameColumn'
|
||||
],
|
||||
function (DomainColumn, RangeColumn, NameColumn) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Class that manages table metadata, state, and contents.
|
||||
* @memberof platform/features/table
|
||||
* @param domainObject
|
||||
* @constructor
|
||||
*/
|
||||
function Table(domainObject, telemetryFormatter) {
|
||||
this.domainObject = domainObject;
|
||||
this.columns = [];
|
||||
this.telemetryFormatter = telemetryFormatter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build column definitions based on supplied telemetry metadata
|
||||
* @param metadata Metadata describing the domains and ranges available
|
||||
* @returns {Table} This object
|
||||
*/
|
||||
Table.prototype.buildColumns = function(metadata) {
|
||||
var self = this;
|
||||
|
||||
this.columns = [];
|
||||
|
||||
if (metadata) {
|
||||
metadata.forEach(function (metadatum) {
|
||||
//Push domains first
|
||||
metadatum.domains.forEach(function (domainMetadata) {
|
||||
self.addColumn(new DomainColumn(domainMetadata, self.telemetryFormatter));
|
||||
});
|
||||
metadatum.ranges.forEach(function (rangeMetadata) {
|
||||
self.addColumn(new RangeColumn(rangeMetadata, self.telemetryFormatter));
|
||||
});
|
||||
});
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a column definition to this Table
|
||||
* @param {RangeColumn | DomainColumn | NameColumn} column
|
||||
* @param {Number} [index] Where the column should appear (will be
|
||||
* affected by column filtering)
|
||||
*/
|
||||
Table.prototype.addColumn = function (column, index) {
|
||||
if (typeof index === 'undefined') {
|
||||
this.columns.push(column);
|
||||
} else {
|
||||
this.columns.splice(index, 0, column);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param column
|
||||
* @returns {*|string}
|
||||
*/
|
||||
Table.prototype.getColumnTitle = function (column) {
|
||||
return column.getTitle();
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a simple list of column titles
|
||||
* @returns {Array} The titles of the columns
|
||||
*/
|
||||
Table.prototype.getHeaders = function() {
|
||||
var self = this;
|
||||
return this.columns.map(function (column){
|
||||
return self.getColumnTitle(column);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve and format values for a given telemetry datum.
|
||||
* @param telemetryObject The object that the telemetry data is
|
||||
* associated with
|
||||
* @param datum The telemetry datum to retrieve values from
|
||||
* @returns {Object} Key value pairs where the key is the column
|
||||
* title, and the value is the formatted value from the provided datum.
|
||||
*/
|
||||
Table.prototype.getRowValues = function(telemetryObject, datum) {
|
||||
var self = this;
|
||||
return this.columns.reduce(function(rowObject, column){
|
||||
var columnTitle = self.getColumnTitle(column),
|
||||
columnValue = column.getValue(telemetryObject, datum);
|
||||
|
||||
if (columnValue !== undefined && columnValue.text === undefined){
|
||||
columnValue.text = '';
|
||||
}
|
||||
// Don't replace something with nothing.
|
||||
// This occurs when there are multiple columns with the
|
||||
// column title
|
||||
if (rowObject[columnTitle] === undefined || rowObject[columnTitle].text === undefined || rowObject[columnTitle].text.length === 0) {
|
||||
rowObject[columnTitle] = columnValue;
|
||||
}
|
||||
return rowObject;
|
||||
}, {});
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
Table.prototype.defaultColumnConfiguration = function () {
|
||||
return ((this.domainObject.getModel().configuration || {}).table || {}).columns || {};
|
||||
};
|
||||
|
||||
/**
|
||||
* As part of the process of building the table definition, extract
|
||||
* configuration from column definitions.
|
||||
* @returns {Object} A configuration object consisting of key-value
|
||||
* pairs where the key is the column title, and the value is a
|
||||
* boolean indicating whether the column should be shown.
|
||||
*/
|
||||
Table.prototype.getColumnConfiguration = function() {
|
||||
var configuration = {},
|
||||
//Use existing persisted config, or default it
|
||||
defaultConfig = this.defaultColumnConfiguration();
|
||||
|
||||
/**
|
||||
* For each column header, define a configuration value
|
||||
* specifying whether the column is visible or not. Default to
|
||||
* existing (persisted) configuration if available
|
||||
*/
|
||||
this.getHeaders().forEach(function(columnTitle) {
|
||||
configuration[columnTitle] = typeof defaultConfig[columnTitle] === 'undefined' ? true : defaultConfig[columnTitle];
|
||||
});
|
||||
|
||||
return configuration;
|
||||
};
|
||||
|
||||
return Table;
|
||||
}
|
||||
);
|
354
platform/features/table/src/controllers/MCTTableController.js
Normal file
354
platform/features/table/src/controllers/MCTTableController.js
Normal file
@ -0,0 +1,354 @@
|
||||
/*global define*/
|
||||
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
"use strict";
|
||||
|
||||
function MCTTableController($scope, $timeout, element) {
|
||||
var self = this;
|
||||
|
||||
this.$scope = $scope;
|
||||
this.element = element;
|
||||
this.$timeout = $timeout;
|
||||
this.maxDisplayRows = 50;
|
||||
|
||||
$scope.visibleRows = [];
|
||||
$scope.overrideRowPositioning = false;
|
||||
|
||||
/**
|
||||
* Set default values for optional parameters on a given scope
|
||||
*/
|
||||
function setDefaults($scope) {
|
||||
if (typeof $scope.enableFilter === 'undefined') {
|
||||
$scope.enableFilter = true;
|
||||
$scope.filters = {};
|
||||
}
|
||||
if (typeof $scope.enableSort === 'undefined') {
|
||||
$scope.enableSort = true;
|
||||
$scope.sortColumn = undefined;
|
||||
$scope.sortDirection = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
setDefaults($scope);
|
||||
|
||||
element.find('div').on('scroll', this.onScroll.bind(this));
|
||||
|
||||
$scope.toggleSort = function (key) {
|
||||
if (!$scope.enableSort) {
|
||||
return;
|
||||
}
|
||||
if ($scope.sortColumn !== key) {
|
||||
$scope.sortColumn = key;
|
||||
$scope.sortDirection = 'asc';
|
||||
} else if ($scope.sortDirection === 'asc') {
|
||||
$scope.sortDirection = 'desc';
|
||||
} else if ($scope.sortDirection === 'desc') {
|
||||
$scope.sortColumn = undefined;
|
||||
$scope.sortDirection = undefined;
|
||||
}
|
||||
self.updateRows($scope.rows);
|
||||
};
|
||||
|
||||
$scope.$watchCollection('filters', function () {
|
||||
self.updateRows(self.$scope.rows);
|
||||
});
|
||||
$scope.$watchCollection('headers', this.updateHeaders.bind(this));
|
||||
$scope.$watchCollection('rows', this.updateRows.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.
|
||||
*/
|
||||
MCTTableController.prototype.onScroll = function (event) {
|
||||
var self = this,
|
||||
topScroll = event.target.scrollTop,
|
||||
bottomScroll = topScroll + event.target.offsetHeight,
|
||||
firstVisible,
|
||||
lastVisible,
|
||||
totalVisible,
|
||||
numberOffscreen,
|
||||
start,
|
||||
end;
|
||||
|
||||
if (this.$scope.displayRows.length < this.maxDisplayRows) {
|
||||
return;
|
||||
}
|
||||
|
||||
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 = 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.
|
||||
}
|
||||
|
||||
this.$scope.visibleRows = this.$scope.displayRows.slice(start, end)
|
||||
.map(function(row, i) {
|
||||
return {
|
||||
rowIndex: start + i,
|
||||
offsetY: ((start + i) * self.$scope.rowHeight) +
|
||||
self.$scope.headerHeight,
|
||||
contents: row
|
||||
};
|
||||
});
|
||||
|
||||
this.$scope.$digest();
|
||||
};
|
||||
|
||||
/**
|
||||
* Update table headers with new headers. If filtering is
|
||||
* enabled, reset filters. If sorting is enabled, reset
|
||||
* sorting.
|
||||
*/
|
||||
MCTTableController.prototype.updateHeaders = function (newHeaders) {
|
||||
if (!newHeaders){
|
||||
return;
|
||||
}
|
||||
|
||||
this.$scope.displayHeaders = newHeaders;
|
||||
if (this.$scope.enableFilter) {
|
||||
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;
|
||||
}
|
||||
this.updateRows(this.$scope.rows);
|
||||
};
|
||||
|
||||
/**
|
||||
* Read styles from the DOM and use them to calculate offsets
|
||||
* for individual rows.
|
||||
*/
|
||||
MCTTableController.prototype.setElementSizes = function () {
|
||||
var self = this,
|
||||
thead = this.element.find('thead'),
|
||||
tbody = this.element.find('tbody'),
|
||||
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));
|
||||
|
||||
this.$scope.columnWidths = [];
|
||||
|
||||
while (column.length) {
|
||||
this.$scope.columnWidths.push(column.prop('offsetWidth'));
|
||||
column = column.next();
|
||||
}
|
||||
this.$scope.headerHeight = headerHeight;
|
||||
this.$scope.rowHeight = rowHeight;
|
||||
this.$scope.totalHeight = overallHeight;
|
||||
|
||||
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
|
||||
};
|
||||
});
|
||||
|
||||
this.$scope.overrideRowPositioning = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
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) {
|
||||
//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);
|
||||
|
||||
return genericComparator(valA, valB) *
|
||||
sortDirectionMultiplier;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns an object which contains the largest values
|
||||
* for each key in the given set of rows. This is used to
|
||||
* pre-calculate optimal column sizes without having to render
|
||||
* every row.
|
||||
*/
|
||||
MCTTableController.prototype.findLargestRow = function(rows) {
|
||||
var largestRow = rows.reduce(function (largestRow, row) {
|
||||
Object.keys(row).forEach(function (key) {
|
||||
var currentColumn = row[key].text,
|
||||
currentColumnLength =
|
||||
(currentColumn && currentColumn.length) ?
|
||||
currentColumn.length :
|
||||
currentColumn,
|
||||
largestColumn = largestRow[key].text,
|
||||
largestColumnLength =
|
||||
(largestColumn && largestColumn.length) ?
|
||||
largestColumn.length :
|
||||
largestColumn;
|
||||
|
||||
if (currentColumnLength > largestColumnLength) {
|
||||
largestRow[key] = JSON.parse(JSON.stringify(row[key]));
|
||||
}
|
||||
});
|
||||
return largestRow;
|
||||
}, JSON.parse(JSON.stringify(rows[0] || {})));
|
||||
|
||||
// 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) {
|
||||
var padCharacters,
|
||||
i;
|
||||
|
||||
largestRow[key].text = String(largestRow[key].text);
|
||||
padCharacters = largestRow[key].text.length / 10;
|
||||
for (i = 0; i < padCharacters; i++) {
|
||||
largestRow[key].text = largestRow[key].text + 'W';
|
||||
}
|
||||
largestRow[key].text = largestRow[key].text
|
||||
.replace(/[ \-_]/g, 'W');
|
||||
});
|
||||
return largestRow;
|
||||
};
|
||||
|
||||
MCTTableController.prototype.resize = function (){
|
||||
var largestRow = this.findLargestRow(this.$scope.displayRows);
|
||||
this.$scope.visibleRows = [
|
||||
{
|
||||
rowIndex: 0,
|
||||
offsetY: undefined,
|
||||
contents: largestRow
|
||||
}
|
||||
];
|
||||
|
||||
this.$timeout(this.setElementSizes.bind(this), 0);
|
||||
};
|
||||
|
||||
/**
|
||||
* Update rows with new data. If filtering is enabled, rows
|
||||
* will be sorted before display.
|
||||
*/
|
||||
MCTTableController.prototype.updateRows = function (newRows) {
|
||||
var displayRows = newRows;
|
||||
this.$scope.visibleRows = [];
|
||||
this.$scope.overrideRowPositioning = false;
|
||||
|
||||
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.resize();
|
||||
};
|
||||
|
||||
/**
|
||||
* Filter rows.
|
||||
*/
|
||||
MCTTableController.prototype.filterRows = function(rowsToFilter) {
|
||||
var filters = {},
|
||||
self = this;
|
||||
|
||||
/**
|
||||
* Returns true if row matches all filters.
|
||||
*/
|
||||
function matchRow(filters, row) {
|
||||
return Object.keys(filters).every(function(key) {
|
||||
if (!row[key]) {
|
||||
return false;
|
||||
}
|
||||
var testVal = String(row[key].text).toLowerCase();
|
||||
return testVal.indexOf(filters[key]) !== -1;
|
||||
});
|
||||
}
|
||||
|
||||
if (!Object.keys(this.$scope.filters).length) {
|
||||
return rowsToFilter;
|
||||
}
|
||||
|
||||
Object.keys(this.$scope.filters).forEach(function(key) {
|
||||
if (!self.$scope.filters[key]) {
|
||||
return;
|
||||
}
|
||||
filters[key] = self.$scope.filters[key].toLowerCase();
|
||||
});
|
||||
|
||||
return rowsToFilter.filter(matchRow.bind(null, filters));
|
||||
};
|
||||
|
||||
|
||||
return MCTTableController;
|
||||
}
|
||||
);
|
214
platform/features/table/src/controllers/TableController.js
Normal file
214
platform/features/table/src/controllers/TableController.js
Normal file
@ -0,0 +1,214 @@
|
||||
/*****************************************************************************
|
||||
* 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*/
|
||||
|
||||
/**
|
||||
* This bundle adds a table view for displaying telemetry data.
|
||||
* @namespace platform/features/table
|
||||
*/
|
||||
define(
|
||||
[
|
||||
'../Table',
|
||||
'../NameColumn'
|
||||
],
|
||||
function (Table, NameColumn) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* The TableController is responsible for getting data onto the page
|
||||
* in the table widget. This includes handling composition,
|
||||
* configuration, and telemetry subscriptions.
|
||||
* @memberof platform/features/table
|
||||
* @param $scope
|
||||
* @param telemetryHandler
|
||||
* @param telemetryFormatter
|
||||
* @constructor
|
||||
*/
|
||||
function TableController(
|
||||
$scope,
|
||||
telemetryHandler,
|
||||
telemetryFormatter
|
||||
) {
|
||||
var self = this;
|
||||
|
||||
this.$scope = $scope;
|
||||
this.columns = {}; //Range and Domain columns
|
||||
this.handle = undefined;
|
||||
//this.pending = false;
|
||||
this.telemetryHandler = telemetryHandler;
|
||||
this.table = new Table($scope.domainObject, telemetryFormatter);
|
||||
this.changeListeners = [];
|
||||
|
||||
$scope.rows = [];
|
||||
|
||||
// Subscribe to telemetry when a domain object becomes available
|
||||
this.$scope.$watch('domainObject', function(domainObject){
|
||||
if (!domainObject)
|
||||
return;
|
||||
|
||||
self.subscribe();
|
||||
self.registerChangeListeners();
|
||||
});
|
||||
|
||||
// Unsubscribe when the plot is destroyed
|
||||
this.$scope.$on("$destroy", this.destroy.bind(this));
|
||||
}
|
||||
|
||||
TableController.prototype.registerChangeListeners = function() {
|
||||
//Defer registration of change listeners until domain object is
|
||||
// available in order to avoid race conditions
|
||||
|
||||
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)));
|
||||
|
||||
//Change of bounds in time conductor
|
||||
this.changeListeners.push(this.$scope.$on('telemetry:display:bounds', this.subscribe.bind(this)));
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Release the current subscription (called when scope is destroyed)
|
||||
*/
|
||||
TableController.prototype.destroy = function () {
|
||||
if (this.handle) {
|
||||
this.handle.unsubscribe();
|
||||
this.handle = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
Create a new subscription. This is called when
|
||||
*/
|
||||
TableController.prototype.subscribe = function() {
|
||||
var self = this;
|
||||
|
||||
/*if (this.pending){
|
||||
return;
|
||||
}*/
|
||||
//this.pending = true;
|
||||
|
||||
if (this.handle) {
|
||||
this.handle.unsubscribe();
|
||||
}
|
||||
|
||||
this.$scope.rows = [];
|
||||
|
||||
//Noop because not supporting realtime data right now
|
||||
function noop(){
|
||||
//self.pending = false;
|
||||
}
|
||||
|
||||
this.handle = this.$scope.domainObject && this.telemetryHandler.handle(
|
||||
this.$scope.domainObject,
|
||||
noop,
|
||||
true // Lossless
|
||||
);
|
||||
|
||||
this.handle.request({}, this.addHistoricalData.bind(this));
|
||||
|
||||
this.setup();
|
||||
};
|
||||
|
||||
/**
|
||||
* Add any historical data available
|
||||
*/
|
||||
TableController.prototype.addHistoricalData = function(domainObject, series) {
|
||||
var i;
|
||||
//this.pending = false;
|
||||
for (i=0; i < series.getPointCount(); i++) {
|
||||
this.updateRows(domainObject, this.handle.makeDatum(domainObject, series, i));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the established configuration on the domain object
|
||||
* @private
|
||||
*/
|
||||
TableController.prototype.writeConfigToModel = function (configuration) {
|
||||
this.$scope.domainObject.useCapability('mutation', function (model) {
|
||||
model.configuration = model.configuration || {};
|
||||
model.configuration.table = model.configuration.table || {};
|
||||
model.configuration.table.columns = configuration;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Setup table columns based on domain object metadata
|
||||
*/
|
||||
TableController.prototype.setup = function() {
|
||||
var handle = this.handle,
|
||||
table = this.table,
|
||||
self = this,
|
||||
configuration;
|
||||
|
||||
if (handle) {
|
||||
handle.promiseTelemetryObjects().then(function () {
|
||||
table.buildColumns(handle.getMetadata());
|
||||
|
||||
if (table.columns.length > 0){
|
||||
table.addColumn(new NameColumn(), 0);
|
||||
}
|
||||
|
||||
self.filterColumns();
|
||||
|
||||
// When table column configuration changes, (due to being
|
||||
// selected or deselected), filter columns appropriately.
|
||||
self.changeListeners.push(self.$scope.$watchCollection(
|
||||
'domainObject.getModel().configuration.table.columns',
|
||||
self.filterColumns.bind(self)
|
||||
));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Add data to rows
|
||||
* @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
|
||||
*/
|
||||
TableController.prototype.updateRows = function (object, datum) {
|
||||
this.$scope.rows.push(this.table.getRowValues(object, datum));
|
||||
};
|
||||
|
||||
/**
|
||||
* When column configuration changes, update the visible headers
|
||||
* accordingly.
|
||||
*/
|
||||
TableController.prototype.filterColumns = function () {
|
||||
var config = this.table.getColumnConfiguration();
|
||||
|
||||
this.writeConfigToModel(config);
|
||||
//Populate headers with visible columns (determined by configuration)
|
||||
this.$scope.headers = Object.keys(config).filter(function(column) {
|
||||
return config[column];
|
||||
});
|
||||
};
|
||||
|
||||
return TableController;
|
||||
}
|
||||
);
|
@ -0,0 +1,94 @@
|
||||
/*****************************************************************************
|
||||
* 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(
|
||||
[],
|
||||
function () {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Notes on implementation of plot options
|
||||
*
|
||||
* Multiple y-axes will have to be handled with multiple forms as
|
||||
* they will need to be stored on distinct model object
|
||||
*
|
||||
* Likewise plot series options per-child will need to be separate
|
||||
* forms.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The LayoutController is responsible for supporting the
|
||||
* Layout view. It arranges frames according to saved configuration
|
||||
* and provides methods for updating these based on mouse
|
||||
* movement.
|
||||
* @memberof platform/features/plot
|
||||
* @constructor
|
||||
* @param {Scope} $scope the controller's Angular scope
|
||||
*/
|
||||
function TableOptionsController($scope) {
|
||||
|
||||
var self = this;
|
||||
|
||||
this.$scope = $scope;
|
||||
this.domainObject = $scope.domainObject;
|
||||
|
||||
$scope.columnsForm = {};
|
||||
|
||||
this.domainObject.getCapability('mutation').listen(function (model) {
|
||||
self.populateForm(model);
|
||||
});
|
||||
|
||||
$scope.$watchCollection('configuration.table.columns', function(columns){
|
||||
if (columns){
|
||||
self.domainObject.useCapability('mutation', function(model) {
|
||||
model.configuration.table.columns = columns;
|
||||
});
|
||||
self.domainObject.getCapability('persistence').persist();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
TableOptionsController.prototype.populateForm = function (model) {
|
||||
var columnsDefinition = (((model.configuration || {}).table || {}).columns || {}),
|
||||
rows = [];
|
||||
this.$scope.columnsForm = {
|
||||
'name':'Columns',
|
||||
'sections': [{
|
||||
'name': 'Columns',
|
||||
'rows': rows
|
||||
}]};
|
||||
|
||||
Object.keys(columnsDefinition).forEach(function (key){
|
||||
rows.push({
|
||||
'name': key,
|
||||
'control': 'checkbox',
|
||||
'key': key
|
||||
});
|
||||
});
|
||||
this.$scope.configuration = JSON.parse(JSON.stringify(model.configuration));
|
||||
};
|
||||
|
||||
return TableOptionsController;
|
||||
}
|
||||
);
|
24
platform/features/table/src/directives/MCTTable.js
Normal file
24
platform/features/table/src/directives/MCTTable.js
Normal file
@ -0,0 +1,24 @@
|
||||
/*global define*/
|
||||
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
"use strict";
|
||||
|
||||
function MCTTable($timeout) {
|
||||
return {
|
||||
restrict: "E",
|
||||
templateUrl: "platform/features/table/res/templates/mct-data-table.html",
|
||||
controller: 'MCTTableController',
|
||||
scope: {
|
||||
headers: "=",
|
||||
rows: "=",
|
||||
enableFilter: "=?",
|
||||
enableSort: "=?"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return MCTTable;
|
||||
}
|
||||
);
|
Reference in New Issue
Block a user