Skip to content

Commit

Permalink
support rtl sticky columns
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewseguin committed May 29, 2018
1 parent c3dd30f commit 8b4dc94
Show file tree
Hide file tree
Showing 7 changed files with 78 additions and 13 deletions.
1 change: 1 addition & 0 deletions src/cdk/table/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ ng_module(
srcs = glob(["**/*.ts"], exclude=["**/*.spec.ts"]),
module_name = "@angular/cdk/table",
deps = [
"//src/cdk/bidi",
"//src/cdk/collections",
"//src/cdk/coercion",
"@rxjs",
Expand Down
13 changes: 10 additions & 3 deletions src/cdk/table/sticky-styler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
* Directions that can be used when setting sticky positioning.
* @docs-private
*/
import {Direction} from '@angular/cdk/bidi';

export type StickyDirection = 'top' | 'bottom' | 'left' | 'right';

/**
Expand All @@ -28,8 +30,12 @@ export class StickyStyler {
* that uses the native `<table>` element.
* @param stickCellCSS The CSS class that will be applied to every row/cell that has
* sticky positioning applied.
* @param direction The directionality context of the table (ltr/rtl); affects column positioning
* by reversing left/right positions.
*/
constructor(private isNativeHtmlTable: boolean, private stickCellCSS: string) { }
constructor(private isNativeHtmlTable: boolean,
private stickCellCSS: string,
public direction: Direction) { }

/**
* Clears the sticky positioning styles from the row and its cells by resetting the `position`
Expand Down Expand Up @@ -68,16 +74,17 @@ export class StickyStyler {
const cellWidths: number[] = this._getCellWidths(rows[0]);
const startPositions = this._getStickyStartColumnPositions(cellWidths, stickyStartStates);
const endPositions = this._getStickyEndColumnPositions(cellWidths, stickyEndStates);
const isLtr = this.direction === 'ltr';

for (let row of rows) {
for (let i = 0; i < numCells; i++) {
const cell = row.children[i] as HTMLElement;
if (stickyStartStates[i]) {
this._addStickyStyle(cell, 'left', startPositions[i]);
this._addStickyStyle(cell, isLtr ? 'left' : 'right', startPositions[i]);
}

if (stickyEndStates[i]) {
this._addStickyStyle(cell, 'right', endPositions[i]);
this._addStickyStyle(cell, isLtr ? 'right' : 'left', endPositions[i]);
}
}
}
Expand Down
36 changes: 34 additions & 2 deletions src/cdk/table/table.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
getTableUnknownColumnError,
getTableUnknownDataSourceError
} from './table-errors';
import {BidiModule} from '@angular/cdk/bidi';

describe('CdkTable', () => {
let fixture: ComponentFixture<any>;
Expand All @@ -33,7 +34,7 @@ describe('CdkTable', () => {
function createComponent<T>(componentType: Type<T>, declarations: any[] = []):
ComponentFixture<T> {
TestBed.configureTestingModule({
imports: [CdkTableModule],
imports: [CdkTableModule, BidiModule],
declarations: [componentType, ...declarations],
}).compileComponents();

Expand Down Expand Up @@ -831,6 +832,36 @@ describe('CdkTable', () => {
footerRows.forEach(row => expectNoStickyStyles(getFooterCells(row)));
});

it('should reverse directions for sticky columns in rtl', () => {
component.dir = 'rtl';
component.stickyStartColumns = ['column-1', 'column-2'];
component.stickyEndColumns = ['column-5', 'column-6'];
fixture.detectChanges();

const firstColumnWidth = getHeaderCells(headerRows[0])[0].getBoundingClientRect().width;
const lastColumnWidth = getHeaderCells(headerRows[0])[5].getBoundingClientRect().width;

let headerCells = getHeaderCells(headerRows[0]);
expectStickyStyles(headerCells[0], '1', {right: '0px'});
expectStickyStyles(headerCells[1], '1', {right: `${firstColumnWidth}px`});
expectStickyStyles(headerCells[4], '1', {left: `${lastColumnWidth}px`});
expectStickyStyles(headerCells[5], '1', {left: '0px'});

dataRows.forEach(row => {
let cells = getCells(row);
expectStickyStyles(cells[0], '1', {right: '0px'});
expectStickyStyles(cells[1], '1', {right: `${firstColumnWidth}px`});
expectStickyStyles(cells[4], '1', {left: `${lastColumnWidth}px`});
expectStickyStyles(cells[5], '1', {left: '0px'});
});

let footerCells = getFooterCells(footerRows[0]);
expectStickyStyles(footerCells[0], '1', {right: '0px'});
expectStickyStyles(footerCells[1], '1', {right: `${firstColumnWidth}px`});
expectStickyStyles(footerCells[4], '1', {left: `${lastColumnWidth}px`});
expectStickyStyles(footerCells[5], '1', {left: '0px'});
});

it('should stick and unstick combination of sticky header, footer, and columns', () => {
component.stickyHeaders = ['header-1'];
component.stickyFooters = ['footer-3'];
Expand Down Expand Up @@ -1710,7 +1741,7 @@ class TrackByCdkTableApp {

@Component({
template: `
<cdk-table [dataSource]="dataSource">
<cdk-table [dataSource]="dataSource" [dir]="dir">
<ng-container [cdkColumnDef]="column" *ngFor="let column of columns"
[sticky]="isStuck(stickyStartColumns, column)"
[stickyEnd]="isStuck(stickyEndColumns, column)">
Expand Down Expand Up @@ -1749,6 +1780,7 @@ class StickyFlexLayoutCdkTableApp {

@ViewChild(CdkTable) table: CdkTable<TestData>;

dir = 'ltr';
stickyHeaders: string[] = [];
stickyFooters: string[] = [];
stickyStartColumns: string[] = [];
Expand Down
23 changes: 21 additions & 2 deletions src/cdk/table/table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
IterableDiffers,
OnDestroy,
OnInit,
Optional,
QueryList,
TemplateRef,
TrackByFunction,
Expand Down Expand Up @@ -53,6 +54,7 @@ import {
} from './table-errors';
import {coerceBooleanProperty} from '@angular/cdk/coercion';
import {StickyStyler} from './sticky-styler';
import {Direction, Directionality} from '@angular/cdk/bidi';

/** Interface used to provide an outlet for rows to be inserted into. */
export interface RowOutlet {
Expand Down Expand Up @@ -356,7 +358,8 @@ export class CdkTable<T> implements AfterContentChecked, CollectionViewer, OnDes
constructor(protected readonly _differs: IterableDiffers,
protected readonly _changeDetectorRef: ChangeDetectorRef,
protected readonly _elementRef: ElementRef,
@Attribute('role') role: string) {
@Attribute('role') role: string,
@Optional() protected readonly _dir: Directionality) {
if (!role) {
this._elementRef.nativeElement.setAttribute('role', 'grid');
}
Expand All @@ -365,7 +368,7 @@ export class CdkTable<T> implements AfterContentChecked, CollectionViewer, OnDes
}

ngOnInit() {
this._stickyStyler = new StickyStyler(this._isNativeHtmlTable, this.stickyCssClass);
this._setupStickyStyler();

if (this._isNativeHtmlTable) {
this._applyNativeTableSections();
Expand Down Expand Up @@ -988,6 +991,22 @@ export class CdkTable<T> implements AfterContentChecked, CollectionViewer, OnDes
this.updateStickyColumnStyles();
}
}

/**
* Creates the sticky styler that will be used for sticky rows and columns. Listens
* for directionality changes and provides the latest direction to the styler. Re-applies column
* stickiness when directionality changes.
*/
private _setupStickyStyler() {
const direction: Direction = this._dir ? this._dir.value : 'ltr';
this._stickyStyler = new StickyStyler(this._isNativeHtmlTable, this.stickyCssClass, direction);
(this._dir ? this._dir.change : observableOf<Direction>())
.pipe(takeUntil(this._onDestroy))
.subscribe(value => {
this._stickyStyler.direction = value;
this.updateStickyColumnStyles();
});
}
}

/** Utility function that gets a merged list of the entries in a QueryList and values of a Set. */
Expand Down
10 changes: 6 additions & 4 deletions src/demo-app/example/example.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/

import {coerceBooleanProperty} from '@angular/cdk/coercion';
import {Component, ElementRef, Input, OnInit} from '@angular/core';
import {Component, ElementRef, Injector, Input, OnInit} from '@angular/core';
import {EXAMPLE_COMPONENTS} from '@angular/material-examples';

@Component({
Expand Down Expand Up @@ -54,11 +54,13 @@ export class Example implements OnInit {

title: string;

constructor(private elementRef: ElementRef) { }
constructor(private elementRef: ElementRef, private injector: Injector) { }

ngOnInit() {
const element = document.createElement(this.id);
this.elementRef.nativeElement.appendChild(element);
// Should be created with this component's injector to capture the whole injector which may
// include provided things like Directionality.
const exampleElementCtor = customElements.get(this.id);
this.elementRef.nativeElement.appendChild(new exampleElementCtor(this.injector));

this.title = EXAMPLE_COMPONENTS[this.id] ? EXAMPLE_COMPONENTS[this.id].title : '';
}
Expand Down
1 change: 1 addition & 0 deletions src/lib/table/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ ng_module(
module_name = "@angular/material/table",
assets = [":table_css"],
deps = [
"//src/cdk/bidi",
"//src/lib/core",
"//src/lib/paginator",
"//src/lib/sort",
Expand Down
7 changes: 5 additions & 2 deletions src/lib/table/table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ import {
Component,
ElementRef,
IterableDiffers,
Optional,
ViewEncapsulation
} from '@angular/core';
import {CDK_TABLE_TEMPLATE, CdkTable} from '@angular/cdk/table';
import {Directionality} from '@angular/cdk/bidi';

/**
* Wrapper for the CdkTable with Material design styles.
Expand Down Expand Up @@ -44,7 +46,8 @@ export class MatTable<T> extends CdkTable<T> {
constructor(protected _differs: IterableDiffers,
protected _changeDetectorRef: ChangeDetectorRef,
protected _elementRef: ElementRef,
@Attribute('role') role: string) {
super(_differs, _changeDetectorRef, _elementRef, role);
@Attribute('role') role: string,
@Optional() protected readonly _dir: Directionality) {
super(_differs, _changeDetectorRef, _elementRef, role, _dir);
}
}

0 comments on commit 8b4dc94

Please sign in to comment.