[Table] #798 Simplified markup, moved styles to external stylesheet

This commit is contained in:
Henry 2016-04-03 14:25:09 -07:00
parent c591ade479
commit 23a8c305c1
5 changed files with 148 additions and 80 deletions

View File

@ -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"
}
] ]
} }
}); });

View 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;
}
}
}

View File

@ -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 }}

View File

@ -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
@ -100,14 +103,31 @@ define(
* @private * @private
*/ */
MCTTableController.prototype.newRow = function (event, rowIndex) { MCTTableController.prototype.newRow = function (event, rowIndex) {
var row = this.$scope.rows[rowIndex]; var self = this,
//Add row to the filtered, sorted list of all rows row = this.$scope.rows[rowIndex],
if (this.filterRows([row]).length > 0) { largestRow;
this.insertSorted(this.$scope.displayRows, row);
function sizeAndScroll () {
self.setElementSizes();
self.scrollToBottom();
} }
this.$timeout(this.setElementSizes.bind(this)) //Does the row pass the current filter?
.then(this.scrollToBottom.bind(this)); if (this.filterRows([row]).length === 1) {
this.insertSorted(this.$scope.displayRows, row);
//Calculate largest row
largestRow = this.buildLargestRow([this.$scope.sizingRow, row]);
// Has it changed? If so, set the the 'sizing' row which
// determines column widths
if (JSON.stringify(largestRow) !== JSON.stringify(this.$scope.sizingRow)){
this.$scope.sizingRow = largestRow;
this.$timeout(sizeAndScroll);
} else {
sizeAndScroll();
}
}
}; };
/** /**
@ -249,13 +269,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 *
@ -279,8 +298,6 @@ define(
} else { } else {
this.$scope.totalWidth = 'none'; this.$scope.totalWidth = 'none';
} }
this.$scope.overrideRowPositioning = true;
}; };
/** /**
@ -400,43 +417,32 @@ 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]){
//do nothing, no value for this column;
} else {
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;
}; };
@ -447,20 +453,13 @@ define(
* @private * @private
*/ */
MCTTableController.prototype.resize = function (){ MCTTableController.prototype.resize = function (){
var largestRow = this.findLargestRow(this.$scope.displayRows), var self = this;
self = this;
this.$scope.visibleRows = [ this.$scope.sizingRow = this.buildLargestRow(this.$scope.displayRows);
{
rowIndex: 0,
offsetY: undefined,
contents: largestRow
}
];
//Wait a timeout to allow digest of previous change to visible //Wait a timeout to allow digest of previous change to visible
// rows to happen. // rows to happen.
this.$timeout(function () { this.$timeout(function () {
//Remove temporary padding row used for setting column widths
self.$scope.visibleRows = []; self.$scope.visibleRows = [];
self.setElementSizes(); self.setElementSizes();
}); });
@ -489,8 +488,6 @@ define(
//Reset visible rows because new row data available. //Reset visible rows because new row data available.
this.$scope.visibleRows = []; 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) {
return; return;

View File

@ -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() {
@ -138,8 +141,6 @@ define(
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.updateRows(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();
@ -266,6 +267,25 @@ define(
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.newRow(undefined, mockScope.rows.length-1);
expect(controller.$scope.sizingRow.col2).toEqual({text: 'some longer string'});
});
}); });
}); });