Open billdami opened 5 years ago
Hey @billdami,
I've managed to do just that after a lot of trial and error, but I now have a satisfying solution, here's how I've done it:
@fill
prop is used to control whether the table should fill its container (default behaviour of ember-table) or go with the document flow. I don't create the table with the sticky header if @fill={{true}}
<div class="table-header" {{did-insert this.handleTableHeaderInsert}}>
{{#unless @fill}}
<EmberTable class="table" ...attributes as |et|>
<et.head
@columns={{@columns}}
@scrollIndicators="horizontal"
class="et-header"
{{create-ref "thead"}} as |h|
>
<h.row as |r|>
<r.cell as |columnValue columnMeta|>
{{columnValue}}
</r.cell>
</h.row>
</et.head>
</EmberTable>
{{/unless}}
</div>
<EmberTable
class="table"
...attributes
{{did-resize this.handleTableResize}} as |et|
>
<et.head
@columns={{@columns}}
@sorts={{@sorts}}
@scrollIndicators="all"
@columnKeyPath="valuePath"
class={{concat "et-header" (unless @fill " u-d-none")}} as |h|
>
<h.row as |r|>
<r.cell as |columnValue columnMeta|>
{{columnValue}}
</r.cell>
</h.row>
</et.head>
<et.body
@rows={{@rows}}
@key={{@key}}
@staticHeight={{or-else @staticHeight true}}
@containerSelector={{this.containerSelector}}
@bufferSize={{@bufferSize}}
@renderAll={{this.renderAll}}
{{did-insert this.handleTBodyInsert}} as |b|
>
<b.row as |r|>
{{#if
(or
(or (not @hideLeaves) (not @enableTree))
(and @hideLeaves @enableTree r.api.rowValue.children.length)
)
}}
<r.cell as |cellValue columnValue|>
{{cellValue}}
</r.cell>
{{/if}}
</b.row>
</et.body>
{{#if @footerRows.length}}
<et.foot @rows={{@footerRows}} as |f|>
<f.row as |r|>
<r.cell as |cellValue columnValue|>
{{cellValue}}
</r.cell>
</f.row>
</et.foot>
{{/if}}
</EmberTable>
import Component from '@glimmer/component';
import { action } from '@ember/object';
import { tracked } from '@glimmer/tracking';
import { ref } from 'ember-ref-bucket';
import { closest, getScrollParent } from 'app/utils/dom-utils';
export default class Table extends Component<IArgs> {
@tracked scrollContainer!: Element;
@tracked containerSelector?: string;
@ref('thead') thead!: HTMLElement;
xScrollContainer?: HTMLElement;
headerScrollContainer: HTMLElement | null = null;
willDestroy() {
this.xScrollContainer?.removeEventListener('scroll', this.scrollEventHandler);
}
/**
* When the body is ready, we setup the scroll sync event handler
*
* @param tbody `table tbody` element
*/
@action
handleTBodyInsert(tbody: HTMLElement) {
const { thead } = this;
this.headerScrollContainer = thead ? closest(thead, '.ember-table-overflow') : null;
this.xScrollContainer = closest(tbody, '.ember-table-overflow') as HTMLElement;
if (this.headerScrollContainer && this.xScrollContainer && !this.args.fill) {
this.xScrollContainer.addEventListener('scroll', this.scrollEventHandler);
}
}
/**
* When the .table-header element is inserted into the DOM,
* we initialise the container selector by trying to find the nearest scroll parent element
* The container selector will be used by vertical-collection to calculate the visibility of items
* in relation to the visible scrolling view.
*
* @param tableHeader `.table-header` element
*/
@action
handleTableHeaderInsert(tableHeader: HTMLElement) {
const scrollContainer =
getScrollParent(tableHeader)! ?? document.querySelector('body');
this.containerSelector =
this.args.fill || !scrollContainer
? undefined
: '.' + scrollContainer?.className.split(' ').join('.');
}
/**
* This function syncs the horizontal scroll of the table sticky header
* with the body horizontal scroll
*/
scrollEventHandler = () => {
const { headerScrollContainer, xScrollContainer } = this;
if (headerScrollContainer && xScrollContainer) {
headerScrollContainer.scrollLeft = xScrollContainer.scrollLeft;
}
};
}
.table-header {
position: sticky;
top: var(--table-header-top, 0);
z-index: 4;
width: 100%;
height: 48px;
background: white;
overflow: hidden;
.ember-table-overflow {
overflow: hidden;
}
}
https://user-images.githubusercontent.com/176766/181385041-4f368a52-6016-4207-9ccf-a94c35bcbc02.mov
Of course it would be so much easier if css had something like position: sticky-vertical; overflow-x: hidden;
I hope it helps anyway.
Cheers
Is there any plans (or is it possible) to allow for sticky column headers (and footers) for non-fixed height tables, i.e. tables that do not have their own scroll container, but who has some ancestor element that will scroll to fit the content (which may be the
<body>
)?I have a page layout where having an independently scrolling fixed-height table is not feasible (or at least would not result in a good UX). There is quite a bit of content above the table, such that a fixed height table would be too short on most screen resolutions. The content above it needs to be able to scroll out of view and let the table occupy the entire viewport when scrolled down. But we still want the table header to be sticky and always in view.
I was able to partially get this working by just removing the
overflow: auto;
style from the.ember-table
container, however, as expected, this breaks other things..most noticeably the table's horizontal scrolling.