[Tables] - Sticky headers (#2071)

* first revision

* [Frontend] Styling for sticky table headers

Fixes #1481
- WIP convert mct-table layout to use flex;
- TODO: fix flex layout when a small number of rows;
- Rename CSS classes used as selectors by JS;

* remove header height from calculations since it is outside in its own table now

* [Frontend] Styling for sticky table headers

Fixes #1481
- Fixed flex layout when a small number of rows;
- Refined input padding and dropshadow for more compactness;

* fix tests and verify tables works properly in layout and large view

* add mct-scroll to header table to allow scrolling in sync with the rest of mct-table

* Various fixes and polishing

Fixes #2071
- Fix headers height issue;
- Move inline styles to classes;
- First round fix for horz overflow due to scrollbar problem;

* WIP horz overflow

Fixes #2071
- Commented out CSS-based scrollbar with approach in
anticipation of better JS solution;

* Horz overflow/scrollbar problem fixed

Fixes #2071
- Added calcTableWidthPx to allow sizing-table to subtract
width of scrollbar;

* Remove commented code

* add clear icon back into filter text boxes

* Polishing on sticky table headers filtering

Fixes #1481
Fixes #2071
- Now hides the magnify glass in table header filters when typing;
This commit is contained in:
Deep Tailor 2018-06-29 11:38:18 -07:00 committed by Pete Richards
parent 9d2c7a6de5
commit b8f278cabf
9 changed files with 146 additions and 113 deletions

View File

@ -111,7 +111,7 @@ $bubbleMaxW: 300px;
$reqSymbolW: 15px;
$reqSymbolM: $interiorMargin * 2;
$reqSymbolFontSize: 0.75em;
$inputTextPTopBtm: 3px;
$inputTextPTopBtm: 2px;
$inputTextPLeftRight: 5px;
$inputTextP: $inputTextPTopBtm $inputTextPLeftRight;
/*************** Wait Spinner Defaults */

View File

@ -199,12 +199,18 @@ a.disabled {
}
.vscroll {
overflow-x: hidden;
overflow-y: auto;
&.scroll-pad {
padding-right: $interiorMargin;
}
}
.vscroll--persist {
overflow-x: hidden;
overflow-y: scroll;
}
.slidable {
cursor: move; // Fallback
cursor: grab;

View File

@ -334,7 +334,7 @@
color: $fg;
}
@mixin nice-input($bg: $colorInputBg, $fg: $colorInputFg, $shdw: rgba(black, 0.6) 0 1px 3px) {
@mixin nice-input($bg: $colorInputBg, $fg: $colorInputFg, $shdw: rgba(black, 0.5) 0 0px 2px) {
@include s-input($bg, $fg, $shdw);
border: none;
outline: none;

View File

@ -26,6 +26,20 @@
.tabular-holder {
@include absPosDefault();
display: flex;
flex-flow: column nowrap;
justify-content: flex-start;
> * {
position: relative;
flex: 0 0 auto;
}
&.l-sticky-headers {
.l-tabular-body {
flex: 1 1 99%;
overflow-x: auto;
}
}
}
.tabular,
@ -41,19 +55,20 @@ table {
tbody tr, .tbody .tr {
width: 100%;
}
thead, .thead {
border-bottom: 1px solid $colorTabHeaderBorder;
}
&:not(.fixed-header) tr th {
thead tr th {
// Add some bg to the headers. Note that this is overwritten below
// by .mct-table-headers-w when headers are wrapped by that container.
background-color: $colorTabHeaderBg;
}
tbody, .tbody {
display: table-row-group;
}
tr, .tr {
border-top: 1px solid $colorTabBorder;
}
}
tr, .tr {
display: table-row;
&:first-child .td {
border-top: none;
@ -118,42 +133,40 @@ table {
tbody, .tbody {
top: $tabularHeaderH * 2;
}
.l-filter {
input[type="text"],
input[type="search"] {
$p: 20px;
transition: padding 200ms ease-in-out;
box-sizing: border-box;
padding-right: $p; // Fend off from icon
padding-left: $p; // Fend off from icon
width: 100%;
}
&.active {
// When user has typed something, hide the icon and collapse left padding
&:before {
opacity: 0;
}
input[type="text"],
input[type="search"] {
padding-left: $interiorMargin;
}
}
}
}
&.fixed-header {
height: 100%;
thead, .thead,
tbody tr, .tbody .tr {
display: table;
table-layout: fixed;
}
thead, .thead {
width: calc(100% - 10px);
&:before {
content: "";
display: block;
z-index: 0;
position: absolute;
width: 100%;
height: $tabularHeaderH;
background-color: $colorTabHeaderBg;
}
}
tbody, .tbody {
@include absPosDefault(0);
top: $tabularHeaderH;
display: block;
overflow-y: scroll;
}
}
&.t-event-messages {
td, .td {
min-width: 150px;
}
}
}
.mct-table-headers-w {
background: $colorTabHeaderBg;
overflow: hidden;
thead tr th {
background: none;
}
}

View File

@ -20,11 +20,11 @@
* at runtime from the About dialog for additional information.
*****************************************************************************/
.sizing-table {
min-width: 100%;
mct-table {
.mct-sizing-table {
z-index: -1;
visibility: hidden;
position: absolute;
position: absolute !important;
//Add some padding to allow for decorations such as limits indicator
td {
@ -32,9 +32,9 @@
padding-left: 10px;
white-space: nowrap;
}
}
.mct-table {
table-layout: fixed;
}
.mct-table {
thead {
display: block;
tr {
@ -59,4 +59,9 @@
display: inline-block;
}
}
}
.l-control-bar {
margin-bottom: 3px;
}
}

View File

@ -5,20 +5,9 @@
Export
</a>
</div>
<div class="l-view-section scrolling" mct-resize="resize()">
<table class="sizing-table">
<tbody>
<tr>
<td ng-repeat="header in displayHeaders">{{header}}</td>
</tr>
<tr><td ng-repeat="header in displayHeaders" >
{{sizingRow[header].text}}
</td></tr>
</tbody>
</table>
<table class="filterable mct-table"
<div class="mct-table-headers-w" mct-scroll-x="scroll.x">
<table class="mct-table l-tabular-headers filterable"
ng-style="{
height: totalHeight + 'px',
'max-width': totalWidth
}">
<thead>
@ -43,14 +32,38 @@
width: columnWidths[$index] + 'px',
'max-width': columnWidths[$index] + 'px',
}">
<div class="holder l-filter flex-elem grows">
<input type="search"
<div class="holder l-filter flex-elem grows"
ng-class="{active: filters[header]}">
<input type="text"
ng-model="filters[header]"/>
<a class="clear-icon clear-input icon-x-in-circle" ng-class="{show: filters[header]}" ng-click="filters[header] = undefined"></a>
<a class="clear-icon clear-input icon-x-in-circle"
ng-class="{show: filters[header]}"
ng-click="filters[header] = undefined"></a>
</div>
</th>
</tr>
</thead>
</table>
</div>
<table class="mct-sizing-table t-sizing-table"
ng-style="{
width: calcTableWidthPx
}">
<tbody>
<tr>
<td ng-repeat="header in displayHeaders">{{header}}</td>
</tr>
<tr><td ng-repeat="header in displayHeaders" >
{{sizingRow[header].text}}
</td></tr>
</tbody>
</table>
<div class="l-tabular-body t-scrolling vscroll--persist" mct-resize="resize()" mct-scroll-x="scroll.x">
<table class="mct-table"
ng-style="{
height: totalHeight + 'px',
'max-width': totalWidth
}">
<tbody>
<tr ng-repeat-start="visibleRow in visibleRows track by $index"
ng-if="visibleRow.rowIndex === toiRowIndex"

View File

@ -10,6 +10,6 @@
auto-scroll="autoScroll"
default-sort="defaultSort"
export-as="{{ exportAs }}"
class="tabular-holder has-control-bar">
class="tabular-holder l-sticky-headers has-control-bar">
</mct-table>
</div>

View File

@ -23,10 +23,11 @@ define(
this.$window = $window;
this.maxDisplayRows = 100;
this.scrollable = this.element.find('.l-view-section.scrolling').first();
this.scrollable = this.element.find('.t-scrolling').first();
this.resultsHeader = this.element.find('.mct-table>thead').first();
this.sizingTableBody = this.element.find('.sizing-table>tbody').first();
this.sizingTableBody = this.element.find('.t-sizing-table>tbody').first();
this.$scope.sizingRow = {};
this.$scope.calcTableWidthPx = '100%';
this.timeApi = openmct.time;
this.toiFormatter = undefined;
this.formatService = formatService;
@ -286,14 +287,9 @@ define(
topScroll = target.scrollTop,
firstVisible;
if (topScroll < this.$scope.headerHeight) {
firstVisible = 0;
} else {
firstVisible = Math.floor(
(topScroll - this.$scope.headerHeight) /
this.$scope.rowHeight
(topScroll) / this.$scope.rowHeight
);
}
return firstVisible;
};
@ -309,7 +305,7 @@ define(
lastVisible;
lastVisible = Math.ceil(
(bottomScroll - this.$scope.headerHeight) /
(bottomScroll) /
this.$scope.rowHeight
);
return lastVisible;
@ -360,8 +356,7 @@ define(
.map(function (row, i) {
return {
rowIndex: start + i,
offsetY: ((start + i) * self.$scope.rowHeight) +
self.$scope.headerHeight,
offsetY: ((start + i) * self.$scope.rowHeight),
contents: row
};
});
@ -397,15 +392,13 @@ define(
* for individual rows.
*/
MCTTableController.prototype.setElementSizes = function () {
var thead = this.resultsHeader,
tbody = this.sizingTableBody,
var tbody = this.sizingTableBody,
firstRow = tbody.find('tr'),
column = firstRow.find('td'),
headerHeight = thead.prop('offsetHeight'),
rowHeight = firstRow.prop('offsetHeight'),
columnWidth,
tableWidth = 0,
overallHeight = headerHeight + (rowHeight *
overallHeight = (rowHeight *
(this.$scope.displayRows ? this.$scope.displayRows.length - 1 : 0));
this.$scope.columnWidths = [];
@ -416,10 +409,14 @@ define(
tableWidth += columnWidth;
column = column.next();
}
this.$scope.headerHeight = headerHeight;
this.$scope.rowHeight = rowHeight;
this.$scope.totalHeight = overallHeight;
var scrollW = this.scrollable[0].offsetWidth - this.scrollable[0].clientWidth;
if (scrollW && scrollW > 0) {
this.$scope.calcTableWidthPx = 'calc(100% - ' + scrollW + 'px)';
}
if (tableWidth > 0) {
this.$scope.totalWidth = tableWidth + 'px';
} else {
@ -761,7 +758,6 @@ define(
if (!visible) {
var scrollTop = displayRowIndex * this.$scope.rowHeight +
this.$scope.headerHeight -
(this.scrollable[0].offsetHeight / 2);
this.scrollable[0].scrollTop = scrollTop;
this.setVisibleRows();

View File

@ -29,7 +29,7 @@ define(
function ($, moment, MCTTableController) {
var MOCK_ELEMENT_TEMPLATE =
'<div><div class="l-view-section scrolling">' +
'<div><div class="l-view-section t-scrolling">' +
'<table class="sizing-table"><tbody></tbody></table>' +
'<table class="mct-table"><thead></thead></table>' +
'</div></div>';