Skip to content

Commit

Permalink
feat(table): improve table component (selection, filter)
Browse files Browse the repository at this point in the history
  • Loading branch information
mbarbeau committed Aug 28, 2018
1 parent 945664f commit 560308b
Show file tree
Hide file tree
Showing 6 changed files with 185 additions and 60 deletions.
23 changes: 15 additions & 8 deletions projects/common/src/lib/table/table-datasource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<any> {
Expand Down Expand Up @@ -54,7 +56,7 @@ export class TableDataSource extends DataSource<any> {
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();

Expand All @@ -68,14 +70,19 @@ export class TableDataSource extends DataSource<any> {
}

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
);
});
}
Expand Down
17 changes: 17 additions & 0 deletions projects/common/src/lib/table/table-model.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -19,4 +33,7 @@ export interface TableAction {
export interface TableModel {
columns: TableColumn[];
actions?: TableAction[];
selectionCheckbox?: boolean;
rowClassFunc?: RowClassFunc;
cellClassFunc?: CellClassFunc;
}
90 changes: 62 additions & 28 deletions projects/common/src/lib/table/table.component.html
Original file line number Diff line number Diff line change
@@ -1,40 +1,74 @@
<div class='table-container'>
<div class='table-header'>
<div class='table-box'>
<div class='table-header' *ngIf="hasFilterInput">
<mat-form-field floatPlaceholder='never'>
<input matInput #filter [placeholder]="'igo.common.table.filter' | translate">
</mat-form-field>
</div>

<mat-table #table [dataSource]='dataSource' matSort>
<div class='table-container'>
<table mat-table #table [dataSource]='dataSource' matSort>

<ng-container [cdkColumnDef]='column.name' *ngFor='let column of model.columns'>
<ng-container *ngIf='column.sortable'>
<mat-header-cell *cdkHeaderCellDef mat-sort-header> {{column.title}} </mat-header-cell>
<!-- Checkbox Column -->
<ng-container matColumnDef="selectionCheckbox">
<th mat-header-cell *matHeaderCellDef>
<mat-checkbox (change)="$event ? masterToggle() : null"
[checked]="selection.hasValue() && isAllSelected()"
[indeterminate]="selection.hasValue() && !isAllSelected()">
</mat-checkbox>
</th>
<td mat-cell *matCellDef="let row">
<mat-checkbox (click)="$event.stopPropagation()"
(change)="$event ? selection.toggle(row) : null"
[checked]="selection.isSelected(row)">
</mat-checkbox>
</td>
</ng-container>

<ng-container *ngIf='!column.sortable'>
<mat-header-cell *cdkHeaderCellDef> {{column.title}} </mat-header-cell>
<ng-container [matColumnDef]='column.name' *ngFor='let column of model.columns'>
<ng-container *ngIf='column.sortable'>
<th mat-header-cell *matHeaderCellDef mat-sort-header> {{column.title}} </th>
</ng-container>

<ng-container *ngIf='!column.sortable'>
<th mat-header-cell *matHeaderCellDef> {{column.title}} </th>
</ng-container>

<ng-container *ngIf="!column.html; else cellHTML">
<td mat-cell *matCellDef='let row' class="mat-cell-text"
[ngClass]="model.cellClassFunc ? model.cellClassFunc(row, column) : {}">
{{getValue(row, column.name)}}
</td>
</ng-container>

<ng-template #cellHTML>
<td mat-cell *matCellDef='let row' class="mat-cell-text"
[ngClass]="model.cellClassFunc ? model.cellClassFunc(row, column) : {}"
[innerHTML]="getValue(row, column.name)">
</td>
</ng-template>
</ng-container>

<mat-cell *cdkCellDef='let row' class="mat-cell-text"> {{getValue(row, column.name)}} </mat-cell>
</ng-container>

<!-- Action Column -->
<ng-container cdkColumnDef='action'>
<mat-header-cell *cdkHeaderCellDef></mat-header-cell>
<mat-cell *cdkCellDef='let row'>
<button *ngFor='let action of model.actions'
mat-mini-fab
[color]='getActionColor(action.color)'
(click)='action.click(row)'>
<mat-icon>{{action.icon}}</mat-icon>
</button>
</mat-cell>
</ng-container>

<mat-header-row *cdkHeaderRowDef='displayedColumns'></mat-header-row>
<mat-row *cdkRowDef='let row; columns: displayedColumns;'></mat-row>

</mat-table>
<!-- Action Column -->
<ng-container matColumnDef='action'>
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef='let row'>
<button *ngFor='let action of model.actions'
mat-mini-fab
[color]='getActionColor(action.color)'
(click)='handleClickAction($event, action, row)'>
<mat-icon>{{action.icon}}</mat-icon>
</button>
</td>
</ng-container>

<tr mat-header-row *matHeaderRowDef='displayedColumns;'></tr>
<tr mat-row
*matRowDef='let row; columns: displayedColumns;'
[ngClass]="model.rowClassFunc ? model.rowClassFunc(row) : {}"
(click)="selection.toggle(row)">
</tr>

</table>
</div>

</div>
33 changes: 20 additions & 13 deletions projects/common/src/lib/table/table.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -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;
}
72 changes: 62 additions & 10 deletions projects/common/src/lib/table/table.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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;
Expand All @@ -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<any>(true, []);

@Output()
select = new EventEmitter<{
added: any[];
removed: any[];
source: SelectionModel<any>;
}>();

@ViewChild('filter') filter: ElementRef;
@ViewChild(MatSort) sort: MatSort;
Expand All @@ -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) {
Expand All @@ -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);
}
}
10 changes: 9 additions & 1 deletion projects/common/src/lib/table/table.module.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -15,11 +19,15 @@ import { TableComponent } from './table.component';
@NgModule({
imports: [
CommonModule,
FormsModule,
CdkTableModule,
MatIconModule,
MatButtonModule,
MatTableModule,
MatFormFieldModule,
MatInputModule,
MatSortModule,
MatCheckboxModule,
IgoLanguageModule
],
declarations: [TableComponent],
Expand Down

0 comments on commit 560308b

Please sign in to comment.