extended-mat-table
Version:
A simple and powerful Datatable for Angular based on Angular Mat Table with some additional features ## Install
303 lines (298 loc) • 20.1 kB
JavaScript
import { Component, Input, ViewChild, NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { moveItemInArray, DragDropModule } from '@angular/cdk/drag-drop';
import { FormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatButtonToggleModule } from '@angular/material/button-toggle';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatInputModule } from '@angular/material/input';
import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator';
import { MatSort, MatSortModule } from '@angular/material/sort';
import { MatTableDataSource, MatTableModule } from '@angular/material/table';
import { SelectionModel } from '@angular/cdk/collections';
import { utils, writeFile } from 'xlsx';
import { includes, findIndex, filter, pick, remove } from 'lodash';
import * as moment from 'moment';
class ExtendedMatTable {
constructor() {
this.options = {};
this.data = [];
this.columns = [];
this.fetching = false;
this.actions = [];
this.displayedColumns = [];
this.availableColumns = [];
this.length = 0;
this.goTo = 0;
this.pageSizeOptions = ['5', '10', '20', '100'];
this.showColumns = false;
this.tableMinHeight = 0;
this.rowActions = [];
this.enableBulkEdit = false;
this.enableRowSelection = false;
this.pageNumbers = [];
this._options = {
tableId: 'DEFAULT_TABLE',
pageIndex: 0,
pageSize: 10,
hiddenColumnsIndex: [],
htmlColumns: [],
buttonColor: "#23758e",
exportPrefix: "ex_",
allowMultiSelection: false,
enableRowSelection: false,
removeHTMLBeforeExport: false,
enableClickOnDetail: false,
enableButton: true,
enableFilter: true,
enableColumnFilter: true,
selectedRowCallback: null,
returnColumnsOrderCallback: null,
lineClamp: 4,
fetching_text: 'fetching data...',
};
this.selection = new SelectionModel(this._options.allowMultiSelection, []);
this.render = (data, renderer, row) => {
if (!renderer)
return data;
return renderer(data, row);
};
}
ngOnInit() {
this._options = Object.assign(Object.assign({}, this._options), this.options);
this.enableBulkEdit = this._options.enableRowSelection;
this.displayedColumns = this.columns.filter((t, i) => {
return !includes(this._options.hiddenColumnsIndex, i);
}).map(c => c.data);
this.availableColumns = this.columns.map(c => c.data);
this.dataSource = new MatTableDataSource(this.data);
this.rowActions = this.actions.filter(a => {
return (typeof a.action == "function");
});
if (this.rowActions.length > 0)
this.displayedColumns.push('actions');
// if (this.enableRowSelection)
// this.displayedColumns.unshift('select');
this.updateGoto();
}
ngAfterViewInit() {
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
}
ngOnChanges() {
if (this.dataSource) {
this.dataSource.data = this.data;
this.dataSource._updateChangeSubscription();
}
}
checkIsColumnsDisplayed(c) {
return (this.displayedColumns.indexOf(c) !== -1);
}
addRows(rows) {
this.dataSource.data.push(...rows);
this.dataSource._updateChangeSubscription();
}
removeRow(row, idField) {
let index = findIndex(this.dataSource.data, ((x) => x[idField] == row[idField]));
if (index !== -1) {
this.dataSource.data.splice(index, 1);
this.dataSource._updateChangeSubscription();
}
}
updateDisplayColumn(displayedColumns) {
this.displayedColumns = filter(this.availableColumns, (f) => includes(displayedColumns, f));
}
updateGoto() {
this.length = this.data.length;
this.goTo = (this._options.pageIndex || 0) + 1;
this.pageNumbers = [];
for (let i = 1; i <= this.length / this._options.pageSize; i++) {
this.pageNumbers.push(i);
}
}
paginationChange(pageEvt) {
this._options.pageIndex = pageEvt.pageIndex;
this._options.pageSize = pageEvt.pageSize;
this.updateGoto();
}
goToChange() {
this.paginator.pageIndex = this.goTo - 1;
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
}
exportDocument(type = 'xlsx') {
let export_data = this.dataSource.filteredData.map(d => {
if (this._options.removeHTMLBeforeExport && Array.isArray(this._options.htmlColumns)) {
this._options.htmlColumns.map(column => {
var regex = /<[a-zA-Z][^>]*>(.*?)<\/[a-zA-Z]>/gm;
var strToMatch = d[column];
var results = [], match;
if (strToMatch) {
do {
match = regex.exec(strToMatch);
if (match)
results.push(match[1]);
} while (match);
d[column] = results.join(',');
}
});
}
return pick(d, this.displayedColumns);
});
var ws = utils.json_to_sheet(export_data);
var wb = utils.book_new();
utils.book_append_sheet(wb, ws, "Summary");
let options = {};
switch (type) {
case 'xlsx':
options = {
bookSST: true,
bookType: 'xlsx'
};
break;
case 'csv':
options = {
bookSST: true,
bookType: 'csv'
};
break;
default: options = {
bookSST: true,
bookType: 'xlsx'
};
}
writeFile(wb, `${this._options.exportPrefix}-${moment().format('YYYYMMDD')}.${type}`, options);
}
applyFilter(event) {
const filterValue = event.target.value;
this.dataSource.filter = filterValue.trim().toLowerCase();
}
applyColumnFilter(event, column) {
if (!column || !column.data)
return;
const filterValue = event.target.value;
this.dataSource.filter = filterValue.trim().toLowerCase();
this.dataSource.filterPredicate = (data, filter) => {
if (!data[column.data])
return false;
return data[column.data].toLowerCase().indexOf(filter) !== -1;
};
}
triggerColumnFilter() {
this._options.columnFilter = !this._options.columnFilter;
if (!this._options.columnFilter) {
this.dataSource.filter = "";
}
}
onToggleChange(event) {
this.displayedColumns = filter(this.availableColumns, (f) => includes(event.value, f));
if (this.rowActions.length > 0)
this.displayedColumns.push('actions');
if (this.enableRowSelection)
this.displayedColumns.unshift('select');
if (this._options.returnColumnsOrderCallback && typeof this._options.returnColumnsOrderCallback == 'function') {
this._options.returnColumnsOrderCallback({ tableId: this._options.tableId, displayedColumns: this.displayedColumns, availableColumns: this.availableColumns });
}
}
drop(event) {
moveItemInArray(this.availableColumns, event.previousIndex, event.currentIndex);
this.displayedColumns = filter(this.availableColumns, (f) => includes(this.displayedColumns, f));
if (this.rowActions.length > 0)
this.displayedColumns.push('actions');
if (this.enableRowSelection)
this.displayedColumns.unshift('select');
if (this._options.returnColumnsOrderCallback && typeof this._options.returnColumnsOrderCallback == 'function') {
this._options.returnColumnsOrderCallback({ tableId: this._options.tableId, displayedColumns: this.displayedColumns, availableColumns: this.availableColumns });
}
}
triggerRowSelect(row) {
if (this.enableRowSelection) {
this.selection.toggle(row);
}
}
isAllSelected() {
const numSelected = this.selection.selected.length;
const numRows = this.dataSource.data.length;
return numSelected === numRows;
}
/** Selects all rows if they are not all selected; otherwise clear selection. */
masterToggle() {
this.isAllSelected() ? this.selection.clear() : this.dataSource.filteredData.forEach(row => this.selection.select(row));
}
/** The label for the checkbox on the passed row */
checkboxLabel(row) {
if (!row) {
return `${this.isAllSelected() ? 'select' : 'deselect'} all`;
}
return `${this.selection.isSelected(row) ? 'deselect' : 'select'} row ${row.position + 1}`;
}
triggerBulkEdit() {
// this._options.allowMultiSelection = !this._options.allowMultiSelection;
this.enableRowSelection = !this.enableRowSelection;
this.selection = new SelectionModel(this._options.allowMultiSelection, []);
if (this.enableRowSelection) {
this.displayedColumns.unshift('select');
}
else {
remove(this.displayedColumns, (x => x == 'select'));
}
;
}
triggerSelectedRowCallback() {
if (this._options.selectedRowCallback && typeof this._options.selectedRowCallback == "function") {
this._options.selectedRowCallback({ tableId: this._options.tableId, selected: this.selection.selected });
}
}
onClickFunc(row, column) {
if (typeof this.cellOnClickFunction !== "function")
return;
this.cellOnClickFunction(row, column, this.pageSizeOptions);
}
triggerColumnSelectBoxAndCalculateTableMinHeight() {
let minHeight = this.availableColumns.length * 48;
this.tableMinHeight = (this.showColumns) ? (minHeight > 400) ? 400 : minHeight : 0;
}
}
ExtendedMatTable.decorators = [
{ type: Component, args: [{
selector: 'app-extended-mat-table',
template: "<div class=\"extended-mat-table-container\">\n <div class=\"mat-filter-container\">\n <button mat-stroked-button *ngIf=\"_options.enableButton\" [ngStyle]=\"{'background-color':_options.buttonColor}\" class=\"filter-button\" (click)=\"exportDocument('xlsx')\">Excel</button>\n <button mat-stroked-button *ngIf=\"_options.enableButton\" [ngStyle]=\"{'background-color':_options.buttonColor}\" class=\"filter-button\" (click)=\"exportDocument('csv')\">CSV</button>\n <div class=\"columns_button\" *ngIf=\"_options.enableButton\" ><button mat-stroked-button [ngStyle]=\"{'background-color':_options.buttonColor}\" class=\"filter-button\" (click)=\"showColumns = !showColumns;triggerColumnSelectBoxAndCalculateTableMinHeight();\">Columns</button>\n <mat-button-toggle-group cdkDropList (cdkDropListDropped)=\"drop($event)\" name=\"fontStyle\" aria-label=\"Font Style\" multiple vertical (change)=\"onToggleChange($event)\" *ngIf=\"showColumns\">\n <mat-button-toggle [value]=\"c\" [checked]=\"checkIsColumnsDisplayed(c)\" *ngFor=\"let c of availableColumns\" cdkDrag>{{c}}</mat-button-toggle>\n </mat-button-toggle-group>\n </div>\n <button mat-stroked-button [ngStyle]=\"{'background-color':_options.buttonColor}\" class=\"filter-button\" (click)=\"triggerColumnFilter()\">Filter</button>\n <button mat-stroked-button [ngStyle]=\"{'background-color':_options.buttonColor}\" class=\"filter-button\" *ngIf=\"enableBulkEdit\" (click)=\"triggerBulkEdit()\">Select Row</button>\n <button mat-stroked-button class=\"bg-dark filter-button\" *ngIf=\"enableBulkEdit && selection.hasValue()\" (click)=\"triggerSelectedRowCallback()\">Edit Selected</button>\n <mat-form-field *ngIf=\"_options.enableFilter\">\n <mat-label>{{(fetching)?'Loading':'Filter'}}</mat-label>\n <input matInput (keyup)=\"applyFilter($event)\" placeholder=\"Ex. sample\" [disabled]=\"fetching\">\n </mat-form-field>\n </div>\n \n <table class=\"extended-mat-table-block\" mat-table [dataSource]=\"dataSource\" matSort [ngStyle]=\"{'min-height.px':tableMinHeight}\">\n\n <ng-container matColumnDef=\"select\">\n <th mat-header-cell *matHeaderCellDef>\n <mat-checkbox (change)=\"$event ? masterToggle() : null\"\n [checked]=\"selection.hasValue() && isAllSelected()\"\n [indeterminate]=\"selection.hasValue() && !isAllSelected()\"\n [aria-label]=\"checkboxLabel()\">\n </mat-checkbox>\n </th>\n <td mat-cell *matCellDef=\"let element\">\n <mat-checkbox (click)=\"$event.stopPropagation()\"\n (change)=\"$event ? selection.toggle(element) : null\"\n [checked]=\"selection.isSelected(element)\"\n [aria-label]=\"checkboxLabel(element)\">\n </mat-checkbox>\n </td>\n </ng-container>\n\n <ng-container [matColumnDef]=\"item.data\" *ngFor=\"let item of columns\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header [disabled]=\"_options.columnFilter\"><div class=\"d-flex flex-column\"><span class=\"theader_block\">{{item.title}}</span><span *ngIf=\"_options.columnFilter\"><input matInput (keyup)=\"applyColumnFilter($event, item)\" placeholder=\"search..\" [disabled]=\"fetching\"></span></div></th>\n <td mat-cell *matCellDef=\"let element\">\n <div *ngIf=\"!_options.enableClickOnDetail\" title=\"{{render(element[item.data], item.render,element)}}\" [ngStyle]=\"{'-webkit-line-clamp':_options.lineClamp,'line-clamp':_options.lineClamp}\" [ngClass]=\"{'white-space-pre':item.wrap_text,'white-space-nowrap':!item.wrap_text}\" [innerHTML]=\"render(element[item.data], item.render,element)\"></div>\n <div *ngIf=\"_options.enableClickOnDetail\" (click)=\"onClickFunc(element,item)\" title=\"{{render(element[item.data], item.render,element)}}\" class=\"btn text-left px-0\" [ngClass]=\"{'white-space-pre':item.wrap_text}\" [innerHTML]=\"render(element[item.data], item.render,element)\"></div>\n </td>\n </ng-container>\n\n <ng-container matColumnDef=\"actions\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header></th>\n <td mat-cell *matCellDef=\"let element\">\n <div class=\"d-flex\">\n <button *ngFor=\"let act_button of rowActions\" mat-stroked-button class=\"bg-light mr-2 ml-1\" (click)=\"act_button.action(element);\" [disabled]=\"fetching\">{{act_button.title}}</button>\n </div>\n </td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\" [ngClass]=\"{'selected':selection.isSelected(row)}\" (click)=\"triggerRowSelect(row)\"></tr>\n </table>\n\n <div class=\"mat-pag-container\">\n <mat-paginator [length]=\"length\" [pageSize]=\"_options.pageSize\" [pageSizeOptions]=\"pageSizeOptions\" showFirstLastButtons (page)=\"paginationChange($event)\"></mat-paginator>\n <div class=\"fetching\" [ngClass]=\"{'show_s':fetching}\"><span>{{_options.fetching_text}}</span></div>\n <div class=\"go-to-container\">\n <div class=\"go-to-label\">Go To: </div>\n <mat-form-field>\n <input matInput type=\"number\" [(ngModel)]=\"goTo\" (keyup)=\"goToChange()\">\n </mat-form-field>\n </div>\n </div>\n</div>",
styles: [".bg-primary{background-color:#222b7c}.bg-dark{background-color:#333}.bg-orange{background-color:#df9800}.d-flex{display:flex}.flex-column{flex-direction:column}.text-left{text-align:left}.text-white{color:#fff}.px-0{padding-left:0;padding-right:0}.mr-2,.mx-2{margin-right:.5rem!important}.ml-1,.mx-1{margin-left:.25rem!important}table{width:100%}td.mat-cell,td.mat-footer-cell,th.mat-header-cell{padding:0 15px}.mat-form-field{font-size:14px;width:30%;max-width:300px;float:right}.extended-mat-table-container{width:100%;overflow-y:hidden;height:-webkit-fit-content;height:-moz-fit-content;height:fit-content;overflow-x:scroll}.extended-mat-table-container ::ng-deep th div{font-weight:560;color:#000;font-size:13px}.extended-mat-table-container .filter-button{color:#fff;margin-left:6px}.mat-pag-container{position:sticky;left:0;width:100%;display:flex}.mat-pag-container .mat-paginator{flex:1}.mat-pag-container .fetching{background-color:#fff;display:flex!important;justify-content:center;align-items:center;width:0;transition:width 1s ease-in-out}.mat-pag-container .fetching span{color:rgba(0,0,0,.54);font-family:Roboto,Helvetica Neue,sans-serif;font-size:12px;opacity:0;transition:all 1s ease-in-out}.mat-pag-container .fetching.show_s{width:100px}.mat-pag-container .fetching.show_s span{animation:loading 3s linear infinite}.mat-pag-container .go-to-container{display:flex;flex-direction:row;align-items:center;justify-content:center;max-width:150px;min-width:150px;background-color:#fff;color:rgba(0,0,0,.54);font-family:Roboto,Helvetica Neue,sans-serif;font-size:12px}.mat-pag-container .go-to-container input{text-align:center}.mat-filter-container{position:sticky;left:0;z-index:100}.extended-mat-table-block{z-index:99}.columns_button{position:relative;display:inline-block}.columns_button mat-button-toggle-group{width:90%;position:absolute;left:10px;top:36px;max-height:400px;overflow-y:scroll;box-shadow:2px 2px 5px #ccc}.columns_button mat-button-toggle-group::-webkit-scrollbar{width:5px}.columns_button mat-button-toggle-group::-webkit-scrollbar-track{-webkit-box-shadow:inset 0 0 6px rgba(0,0,0,.3);border-radius:10px}.columns_button mat-button-toggle-group::-webkit-scrollbar-thumb{border-radius:10px;-webkit-box-shadow:inset 0 0 6px rgba(0,0,0,.5)}.columns_button .mat-button-toggle-group-appearance-standard.mat-button-toggle-vertical .mat-button-toggle+.mat-button-toggle{border:0}.columns_button .mat-button-toggle-appearance-standard .mat-button-toggle-label-content{line-height:28px}tr.selected{background:#ccc}td.cdk-column-actions,th.cdk-column-actions{position:sticky;right:0;background:#fff}td.cdk-column-actions{box-shadow:-1px 1px 3px #ccc;border:0}.white-space-pre{display:-webkit-box;-webkit-line-clamp:4;-webkit-box-orient:vertical;overflow:hidden;text-overflow:ellipsis}.white-space-nowrap{white-space:nowrap}th .theader_block{min-height:60px;display:flex;align-items:center;justify-content:center}@keyframes loading{0%{opacity:0}50%{opacity:1}to{opacity:0}}"]
},] }
];
ExtendedMatTable.propDecorators = {
options: [{ type: Input }],
data: [{ type: Input }],
columns: [{ type: Input }],
fetching: [{ type: Input }],
actions: [{ type: Input }],
cellOnClickFunction: [{ type: Input }],
paginator: [{ type: ViewChild, args: [MatPaginator,] }],
sort: [{ type: ViewChild, args: [MatSort,] }]
};
class ExtendedMatTableModule {
}
ExtendedMatTableModule.decorators = [
{ type: NgModule, args: [{
declarations: [ExtendedMatTable],
imports: [
CommonModule,
DragDropModule,
FormsModule,
MatButtonModule,
MatButtonToggleModule,
MatCheckboxModule,
// MatFormFieldModule,
MatInputModule,
MatPaginatorModule,
MatSortModule,
MatTableModule
],
exports: [ExtendedMatTable]
},] }
];
/**
* Generated bundle index. Do not edit.
*/
export { ExtendedMatTable, ExtendedMatTableModule };
//# sourceMappingURL=extended-mat-table.js.map