mirror of
https://github.com/nasa/openmct.git
synced 2025-05-02 08:43:17 +00:00
Revert "Revert "[Tables] Fix to correct sorting in realtime tables""
This commit is contained in:
parent
abb511521b
commit
6bf1ef5bcc
@ -6,12 +6,13 @@ Victor Woeltjen
|
|||||||
September 23, 2015
|
September 23, 2015
|
||||||
Document Version 1.1
|
Document Version 1.1
|
||||||
|
|
||||||
Date | Version | Summary of Changes | Author
|
Date | Version | Summary of Changes | Author
|
||||||
------------------- | --------- | ----------------------- | ---------------
|
------------------- | --------- | ------------------------- | ---------------
|
||||||
April 29, 2015 | 0 | Initial Draft | Victor Woeltjen
|
April 29, 2015 | 0 | Initial Draft | Victor Woeltjen
|
||||||
May 12, 2015 | 0.1 | | Victor Woeltjen
|
May 12, 2015 | 0.1 | | Victor Woeltjen
|
||||||
June 4, 2015 | 1.0 | Name Changes | Victor Woeltjen
|
June 4, 2015 | 1.0 | Name Changes | Victor Woeltjen
|
||||||
October 4, 2015 | 1.1 | Conversion to MarkDown | Andrew Henry
|
October 4, 2015 | 1.1 | Conversion to MarkDown | Andrew Henry
|
||||||
|
April 5, 2016 | 1.2 | Added Mct-table directive | Andrew Henry
|
||||||
|
|
||||||
# Introduction
|
# Introduction
|
||||||
The purpose of this guide is to familiarize software developers with the Open
|
The purpose of this guide is to familiarize software developers with the Open
|
||||||
@ -1600,6 +1601,61 @@ there are items .
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
## Table
|
||||||
|
|
||||||
|
The `mct-table` directive provides a generic table component, with optional
|
||||||
|
sorting and filtering capabilities. The table can be pre-populated with data
|
||||||
|
by setting the `rows` parameter, and it can be updated in real-time using the
|
||||||
|
`add:row` and `remove:row` broadcast events. The table will expand to occupy
|
||||||
|
100% of the size of its containing element. The table is highly optimized for
|
||||||
|
very large data sets.
|
||||||
|
|
||||||
|
### Events
|
||||||
|
|
||||||
|
The table supports two events for notifying that the rows have changed. For
|
||||||
|
performance reasons, the table does not monitor the content of `rows`
|
||||||
|
constantly.
|
||||||
|
|
||||||
|
* `add:row`: A `$broadcast` event that will notify the table that a new row
|
||||||
|
has been added to the table.
|
||||||
|
|
||||||
|
eg. The code below adds a new row, and alerts the table using the `add:row`
|
||||||
|
event. Sorting and filtering will be applied automatically by the table component.
|
||||||
|
|
||||||
|
```
|
||||||
|
$scope.rows.push(newRow);
|
||||||
|
$scope.$broadcast('add:row', $scope.rows.length-1);
|
||||||
|
```
|
||||||
|
|
||||||
|
* `remove:row`: A `$broadcast` event that will notify the table that a row
|
||||||
|
should be removed from the table.
|
||||||
|
|
||||||
|
eg. The code below removes a row from the rows array, and then alerts the table
|
||||||
|
to its removal.
|
||||||
|
|
||||||
|
```
|
||||||
|
$scope.rows.slice(5, 1);
|
||||||
|
$scope.$broadcast('remove:row', 5);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Parameters
|
||||||
|
|
||||||
|
* `headers`: An array of string values which will constitute the column titles
|
||||||
|
that appear at the top of the table. Corresponding values are specified in
|
||||||
|
the rows using the header title provided here.
|
||||||
|
* `rows`: An array of objects containing row values. Each element in the
|
||||||
|
array must be an associative array, where the key corresponds to a column header.
|
||||||
|
* `enableFilter`: A boolean that if true, will enable searching and result
|
||||||
|
filtering. When enabled, each column will have a text input field that can be
|
||||||
|
used to filter the table rows in real time.
|
||||||
|
* `enableSort`: A boolean determining whether rows can be sorted. If true,
|
||||||
|
sorting will be enabled allowing sorting by clicking on column headers. Only
|
||||||
|
one column may be sorted at a time.
|
||||||
|
* `autoScroll`: A boolean value that if true, will cause the table to automatically
|
||||||
|
scroll to the bottom as new data arrives. Auto-scroll can be disengaged manually
|
||||||
|
by scrolling away from the bottom of the table, and can also be enabled manually
|
||||||
|
by scrolling to the bottom of the table rows.
|
||||||
|
|
||||||
# Services
|
# Services
|
||||||
|
|
||||||
The Open MCT Web platform provides a variety of services which can be retrieved
|
The Open MCT Web platform provides a variety of services which can be retrieved
|
||||||
|
@ -23,16 +23,16 @@
|
|||||||
|
|
||||||
define([
|
define([
|
||||||
"./src/directives/MCTTable",
|
"./src/directives/MCTTable",
|
||||||
"./src/controllers/RTTelemetryTableController",
|
"./src/controllers/RealtimeTableController",
|
||||||
"./src/controllers/TelemetryTableController",
|
"./src/controllers/HistoricalTableController",
|
||||||
"./src/controllers/TableOptionsController",
|
"./src/controllers/TableOptionsController",
|
||||||
'../../commonUI/regions/src/Region',
|
'../../commonUI/regions/src/Region',
|
||||||
'../../commonUI/browse/src/InspectorRegion',
|
'../../commonUI/browse/src/InspectorRegion',
|
||||||
"legacyRegistry"
|
"legacyRegistry"
|
||||||
], function (
|
], function (
|
||||||
MCTTable,
|
MCTTable,
|
||||||
RTTelemetryTableController,
|
RealtimeTableController,
|
||||||
TelemetryTableController,
|
HistoricalTableController,
|
||||||
TableOptionsController,
|
TableOptionsController,
|
||||||
Region,
|
Region,
|
||||||
InspectorRegion,
|
InspectorRegion,
|
||||||
@ -109,13 +109,13 @@ define([
|
|||||||
],
|
],
|
||||||
"controllers": [
|
"controllers": [
|
||||||
{
|
{
|
||||||
"key": "TelemetryTableController",
|
"key": "HistoricalTableController",
|
||||||
"implementation": TelemetryTableController,
|
"implementation": HistoricalTableController,
|
||||||
"depends": ["$scope", "telemetryHandler", "telemetryFormatter"]
|
"depends": ["$scope", "telemetryHandler", "telemetryFormatter"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "RTTelemetryTableController",
|
"key": "RealtimeTableController",
|
||||||
"implementation": RTTelemetryTableController,
|
"implementation": RealtimeTableController,
|
||||||
"depends": ["$scope", "telemetryHandler", "telemetryFormatter"]
|
"depends": ["$scope", "telemetryHandler", "telemetryFormatter"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -130,7 +130,7 @@ define([
|
|||||||
"name": "Historical Table",
|
"name": "Historical Table",
|
||||||
"key": "table",
|
"key": "table",
|
||||||
"glyph": "\ue604",
|
"glyph": "\ue604",
|
||||||
"templateUrl": "templates/table.html",
|
"templateUrl": "templates/historical-table.html",
|
||||||
"needs": [
|
"needs": [
|
||||||
"telemetry"
|
"telemetry"
|
||||||
],
|
],
|
||||||
@ -161,6 +161,12 @@ define([
|
|||||||
"key": "table-options-edit",
|
"key": "table-options-edit",
|
||||||
"templateUrl": "templates/table-options-edit.html"
|
"templateUrl": "templates/table-options-edit.html"
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"stylesheets": [
|
||||||
|
{
|
||||||
|
"stylesheetUrl": "css/table.css",
|
||||||
|
"priority": "mandatory"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
50
platform/features/table/res/sass/table.scss
Normal file
50
platform/features/table/res/sass/table.scss
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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.
|
||||||
|
*****************************************************************************/
|
||||||
|
.sizing-table {
|
||||||
|
min-width: 100%;
|
||||||
|
z-index: -1;
|
||||||
|
visibility: hidden;
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
//Add some padding to allow for decorations such as limits indicator
|
||||||
|
td {
|
||||||
|
padding-right: 15px;
|
||||||
|
padding-left: 10px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.mct-table {
|
||||||
|
table-layout: fixed;
|
||||||
|
th {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
tbody {
|
||||||
|
tr {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
td {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
<div ng-controller="TelemetryTableController">
|
<div ng-controller="HistoricalTableController">
|
||||||
<mct-table
|
<mct-table
|
||||||
headers="headers"
|
headers="headers"
|
||||||
rows="rows"
|
rows="rows"
|
@ -1,22 +1,25 @@
|
|||||||
<div class="l-view-section scrolling"
|
<div class="l-view-section scrolling" style="overflow: auto;">
|
||||||
ng-style="overrideRowPositioning ?
|
<table class="sizing-table">
|
||||||
{'overflow': 'auto'} :
|
<tbody>
|
||||||
{'overflow': 'scroll'}"
|
<tr>
|
||||||
>
|
<td ng-repeat="header in displayHeaders">{{header}}</td>
|
||||||
<table class="filterable"
|
</tr>
|
||||||
ng-style="overrideRowPositioning && {
|
<tr><td ng-repeat="header in displayHeaders" >
|
||||||
|
{{sizingRow[header].text}}
|
||||||
|
</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<table class="filterable mct-table"
|
||||||
|
ng-style="{
|
||||||
height: totalHeight + 'px',
|
height: totalHeight + 'px',
|
||||||
'table-layout': overrideRowPositioning ? 'fixed' : 'auto',
|
|
||||||
'max-width': totalWidth
|
'max-width': totalWidth
|
||||||
}">
|
}">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th ng-repeat="header in displayHeaders"
|
<th ng-repeat="header in displayHeaders"
|
||||||
ng-style="overrideRowPositioning && {
|
ng-style="{
|
||||||
width: columnWidths[$index] + 'px',
|
width: columnWidths[$index] + 'px',
|
||||||
'max-width': columnWidths[$index] + 'px',
|
'max-width': columnWidths[$index] + 'px',
|
||||||
overflow: 'none',
|
|
||||||
'box-sizing': 'border-box'
|
|
||||||
}"
|
}"
|
||||||
ng-class="[
|
ng-class="[
|
||||||
enableSort ? 'sortable' : '',
|
enableSort ? 'sortable' : '',
|
||||||
@ -29,11 +32,9 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr ng-if="enableFilter" class="s-filters">
|
<tr ng-if="enableFilter" class="s-filters">
|
||||||
<th ng-repeat="header in displayHeaders"
|
<th ng-repeat="header in displayHeaders"
|
||||||
ng-style="overrideRowPositioning && {
|
ng-style="{
|
||||||
width: columnWidths[$index] + 'px',
|
width: columnWidths[$index] + 'px',
|
||||||
'max-width': columnWidths[$index] + 'px',
|
'max-width': columnWidths[$index] + 'px',
|
||||||
overflow: 'none',
|
|
||||||
'box-sizing': 'border-box'
|
|
||||||
}">
|
}">
|
||||||
<input type="text"
|
<input type="text"
|
||||||
ng-model="filters[header]"/>
|
ng-model="filters[header]"/>
|
||||||
@ -41,21 +42,15 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody ng-style="overrideRowPositioning ? '' : {
|
<tbody>
|
||||||
'opacity': '0.0'
|
|
||||||
}">
|
|
||||||
<tr ng-repeat="visibleRow in visibleRows track by visibleRow.rowIndex"
|
<tr ng-repeat="visibleRow in visibleRows track by visibleRow.rowIndex"
|
||||||
ng-style="overrideRowPositioning && {
|
ng-style="{
|
||||||
position: 'absolute',
|
|
||||||
top: visibleRow.offsetY + 'px',
|
top: visibleRow.offsetY + 'px',
|
||||||
}">
|
}">
|
||||||
<td ng-repeat="header in displayHeaders"
|
<td ng-repeat="header in displayHeaders"
|
||||||
ng-style="overrideRowPositioning && {
|
ng-style=" {
|
||||||
width: columnWidths[$index] + 'px',
|
width: columnWidths[$index] + 'px',
|
||||||
'white-space': 'nowrap',
|
|
||||||
'max-width': columnWidths[$index] + 'px',
|
'max-width': columnWidths[$index] + 'px',
|
||||||
overflow: 'hidden',
|
|
||||||
'box-sizing': 'border-box'
|
|
||||||
}"
|
}"
|
||||||
class="{{visibleRow.contents[header].cssClass}}">
|
class="{{visibleRow.contents[header].cssClass}}">
|
||||||
{{ visibleRow.contents[header].text }}
|
{{ visibleRow.contents[header].text }}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<div ng-controller="RTTelemetryTableController">
|
<div ng-controller="RealtimeTableController">
|
||||||
<mct-table
|
<mct-table
|
||||||
headers="headers"
|
headers="headers"
|
||||||
rows="rows"
|
rows="rows"
|
||||||
|
@ -47,7 +47,7 @@ define(
|
|||||||
* @param metadata Metadata describing the domains and ranges available
|
* @param metadata Metadata describing the domains and ranges available
|
||||||
* @returns {TableConfiguration} This object
|
* @returns {TableConfiguration} This object
|
||||||
*/
|
*/
|
||||||
TableConfiguration.prototype.buildColumns = function (metadata) {
|
TableConfiguration.prototype.populateColumns = function (metadata) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
this.columns = [];
|
this.columns = [];
|
||||||
@ -140,8 +140,7 @@ define(
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
TableConfiguration.prototype.defaultColumnConfiguration = function () {
|
TableConfiguration.prototype.defaultColumnConfiguration = function () {
|
||||||
return ((this.domainObject.getModel().configuration || {}).table ||
|
return ((this.domainObject.getModel().configuration || {}).table || {}).columns || {};
|
||||||
{}).columns || {};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -156,6 +155,16 @@ define(
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function configChanged(config1, config2) {
|
||||||
|
var config1Keys = Object.keys(config1),
|
||||||
|
config2Keys = Object.keys(config2);
|
||||||
|
|
||||||
|
return (config1Keys.length !== config2Keys.length) ||
|
||||||
|
config1Keys.some(function(key){
|
||||||
|
return config1[key] !== config2[key];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* As part of the process of building the table definition, extract
|
* As part of the process of building the table definition, extract
|
||||||
* configuration from column definitions.
|
* configuration from column definitions.
|
||||||
@ -163,7 +172,7 @@ define(
|
|||||||
* pairs where the key is the column title, and the value is a
|
* pairs where the key is the column title, and the value is a
|
||||||
* boolean indicating whether the column should be shown.
|
* boolean indicating whether the column should be shown.
|
||||||
*/
|
*/
|
||||||
TableConfiguration.prototype.getColumnConfiguration = function () {
|
TableConfiguration.prototype.buildColumnConfiguration = function () {
|
||||||
var configuration = {},
|
var configuration = {},
|
||||||
//Use existing persisted config, or default it
|
//Use existing persisted config, or default it
|
||||||
defaultConfig = this.defaultColumnConfiguration();
|
defaultConfig = this.defaultColumnConfiguration();
|
||||||
@ -179,6 +188,11 @@ define(
|
|||||||
defaultConfig[columnTitle];
|
defaultConfig[columnTitle];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//Synchronize column configuration with model
|
||||||
|
if (configChanged(configuration, defaultConfig)) {
|
||||||
|
this.saveColumnConfiguration(configuration);
|
||||||
|
}
|
||||||
|
|
||||||
return configuration;
|
return configuration;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -0,0 +1,70 @@
|
|||||||
|
/*****************************************************************************
|
||||||
|
* 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 HistoricalTableController($scope, telemetryHandler, telemetryFormatter) {
|
||||||
|
TableController.call(this, $scope, telemetryHandler, telemetryFormatter);
|
||||||
|
}
|
||||||
|
|
||||||
|
HistoricalTableController.prototype = Object.create(TableController.prototype);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Populates historical data on scope when it becomes available from
|
||||||
|
* the telemetry API
|
||||||
|
*/
|
||||||
|
HistoricalTableController.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;
|
||||||
|
};
|
||||||
|
|
||||||
|
return HistoricalTableController;
|
||||||
|
}
|
||||||
|
);
|
@ -23,10 +23,13 @@ define(
|
|||||||
this.maxDisplayRows = 50;
|
this.maxDisplayRows = 50;
|
||||||
|
|
||||||
this.scrollable = element.find('div');
|
this.scrollable = element.find('div');
|
||||||
|
this.thead = element.find('thead');
|
||||||
|
this.tbody = element.find('tbody');
|
||||||
|
this.$scope.sizingRow = {};
|
||||||
|
|
||||||
this.scrollable.on('scroll', this.onScroll.bind(this));
|
this.scrollable.on('scroll', this.onScroll.bind(this));
|
||||||
|
|
||||||
$scope.visibleRows = [];
|
$scope.visibleRows = [];
|
||||||
$scope.overrideRowPositioning = false;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set default values for optional parameters on a given scope
|
* Set default values for optional parameters on a given scope
|
||||||
@ -58,22 +61,22 @@ define(
|
|||||||
$scope.sortColumn = undefined;
|
$scope.sortColumn = undefined;
|
||||||
$scope.sortDirection = undefined;
|
$scope.sortDirection = undefined;
|
||||||
}
|
}
|
||||||
self.updateRows($scope.rows);
|
self.setRows($scope.rows);
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Define watches to listen for changes to headers and rows.
|
* Define watches to listen for changes to headers and rows.
|
||||||
*/
|
*/
|
||||||
$scope.$watchCollection('filters', function () {
|
$scope.$watchCollection('filters', function () {
|
||||||
self.updateRows($scope.rows);
|
self.setRows($scope.rows);
|
||||||
});
|
});
|
||||||
$scope.$watch('headers', this.updateHeaders.bind(this));
|
$scope.$watch('headers', this.setHeaders.bind(this));
|
||||||
$scope.$watch('rows', this.updateRows.bind(this));
|
$scope.$watch('rows', this.setRows.bind(this));
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Listen for rows added individually (eg. for real-time tables)
|
* Listen for rows added individually (eg. for real-time tables)
|
||||||
*/
|
*/
|
||||||
$scope.$on('add:row', this.newRow.bind(this));
|
$scope.$on('add:row', this.addRow.bind(this));
|
||||||
$scope.$on('remove:row', this.removeRow.bind(this));
|
$scope.$on('remove:row', this.removeRow.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,23 +99,27 @@ define(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles a row add event. Rows can be added as needed using the
|
* Handles a row add event. Rows can be added as needed using the
|
||||||
* `addRow` broadcast event.
|
* `add:row` broadcast event.
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
MCTTableController.prototype.newRow = function (event, rowIndex) {
|
MCTTableController.prototype.addRow = function (event, rowIndex) {
|
||||||
var row = this.$scope.rows[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))
|
//Does the row pass the current filter?
|
||||||
.then(this.scrollToBottom.bind(this));
|
if (this.filterRows([row]).length === 1) {
|
||||||
|
//Insert the row into the correct position in the array
|
||||||
|
this.insertSorted(this.$scope.displayRows, row);
|
||||||
|
|
||||||
|
//Resize the columns , then update the rows visible in the table
|
||||||
|
this.resize([this.$scope.sizingRow, row])
|
||||||
|
.then(this.setVisibleRows.bind(this))
|
||||||
|
.then(this.scrollToBottom.bind(this));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles a row add event. Rows can be added as needed using the
|
* Handles a row remove event. Rows can be removed as needed using the
|
||||||
* `addRow` broadcast event.
|
* `remove:row` broadcast event.
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
MCTTableController.prototype.removeRow = function (event, rowIndex) {
|
MCTTableController.prototype.removeRow = function (event, rowIndex) {
|
||||||
@ -225,7 +232,7 @@ define(
|
|||||||
* enabled, reset filters. If sorting is enabled, reset
|
* enabled, reset filters. If sorting is enabled, reset
|
||||||
* sorting.
|
* sorting.
|
||||||
*/
|
*/
|
||||||
MCTTableController.prototype.updateHeaders = function (newHeaders) {
|
MCTTableController.prototype.setHeaders = function (newHeaders) {
|
||||||
if (!newHeaders){
|
if (!newHeaders){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -241,7 +248,7 @@ define(
|
|||||||
this.$scope.sortColumn = undefined;
|
this.$scope.sortColumn = undefined;
|
||||||
this.$scope.sortDirection = undefined;
|
this.$scope.sortDirection = undefined;
|
||||||
}
|
}
|
||||||
this.updateRows(this.$scope.rows);
|
this.setRows(this.$scope.rows);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -249,13 +256,12 @@ define(
|
|||||||
* for individual rows.
|
* for individual rows.
|
||||||
*/
|
*/
|
||||||
MCTTableController.prototype.setElementSizes = function () {
|
MCTTableController.prototype.setElementSizes = function () {
|
||||||
var self = this,
|
var thead = this.thead,
|
||||||
thead = this.element.find('thead'),
|
tbody = this.tbody,
|
||||||
tbody = this.element.find('tbody'),
|
|
||||||
firstRow = tbody.find('tr'),
|
firstRow = tbody.find('tr'),
|
||||||
column = firstRow.find('td'),
|
column = firstRow.find('td'),
|
||||||
headerHeight = thead.prop('offsetHeight'),
|
headerHeight = thead.prop('offsetHeight'),
|
||||||
rowHeight = 20,
|
rowHeight = firstRow.prop('offsetHeight'),
|
||||||
columnWidth,
|
columnWidth,
|
||||||
tableWidth = 0,
|
tableWidth = 0,
|
||||||
overallHeight = headerHeight + (rowHeight *
|
overallHeight = headerHeight + (rowHeight *
|
||||||
@ -272,15 +278,12 @@ define(
|
|||||||
this.$scope.headerHeight = headerHeight;
|
this.$scope.headerHeight = headerHeight;
|
||||||
this.$scope.rowHeight = rowHeight;
|
this.$scope.rowHeight = rowHeight;
|
||||||
this.$scope.totalHeight = overallHeight;
|
this.$scope.totalHeight = overallHeight;
|
||||||
this.setVisibleRows();
|
|
||||||
|
|
||||||
if (tableWidth > 0) {
|
if (tableWidth > 0) {
|
||||||
this.$scope.totalWidth = tableWidth + 'px';
|
this.$scope.totalWidth = tableWidth + 'px';
|
||||||
} else {
|
} else {
|
||||||
this.$scope.totalWidth = 'none';
|
this.$scope.totalWidth = 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$scope.overrideRowPositioning = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -292,21 +295,14 @@ define(
|
|||||||
sortKey = this.$scope.sortColumn;
|
sortKey = this.$scope.sortColumn;
|
||||||
|
|
||||||
function binarySearch(searchArray, searchElement, min, max){
|
function binarySearch(searchArray, searchElement, min, max){
|
||||||
var sampleAt = Math.floor((max - min) / 2) + min,
|
var sampleAt = Math.floor((max - min) / 2) + min;
|
||||||
valA,
|
|
||||||
valB;
|
|
||||||
if (max < min) {
|
if (max < min) {
|
||||||
return min; // Element is not in array, min gives direction
|
return min; // Element is not in array, min gives direction
|
||||||
}
|
}
|
||||||
|
|
||||||
valA = isNaN(searchElement[sortKey].text) ?
|
switch(self.sortComparator(searchElement[sortKey].text,
|
||||||
searchElement[sortKey].text :
|
searchArray[sampleAt][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:
|
case -1:
|
||||||
return binarySearch(searchArray, searchElement, min,
|
return binarySearch(searchArray, searchElement, min,
|
||||||
sampleAt - 1);
|
sampleAt - 1);
|
||||||
@ -344,8 +340,34 @@ define(
|
|||||||
*/
|
*/
|
||||||
MCTTableController.prototype.sortComparator = function (a, b) {
|
MCTTableController.prototype.sortComparator = function (a, b) {
|
||||||
var result = 0,
|
var result = 0,
|
||||||
sortDirectionMultiplier;
|
sortDirectionMultiplier,
|
||||||
|
numberA,
|
||||||
|
numberB;
|
||||||
|
/**
|
||||||
|
* Given a value, if it is a number, or a string representation of a
|
||||||
|
* number, then return a number representation. Otherwise, return
|
||||||
|
* the original value. It's a little more robust than using just
|
||||||
|
* Number() or parseFloat, or isNaN in isolation, all of which are
|
||||||
|
* fairly inconsistent in their results.
|
||||||
|
* @param value The value to return as a number.
|
||||||
|
* @returns {*} The value cast to a Number, or the original value if
|
||||||
|
* a Number representation is not possible.
|
||||||
|
*/
|
||||||
|
function toNumber (value){
|
||||||
|
var val = !isNaN(Number(value)) && !isNaN(parseFloat(value)) ? Number(value) : value;
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
numberA = toNumber(a);
|
||||||
|
numberB = toNumber(b);
|
||||||
|
|
||||||
|
//If they're both numbers, then compare them as numbers
|
||||||
|
if (typeof numberA === "number" && typeof numberB === "number") {
|
||||||
|
a = numberA;
|
||||||
|
b = numberB;
|
||||||
|
}
|
||||||
|
|
||||||
|
//If they're both strings, then ignore case
|
||||||
if (typeof a === "string" && typeof b === "string") {
|
if (typeof a === "string" && typeof b === "string") {
|
||||||
a = a.toLowerCase();
|
a = a.toLowerCase();
|
||||||
b = b.toLowerCase();
|
b = b.toLowerCase();
|
||||||
@ -382,15 +404,7 @@ define(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return rowsToSort.sort(function (a, b) {
|
return rowsToSort.sort(function (a, b) {
|
||||||
//If the values to compare can be compared as
|
return self.sortComparator(a[sortKey].text, b[sortKey].text);
|
||||||
// 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 self.sortComparator(valA, valB);
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -400,74 +414,48 @@ define(
|
|||||||
* pre-calculate optimal column sizes without having to render
|
* pre-calculate optimal column sizes without having to render
|
||||||
* every row.
|
* every row.
|
||||||
*/
|
*/
|
||||||
MCTTableController.prototype.findLargestRow = function (rows) {
|
MCTTableController.prototype.buildLargestRow = function (rows) {
|
||||||
var largestRow = rows.reduce(function (largestRow, row) {
|
var largestRow = rows.reduce(function (prevLargest, row) {
|
||||||
Object.keys(row).forEach(function (key) {
|
Object.keys(row).forEach(function (key) {
|
||||||
var currentColumn = row[key].text,
|
var currentColumn,
|
||||||
|
currentColumnLength,
|
||||||
|
largestColumn,
|
||||||
|
largestColumnLength;
|
||||||
|
if (row[key]){
|
||||||
|
currentColumn = (row[key]).text;
|
||||||
currentColumnLength =
|
currentColumnLength =
|
||||||
(currentColumn && currentColumn.length) ?
|
(currentColumn && currentColumn.length) ?
|
||||||
currentColumn.length :
|
currentColumn.length :
|
||||||
currentColumn,
|
currentColumn;
|
||||||
largestColumn = largestRow[key].text,
|
largestColumn = prevLargest[key] ? prevLargest[key].text : "";
|
||||||
largestColumnLength =
|
largestColumnLength = largestColumn.length;
|
||||||
(largestColumn && largestColumn.length) ?
|
|
||||||
largestColumn.length :
|
|
||||||
largestColumn;
|
|
||||||
|
|
||||||
if (currentColumnLength > largestColumnLength) {
|
if (currentColumnLength > largestColumnLength) {
|
||||||
largestRow[key] = JSON.parse(JSON.stringify(row[key]));
|
prevLargest[key] = JSON.parse(JSON.stringify(row[key]));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return largestRow;
|
return prevLargest;
|
||||||
}, JSON.parse(JSON.stringify(rows[0] || {})));
|
}, 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.
|
|
||||||
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;
|
return largestRow;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates the widest row in the table, pads that row, and adds
|
* Calculates the widest row in the table, and if necessary, resizes
|
||||||
* it to the table. Allows the table to size itself, then uses this
|
* the table accordingly
|
||||||
* as basis for column dimensions.
|
*
|
||||||
|
* @param rows the rows on which to resize
|
||||||
|
* @returns {Promise} a promise that will resolve when resizing has
|
||||||
|
* occurred.
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
MCTTableController.prototype.resize = function (){
|
MCTTableController.prototype.resize = function (rows) {
|
||||||
var largestRow = this.findLargestRow(this.$scope.displayRows),
|
this.$scope.sizingRow = this.buildLargestRow(rows);
|
||||||
self = this;
|
return this.$timeout(this.setElementSizes.bind(this));
|
||||||
this.$scope.visibleRows = [
|
|
||||||
{
|
|
||||||
rowIndex: 0,
|
|
||||||
offsetY: undefined,
|
|
||||||
contents: largestRow
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
//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
|
* @private
|
||||||
*/
|
*/
|
||||||
MCTTableController.prototype.filterAndSort = function (rows) {
|
MCTTableController.prototype.filterAndSort = function (rows) {
|
||||||
var displayRows = rows;
|
var displayRows = rows;
|
||||||
@ -478,26 +466,21 @@ define(
|
|||||||
if (this.$scope.enableSort) {
|
if (this.$scope.enableSort) {
|
||||||
displayRows = this.sortRows(displayRows.slice(0));
|
displayRows = this.sortRows(displayRows.slice(0));
|
||||||
}
|
}
|
||||||
this.$scope.displayRows = displayRows;
|
return displayRows;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update rows with new data. If filtering is enabled, rows
|
* Update rows with new data. If filtering is enabled, rows
|
||||||
* will be sorted before display.
|
* will be sorted before display.
|
||||||
*/
|
*/
|
||||||
MCTTableController.prototype.updateRows = function (newRows) {
|
MCTTableController.prototype.setRows = function (newRows) {
|
||||||
//Reset visible rows because new row data available.
|
|
||||||
this.$scope.visibleRows = [];
|
|
||||||
|
|
||||||
this.$scope.overrideRowPositioning = false;
|
|
||||||
|
|
||||||
//Nothing to show because no columns visible
|
//Nothing to show because no columns visible
|
||||||
if (!this.$scope.displayHeaders) {
|
if (!this.$scope.displayHeaders || !newRows) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.filterAndSort(newRows || []);
|
this.$scope.displayRows = this.filterAndSort(newRows || []);
|
||||||
this.resize();
|
this.resize(newRows).then(this.setVisibleRows.bind(this));
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -37,7 +37,7 @@ define(
|
|||||||
* @param telemetryFormatter
|
* @param telemetryFormatter
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
function RTTelemetryTableController($scope, telemetryHandler, telemetryFormatter) {
|
function RealtimeTableController($scope, telemetryHandler, telemetryFormatter) {
|
||||||
TableController.call(this, $scope, telemetryHandler, telemetryFormatter);
|
TableController.call(this, $scope, telemetryHandler, telemetryFormatter);
|
||||||
|
|
||||||
$scope.autoScroll = false;
|
$scope.autoScroll = false;
|
||||||
@ -66,58 +66,35 @@ define(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
RTTelemetryTableController.prototype = Object.create(TableController.prototype);
|
RealtimeTableController.prototype = Object.create(TableController.prototype);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Override the subscribe function defined on the parent controller in
|
* Overrides method on TelemetryTableController providing handling
|
||||||
order to handle realtime telemetry instead of historical.
|
* for realtime data.
|
||||||
*/
|
*/
|
||||||
RTTelemetryTableController.prototype.subscribe = function () {
|
RealtimeTableController.prototype.addRealtimeData = function() {
|
||||||
var self = this;
|
var self = this,
|
||||||
self.$scope.rows = undefined;
|
datum,
|
||||||
(this.subscriptions || []).forEach(function (unsubscribe){
|
row;
|
||||||
unsubscribe();
|
this.handle.getTelemetryObjects().forEach(function (telemetryObject){
|
||||||
});
|
datum = self.handle.getDatum(telemetryObject);
|
||||||
|
if (datum) {
|
||||||
|
//Populate row values from telemetry datum
|
||||||
|
row = self.table.getRowValues(telemetryObject, datum);
|
||||||
|
self.$scope.rows.push(row);
|
||||||
|
|
||||||
if (this.handle) {
|
//Inform table that a new row has been added
|
||||||
this.handle.unsubscribe();
|
if (self.$scope.rows.length > self.maxRows) {
|
||||||
}
|
self.$scope.$broadcast('remove:row', 0);
|
||||||
|
self.$scope.rows.shift();
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
}
|
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;
|
return RealtimeTableController;
|
||||||
}
|
}
|
||||||
);
|
);
|
@ -51,13 +51,29 @@ define(
|
|||||||
|
|
||||||
this.$scope = $scope;
|
this.$scope = $scope;
|
||||||
this.domainObject = $scope.domainObject;
|
this.domainObject = $scope.domainObject;
|
||||||
|
this.listeners = [];
|
||||||
|
|
||||||
$scope.columnsForm = {};
|
$scope.columnsForm = {};
|
||||||
|
|
||||||
this.domainObject.getCapability('mutation').listen(function (model) {
|
function unlisten() {
|
||||||
self.populateForm(model);
|
self.listeners.forEach(function (listener) {
|
||||||
|
listener();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.$watch('domainObject', function(domainObject) {
|
||||||
|
unlisten();
|
||||||
|
self.populateForm(domainObject.getModel());
|
||||||
|
|
||||||
|
self.listeners.push(self.domainObject.getCapability('mutation').listen(function (model) {
|
||||||
|
self.populateForm(model);
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maintain a configuration object on scope that stores column
|
||||||
|
* configuration. On change, synchronize with object model.
|
||||||
|
*/
|
||||||
$scope.$watchCollection('configuration.table.columns', function (columns){
|
$scope.$watchCollection('configuration.table.columns', function (columns){
|
||||||
if (columns){
|
if (columns){
|
||||||
self.domainObject.useCapability('mutation', function (model) {
|
self.domainObject.useCapability('mutation', function (model) {
|
||||||
@ -67,6 +83,11 @@ define(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy all mutation listeners
|
||||||
|
*/
|
||||||
|
$scope.$on('$destroy', unlisten);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TableOptionsController.prototype.populateForm = function (model) {
|
TableOptionsController.prototype.populateForm = function (model) {
|
||||||
@ -86,7 +107,7 @@ define(
|
|||||||
'key': key
|
'key': key
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
this.$scope.configuration = JSON.parse(JSON.stringify(model.configuration));
|
this.$scope.configuration = JSON.parse(JSON.stringify(model.configuration || {}));
|
||||||
};
|
};
|
||||||
|
|
||||||
return TableOptionsController;
|
return TableOptionsController;
|
||||||
|
@ -52,19 +52,15 @@ define(
|
|||||||
this.$scope = $scope;
|
this.$scope = $scope;
|
||||||
this.columns = {}; //Range and Domain columns
|
this.columns = {}; //Range and Domain columns
|
||||||
this.handle = undefined;
|
this.handle = undefined;
|
||||||
//this.pending = false;
|
|
||||||
this.telemetryHandler = telemetryHandler;
|
this.telemetryHandler = telemetryHandler;
|
||||||
this.table = new TableConfiguration($scope.domainObject,
|
this.table = new TableConfiguration($scope.domainObject,
|
||||||
telemetryFormatter);
|
telemetryFormatter);
|
||||||
this.changeListeners = [];
|
this.changeListeners = [];
|
||||||
|
|
||||||
$scope.rows = undefined;
|
$scope.rows = [];
|
||||||
|
|
||||||
// Subscribe to telemetry when a domain object becomes available
|
// Subscribe to telemetry when a domain object becomes available
|
||||||
this.$scope.$watch('domainObject', function(domainObject){
|
this.$scope.$watch('domainObject', function(){
|
||||||
if (!domainObject)
|
|
||||||
return;
|
|
||||||
|
|
||||||
self.subscribe();
|
self.subscribe();
|
||||||
self.registerChangeListeners();
|
self.registerChangeListeners();
|
||||||
});
|
});
|
||||||
@ -73,16 +69,24 @@ define(
|
|||||||
this.$scope.$on("$destroy", this.destroy.bind(this));
|
this.$scope.$on("$destroy", this.destroy.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
TelemetryTableController.prototype.unregisterChangeListeners = function () {
|
||||||
|
this.changeListeners.forEach(function (listener) {
|
||||||
|
return listener && listener();
|
||||||
|
});
|
||||||
|
this.changeListeners = [];
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defer registration of change listeners until domain object is
|
* Defer registration of change listeners until domain object is
|
||||||
* available in order to avoid race conditions
|
* available in order to avoid race conditions
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
TelemetryTableController.prototype.registerChangeListeners = function () {
|
TelemetryTableController.prototype.registerChangeListeners = function () {
|
||||||
this.changeListeners.forEach(function (listener) {
|
this.unregisterChangeListeners();
|
||||||
return listener && listener();
|
|
||||||
});
|
|
||||||
this.changeListeners = [];
|
|
||||||
// When composition changes, re-subscribe to the various
|
// When composition changes, re-subscribe to the various
|
||||||
// telemetry subscriptions
|
// telemetry subscriptions
|
||||||
this.changeListeners.push(this.$scope.$watchCollection(
|
this.changeListeners.push(this.$scope.$watchCollection(
|
||||||
@ -103,25 +107,37 @@ define(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function for handling realtime data when it is available. This
|
||||||
|
* will be called by the telemetry framework when new data is
|
||||||
|
* available.
|
||||||
|
*
|
||||||
|
* Method should be overridden by specializing class.
|
||||||
|
*/
|
||||||
|
TelemetryTableController.prototype.addRealtimeData = function () {
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function for handling historical data. Will be called by
|
||||||
|
* telemetry framework when requested historical data is available.
|
||||||
|
* Should be overridden by specializing class.
|
||||||
|
*/
|
||||||
|
TelemetryTableController.prototype.addHistoricalData = function () {
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Create a new subscription. This can be overridden by children to
|
Create a new subscription. This can be overridden by children to
|
||||||
change default behaviour (which is to retrieve historical telemetry
|
change default behaviour (which is to retrieve historical telemetry
|
||||||
only).
|
only).
|
||||||
*/
|
*/
|
||||||
TelemetryTableController.prototype.subscribe = function () {
|
TelemetryTableController.prototype.subscribe = function () {
|
||||||
var self = this;
|
|
||||||
|
|
||||||
if (this.handle) {
|
if (this.handle) {
|
||||||
this.handle.unsubscribe();
|
this.handle.unsubscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
//Noop because not supporting realtime data right now
|
|
||||||
function noop(){
|
|
||||||
}
|
|
||||||
|
|
||||||
this.handle = this.$scope.domainObject && this.telemetryHandler.handle(
|
this.handle = this.$scope.domainObject && this.telemetryHandler.handle(
|
||||||
this.$scope.domainObject,
|
this.$scope.domainObject,
|
||||||
noop,
|
this.addRealtimeData.bind(this),
|
||||||
true // Lossless
|
true // Lossless
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -130,28 +146,6 @@ define(
|
|||||||
this.setup();
|
this.setup();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Populates historical data on scope when it becomes available
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
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
|
* Setup table columns based on domain object metadata
|
||||||
*/
|
*/
|
||||||
@ -162,7 +156,9 @@ define(
|
|||||||
|
|
||||||
if (handle) {
|
if (handle) {
|
||||||
handle.promiseTelemetryObjects().then(function () {
|
handle.promiseTelemetryObjects().then(function () {
|
||||||
table.buildColumns(handle.getMetadata());
|
self.$scope.headers = [];
|
||||||
|
self.$scope.rows = [];
|
||||||
|
table.populateColumns(handle.getMetadata());
|
||||||
|
|
||||||
self.filterColumns();
|
self.filterColumns();
|
||||||
|
|
||||||
@ -176,26 +172,14 @@ define(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* @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
|
|
||||||
*/
|
|
||||||
TelemetryTableController.prototype.updateRows = function (object, datum) {
|
|
||||||
this.$scope.rows.push(this.table.getRowValues(object, datum));
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When column configuration changes, update the visible headers
|
* When column configuration changes, update the visible headers
|
||||||
* accordingly.
|
* accordingly.
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
TelemetryTableController.prototype.filterColumns = function (columnConfig) {
|
TelemetryTableController.prototype.filterColumns = function () {
|
||||||
if (!columnConfig){
|
var columnConfig = this.table.buildColumnConfiguration();
|
||||||
columnConfig = this.table.getColumnConfiguration();
|
|
||||||
this.table.saveColumnConfiguration(columnConfig);
|
|
||||||
}
|
|
||||||
//Populate headers with visible columns (determined by configuration)
|
//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];
|
return columnConfig[column];
|
||||||
|
@ -12,6 +12,51 @@ define(
|
|||||||
* Defines a generic 'Table' component. The table can be populated
|
* Defines a generic 'Table' component. The table can be populated
|
||||||
* en-masse by setting the rows attribute, or rows can be added as
|
* en-masse by setting the rows attribute, or rows can be added as
|
||||||
* needed via a broadcast 'addRow' event.
|
* needed via a broadcast 'addRow' event.
|
||||||
|
*
|
||||||
|
* This directive accepts parameters specifying header and row
|
||||||
|
* content, as well as some additional options.
|
||||||
|
*
|
||||||
|
* Two broadcast events for notifying the table that the rows have
|
||||||
|
* changed. For performance reasons, the table does not monitor the
|
||||||
|
* content of `rows` constantly.
|
||||||
|
* - 'add:row': A $broadcast event that will notify the table that
|
||||||
|
* a new row has been added to the table.
|
||||||
|
* eg.
|
||||||
|
* <pre><code>
|
||||||
|
* $scope.rows.push(newRow);
|
||||||
|
* $scope.$broadcast('add:row', $scope.rows.length-1);
|
||||||
|
* </code></pre>
|
||||||
|
* The code above adds a new row, and alerts the table using the
|
||||||
|
* add:row event. Sorting and filtering will be applied
|
||||||
|
* automatically by the table component.
|
||||||
|
*
|
||||||
|
* - 'remove:row': A $broadcast event that will notify the table that a
|
||||||
|
* row should be removed from the table.
|
||||||
|
* eg.
|
||||||
|
* <pre><code>
|
||||||
|
* $scope.rows.slice(5, 1);
|
||||||
|
* $scope.$broadcast('remove:row', 5);
|
||||||
|
* </code></pre>
|
||||||
|
* The code above removes a row from the rows array, and then alerts
|
||||||
|
* the table to its removal.
|
||||||
|
*
|
||||||
|
* @memberof platform/features/table
|
||||||
|
* @param {string[]} headers The column titles to appear at the top
|
||||||
|
* of the table. Corresponding values are specified in the rows
|
||||||
|
* using the header title provided here.
|
||||||
|
* @param {Object[]} rows The row content. Each row is an object
|
||||||
|
* with key-value pairs where the key corresponds to a header
|
||||||
|
* specified in the headers parameter.
|
||||||
|
* @param {boolean} enableFilter If true, values will be searchable
|
||||||
|
* and results filtered
|
||||||
|
* @param {boolean} enableSort If true, sorting will be enabled
|
||||||
|
* allowing sorting by clicking on column headers
|
||||||
|
* @param {boolean} autoScroll If true, table will automatically
|
||||||
|
* scroll to the bottom as new data arrives. Auto-scroll can be
|
||||||
|
* disengaged manually by scrolling away from the bottom of the
|
||||||
|
* table, and can also be enabled manually by scrolling to the bottom of
|
||||||
|
* the table rows.
|
||||||
|
*
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
function MCTTable($timeout) {
|
function MCTTable($timeout) {
|
||||||
|
@ -116,10 +116,10 @@ define(
|
|||||||
}];
|
}];
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
table.buildColumns(metadata);
|
table.populateColumns(metadata);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("populates the columns attribute", function() {
|
it("populates columns", function() {
|
||||||
expect(table.columns.length).toBe(5);
|
expect(table.columns.length).toBe(5);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -141,7 +141,7 @@ define(
|
|||||||
|
|
||||||
it("Provides a default configuration with all columns" +
|
it("Provides a default configuration with all columns" +
|
||||||
" visible", function() {
|
" visible", function() {
|
||||||
var configuration = table.getColumnConfiguration();
|
var configuration = table.buildColumnConfiguration();
|
||||||
|
|
||||||
expect(configuration).toBeDefined();
|
expect(configuration).toBeDefined();
|
||||||
expect(Object.keys(configuration).every(function(key){
|
expect(Object.keys(configuration).every(function(key){
|
||||||
@ -160,7 +160,7 @@ define(
|
|||||||
};
|
};
|
||||||
mockModel.configuration = modelConfig;
|
mockModel.configuration = modelConfig;
|
||||||
|
|
||||||
tableConfig = table.getColumnConfiguration();
|
tableConfig = table.buildColumnConfiguration();
|
||||||
|
|
||||||
expect(tableConfig).toBeDefined();
|
expect(tableConfig).toBeDefined();
|
||||||
expect(tableConfig['Range 1']).toBe(false);
|
expect(tableConfig['Range 1']).toBe(false);
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
|
|
||||||
define(
|
define(
|
||||||
[
|
[
|
||||||
"../../src/controllers/TelemetryTableController"
|
"../../src/controllers/HistoricalTableController"
|
||||||
],
|
],
|
||||||
function (TableController) {
|
function (TableController) {
|
||||||
"use strict";
|
"use strict";
|
||||||
@ -73,14 +73,14 @@ define(
|
|||||||
|
|
||||||
mockTable = jasmine.createSpyObj('table',
|
mockTable = jasmine.createSpyObj('table',
|
||||||
[
|
[
|
||||||
'buildColumns',
|
'populateColumns',
|
||||||
'getColumnConfiguration',
|
'buildColumnConfiguration',
|
||||||
'getRowValues',
|
'getRowValues',
|
||||||
'saveColumnConfiguration'
|
'saveColumnConfiguration'
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
mockTable.columns = [];
|
mockTable.columns = [];
|
||||||
mockTable.getColumnConfiguration.andReturn(mockConfiguration);
|
mockTable.buildColumnConfiguration.andReturn(mockConfiguration);
|
||||||
|
|
||||||
mockDomainObject= jasmine.createSpyObj('domainObject', [
|
mockDomainObject= jasmine.createSpyObj('domainObject', [
|
||||||
'getCapability',
|
'getCapability',
|
||||||
@ -126,21 +126,18 @@ define(
|
|||||||
expect(mockTelemetryHandle.unsubscribe).toHaveBeenCalled();
|
expect(mockTelemetryHandle.unsubscribe).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('the controller makes use of the table', function () {
|
describe('makes use of the table', function () {
|
||||||
|
|
||||||
it('to create column definitions from telemetry' +
|
it('to create column definitions from telemetry' +
|
||||||
' metadata', function () {
|
' metadata', function () {
|
||||||
controller.setup();
|
controller.setup();
|
||||||
expect(mockTable.buildColumns).toHaveBeenCalled();
|
expect(mockTable.populateColumns).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('to create column configuration, which is written to the' +
|
it('to create column configuration, which is written to the' +
|
||||||
' object model', function () {
|
' object model', function () {
|
||||||
var mockModel = {};
|
|
||||||
|
|
||||||
controller.setup();
|
controller.setup();
|
||||||
expect(mockTable.getColumnConfiguration).toHaveBeenCalled();
|
expect(mockTable.buildColumnConfiguration).toHaveBeenCalled();
|
||||||
expect(mockTable.saveColumnConfiguration).toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -58,15 +58,18 @@ define(
|
|||||||
|
|
||||||
mockElement = jasmine.createSpyObj('element', [
|
mockElement = jasmine.createSpyObj('element', [
|
||||||
'find',
|
'find',
|
||||||
|
'prop',
|
||||||
'on'
|
'on'
|
||||||
]);
|
]);
|
||||||
mockElement.find.andReturn(mockElement);
|
mockElement.find.andReturn(mockElement);
|
||||||
|
mockElement.prop.andReturn(0);
|
||||||
|
|
||||||
mockScope.displayHeaders = true;
|
mockScope.displayHeaders = true;
|
||||||
mockTimeout = jasmine.createSpy('$timeout');
|
mockTimeout = jasmine.createSpy('$timeout');
|
||||||
mockTimeout.andReturn(promise(undefined));
|
mockTimeout.andReturn(promise(undefined));
|
||||||
|
|
||||||
controller = new MCTTableController(mockScope, mockTimeout, mockElement);
|
controller = new MCTTableController(mockScope, mockTimeout, mockElement);
|
||||||
|
spyOn(controller, 'setVisibleRows');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Reacts to changes to filters, headers, and rows', function() {
|
it('Reacts to changes to filters, headers, and rows', function() {
|
||||||
@ -115,7 +118,7 @@ define(
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Sets rows on scope when rows change', function() {
|
it('Sets rows on scope when rows change', function() {
|
||||||
controller.updateRows(testRows);
|
controller.setRows(testRows);
|
||||||
expect(mockScope.displayRows.length).toBe(3);
|
expect(mockScope.displayRows.length).toBe(3);
|
||||||
expect(mockScope.displayRows).toEqual(testRows);
|
expect(mockScope.displayRows).toEqual(testRows);
|
||||||
});
|
});
|
||||||
@ -127,7 +130,7 @@ define(
|
|||||||
'col2': {'text': 'ghi'},
|
'col2': {'text': 'ghi'},
|
||||||
'col3': {'text': 'row3 col3'}
|
'col3': {'text': 'row3 col3'}
|
||||||
};
|
};
|
||||||
controller.updateRows(testRows);
|
controller.setRows(testRows);
|
||||||
expect(mockScope.displayRows.length).toBe(3);
|
expect(mockScope.displayRows.length).toBe(3);
|
||||||
testRows.push(row4);
|
testRows.push(row4);
|
||||||
addRowFunc(undefined, 3);
|
addRowFunc(undefined, 3);
|
||||||
@ -136,10 +139,8 @@ define(
|
|||||||
|
|
||||||
it('Supports removing rows individually', function() {
|
it('Supports removing rows individually', function() {
|
||||||
var removeRowFunc = mockScope.$on.calls[mockScope.$on.calls.length-1].args[1];
|
var removeRowFunc = mockScope.$on.calls[mockScope.$on.calls.length-1].args[1];
|
||||||
controller.updateRows(testRows);
|
controller.setRows(testRows);
|
||||||
expect(mockScope.displayRows.length).toBe(3);
|
expect(mockScope.displayRows.length).toBe(3);
|
||||||
spyOn(controller, 'setVisibleRows');
|
|
||||||
//controller.setVisibleRows.andReturn(undefined);
|
|
||||||
removeRowFunc(undefined, 2);
|
removeRowFunc(undefined, 2);
|
||||||
expect(mockScope.displayRows.length).toBe(2);
|
expect(mockScope.displayRows.length).toBe(2);
|
||||||
expect(controller.setVisibleRows).toHaveBeenCalled();
|
expect(controller.setVisibleRows).toHaveBeenCalled();
|
||||||
@ -179,7 +180,54 @@ define(
|
|||||||
expect(sortedRows[2].col2.text).toEqual('abc');
|
expect(sortedRows[2].col2.text).toEqual('abc');
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Adding new rows', function() {
|
it('correctly sorts rows of differing types', function () {
|
||||||
|
mockScope.sortColumn = 'col2';
|
||||||
|
mockScope.sortDirection = 'desc';
|
||||||
|
|
||||||
|
testRows.push({
|
||||||
|
'col1': {'text': 'row4 col1'},
|
||||||
|
'col2': {'text': '123'},
|
||||||
|
'col3': {'text': 'row4 col3'}
|
||||||
|
});
|
||||||
|
testRows.push({
|
||||||
|
'col1': {'text': 'row5 col1'},
|
||||||
|
'col2': {'text': '456'},
|
||||||
|
'col3': {'text': 'row5 col3'}
|
||||||
|
});
|
||||||
|
testRows.push({
|
||||||
|
'col1': {'text': 'row5 col1'},
|
||||||
|
'col2': {'text': ''},
|
||||||
|
'col3': {'text': 'row5 col3'}
|
||||||
|
});
|
||||||
|
|
||||||
|
sortedRows = controller.sortRows(testRows);
|
||||||
|
expect(sortedRows[0].col2.text).toEqual('ghi');
|
||||||
|
expect(sortedRows[1].col2.text).toEqual('def');
|
||||||
|
expect(sortedRows[2].col2.text).toEqual('abc');
|
||||||
|
|
||||||
|
expect(sortedRows[sortedRows.length-3].col2.text).toEqual('456');
|
||||||
|
expect(sortedRows[sortedRows.length-2].col2.text).toEqual('123');
|
||||||
|
expect(sortedRows[sortedRows.length-1].col2.text).toEqual('');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('The sort comparator', function () {
|
||||||
|
it('Correctly sorts different data types', function () {
|
||||||
|
var val1 = "",
|
||||||
|
val2 = "1",
|
||||||
|
val3 = "2016-04-05 18:41:30.713Z",
|
||||||
|
val4 = "1.1",
|
||||||
|
val5 = "8.945520958175627e-13";
|
||||||
|
mockScope.sortDirection = "asc";
|
||||||
|
|
||||||
|
expect(controller.sortComparator(val1, val2)).toEqual(-1);
|
||||||
|
expect(controller.sortComparator(val3, val1)).toEqual(1);
|
||||||
|
expect(controller.sortComparator(val3, val2)).toEqual(1);
|
||||||
|
expect(controller.sortComparator(val4, val2)).toEqual(1);
|
||||||
|
expect(controller.sortComparator(val2, val5)).toEqual(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Adding new rows', function () {
|
||||||
var row4,
|
var row4,
|
||||||
row5,
|
row5,
|
||||||
row6;
|
row6;
|
||||||
@ -210,20 +258,20 @@ define(
|
|||||||
mockScope.displayRows = controller.sortRows(testRows.slice(0));
|
mockScope.displayRows = controller.sortRows(testRows.slice(0));
|
||||||
|
|
||||||
mockScope.rows.push(row4);
|
mockScope.rows.push(row4);
|
||||||
controller.newRow(undefined, mockScope.rows.length-1);
|
controller.addRow(undefined, mockScope.rows.length-1);
|
||||||
expect(mockScope.displayRows[0].col2.text).toEqual('xyz');
|
expect(mockScope.displayRows[0].col2.text).toEqual('xyz');
|
||||||
|
|
||||||
mockScope.rows.push(row5);
|
mockScope.rows.push(row5);
|
||||||
controller.newRow(undefined, mockScope.rows.length-1);
|
controller.addRow(undefined, mockScope.rows.length-1);
|
||||||
expect(mockScope.displayRows[4].col2.text).toEqual('aaa');
|
expect(mockScope.displayRows[4].col2.text).toEqual('aaa');
|
||||||
|
|
||||||
mockScope.rows.push(row6);
|
mockScope.rows.push(row6);
|
||||||
controller.newRow(undefined, mockScope.rows.length-1);
|
controller.addRow(undefined, mockScope.rows.length-1);
|
||||||
expect(mockScope.displayRows[2].col2.text).toEqual('ggg');
|
expect(mockScope.displayRows[2].col2.text).toEqual('ggg');
|
||||||
|
|
||||||
//Add a duplicate row
|
//Add a duplicate row
|
||||||
mockScope.rows.push(row6);
|
mockScope.rows.push(row6);
|
||||||
controller.newRow(undefined, mockScope.rows.length-1);
|
controller.addRow(undefined, mockScope.rows.length-1);
|
||||||
expect(mockScope.displayRows[2].col2.text).toEqual('ggg');
|
expect(mockScope.displayRows[2].col2.text).toEqual('ggg');
|
||||||
expect(mockScope.displayRows[3].col2.text).toEqual('ggg');
|
expect(mockScope.displayRows[3].col2.text).toEqual('ggg');
|
||||||
});
|
});
|
||||||
@ -239,18 +287,18 @@ define(
|
|||||||
mockScope.displayRows = controller.filterRows(testRows);
|
mockScope.displayRows = controller.filterRows(testRows);
|
||||||
|
|
||||||
mockScope.rows.push(row5);
|
mockScope.rows.push(row5);
|
||||||
controller.newRow(undefined, mockScope.rows.length-1);
|
controller.addRow(undefined, mockScope.rows.length-1);
|
||||||
expect(mockScope.displayRows.length).toBe(2);
|
expect(mockScope.displayRows.length).toBe(2);
|
||||||
expect(mockScope.displayRows[1].col2.text).toEqual('aaa');
|
expect(mockScope.displayRows[1].col2.text).toEqual('aaa');
|
||||||
|
|
||||||
mockScope.rows.push(row6);
|
mockScope.rows.push(row6);
|
||||||
controller.newRow(undefined, mockScope.rows.length-1);
|
controller.addRow(undefined, mockScope.rows.length-1);
|
||||||
expect(mockScope.displayRows.length).toBe(2);
|
expect(mockScope.displayRows.length).toBe(2);
|
||||||
//Row was not added because does not match filter
|
//Row was not added because does not match filter
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Adds new rows at the correct sort position when' +
|
it('Adds new rows at the correct sort position when' +
|
||||||
' not sorted ', function() {
|
' not sorted ', function () {
|
||||||
mockScope.sortColumn = undefined;
|
mockScope.sortColumn = undefined;
|
||||||
mockScope.sortDirection = undefined;
|
mockScope.sortDirection = undefined;
|
||||||
mockScope.filters = {};
|
mockScope.filters = {};
|
||||||
@ -258,14 +306,33 @@ define(
|
|||||||
mockScope.displayRows = testRows.slice(0);
|
mockScope.displayRows = testRows.slice(0);
|
||||||
|
|
||||||
mockScope.rows.push(row5);
|
mockScope.rows.push(row5);
|
||||||
controller.newRow(undefined, mockScope.rows.length-1);
|
controller.addRow(undefined, mockScope.rows.length-1);
|
||||||
expect(mockScope.displayRows[3].col2.text).toEqual('aaa');
|
expect(mockScope.displayRows[3].col2.text).toEqual('aaa');
|
||||||
|
|
||||||
mockScope.rows.push(row6);
|
mockScope.rows.push(row6);
|
||||||
controller.newRow(undefined, mockScope.rows.length-1);
|
controller.addRow(undefined, mockScope.rows.length-1);
|
||||||
expect(mockScope.displayRows[4].col2.text).toEqual('ggg');
|
expect(mockScope.displayRows[4].col2.text).toEqual('ggg');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Resizes columns if length of any columns in new' +
|
||||||
|
' row exceeds corresponding existing column', function() {
|
||||||
|
var row7 = {
|
||||||
|
'col1': {'text': 'row6 col1'},
|
||||||
|
'col2': {'text': 'some longer string'},
|
||||||
|
'col3': {'text': 'row6 col3'}
|
||||||
|
};
|
||||||
|
|
||||||
|
mockScope.sortColumn = undefined;
|
||||||
|
mockScope.sortDirection = undefined;
|
||||||
|
mockScope.filters = {};
|
||||||
|
|
||||||
|
mockScope.displayRows = testRows.slice(0);
|
||||||
|
|
||||||
|
mockScope.rows.push(row7);
|
||||||
|
controller.addRow(undefined, mockScope.rows.length-1);
|
||||||
|
expect(controller.$scope.sizingRow.col2).toEqual({text: 'some longer string'});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
|
|
||||||
define(
|
define(
|
||||||
[
|
[
|
||||||
"../../src/controllers/RTTelemetryTableController"
|
"../../src/controllers/RealtimeTableController"
|
||||||
],
|
],
|
||||||
function (TableController) {
|
function (TableController) {
|
||||||
"use strict";
|
"use strict";
|
||||||
@ -77,14 +77,14 @@ define(
|
|||||||
|
|
||||||
mockTable = jasmine.createSpyObj('table',
|
mockTable = jasmine.createSpyObj('table',
|
||||||
[
|
[
|
||||||
'buildColumns',
|
'populateColumns',
|
||||||
'getColumnConfiguration',
|
'buildColumnConfiguration',
|
||||||
'getRowValues',
|
'getRowValues',
|
||||||
'saveColumnConfiguration'
|
'saveColumnConfiguration'
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
mockTable.columns = [];
|
mockTable.columns = [];
|
||||||
mockTable.getColumnConfiguration.andReturn(mockConfiguration);
|
mockTable.buildColumnConfiguration.andReturn(mockConfiguration);
|
||||||
mockTable.getRowValues.andReturn(mockTableRow);
|
mockTable.getRowValues.andReturn(mockTableRow);
|
||||||
|
|
||||||
mockDomainObject= jasmine.createSpyObj('domainObject', [
|
mockDomainObject= jasmine.createSpyObj('domainObject', [
|
||||||
@ -107,13 +107,16 @@ define(
|
|||||||
'unsubscribe',
|
'unsubscribe',
|
||||||
'getDatum',
|
'getDatum',
|
||||||
'promiseTelemetryObjects',
|
'promiseTelemetryObjects',
|
||||||
'getTelemetryObjects'
|
'getTelemetryObjects',
|
||||||
|
'request'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Arbitrary array with non-zero length, contents are not
|
// Arbitrary array with non-zero length, contents are not
|
||||||
// used by mocks
|
// used by mocks
|
||||||
mockTelemetryHandle.getTelemetryObjects.andReturn([{}]);
|
mockTelemetryHandle.getTelemetryObjects.andReturn([{}]);
|
||||||
mockTelemetryHandle.promiseTelemetryObjects.andReturn(promise(undefined));
|
mockTelemetryHandle.promiseTelemetryObjects.andReturn(promise(undefined));
|
||||||
mockTelemetryHandle.getDatum.andReturn({});
|
mockTelemetryHandle.getDatum.andReturn({});
|
||||||
|
mockTelemetryHandle.request.andReturn(promise(undefined));
|
||||||
|
|
||||||
mockTelemetryHandler = jasmine.createSpyObj('telemetryHandler', [
|
mockTelemetryHandler = jasmine.createSpyObj('telemetryHandler', [
|
||||||
'handle'
|
'handle'
|
@ -47,18 +47,36 @@ define(
|
|||||||
'listen'
|
'listen'
|
||||||
]);
|
]);
|
||||||
mockDomainObject = jasmine.createSpyObj('domainObject', [
|
mockDomainObject = jasmine.createSpyObj('domainObject', [
|
||||||
'getCapability'
|
'getCapability',
|
||||||
|
'getModel'
|
||||||
]);
|
]);
|
||||||
mockDomainObject.getCapability.andReturn(mockCapability);
|
mockDomainObject.getCapability.andReturn(mockCapability);
|
||||||
|
mockDomainObject.getModel.andReturn({});
|
||||||
|
|
||||||
mockScope = jasmine.createSpyObj('scope', [
|
mockScope = jasmine.createSpyObj('scope', [
|
||||||
'$watchCollection'
|
'$watchCollection',
|
||||||
|
'$watch',
|
||||||
|
'$on'
|
||||||
]);
|
]);
|
||||||
mockScope.domainObject = mockDomainObject;
|
mockScope.domainObject = mockDomainObject;
|
||||||
|
|
||||||
controller = new TableOptionsController(mockScope);
|
controller = new TableOptionsController(mockScope);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Listens for changing domain object', function() {
|
||||||
|
expect(mockScope.$watch).toHaveBeenCalledWith('domainObject', jasmine.any(Function));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('On destruction of controller, destroys listeners', function() {
|
||||||
|
var unlistenFunc = jasmine.createSpy("unlisten");
|
||||||
|
controller.listeners.push(unlistenFunc);
|
||||||
|
expect(mockScope.$on).toHaveBeenCalledWith('$destroy', jasmine.any(Function));
|
||||||
|
mockScope.$on.mostRecentCall.args[1]();
|
||||||
|
expect(unlistenFunc).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
it('Registers a listener for mutation events on the object', function() {
|
it('Registers a listener for mutation events on the object', function() {
|
||||||
|
mockScope.$watch.mostRecentCall.args[1](mockDomainObject);
|
||||||
expect(mockCapability.listen).toHaveBeenCalled();
|
expect(mockCapability.listen).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user