Skip to content

Commit

Permalink
feat(cdk/table): fixed table layouts
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaelJamesParsons committed Aug 10, 2020
1 parent 25ce323 commit ef2ead2
Show file tree
Hide file tree
Showing 9 changed files with 152 additions and 10 deletions.
7 changes: 7 additions & 0 deletions src/cdk/table/BUILD.bazel
Expand Up @@ -4,6 +4,7 @@ load(
"ng_module",
"ng_test_library",
"ng_web_test_suite",
"sass_binary",
)

package(default_visibility = ["//visibility:public"])
Expand All @@ -14,6 +15,7 @@ ng_module(
["**/*.ts"],
exclude = ["**/*.spec.ts"],
),
assets = [":table.css"],
module_name = "@angular/cdk/table",
deps = [
"//src/cdk/bidi",
Expand All @@ -26,6 +28,11 @@ ng_module(
],
)

sass_binary(
name = "table_scss",
src = "table.scss",
)

ng_test_library(
name = "unit_test_sources",
srcs = glob(
Expand Down
15 changes: 12 additions & 3 deletions src/cdk/table/sticky-styler.ts
Expand Up @@ -27,6 +27,8 @@ export const STICKY_DIRECTIONS: StickyDirection[] = ['top', 'bottom', 'left', 'r
* @docs-private
*/
export class StickyStyler {
private _cachedCellWidths: number[] = [];

/**
* @param _isNativeHtmlTable Whether the sticky logic should be based on a table
* that uses the native `<table>` element.
Expand Down Expand Up @@ -83,17 +85,19 @@ export class StickyStyler {
* in this index position should be stuck to the start of the row.
* @param stickyEndStates A list of boolean states where each state represents whether the cell
* in this index position should be stuck to the end of the row.
* @param recalculateCellWidths Whether the sticky styler should recalculate the width of each
* column cell. If `false` cached widths will be used instead.
*/
updateStickyColumns(
rows: HTMLElement[], stickyStartStates: boolean[], stickyEndStates: boolean[]) {
rows: HTMLElement[], stickyStartStates: boolean[], stickyEndStates: boolean[], recalculateCellWidths = true) {
if (!rows.length || !this._isBrowser || !(stickyStartStates.some(state => state) ||
stickyEndStates.some(state => state))) {
return;
}

const firstRow = rows[0];
const numCells = firstRow.children.length;
const cellWidths: number[] = this._getCellWidths(firstRow);
const cellWidths: number[] = this._getCellWidths(firstRow, recalculateCellWidths);

const startPositions = this._getStickyStartColumnPositions(cellWidths, stickyStartStates);
const endPositions = this._getStickyEndColumnPositions(cellWidths, stickyEndStates);
Expand Down Expand Up @@ -275,14 +279,19 @@ export class StickyStyler {
}

/** Gets the widths for each cell in the provided row. */
_getCellWidths(row: HTMLElement): number[] {
_getCellWidths(row: HTMLElement, recalculateCellWidths = true): number[] {
if (!recalculateCellWidths && this._cachedCellWidths) {
return this._cachedCellWidths;
}

const cellWidths: number[] = [];
const firstRowCells = row.children;
for (let i = 0; i < firstRowCells.length; i++) {
let cell: HTMLElement = firstRowCells[i] as HTMLElement;
cellWidths.push(cell.getBoundingClientRect().width);
}

this._cachedCellWidths = cellWidths;
return cellWidths;
}

Expand Down
3 changes: 3 additions & 0 deletions src/cdk/table/table.scss
@@ -0,0 +1,3 @@
table.cdk-table-fixed-column-widths {
table-layout: fixed;
}
41 changes: 34 additions & 7 deletions src/cdk/table/table.ts
Expand Up @@ -188,8 +188,10 @@ export interface RenderRow<T> {
selector: 'cdk-table, table[cdk-table]',
exportAs: 'cdkTable',
template: CDK_TABLE_TEMPLATE,
styleUrls: ['table.css'],
host: {
'class': 'cdk-table',
'[class.cdk-table-fixed-column-widths]': 'fixedColumnWidths',
},
encapsulation: ViewEncapsulation.None,
// The "OnPush" status for the `MatTable` component is effectively a noop, so we are removing it.
Expand Down Expand Up @@ -291,6 +293,12 @@ export class CdkTable<T> implements AfterContentChecked, CollectionViewer, OnDes
*/
private _footerRowDefChanged = true;

/**
* Whether the sticky column styles need to be updated. Set to `true` when the visible columns
* change.
*/
private _stickyColumnStyleUpdateNeeded = true;

/**
* Cache of the latest rendered `RenderRow` objects as a map for easy retrieval when constructing
* a new list of `RenderRow` objects for rendering rows. Since the new list is constructed with
Expand Down Expand Up @@ -403,6 +411,19 @@ export class CdkTable<T> implements AfterContentChecked, CollectionViewer, OnDes
}
_multiTemplateDataRows: boolean = false;

/**
* Whether to use a fixed table layout. Enabling this option will enforce consistent column widths
* and optimize rendering sticky styles for native tables. No-op for flex tables.
*/
@Input()
get fixedColumnWidths(): boolean {
return this._fixedColumnWidths;
}
set fixedColumnWidths(v: boolean) {
this._fixedColumnWidths = coerceBooleanProperty(v);
}
private _fixedColumnWidths: boolean = false;

// TODO(andrewseguin): Remove max value as the end index
// and instead calculate the view on init and scroll.
/**
Expand Down Expand Up @@ -488,7 +509,7 @@ export class CdkTable<T> implements AfterContentChecked, CollectionViewer, OnDes

// Render updates if the list of columns have been changed for the header, row, or footer defs.
const columnsChanged = this._renderUpdatedColumns();
const stickyColumnStyleUpdateNeeded =
this._stickyColumnStyleUpdateNeeded =
columnsChanged || this._headerRowDefChanged || this._footerRowDefChanged;

// If the header row definition has been changed, trigger a render to the header row.
Expand All @@ -507,7 +528,7 @@ export class CdkTable<T> implements AfterContentChecked, CollectionViewer, OnDes
// connection has already been made.
if (this.dataSource && this._rowDefs.length > 0 && !this._renderChangeSubscription) {
this._observeRenderChanges();
} else if (stickyColumnStyleUpdateNeeded) {
} else if (this._stickyColumnStyleUpdateNeeded) {
// In the above case, _observeRenderChanges will result in updateStickyColumnStyles being
// called when it row data arrives. Otherwise, we need to call it proactively.
this.updateStickyColumnStyles();
Expand Down Expand Up @@ -689,10 +710,15 @@ export class CdkTable<T> implements AfterContentChecked, CollectionViewer, OnDes
const dataRows = this._getRenderedRows(this._rowOutlet);
const footerRows = this._getRenderedRows(this._footerRowOutlet);

// Clear the left and right positioning from all columns in the table across all rows since
// sticky columns span across all table sections (header, data, footer)
this._stickyStyler.clearStickyPositioning(
[...headerRows, ...dataRows, ...footerRows], ['left', 'right']);
// Left/right sticky styles should only be reset for tables using non-fixed layouts or when the
// columns have changed.
if ((this._isNativeHtmlTable && !this._fixedColumnWidths) || this._stickyColumnStyleUpdateNeeded) {
// Clear the left and right positioning from all columns in the table across all rows since
// sticky columns span across all table sections (header, data, footer)
this._stickyStyler.clearStickyPositioning(
[...headerRows, ...dataRows, ...footerRows], ['left', 'right']);
this._stickyColumnStyleUpdateNeeded = false;
}

// Update the sticky styles for each header row depending on the def's sticky state
headerRows.forEach((headerRow, i) => {
Expand Down Expand Up @@ -934,7 +960,7 @@ export class CdkTable<T> implements AfterContentChecked, CollectionViewer, OnDes
});
const stickyStartStates = columnDefs.map(columnDef => columnDef.sticky);
const stickyEndStates = columnDefs.map(columnDef => columnDef.stickyEnd);
this._stickyStyler.updateStickyColumns(rows, stickyStartStates, stickyEndStates);
this._stickyStyler.updateStickyColumns(rows, stickyStartStates, stickyEndStates, !this._fixedColumnWidths);
}

/** Gets the list of rows that have been rendered in the row outlet. */
Expand Down Expand Up @@ -1154,6 +1180,7 @@ export class CdkTable<T> implements AfterContentChecked, CollectionViewer, OnDes
}

static ngAcceptInputType_multiTemplateDataRows: BooleanInput;
static ngAcceptInputType_fixedColumnWidths: BooleanInput;
}

/** Utility function that gets a merged list of the entries in an array and values of a Set. */
Expand Down
@@ -0,0 +1,7 @@
table {
width: 100%;
}

th {
text-align: left;
}
@@ -0,0 +1,28 @@
<table cdk-table [dataSource]="dataSource" fixedColumnWidths>
<!-- Position Column -->
<ng-container cdkColumnDef="position">
<th cdk-header-cell *cdkHeaderCellDef> No. </th>
<td cdk-cell *cdkCellDef="let element"> {{element.position}} </td>
</ng-container>

<!-- Name Column -->
<ng-container cdkColumnDef="name">
<th cdk-header-cell *cdkHeaderCellDef> Name </th>
<td cdk-cell *cdkCellDef="let element"> {{element.name}} </td>
</ng-container>

<!-- Weight Column -->
<ng-container cdkColumnDef="weight">
<th cdk-header-cell *cdkHeaderCellDef> Weight </th>
<td cdk-cell *cdkCellDef="let element"> {{element.weight}} </td>
</ng-container>

<!-- Symbol Column -->
<ng-container cdkColumnDef="symbol">
<th cdk-header-cell *cdkHeaderCellDef> Symbol </th>
<td cdk-cell *cdkCellDef="let element"> {{element.symbol}} </td>
</ng-container>

<tr cdk-header-row *cdkHeaderRowDef="displayedColumns"></tr>
<tr cdk-row *cdkRowDef="let row; columns: displayedColumns;"></tr>
</table>
@@ -0,0 +1,55 @@
import {DataSource} from '@angular/cdk/collections';
import {Component} from '@angular/core';
import {BehaviorSubject, Observable} from 'rxjs';

export interface PeriodicElement {
name: string;
position: number;
weight: number;
symbol: string;
}

const ELEMENT_DATA: PeriodicElement[] = [
{position: 1, name: 'Hydrogen', weight: 1.0079, symbol: 'H'},
{position: 2, name: 'Helium', weight: 4.0026, symbol: 'He'},
{position: 3, name: 'Lithium', weight: 6.941, symbol: 'Li'},
{position: 4, name: 'Beryllium', weight: 9.0122, symbol: 'Be'},
{position: 5, name: 'Boron', weight: 10.811, symbol: 'B'},
{position: 6, name: 'Carbon', weight: 12.0107, symbol: 'C'},
{position: 7, name: 'Nitrogen', weight: 14.0067, symbol: 'N'},
{position: 8, name: 'Oxygen', weight: 15.9994, symbol: 'O'},
{position: 9, name: 'Fluorine', weight: 18.9984, symbol: 'F'},
{position: 10, name: 'Neon', weight: 20.1797, symbol: 'Ne'},
];

/**
* @title CDK table with fixed column widths.
*/
@Component({
selector: 'cdk-table-fixed-column-widths-example',
styleUrls: ['cdk-table-fixed-column-widths-example.css'],
templateUrl: 'cdk-table-fixed-column-widths-example.html',
})
export class CdkTableFixedColumnWidthsExample {
displayedColumns: string[] = ['position', 'name', 'weight', 'symbol'];
dataSource = new ExampleDataSource();
}

/**
* Data source to provide what data should be rendered in the table. Note that the data source
* can retrieve its data in any way. In this case, the data source is provided a reference
* to a common data base, ExampleDatabase. It is not the data source's responsibility to manage
* the underlying data. Instead, it only needs to take the data and send the table exactly what
* should be rendered.
*/
export class ExampleDataSource extends DataSource<PeriodicElement> {
/** Stream of data that is provided to the table. */
data = new BehaviorSubject<PeriodicElement[]>(ELEMENT_DATA);

/** Connect function called by the table to retrieve one stream containing the data to render. */
connect(): Observable<PeriodicElement[]> {
return this.data;
}

disconnect() {}
}
3 changes: 3 additions & 0 deletions src/components-examples/cdk/table/index.ts
Expand Up @@ -2,15 +2,18 @@ import {CdkTableModule} from '@angular/cdk/table';
import {NgModule} from '@angular/core';
import {CdkTableBasicFlexExample} from './cdk-table-basic-flex/cdk-table-basic-flex-example';
import {CdkTableBasicExample} from './cdk-table-basic/cdk-table-basic-example';
import {CdkTableFixedColumnWidthsExample} from './cdk-table-fixed-column-widths/cdk-table-fixed-column-widths-example';

export {
CdkTableBasicExample,
CdkTableBasicFlexExample,
CdkTableFixedColumnWidthsExample,
};

const EXAMPLES = [
CdkTableBasicExample,
CdkTableBasicFlexExample,
CdkTableFixedColumnWidthsExample,
];

@NgModule({
Expand Down
3 changes: 3 additions & 0 deletions src/dev-app/table/table-demo.html
@@ -1,6 +1,9 @@
<h3>Cdk table basic</h3>
<cdk-table-basic-example></cdk-table-basic-example>

<h3>Cdk table basic with fixed column widths</h3>
<cdk-table-fixed-column-widths-example></cdk-table-fixed-column-widths-example>

<h3>Cdk table basic flex</h3>
<cdk-table-basic-flex-example></cdk-table-basic-flex-example>

Expand Down

0 comments on commit ef2ead2

Please sign in to comment.