From 560308b95e4eb09649938ae638d70c89519a47e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Barbeau?= Date: Tue, 28 Aug 2018 16:51:20 -0400 Subject: [PATCH] feat(table): improve table component (selection, filter) --- .../common/src/lib/table/table-datasource.ts | 23 +++-- .../src/lib/table/table-model.interface.ts | 17 ++++ .../common/src/lib/table/table.component.html | 90 +++++++++++++------ .../common/src/lib/table/table.component.scss | 33 ++++--- .../common/src/lib/table/table.component.ts | 72 ++++++++++++--- projects/common/src/lib/table/table.module.ts | 10 ++- 6 files changed, 185 insertions(+), 60 deletions(-) diff --git a/projects/common/src/lib/table/table-datasource.ts b/projects/common/src/lib/table/table-datasource.ts index 683c5371fb..11f68b78ab 100644 --- a/projects/common/src/lib/table/table-datasource.ts +++ b/projects/common/src/lib/table/table-datasource.ts @@ -4,6 +4,8 @@ import { MatSort } from '@angular/material'; import { Observable, BehaviorSubject, merge } from 'rxjs'; import { map } from 'rxjs/operators'; +import { ObjectUtils } from '@igo2/utils'; + import { TableDatabase, TableModel } from './index'; export class TableDataSource extends DataSource { @@ -54,7 +56,7 @@ export class TableDataSource extends DataSource { return data.slice().filter((item: any) => { const searchStr: string = this._model.columns .filter(c => c.filterable) - .map(c => item[c.name]) + .map(c => ObjectUtils.resolve(item, c.name)) .join(' ') .toLowerCase(); @@ -68,14 +70,19 @@ export class TableDataSource extends DataSource { } return data.sort((a, b) => { - const propertyA: number | string = a[this._sort.active]; - const propertyB: number | string = b[this._sort.active]; - - const valueA = isNaN(+propertyA) ? propertyA : +propertyA; - const valueB = isNaN(+propertyB) ? propertyB : +propertyB; + const propertyA: number | string = ObjectUtils.resolve( + a, + this._sort.active + ); + const propertyB: number | string = ObjectUtils.resolve( + b, + this._sort.active + ); - return ( - (valueA < valueB ? -1 : 1) * (this._sort.direction === 'asc' ? 1 : -1) + return ObjectUtils.naturalCompare( + propertyB, + propertyA, + this._sort.direction ); }); } diff --git a/projects/common/src/lib/table/table-model.interface.ts b/projects/common/src/lib/table/table-model.interface.ts index 8e46613395..f514529bcd 100644 --- a/projects/common/src/lib/table/table-model.interface.ts +++ b/projects/common/src/lib/table/table-model.interface.ts @@ -6,8 +6,22 @@ export interface TableColumn { sortable?: boolean; filterable?: boolean; displayed?: boolean; + html?: boolean; } +export type RowClassFunc = ( + row: any +) => { + [key: string]: string; +}; + +export type CellClassFunc = ( + row: any, + column: TableColumn +) => { + [key: string]: string; +}; + export type ClickAction = (item: any) => void; export interface TableAction { @@ -19,4 +33,7 @@ export interface TableAction { export interface TableModel { columns: TableColumn[]; actions?: TableAction[]; + selectionCheckbox?: boolean; + rowClassFunc?: RowClassFunc; + cellClassFunc?: CellClassFunc; } diff --git a/projects/common/src/lib/table/table.component.html b/projects/common/src/lib/table/table.component.html index 8393c1f82d..778e099911 100644 --- a/projects/common/src/lib/table/table.component.html +++ b/projects/common/src/lib/table/table.component.html @@ -1,40 +1,74 @@ -
-
+
+
- +
+ - - - {{column.title}} + + + + - - {{column.title}} + + + + + + + + + + + + + + + + - {{getValue(row, column.name)}} - - - - - - - - - - - - - - + + + + + + + + + + +
+ + + + + + {{column.title}} {{column.title}} + {{getValue(row, column.name)}} + + + +
+
diff --git a/projects/common/src/lib/table/table.component.scss b/projects/common/src/lib/table/table.component.scss index c40e5fc958..7a1c469ae2 100644 --- a/projects/common/src/lib/table/table.component.scss +++ b/projects/common/src/lib/table/table.component.scss @@ -8,31 +8,30 @@ .table-container { display: flex; flex-direction: column; - max-height: 100%; + height: 100%; + overflow: auto; + flex: 1 1 auto; +} + +.table-box { + height: 100%; + display: flex; + flex-direction: column; } .table-header { min-height: 64px; max-width: 500px; display: flex; + flex: 0 1 auto; align-items: baseline; padding: 8px 24px 0; font-size: 20px; justify-content: space-between; } -.mat-input-container { - font-size: 14px; - flex-grow: 1; - margin-left: 32px; -} - -.mat-table { - overflow: auto; -} - -.mat-header-row, -.mat-row { +tr[mat-header-row], +tr[mat-row] { height: 60px; } @@ -41,6 +40,14 @@ word-wrap: break-word; } +td[mat-cell] { + padding-right: 15px; +} + +th.mat-header-cell { + padding-right: 5px; +} + button { margin-right: 10px; } diff --git a/projects/common/src/lib/table/table.component.ts b/projects/common/src/lib/table/table.component.ts index a89b886365..e1c3d5e789 100644 --- a/projects/common/src/lib/table/table.component.ts +++ b/projects/common/src/lib/table/table.component.ts @@ -3,10 +3,14 @@ import { ElementRef, ViewChild, Input, + Output, OnChanges, - OnInit + OnInit, + AfterViewInit, + EventEmitter } from '@angular/core'; import { MatSort } from '@angular/material'; +import { SelectionModel } from '@angular/cdk/collections'; import { debounceTime, distinctUntilChanged } from 'rxjs/operators'; import { fromEvent } from 'rxjs'; @@ -23,7 +27,7 @@ import { TableActionColor } from './table-action-color.enum'; templateUrl: './table.component.html', styleUrls: ['./table.component.scss'] }) -export class TableComponent implements OnChanges, OnInit { +export class TableComponent implements OnChanges, OnInit, AfterViewInit { @Input() get database(): TableDatabase { return this._database; @@ -42,8 +46,25 @@ export class TableComponent implements OnChanges, OnInit { } private _model: TableModel; + @Input() + get hasFilterInput(): boolean { + return this._hasFIlterInput; + } + set hasFilterInput(value: boolean) { + this._hasFIlterInput = value; + } + private _hasFIlterInput = true; + public displayedColumns; public dataSource: TableDataSource | null; + public selection = new SelectionModel(true, []); + + @Output() + select = new EventEmitter<{ + added: any[]; + removed: any[]; + source: SelectionModel; + }>(); @ViewChild('filter') filter: ElementRef; @ViewChild(MatSort) sort: MatSort; @@ -56,19 +77,31 @@ export class TableComponent implements OnChanges, OnInit { .filter(c => c.displayed !== false) .map(c => c.name); + if (this.model.selectionCheckbox) { + this.displayedColumns.unshift('selectionCheckbox'); + } if (this.model.actions && this.model.actions.length) { this.displayedColumns.push('action'); } } - fromEvent(this.filter.nativeElement, 'keyup') - .pipe(debounceTime(150), distinctUntilChanged()) - .subscribe(() => { - if (!this.dataSource) { - return; - } - this.dataSource.filter = this.filter.nativeElement.value; - }); + this.selection.onChange.subscribe(e => this.select.emit(e)); + } + + ngAfterViewInit() { + if (this.filter) { + fromEvent(this.filter.nativeElement, 'keyup') + .pipe( + debounceTime(150), + distinctUntilChanged() + ) + .subscribe(() => { + if (!this.dataSource) { + return; + } + this.dataSource.filter = this.filter.nativeElement.value; + }); + } } ngOnChanges(change) { @@ -88,4 +121,23 @@ export class TableComponent implements OnChanges, OnInit { getValue(row, key) { return ObjectUtils.resolve(row, key); } + + /** Whether the number of selected elements matches the total number of rows. */ + isAllSelected() { + const numSelected = this.selection.selected.length; + const numRows = this.database.data.length; + return numSelected === numRows; + } + + /** Selects all rows if they are not all selected; otherwise clear selection. */ + masterToggle() { + this.isAllSelected() + ? this.selection.clear() + : this.database.data.forEach(row => this.selection.select(row)); + } + + handleClickAction(event, action, row) { + event.stopPropagation(); + action.click(row); + } } diff --git a/projects/common/src/lib/table/table.module.ts b/projects/common/src/lib/table/table.module.ts index 4f694422de..5c403f6c2c 100644 --- a/projects/common/src/lib/table/table.module.ts +++ b/projects/common/src/lib/table/table.module.ts @@ -1,11 +1,15 @@ import { NgModule, ModuleWithProviders } from '@angular/core'; import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; import { CdkTableModule } from '@angular/cdk/table'; import { MatIconModule, MatButtonModule, MatTableModule, - MatFormFieldModule + MatFormFieldModule, + MatInputModule, + MatSortModule, + MatCheckboxModule } from '@angular/material'; import { IgoLanguageModule } from '@igo2/core'; @@ -15,11 +19,15 @@ import { TableComponent } from './table.component'; @NgModule({ imports: [ CommonModule, + FormsModule, CdkTableModule, MatIconModule, MatButtonModule, MatTableModule, MatFormFieldModule, + MatInputModule, + MatSortModule, + MatCheckboxModule, IgoLanguageModule ], declarations: [TableComponent],