From 610e35728b8ea9ed80f58e7d695110bc70032fd6 Mon Sep 17 00:00:00 2001 From: Andrew Seguin Date: Thu, 29 Mar 2018 11:31:24 -0700 Subject: [PATCH 1/3] feat(table): enable native table selectors --- src/cdk/table/cell.ts | 4 +- src/cdk/table/row.ts | 4 +- src/cdk/table/table-module.ts | 8 +- src/cdk/table/table.md | 94 +++++++---- src/cdk/table/table.spec.ts | 43 ++++++ src/cdk/table/table.ts | 26 +++- .../table/native-table/native-table.html | 146 ++++++++++++++++++ .../table/native-table/native-table.scss | 60 +++++++ .../table/native-table/native-table.ts | 112 ++++++++++++++ src/demo-app/table/routes.ts | 2 + src/demo-app/table/table-demo-module.ts | 2 + src/demo-app/table/table-demo-page.ts | 1 + src/demo-app/table/table-demo.scss | 31 ++-- src/lib/table/_table-theme.scss | 3 +- src/lib/table/cell.ts | 4 +- src/lib/table/row.ts | 4 +- src/lib/table/table-module.ts | 46 +++--- src/lib/table/table.md | 83 +++++++--- src/lib/table/table.scss | 50 +++++- src/lib/table/table.spec.ts | 52 ++++++- src/lib/table/table.ts | 2 +- .../table-basic/table-basic-example.html | 24 +-- .../table-filtering-example.html | 24 +-- .../table-http/table-http-example.html | 26 ++-- .../table-native-only-example.css | 0 .../table-native-only-example.html | 18 +++ .../table-native-only-example.ts | 19 +++ .../table-overview-example.html | 26 ++-- .../table-pagination-example.html | 24 +-- .../table-selection-example.html | 34 ++-- .../table-sorting/table-sorting-example.html | 24 +-- 31 files changed, 794 insertions(+), 202 deletions(-) create mode 100644 src/demo-app/table/native-table/native-table.html create mode 100644 src/demo-app/table/native-table/native-table.scss create mode 100644 src/demo-app/table/native-table/native-table.ts create mode 100644 src/material-examples/table-native-only/table-native-only-example.css create mode 100644 src/material-examples/table-native-only/table-native-only-example.html create mode 100644 src/material-examples/table-native-only/table-native-only-example.ts diff --git a/src/cdk/table/cell.ts b/src/cdk/table/cell.ts index 67c14081514e..1ce2acedece1 100644 --- a/src/cdk/table/cell.ts +++ b/src/cdk/table/cell.ts @@ -61,7 +61,7 @@ export class CdkColumnDef { /** Header cell template container that adds the right classes and role. */ @Directive({ - selector: 'cdk-header-cell', + selector: 'cdk-header-cell, th[cdk-header-cell]', host: { 'class': 'cdk-header-cell', 'role': 'columnheader', @@ -75,7 +75,7 @@ export class CdkHeaderCell { /** Cell template container that adds the right classes and role. */ @Directive({ - selector: 'cdk-cell', + selector: 'cdk-cell, td[cdk-cell]', host: { 'class': 'cdk-cell', 'role': 'gridcell', diff --git a/src/cdk/table/row.ts b/src/cdk/table/row.ts index afd7d94bebf7..0a9019d49602 100644 --- a/src/cdk/table/row.ts +++ b/src/cdk/table/row.ts @@ -151,7 +151,7 @@ export class CdkCellOutlet { /** Header template container that contains the cell outlet. Adds the right class and role. */ @Component({ moduleId: module.id, - selector: 'cdk-header-row', + selector: 'cdk-header-row, tr[cdk-header-row]', template: CDK_ROW_TEMPLATE, host: { 'class': 'cdk-header-row', @@ -165,7 +165,7 @@ export class CdkHeaderRow { } /** Data row template container that contains the cell outlet. Adds the right class and role. */ @Component({ moduleId: module.id, - selector: 'cdk-row', + selector: 'cdk-row, tr[cdk-row]', template: CDK_ROW_TEMPLATE, host: { 'class': 'cdk-row', diff --git a/src/cdk/table/table-module.ts b/src/cdk/table/table-module.ts index 7bc9f1da81b2..5bca33d90c90 100644 --- a/src/cdk/table/table-module.ts +++ b/src/cdk/table/table-module.ts @@ -8,9 +8,9 @@ import {CommonModule} from '@angular/common'; import {NgModule} from '@angular/core'; -import {HeaderRowPlaceholder, RowPlaceholder, CdkTable} from './table'; +import {CdkTable, HeaderRowPlaceholder, RowPlaceholder} from './table'; import {CdkCellOutlet, CdkHeaderRow, CdkHeaderRowDef, CdkRow, CdkRowDef} from './row'; -import {CdkColumnDef, CdkHeaderCellDef, CdkHeaderCell, CdkCell, CdkCellDef} from './cell'; +import {CdkCell, CdkCellDef, CdkColumnDef, CdkHeaderCell, CdkHeaderCellDef} from './cell'; const EXPORTED_DECLARATIONS = [ CdkTable, @@ -30,8 +30,8 @@ const EXPORTED_DECLARATIONS = [ @NgModule({ imports: [CommonModule], - exports: [EXPORTED_DECLARATIONS], - declarations: [EXPORTED_DECLARATIONS] + exports: EXPORTED_DECLARATIONS, + declarations: EXPORTED_DECLARATIONS }) export class CdkTableModule { } diff --git a/src/cdk/table/table.md b/src/cdk/table/table.md index 365ac59234c7..1d8255def8e5 100644 --- a/src/cdk/table/table.md +++ b/src/cdk/table/table.md @@ -1,4 +1,4 @@ -The `` is an unopinionated, customizable data-table with a fully-templated API, dynamic +The `CdkTable` is an unopinionated, customizable data-table with a fully-templated API, dynamic columns, and an accessible DOM structure. This component acts as the core upon which anyone can build their own tailored data-table experience. @@ -7,7 +7,7 @@ built. Because it enforces no opinions on these matters, developers have full co interaction patterns associated with the table. For a Material Design styled table, see the -[documentation for ``](https://material.angular.io/components/table) which builds on +[documentation for `MatTable`](https://material.angular.io/components/table) which builds on top of the CDK data-table. @@ -23,8 +23,8 @@ the column a name. Each column definition then further defines both a header-cel ```html - User name - {{row.a}} + User name + {{row.a}} ``` @@ -38,8 +38,8 @@ last). The next step is to define the table's header-row (`cdkHeaderRowDef`) and data-row (`cdkRowDef`). ```html - - + + ``` These row templates accept the specific columns to be rendered via the name given to the @@ -53,36 +53,36 @@ above. ##### Example: table with three columns ```html - + - User name - {{row.username}} + + - Age - {{row.age}} + + - Title - {{row.title}} + + - - - + + +
User name {{row.username}} Age {{row.age}} Title {{row.title}}
``` The columns given on the row determine which cells are rendered and in which order. Thus, the columns can be set via binding to support dynamically changing the columns shown at run-time. ```html - + ``` It is not required to display all the columns that are defined within the template, @@ -90,21 +90,21 @@ nor use the same ordering. For example, to display the table with only `age` and `username` and in that order, then the row and header definitions would be written as: ```html - + ``` Event and property bindings can be added directly to the row element. ##### Example: table with event and class binding ```html - - - - - + + + + + ``` ##### Styling columns @@ -136,5 +136,45 @@ To improve performance, a `trackBy` function can be provided to the table simila table how to uniquely identify rows to track how the data changes with each update. ```html - + +``` + +### Alternate HTML to using native table + +The CDK table does not require that you use a native HTML table. If you want to have full control +over the style of the table, it may be easier to follow an alternative template approach that does +not use the native table element tags. + +This alternative approach replaces the native table element tags with the CDK table directive +selectors. For example, `
` becomes ``; ` becomes +``. The following shows a previous example using this alternative template: + +```html + + + + User name + {{row.username}} + + + + + Age + {{row.age}} + + + + + Title + {{row.title}} + + + + + + ``` + +For an example of how to render the structure as a table, see the +[documentation for ``](https://material.angular.io/components/table) which includes +the style support for this approach. \ No newline at end of file diff --git a/src/cdk/table/table.spec.ts b/src/cdk/table/table.spec.ts index d68fa4a59b88..0816a00209fa 100644 --- a/src/cdk/table/table.spec.ts +++ b/src/cdk/table/table.spec.ts @@ -232,6 +232,19 @@ describe('CdkTable', () => { }); }); + it('should render correctly when using native HTML tags', () => { + const thisFixture = createComponent(NativeHtmlTableApp); + const thisTableElement = thisFixture.nativeElement.querySelector('table'); + thisFixture.detectChanges(); + + expectTableToMatchContent(thisTableElement, [ + ['Column A', 'Column B', 'Column C'], + ['a_1', 'b_1', 'c_1'], + ['a_2', 'b_2', 'c_2'], + ['a_3', 'b_3', 'c_3'], + ]); + }); + it('should render cells even if row data is falsy', () => { const booleanRowCdkTableAppFixture = createComponent(BooleanRowCdkTableApp); booleanRowCdkTableAppFixture.detectChanges(); @@ -1349,6 +1362,36 @@ class OuterTableApp { firstRow = i => i === 0; } +@Component({ + template: ` +
+ + + + + + + + + + + + + + + + + +
Column A {{row.a}} Column B {{row.b}} Column C {{row.c}}
+ ` +}) +class NativeHtmlTableApp { + dataSource: FakeDataSource | undefined = new FakeDataSource(); + columnsToRender = ['column_a', 'column_b', 'column_c']; + + @ViewChild(CdkTable) table: CdkTable; +} + function getElements(element: Element, query: string): Element[] { return [].slice.call(element.querySelectorAll(query)); } diff --git a/src/cdk/table/table.ts b/src/cdk/table/table.ts index 15fbb8eaf0f6..130c1fb0e973 100644 --- a/src/cdk/table/table.ts +++ b/src/cdk/table/table.ts @@ -49,7 +49,7 @@ import { */ @Directive({selector: '[rowPlaceholder]'}) export class RowPlaceholder { - constructor(public viewContainer: ViewContainerRef) { } + constructor(public viewContainer: ViewContainerRef, public elementRef: ElementRef) { } } /** @@ -58,7 +58,7 @@ export class RowPlaceholder { */ @Directive({selector: '[headerRowPlaceholder]'}) export class HeaderRowPlaceholder { - constructor(public viewContainer: ViewContainerRef) { } + constructor(public viewContainer: ViewContainerRef, public elementRef: ElementRef) { } } /** @@ -83,7 +83,7 @@ abstract class RowViewRef extends EmbeddedViewRef> */ @Component({ moduleId: module.id, - selector: 'cdk-table', + selector: 'cdk-table, table[cdk-table]', exportAs: 'cdkTable', template: CDK_TABLE_TEMPLATE, host: { @@ -212,14 +212,18 @@ export class CdkTable implements CollectionViewer, OnInit, AfterContentChecke constructor(private readonly _differs: IterableDiffers, private readonly _changeDetectorRef: ChangeDetectorRef, - elementRef: ElementRef, + private readonly _elementRef: ElementRef, @Attribute('role') role: string) { if (!role) { - elementRef.nativeElement.setAttribute('role', 'grid'); + this._elementRef.nativeElement.setAttribute('role', 'grid'); } } ngOnInit() { + if (this._elementRef.nativeElement.tagName === 'TABLE') { + this._applyNativeTableSections(); + } + // TODO(andrewseguin): Setup a listener for scrolling, emit the calculated view to viewChange this._dataDiffer = this._differs.find([]).create(this._trackByFn); @@ -558,4 +562,16 @@ export class CdkTable implements CollectionViewer, OnInit, AfterContentChecke return column.cell; }); } + + /** Adds native table sections (e.g. tbody) and moves the row placeholders into them. */ + private _applyNativeTableSections() { + const tHeadEl = document.createElement('thead'); + const tBodyEl = document.createElement('tbody'); + + this._elementRef.nativeElement.appendChild(tHeadEl); + this._elementRef.nativeElement.appendChild(tBodyEl); + + tHeadEl.appendChild(this._headerRowPlaceholder.elementRef.nativeElement); + tBodyEl.appendChild(this._rowPlaceholder.elementRef.nativeElement); + } } diff --git a/src/demo-app/table/native-table/native-table.html b/src/demo-app/table/native-table/native-table.html new file mode 100644 index 000000000000..8b97a3e5a944 --- /dev/null +++ b/src/demo-app/table/native-table/native-table.html @@ -0,0 +1,146 @@ + + + Filter users + + + +
+ + + + + + +
+ {{selection.selected.length}} + {{selection.selected.length == 1 ? 'user' : 'users'}} + selected +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + ID {{row.id}} Progress +
{{row.progress}}%
+
+
+
+
Name {{row.name}} Color {{row.color}}
+ + + +
+ +

CdkTable

+ + + + + + + + + + +
{{column}} {{element[column]}}
+
+ +

MatTable

+ + + + + + + + + + +
{{column}} {{element[column]}}
+
+ +

Native table with classes

+ + + + + + + + + + + + + + + + + + + + + + +
ID Name
1 Arthur
2 Mike
3 Zephaniah
+
+ diff --git a/src/demo-app/table/native-table/native-table.scss b/src/demo-app/table/native-table/native-table.scss new file mode 100644 index 000000000000..31901f9f1433 --- /dev/null +++ b/src/demo-app/table/native-table/native-table.scss @@ -0,0 +1,60 @@ +.demo-table-card { + margin: 8px 0; + max-height: 400px; + overflow: auto; + padding: 0; +} + +.demo-table { + width: 100%; +} + +.demo-selection-header { + height: 64px; + background: white; + display: flex; + align-items: center; + padding-left: 24px; +} + +.demo-selectable { + .mat-row:hover { + background: #eeeeee; + } + .mat-row.selected { + background: #f5f5f5; + } +} + +table-header-demo { + background: white; +} + +/* Progress bar styling */ +.cdk-column-progress { + &.cdk-cell, &.mat-cell { + display: flex; + align-items: center; + height: inherit; + + .demo-progress-stat { + width: 32px; + padding-right: 8px; + } + + .demo-progress-indicator-container { + flex-grow: 1; + display: flex; + align-items: center; + } + .demo-progress-indicator { + border-radius: 8px; + height: 8px; + } + } +} + +.mat-column-select { width: 48px; } +.mat-column-userId { width: 64px; } +.mat-column-userName { width: 120px; } +.mat-column-color { width: 120px; } diff --git a/src/demo-app/table/native-table/native-table.ts b/src/demo-app/table/native-table/native-table.ts new file mode 100644 index 000000000000..81a0560b04f8 --- /dev/null +++ b/src/demo-app/table/native-table/native-table.ts @@ -0,0 +1,112 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {Component, ElementRef, ViewChild} from '@angular/core'; +import {ELEMENT_DATA} from '../element-data'; +import {MatPaginator, MatSort, MatTableDataSource} from '@angular/material'; +import {PeopleDatabase, UserData} from '../people-database'; +import {SelectionModel} from '@angular/cdk/collections'; +import {debounceTime, distinctUntilChanged} from 'rxjs/operators'; +import {fromEvent} from 'rxjs'; + +@Component({ + moduleId: module.id, + templateUrl: 'native-table.html', + styleUrls: ['native-table.css'], +}) +export class NativeTableDemo { + definedColumns = ['name', 'weight', 'symbol', 'position']; + columnsToDisplay = this.definedColumns; + data = ELEMENT_DATA.slice(); + + dataSource = new MatTableDataSource(); + columns = ['select', 'userId', 'userName', 'progress', 'color']; + selection = new SelectionModel(true, []); + @ViewChild(MatPaginator) paginator: MatPaginator; + @ViewChild(MatSort) sort: MatSort; + @ViewChild('filter') filter: ElementRef; + + constructor(public _peopleDatabase: PeopleDatabase) { + for (let i = 0; i < 3; i++) { + this.columnsToDisplay = this.definedColumns.concat(this.columnsToDisplay); + this.data = this.data.concat(this.data); + } + + this.dataSource.sortingDataAccessor = (data: UserData, property: string) => { + switch (property) { + case 'userId': return +data.id; + case 'userName': return data.name; + case 'progress': return +data.progress; + case 'color': return data.color; + default: return ''; + } + }; + this.dataSource.filterPredicate = + (data: UserData, filter: string) => data.name.indexOf(filter) != -1; + } + + ngAfterViewInit() { + // Needs to be set up after the view is initialized since the data source will look at the sort + // and paginator's initial values to know what data should be rendered. + this.dataSource!.paginator = this.paginator; + this.dataSource!.sort = this.sort; + } + + ngOnInit() { + this.connect(); + fromEvent(this.filter.nativeElement, 'keyup') + .pipe( + debounceTime(150), + distinctUntilChanged() + ).subscribe(() => { + this.paginator.pageIndex = 0; + this.dataSource.filter = this.filter.nativeElement.value; + }); + } + + connect() { + this._peopleDatabase.initialize(); + this.dataSource!.data = this._peopleDatabase.data.slice(); + } + + + /** Whether all filtered rows are selected. */ + isAllFilteredRowsSelected() { + return this.dataSource.filteredData.every(data => this.selection.isSelected(data)); + } + + /** Whether the selection it totally matches the filtered rows. */ + isMasterToggleChecked() { + return this.selection.hasValue() && + this.isAllFilteredRowsSelected() && + this.selection.selected.length >= this.dataSource.filteredData.length; + } + + /** + * Whether there is a selection that doesn't capture all the + * filtered rows there are no filtered rows displayed. + */ + isMasterToggleIndeterminate() { + return this.selection.hasValue() && + (!this.isAllFilteredRowsSelected() || !this.dataSource.filteredData.length); + } + + /** Selects all filtered rows if they are not all selected; otherwise clear selection. */ + masterToggle() { + if (this.isMasterToggleChecked()) { + this.selection.clear(); + } else { + this.dataSource.filteredData.forEach(data => this.selection.select(data)); + } + } + + getOpacity(progress: number) { + let distanceFromMiddle = Math.abs(50 - progress); + return distanceFromMiddle / 50 + .3; + } +} diff --git a/src/demo-app/table/routes.ts b/src/demo-app/table/routes.ts index 980ee726feaa..588470162d97 100644 --- a/src/demo-app/table/routes.ts +++ b/src/demo-app/table/routes.ts @@ -10,10 +10,12 @@ import {Routes} from '@angular/router'; import {TableDemo} from './table-demo'; import {CustomTableDemo} from './custom-table/custom-table'; import {DataInputTableDemo} from './data-input-table/data-input-table'; +import {NativeTableDemo} from './native-table/native-table'; export const TABLE_DEMO_ROUTES: Routes = [ {path: '', redirectTo: 'main-demo', pathMatch: 'full'}, {path: 'main-demo', component: TableDemo}, {path: 'custom-table', component: CustomTableDemo}, {path: 'data-input-table', component: DataInputTableDemo}, + {path: 'native-table', component: NativeTableDemo}, ]; diff --git a/src/demo-app/table/table-demo-module.ts b/src/demo-app/table/table-demo-module.ts index e231bda05844..bb3eb6bb968e 100644 --- a/src/demo-app/table/table-demo-module.ts +++ b/src/demo-app/table/table-demo-module.ts @@ -30,6 +30,7 @@ import {RouterModule} from '@angular/router'; import {WrapperTable} from './custom-table/wrapper-table'; import {SimpleColumn} from './custom-table/simple-column'; import {DataInputTableDemo} from './data-input-table/data-input-table'; +import {NativeTableDemo} from './native-table/native-table'; @NgModule({ imports: [ @@ -57,6 +58,7 @@ import {DataInputTableDemo} from './data-input-table/data-input-table'; TableHeaderDemo, WrapperTable, SimpleColumn, + NativeTableDemo, ], providers: [ PeopleDatabase diff --git a/src/demo-app/table/table-demo-page.ts b/src/demo-app/table/table-demo-page.ts index ebc20f0945db..3dbbdfa70152 100644 --- a/src/demo-app/table/table-demo-page.ts +++ b/src/demo-app/table/table-demo-page.ts @@ -17,5 +17,6 @@ export class TableDemoPage { {name: 'Main Page', link: 'main-demo'}, {name: 'Custom Table', link: 'custom-table'}, {name: 'Direct Data', link: 'data-input-table'}, + {name: 'Native Table', link: 'native-table'}, ]; } diff --git a/src/demo-app/table/table-demo.scss b/src/demo-app/table/table-demo.scss index 4f4a2cacf01e..d3b0c016d630 100644 --- a/src/demo-app/table/table-demo.scss +++ b/src/demo-app/table/table-demo.scss @@ -105,23 +105,24 @@ /* Progress bar styling */ .cdk-column-progress { - &.cdk-cell, &.mat-cell { - display: flex; + display: flex; + height: inherit; + align-items: center; - .demo-progress-stat { - width: 32px; - padding-right: 8px; - } + .demo-progress-stat { + width: 32px; + padding-right: 8px; + } - .demo-progress-indicator-container { - flex-grow: 1; - display: flex; - align-items: center; - } - .demo-progress-indicator { - border-radius: 8px; - height: 8px; - } + .demo-progress-indicator-container { + flex-grow: 1; + display: flex; + align-items: center; + } + + .demo-progress-indicator { + border-radius: 8px; + height: 8px; } } diff --git a/src/lib/table/_table-theme.scss b/src/lib/table/_table-theme.scss index 1d99c56c587f..812a1b65f2df 100644 --- a/src/lib/table/_table-theme.scss +++ b/src/lib/table/_table-theme.scss @@ -10,7 +10,8 @@ background: mat-color($background, 'card'); } - .mat-row, .mat-header-row { + + mat-row, mat-header-row, th.mat-header-cell, td.mat-cell { border-bottom-color: mat-color($foreground, divider); } diff --git a/src/lib/table/cell.ts b/src/lib/table/cell.ts index 51dcf75adf1a..7e0ae29e43d3 100644 --- a/src/lib/table/cell.ts +++ b/src/lib/table/cell.ts @@ -50,7 +50,7 @@ export class MatColumnDef extends CdkColumnDef { /** Header cell template container that adds the right classes and role. */ @Directive({ - selector: 'mat-header-cell', + selector: 'mat-header-cell, th[mat-header-cell]', host: { 'class': 'mat-header-cell', 'role': 'columnheader', @@ -66,7 +66,7 @@ export class MatHeaderCell extends CdkHeaderCell { /** Cell template container that adds the right classes and role. */ @Directive({ - selector: 'mat-cell', + selector: 'mat-cell, td[mat-cell]', host: { 'class': 'mat-cell', 'role': 'gridcell', diff --git a/src/lib/table/row.ts b/src/lib/table/row.ts index b4945e5a3755..6e68efdf3f02 100644 --- a/src/lib/table/row.ts +++ b/src/lib/table/row.ts @@ -42,7 +42,7 @@ export class MatRowDef extends CdkRowDef { /** Header template container that contains the cell outlet. Adds the right class and role. */ @Component({ moduleId: module.id, - selector: 'mat-header-row', + selector: 'mat-header-row, tr[mat-header-row]', template: CDK_ROW_TEMPLATE, host: { 'class': 'mat-header-row', @@ -57,7 +57,7 @@ export class MatHeaderRow extends CdkHeaderRow { } /** Data row template container that contains the cell outlet. Adds the right class and role. */ @Component({ moduleId: module.id, - selector: 'mat-row', + selector: 'mat-row, tr[mat-row]', template: CDK_ROW_TEMPLATE, host: { 'class': 'mat-row', diff --git a/src/lib/table/table-module.ts b/src/lib/table/table-module.ts index 1408cf74cd62..edf816b977b0 100644 --- a/src/lib/table/table-module.ts +++ b/src/lib/table/table-module.ts @@ -14,31 +14,29 @@ import {MatHeaderRow, MatHeaderRowDef, MatRow, MatRowDef} from './row'; import {CommonModule} from '@angular/common'; import {MatCommonModule} from '@angular/material/core'; +const EXPORTED_DECLARATIONS = [ + // Table + MatTable, + + // Template defs + MatCellDef, + MatHeaderCellDef, + MatColumnDef, + MatHeaderRowDef, + MatRowDef, + + // Cell directives + MatHeaderCell, + MatCell, + + // Row directions + MatHeaderRow, + MatRow, +]; + @NgModule({ imports: [CdkTableModule, CommonModule, MatCommonModule], - exports: [ - MatCell, - MatCellDef, - MatColumnDef, - MatHeaderCell, - MatHeaderCellDef, - MatHeaderRow, - MatHeaderRowDef, - MatRow, - MatRowDef, - MatTable, - ], - declarations: [ - MatCell, - MatCellDef, - MatColumnDef, - MatHeaderCell, - MatHeaderCellDef, - MatHeaderRow, - MatHeaderRowDef, - MatRow, - MatRowDef, - MatTable, - ], + exports: EXPORTED_DECLARATIONS, + declarations: EXPORTED_DECLARATIONS, }) export class MatTableModule {} diff --git a/src/lib/table/table.md b/src/lib/table/table.md index 737d1a928bb1..878445577630 100644 --- a/src/lib/table/table.md +++ b/src/lib/table/table.md @@ -13,15 +13,15 @@ the table is implemented, see the #### 1. Write your mat-table and provide data -Begin by creating a `` component in your template and passing in data. +Begin by adding the `` component to your template and passing in data. -The simplest way to provide data to the table is by passing a data array to the table's `data` +The simplest way to provide data to the table is by passing a data array to the table's `dataSource` input. The table will take the array and render a row for each object in the data array. ```html - +
... - +
``` Since the table optimizes for performance, it will not automatically check for changes to the data @@ -44,8 +44,8 @@ Here's a simple column definition with the name `'userName'`. The header cell co ```html - Name - {{user.name}} + Name + {{user.name}} ``` @@ -65,8 +65,8 @@ Then add `mat-header-row` and `mat-row` to the content of your `mat-table` and p column list as inputs. ```html - - + + ``` Note that this list of columns provided to the rows can be in any order, not necessary the order in @@ -117,7 +117,7 @@ these feature to the table, check out their respective sections below. #### Pagination -To paginate the table's data, add a `` after the ``. +To paginate the table's data, add a `` after the table. If you are using the `MatTableDataSource` for your table's data source, simply provide the `MatPaginator` to your data source. It will automatically listen for page changes made by the user @@ -137,14 +137,14 @@ and its interface is not tied to any one specific implementation. #### Sorting -To add sorting behavior to the table, add the `matSort` directive to the `` and add +To add sorting behavior to the table, add the `matSort` directive to the table and add `mat-sort-header` to each column header cell that should trigger sorting. ```html - Name - {{element.position}} + Name + {{element.position}} ``` @@ -216,23 +216,23 @@ this.selection = new SelectionModel(allowMultiSelect, initialSelecti ##### 2. Define a selection column Add a column definition for displaying the row checkboxes, including a master toggle checkbox for -the header. The column name should be added to the list of displayed columns provided to the -`` and ``. +the header. The column name should be added to the list of displayed columns provided to the +header and data row. ```html - + - - + + - + ``` @@ -278,3 +278,50 @@ Table's default role is `grid`, and it can be changed to `treegrid` through `rol `mat-table` does not manage any focus/keyboard interaction on its own. Users can add desired focus/keyboard interactions in their application. + +### Using flex layout + +The `MatTable` does not require that you use a native HTML table. Instead, you can use an +alternative approach that uses flex-layout for the table's styles. + +This alternative approach replaces the native table element tags with the `MatTable` directive +selectors. For example, `` becomes ``; ` becomes +``. The following shows a previous example using this alternative template: + +```html + + + + User name + {{row.username}} + + + + + Age + {{row.age}} + + + + + Title + {{row.title}} + + + + + + +``` + +Note that this approach means you cannot include certain native-table features such colspan/rowspan +or have columns that resize themselves based on their content. + +### Applying material styles to native table + +If you want to have a Material design styled `
` without using the `MatTable`, simply apply +the appropriate classes to the table elements. This may be useful if you have an existing table +or if you do not need the additional benefits and features of the `MatTable`. + + + \ No newline at end of file diff --git a/src/lib/table/table.scss b/src/lib/table/table.scss index edb02cee11a5..822cb3420072 100644 --- a/src/lib/table/table.scss +++ b/src/lib/table/table.scss @@ -2,19 +2,22 @@ $mat-header-row-height: 56px; $mat-row-height: 48px; $mat-row-horizontal-padding: 24px; -.mat-table { +/** + * Flex-based table structure + */ +mat-table { display: block; } -.mat-header-row { +mat-header-row { min-height: $mat-header-row-height; } -.mat-row { +mat-row { min-height: $mat-row-height; } -.mat-row, .mat-header-row { +mat-row, mat-header-row { display: flex; border-bottom-width: 1px; border-bottom-style: solid; @@ -31,15 +34,15 @@ $mat-row-horizontal-padding: 24px; } } -.mat-cell:first-child, .mat-header-cell:first-child { +mat-cell:first-child, mat-header-cell:first-child { padding-left: $mat-row-horizontal-padding; } -.mat-cell:last-child, .mat-header-cell:last-child { +mat-cell:last-child, mat-header-cell:last-child { padding-right: $mat-row-horizontal-padding; } -.mat-cell, .mat-header-cell { +mat-cell, mat-header-cell { flex: 1; display: flex; align-items: center; @@ -47,3 +50,36 @@ $mat-row-horizontal-padding: 24px; word-wrap: break-word; min-height: inherit; } + +/** + * Native HTML table structure + */ +table.mat-table { + border-spacing: 0; +} + +tr.mat-header-row { + height: $mat-header-row-height; +} + +tr.mat-row { + height: $mat-row-height; +} + +th.mat-header-cell { + text-align: left; +} + +th.mat-header-cell, td.mat-cell { + padding: 0; + border-bottom-width: 1px; + border-bottom-style: solid; +} + +th.mat-header-cell:first-child, td.mat-cell:first-child { + padding-left: $mat-row-horizontal-padding; +} + +th.mat-header-cell:last-child, td.mat-cell:last-child { + padding-right: $mat-row-horizontal-padding; +} diff --git a/src/lib/table/table.spec.ts b/src/lib/table/table.spec.ts index 5cbbf4846813..e9cca2d1c373 100644 --- a/src/lib/table/table.spec.ts +++ b/src/lib/table/table.spec.ts @@ -14,7 +14,12 @@ describe('MatTable', () => { beforeEach(async(() => { TestBed.configureTestingModule({ imports: [MatTableModule, MatPaginatorModule, MatSortModule, NoopAnimationsModule], - declarations: [MatTableApp, MatTableWithWhenRowApp, ArrayDataSourceMatTableApp], + declarations: [ + MatTableApp, + MatTableWithWhenRowApp, + ArrayDataSourceMatTableApp, + NativeHtmlTableApp + ], }).compileComponents(); })); @@ -49,6 +54,21 @@ describe('MatTable', () => { }); }); + it('should be able to render a table correctly with native elements', () => { + let fixture = TestBed.createComponent(NativeHtmlTableApp); + fixture.detectChanges(); + + const tableElement = fixture.nativeElement.querySelector('table'); + const data = fixture.componentInstance.dataSource!.data; + expectTableToMatchContent(tableElement, [ + ['Column A', 'Column B', 'Column C'], + [data[0].a, data[0].b, data[0].c], + [data[1].a, data[1].b, data[1].c], + [data[2].a, data[2].b, data[2].c], + [data[3].a, data[3].b, data[3].c], + ]); + }); + describe('with MatTableDataSource', () => { let tableElement: HTMLElement; let fixture: ComponentFixture; @@ -350,6 +370,36 @@ class MatTableApp { @ViewChild(MatTable) table: MatTable; } +@Component({ + template: ` +
+ + + + + + + + + + + + + + + + + +
Column A {{row.a}} Column B {{row.b}} Column C {{row.c}}
+ ` +}) +class NativeHtmlTableApp { + dataSource: FakeDataSource | null = new FakeDataSource(); + columnsToRender = ['column_a', 'column_b', 'column_c']; + + @ViewChild(MatTable) table: MatTable; +} + @Component({ template: ` diff --git a/src/lib/table/table.ts b/src/lib/table/table.ts index 19c5eb22953c..49371dbc285a 100644 --- a/src/lib/table/table.ts +++ b/src/lib/table/table.ts @@ -14,7 +14,7 @@ import {CDK_TABLE_TEMPLATE, CdkTable} from '@angular/cdk/table'; */ @Component({ moduleId: module.id, - selector: 'mat-table', + selector: 'mat-table, table[mat-table]', exportAs: 'matTable', template: CDK_TABLE_TEMPLATE, styleUrls: ['table.css'], diff --git a/src/material-examples/table-basic/table-basic-example.html b/src/material-examples/table-basic/table-basic-example.html index fa571dbb2266..7531ffcc5db2 100644 --- a/src/material-examples/table-basic/table-basic-example.html +++ b/src/material-examples/table-basic/table-basic-example.html @@ -1,34 +1,34 @@
- + - No. - {{element.position}} + + - Name - {{element.name}} + + - Weight - {{element.weight}} + + - Symbol - {{element.symbol}} + + - - - + + +
No. {{element.position}} Name {{element.name}} Weight {{element.weight}} Symbol {{element.symbol}}
diff --git a/src/material-examples/table-filtering/table-filtering-example.html b/src/material-examples/table-filtering/table-filtering-example.html index 889622ab5156..cc52968033a5 100644 --- a/src/material-examples/table-filtering/table-filtering-example.html +++ b/src/material-examples/table-filtering/table-filtering-example.html @@ -5,33 +5,33 @@ - + - No. - {{element.position}} + + - Name - {{element.name}} + + - Weight - {{element.weight}} + + - Symbol - {{element.symbol}} + + - - - + + +
No. {{element.position}} Name {{element.name}} Weight {{element.weight}} Symbol {{element.symbol}}
diff --git a/src/material-examples/table-http/table-http-example.html b/src/material-examples/table-http/table-http-example.html index 1d8fedec25e1..68799c356a10 100644 --- a/src/material-examples/table-http/table-http-example.html +++ b/src/material-examples/table-http/table-http-example.html @@ -7,7 +7,7 @@ - - # - {{ row.number }} + # + {{ row.number }} - Title - {{ row.title }} + Title + {{ row.title }} - State - {{ row.state }} + State + {{ row.state }} - Created - - {{ row.created_at | date }} + + {{ row.created_at | date }} - - - + + + diff --git a/src/material-examples/table-native-only/table-native-only-example.css b/src/material-examples/table-native-only/table-native-only-example.css new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/material-examples/table-native-only/table-native-only-example.html b/src/material-examples/table-native-only/table-native-only-example.html new file mode 100644 index 000000000000..0570e323e005 --- /dev/null +++ b/src/material-examples/table-native-only/table-native-only-example.html @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + +
No. Name Weight Symbol
{{element.position}} {{element.name}} {{element.weight}} {{element.symbol}}
\ No newline at end of file diff --git a/src/material-examples/table-native-only/table-native-only-example.ts b/src/material-examples/table-native-only/table-native-only-example.ts new file mode 100644 index 000000000000..7eb89185b3cc --- /dev/null +++ b/src/material-examples/table-native-only/table-native-only-example.ts @@ -0,0 +1,19 @@ +import {Component} from '@angular/core'; + +/** + * @title Native `` that only applies the Material styles + */ +@Component({ + selector: 'table-native-only-example', + styleUrls: ['table-native-only-example.css'], + templateUrl: 'table-native-only-example.html', +}) +export class TableNativeOnlyExample { + elements = [ + {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'}, + ]; +} diff --git a/src/material-examples/table-overview/table-overview-example.html b/src/material-examples/table-overview/table-overview-example.html index d1fd84fafff6..c9eb65ec1df6 100644 --- a/src/material-examples/table-overview/table-overview-example.html +++ b/src/material-examples/table-overview/table-overview-example.html @@ -6,36 +6,36 @@
- +
- ID - {{row.id}} + + - Progress - {{row.progress}}% + + - Name - {{row.name}} + + - Color - {{row.color}} + + - - - - + + + +
ID {{row.id}} Progress {{row.progress}}% Name {{row.name}} Color {{row.color}}
diff --git a/src/material-examples/table-pagination/table-pagination-example.html b/src/material-examples/table-pagination/table-pagination-example.html index cea5b5f235f7..bfacadd5a42f 100644 --- a/src/material-examples/table-pagination/table-pagination-example.html +++ b/src/material-examples/table-pagination/table-pagination-example.html @@ -1,33 +1,33 @@
- + - No. - {{element.position}} + + - Name - {{element.name}} + + - Weight - {{element.weight}} + + - Symbol - {{element.symbol}} + + - - - + + +
No. {{element.position}} Name {{element.name}} Weight {{element.weight}} Symbol {{element.symbol}}
- + - + + - No. - {{element.position}} + + - Name - {{element.name}} + + - Weight - {{element.weight}} + + - Symbol - {{element.symbol}} + + - - + - - + +
- - + - + No. {{element.position}} Name {{element.name}} Weight {{element.weight}} Symbol {{element.symbol}}
diff --git a/src/material-examples/table-sorting/table-sorting-example.html b/src/material-examples/table-sorting/table-sorting-example.html index e043787717aa..00a89980eb0f 100644 --- a/src/material-examples/table-sorting/table-sorting-example.html +++ b/src/material-examples/table-sorting/table-sorting-example.html @@ -1,31 +1,31 @@
- + - No. - {{element.position}} + + - Name - {{element.name}} + + - Weight - {{element.weight}} + + - Symbol - {{element.symbol}} + + - - - + + +
No. {{element.position}} Name {{element.name}} Weight {{element.weight}} Symbol {{element.symbol}}
From 589bb3716a18f8b697c935cfede69dfe0f980710 Mon Sep 17 00:00:00 2001 From: Andrew Seguin Date: Thu, 29 Mar 2018 15:44:58 -0700 Subject: [PATCH 2/3] nodename, rename tHeadEl/tBodyEl, docs --- src/cdk/table/table.ts | 14 +++++++------- src/lib/table/table.md | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/cdk/table/table.ts b/src/cdk/table/table.ts index 130c1fb0e973..1e628c35773f 100644 --- a/src/cdk/table/table.ts +++ b/src/cdk/table/table.ts @@ -220,7 +220,7 @@ export class CdkTable implements CollectionViewer, OnInit, AfterContentChecke } ngOnInit() { - if (this._elementRef.nativeElement.tagName === 'TABLE') { + if (this._elementRef.nativeElement.nodeName === 'TABLE') { this._applyNativeTableSections(); } @@ -565,13 +565,13 @@ export class CdkTable implements CollectionViewer, OnInit, AfterContentChecke /** Adds native table sections (e.g. tbody) and moves the row placeholders into them. */ private _applyNativeTableSections() { - const tHeadEl = document.createElement('thead'); - const tBodyEl = document.createElement('tbody'); + const thead = document.createElement('thead'); + const tbody = document.createElement('tbody'); - this._elementRef.nativeElement.appendChild(tHeadEl); - this._elementRef.nativeElement.appendChild(tBodyEl); + this._elementRef.nativeElement.appendChild(thead); + this._elementRef.nativeElement.appendChild(tbody); - tHeadEl.appendChild(this._headerRowPlaceholder.elementRef.nativeElement); - tBodyEl.appendChild(this._rowPlaceholder.elementRef.nativeElement); + thead.appendChild(this._headerRowPlaceholder.elementRef.nativeElement); + tbody.appendChild(this._rowPlaceholder.elementRef.nativeElement); } } diff --git a/src/lib/table/table.md b/src/lib/table/table.md index 878445577630..b15713fa65fe 100644 --- a/src/lib/table/table.md +++ b/src/lib/table/table.md @@ -279,10 +279,10 @@ Table's default role is `grid`, and it can be changed to `treegrid` through `rol `mat-table` does not manage any focus/keyboard interaction on its own. Users can add desired focus/keyboard interactions in their application. -### Using flex layout +### Tables with `display: flex` The `MatTable` does not require that you use a native HTML table. Instead, you can use an -alternative approach that uses flex-layout for the table's styles. +alternative approach that uses `display: flex` for the table's styles. This alternative approach replaces the native table element tags with the `MatTable` directive selectors. For example, `` becomes ``; ` becomes From 311854895d643e2e229fae0657aa79dba3a0d565 Mon Sep 17 00:00:00 2001 From: Andrew Seguin Date: Thu, 29 Mar 2018 21:57:32 -0700 Subject: [PATCH 3/3] fix build by importing Element --- src/demo-app/table/native-table/native-table.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/demo-app/table/native-table/native-table.ts b/src/demo-app/table/native-table/native-table.ts index 81a0560b04f8..4d6a59a944a6 100644 --- a/src/demo-app/table/native-table/native-table.ts +++ b/src/demo-app/table/native-table/native-table.ts @@ -7,7 +7,7 @@ */ import {Component, ElementRef, ViewChild} from '@angular/core'; -import {ELEMENT_DATA} from '../element-data'; +import {ELEMENT_DATA, Element} from '../element-data'; import {MatPaginator, MatSort, MatTableDataSource} from '@angular/material'; import {PeopleDatabase, UserData} from '../people-database'; import {SelectionModel} from '@angular/cdk/collections'; @@ -22,7 +22,7 @@ import {fromEvent} from 'rxjs'; export class NativeTableDemo { definedColumns = ['name', 'weight', 'symbol', 'position']; columnsToDisplay = this.definedColumns; - data = ELEMENT_DATA.slice(); + data: Element[] = ELEMENT_DATA.slice(); dataSource = new MatTableDataSource(); columns = ['select', 'userId', 'userName', 'progress', 'color'];