import { | |
AfterContentInit, Component, ContentChildren, | |
EventEmitter, Input, OnChanges, | |
OnInit, Output, TemplateRef | |
} from '@angular/core' | |
import { chunkArray } from '@trapeze-gui/common' | |
@Component({ | |
selector: 'trapeze-table', | |
styleUrls: ['./table.component.scss'], | |
template: ` | |
<ng-container *ngIf="pageSelector === 'top' && pages.length > 1"> | |
<ng-container *ngTemplateOutlet="pageSelect"></ng-container> | |
</ng-container> | |
<ng-container *ngFor="let data of pages; let page = index"><table *ngIf="page===activePage"> | |
<thead *ngIf="displayHeadings"><tr *ngIf="columns"> | |
<th *ngFor="let column of columns" (click)="changeSort(column)"> | |
{{ (map && map[column]) ? map[column] : column }} | |
<i class="fa fa-caret-up" *ngIf="columnIsAscending(column)"></i> | |
<i class="fa fa-caret-down" *ngIf="columnIsDescending(column)"></i> | |
</th> | |
</tr></thead> | |
<tbody> | |
<tr *ngFor="let row of data; let i = index" (click)="onRowClick(row, i)"> | |
<td *ngFor="let column of columns" (click)="onCellClick(column, row)"> | |
<ng-container *ngIf="templates[column]; else display"> | |
<ng-container *ngTemplateOutlet=" | |
templates[column]; | |
context: {$implicit: row[column]} | |
"></ng-container> | |
</ng-container> | |
<ng-template #display>{{row[column]}}</ng-template> | |
</td> | |
</tr> | |
</tbody> | |
</table></ng-container> | |
<ng-container *ngIf="pageSelector === 'bottom' && pages.length > 1"> | |
<ng-container *ngTemplateOutlet="pageSelect"></ng-container> | |
</ng-container> | |
<ng-template #pageSelect><ng-container *ngIf="pages.length > 1"> | |
<ul class="page-selector"> | |
<li *ngIf="activePage > 0 && pages.length > 10"> | |
<a (click)="selectPage(0)"><<</a> | |
</li> | |
<li *ngIf="activePage > 0"> | |
<a (click)="selectPage(activePage - 1)"><</a> | |
</li> | |
<ng-container *ngFor="let page of pages; let i = index"> | |
<li *ngIf="i > activePage - 5 && (i < activePage + 5 || i < 10)"> | |
<a [ngClass]="{active: activePage === i}" (click)="selectPage(i)">{{i+1}}</a> | |
</li> | |
</ng-container> | |
<li *ngIf="activePage < pages.length"> | |
<a (click)="selectPage(activePage + 1)">></a> | |
</li> | |
<li *ngIf="activePage < pages.length && pages.length > 10"> | |
<a (click)="selectPage(pages.length - 1)">>></a> | |
</li> | |
</ul> | |
</ng-container></ng-template> | |
` | |
}) | |
export class TableComponent implements OnChanges { | |
@Input() data: TableData = [] | |
@Input() displayHeadings: boolean = true | |
@Input() map: {[key: string]: string} | |
@Input() includeCols: string[] | |
@Input() excludeCols: string[] | |
@Input() order: string[] | |
@Input() sortBy: {[key: string]: 'ascending'|'descending'} | |
@Input() templates: {[column: string]: TemplateRef<any>} = {} | |
@Input() pageSize: number = Infinity | |
@Input() pageSelector: 'top'|'bottom' = 'top' | |
@Output() rowClick = new EventEmitter() | |
@Output() cellClick = new EventEmitter() | |
columns: string[] = [] | |
pages: TableData[] = [] | |
activePage: number = 0 | |
ngOnChanges(changes): void { | |
if (this.order) | |
this.columns = this.order | |
else | |
this.determineColumns() | |
this.sort() | |
} | |
public determineColumns(): void { | |
let columns: string[] = [] | |
if (this.data) this.data.map(item => Object.getOwnPropertyNames(item)).forEach(keys => { | |
keys.filter(key => { | |
return !this.excludeCols || this.excludeCols.indexOf(key) < 0 | |
}).filter(key => { | |
return !this.includeCols || this.includeCols.indexOf(key) > -1 | |
}).filter(key => columns.indexOf(key) < 0).forEach(newKey => { | |
columns.push(newKey) | |
}) | |
}) | |
this.columns = columns | |
} | |
// TODO: add compound sort? | |
public changeSort(column: string): void { | |
const direction = this.sortBy[column] | |
if (direction && !isDescending(direction)) { | |
this.sortBy = { | |
[column]: 'descending' | |
} | |
} else { | |
this.sortBy = { | |
[column]: 'ascending' | |
} | |
} | |
this.sort() | |
} | |
public sort(): void { | |
if (!this.sortBy) this.sortBy = {[this.columns[0]]: 'ascending'} | |
this.data.sort((a: TableRow, b: TableRow) => { | |
const key = Object.keys(this.sortBy).shift() | |
const direction = this.sortBy[key] | |
let A = (isNaN(a[key])) ? a[key] : Number(a[key]) | |
let B = (isNaN(a[key])) ? b[key] : Number(b[key]) | |
let result = 0 | |
if (A < B) result = -1 | |
else if (A > B) result = 1 | |
if (isDescending(direction)) result = result * -1 | |
return result | |
}) | |
this.paginate() | |
} | |
public paginate(): void { | |
this.pages = chunkArray(this.data, this.pageSize) | |
} | |
public selectPage(num: number): void { | |
this.activePage = num | |
} | |
public columnIsAscending(column: string): boolean { | |
return this.sortBy && isAscending(this.sortBy[column]) | |
} | |
public columnIsDescending(column: string): boolean { | |
return this.sortBy && isDescending(this.sortBy[column]) | |
} | |
public onRowClick(row, index): void { | |
this.rowClick.emit({ | |
row: row, | |
index: index | |
}) | |
} | |
public onCellClick(column, row): void { | |
this.cellClick.emit({ | |
row: row, | |
column: column, | |
value: row[column] | |
}) | |
} | |
} | |
function isDescending(direction: string): boolean { | |
return /desc/i.test(direction) | |
} | |
function isAscending(direction: string): boolean { | |
return /asc/i.test(direction) | |
} | |
export interface TableRow {[column: string]: any} | |
export type TableData = TableRow[] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment