UNPKG

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
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