UNPKG

ngx-mat-dynamic-table

Version:
798 lines (784 loc) 45 kB
import { ɵɵdefineInjectable, Injectable, Component, EventEmitter, ChangeDetectionStrategy, Inject, LOCALE_ID, Input, Output, ViewChild, Pipe, NgModule } from '@angular/core'; import { MatTableDataSource, MatTableModule } from '@angular/material/table'; import { MatSort, MatSortModule } from '@angular/material/sort'; import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator'; import { SelectionModel } from '@angular/cdk/collections'; import { formatDate, CommonModule } from '@angular/common'; import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms'; import { saveAs } from 'file-saver'; import { utils, write } from 'xlsx'; import { values, map, partialRight, pick, get, cloneDeep, set } from 'lodash'; import { Observable, BehaviorSubject, Subject, interval } from 'rxjs'; import { debounce } from 'rxjs/operators'; import { FlexLayoutModule } from '@angular/flex-layout'; import { MatSnackBar } from '@angular/material/snack-bar'; import { MatButtonModule } from '@angular/material/button'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatIconModule } from '@angular/material/icon'; import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatProgressBarModule } from '@angular/material/progress-bar'; import { MatTooltipModule } from '@angular/material/tooltip'; import { MatSelectModule } from '@angular/material/select'; import { MatInputModule } from '@angular/material/input'; import { MatMenuModule } from '@angular/material/menu'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { ENTER, COMMA } from '@angular/cdk/keycodes'; import { moveItemInArray, DragDropModule } from '@angular/cdk/drag-drop'; import { MatChipsModule } from '@angular/material/chips'; import { MatButtonToggleModule } from '@angular/material/button-toggle'; class NgxMatDynamicTableService { constructor() { } } NgxMatDynamicTableService.ɵprov = ɵɵdefineInjectable({ factory: function NgxMatDynamicTableService_Factory() { return new NgxMatDynamicTableService(); }, token: NgxMatDynamicTableService, providedIn: "root" }); NgxMatDynamicTableService.decorators = [ { type: Injectable, args: [{ providedIn: 'root' },] } ]; NgxMatDynamicTableService.ctorParameters = () => []; class NgxMatDynamicTableComponent { constructor() { } ngOnInit() { } } NgxMatDynamicTableComponent.decorators = [ { type: Component, args: [{ selector: 'ngx-mat-ngx-mat-dynamic-table', template: ` <p> ngx-mat-dynamic-table works! </p> ` },] } ]; NgxMatDynamicTableComponent.ctorParameters = () => []; class XlsxExportService { constructor() { this.fileType = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8'; this.fileExtension = '.xlsx'; } exportExcel(jsonData, headers, fileName) { const columnHeaders = values(headers); const ws = utils.json_to_sheet(map(this.renameKeys(jsonData, headers), partialRight(pick, columnHeaders)), { header: columnHeaders }); const wb = { Sheets: { 'data': ws }, SheetNames: ['data'] }; const excelBuffer = write(wb, { bookType: 'xlsx', type: 'array' }); this.saveExcelFile(excelBuffer, fileName); } saveExcelFile(buffer, fileName) { const data = new Blob([buffer], { type: this.fileType }); saveAs(data, fileName + this.fileExtension); } renameKeys(jsonData, headers) { return jsonData.map(j => { let renamed = {}; // Get all the keys of the headers Object.keys(headers).forEach(key => { // make the header value the new key of the renamed object. // use the key of the header with lodash get function to get nested objects. renamed[headers[key]] = get(j, key); }); return renamed; }); } } XlsxExportService.decorators = [ { type: Injectable } ]; XlsxExportService.ctorParameters = () => []; class ColumnStorageService { constructor() { this.dbName = 'ngx-mat-dynamic-table'; this.storeName = 'columns'; this.version = 1; //versions start at 1 var request = indexedDB.open(this.dbName, this.version); request.onerror = (event) => { console.warn('IndexedDB not enabled.'); }; request.onupgradeneeded = (event) => { this.db = request.result; this.objectStore = this.db.createObjectStore(this.storeName, { keyPath: 'tableId' }); console.debug('ngx-mat-dynamic-table: create object store 👍'); }; request.onsuccess = (event) => { this.db = request.result; console.debug('ngx-mat-dynamic-table: db opened 👍' + this.db); }; } isDbOpen() { return !!this.db; } save(doc) { // Dont try to save if there is no db if (!this.db) return; var request = this.db.transaction([this.storeName], 'readwrite') .objectStore(this.storeName) .put(doc); request.onsuccess = (event) => { // console.debug('Saved columns...'); }; } read(tableId) { return new Observable((observer) => { var transaction = this.db.transaction([this.storeName]); if (!transaction) { observer.next(null); observer.complete(); return; } var objectStore = transaction.objectStore(this.storeName); var request = objectStore.get(tableId); request.onerror = (event) => { observer.error(new Error); observer.complete(); }; request.onsuccess = (event) => { if (request.result) { observer.next(request.result); observer.complete(); } else { // observer.error((e) => console.log(e)); observer.next(null); observer.complete(); } }; }); } } ColumnStorageService.ɵprov = ɵɵdefineInjectable({ factory: function ColumnStorageService_Factory() { return new ColumnStorageService(); }, token: ColumnStorageService, providedIn: "root" }); ColumnStorageService.decorators = [ { type: Injectable, args: [{ providedIn: 'root' },] } ]; ColumnStorageService.ctorParameters = () => []; class DynamicTableComponent { constructor(locale, xlsxExportService, columnStorageService) { this.locale = locale; this.xlsxExportService = xlsxExportService; this.columnStorageService = columnStorageService; this.export = false; // Enable export for this table this.filter = false; // Enable filter for this table this.multiple = false; // Allow multiple select for this table this.optionalColumns = false; // Show or hide columns this.emitFilteredData = false; // Show or hide columns this.rowClicked = new EventEmitter(); this.cellClicked = new EventEmitter(); this.selectedRows = new EventEmitter(); this.filterResult = new EventEmitter(); this.selection = new SelectionModel(true, []); this.displayedColumns = []; this.dataSource = new MatTableDataSource(); this.totalsRowVisible = false; // The hidden columns filtered out this.columnsToShow = new FormControl(); this.filterKeyUp = new BehaviorSubject(''); this.columnSearch$ = new Subject(); } ngOnChanges(changes) { // Add headers to of data to display in table if (changes.columns && changes.columns.currentValue) { this.setColumns(); } if (changes.tableData.currentValue) { // TODO: Test this default sort change // if (this.sortActive) { // this.dataSource.data = this.tableData.sort((a, b) => (_get(a, this.sortActive) > _get(b, this.sortActive)) ? this.sortDirection === 'asc' ? 1 : -1 : ((_get(b, this.sortActive) > _get(a, this.sortActive)) ? this.sortDirection === 'asc' ? -1 : 1 : 0)); // } else { // this.dataSource.data = this.tableData; // } // Clear selection if new data is comming in this.selection.clear(); this.dataSource.data = this.tableData; this.updateColumnTotals(); } } ngAfterViewInit() { this.filterKeyUp.pipe(debounce(() => interval(2000))).subscribe(filterValue => { this.searchLoading = false; // apply the filter after 2 seconds this.dataSource.filter = filterValue; if (this.dataSource.paginator) { this.dataSource.paginator.firstPage(); } // update totals this.updateColumnTotals(); // If the flag is set, emit the filtered data if (this.emitFilteredData) { this.getFilteredData(); } }); this.dataSource.sort = this.sort; this.dataSource.paginator = this.paginator; } ngOnInit() { this.dataSource.sortingDataAccessor = (data, attribute) => get(data, attribute); // Changed this sort function from the source to compare alpha numerics https://stackoverflow.com/a/66157044/6044269 // https://github.com/angular/components/blob/master/src/material/table/table-data-source.ts#L165 this.dataSource.sortData = (data, sort) => { const active = sort.active; const direction = sort.direction; if (!active || direction == '') { return data; } return data.sort((a, b) => { let valueA = this.dataSource.sortingDataAccessor(a, active); let valueB = this.dataSource.sortingDataAccessor(b, active); const valueAType = typeof valueA; const valueBType = typeof valueB; if (valueAType !== valueBType) { if (valueAType === 'number') { valueA += ''; } if (valueBType === 'number') { valueB += ''; } } let comparatorResult = 0; if (valueA != null && valueB != null) { // ! This is the If that I changed https://stackoverflow.com/a/66157044/6044269 if (valueAType === 'string' && valueBType === 'string') { comparatorResult = valueA.localeCompare(valueB, 'en', { numeric: true }); } else if (valueA > valueB) { comparatorResult = 1; } else if (valueA < valueB) { comparatorResult = -1; } } else if (valueA != null) { comparatorResult = 1; } else if (valueB != null) { comparatorResult = -1; } return comparatorResult * (direction == 'asc' ? 1 : -1); }); }; // Search for nested objects this.dataSource.filterPredicate = (data, filter) => { let validRow = true; if (!filter) return validRow; // type|searchString:column#searchType,type|searchString:column#searchType... const terms = filter.split(','); // Split multiple search terms.some(term => { let [type, criteria] = term.split('|'); // Split global / column search let dataString = ''; let searchType; // type of search in a column // let criteria; if (type === 'global') { // Search All this.columns.filter(c => c.search).map(c => c.columnDef).forEach(column => { const cellData = get(data, column); if (cellData) dataString += cellData; }); // String contains/equals in all columns if (!dataString || dataString.toString().toLowerCase().indexOf(criteria.toLowerCase()) === -1) { validRow = false; return true; } } else { const [searchString, columnAndType] = criteria.split(':'); // Split search string and criteria let column; [column, searchType] = columnAndType.split('#'); // Split column and type of search criteria = searchString; dataString = get(data, column); // get the data if (searchType === '!=') { // String contains/equals in all columns if (!dataString || dataString.toString().toLowerCase().indexOf(criteria.toLowerCase()) !== -1) { validRow = false; return true; } } else if (searchType === 'empty') { if (dataString) { validRow = false; return true; } } else { // String contains/equals in all columns if (!dataString || dataString.toString().toLowerCase().indexOf(criteria.toLowerCase()) === -1) { validRow = false; return true; } } } }); return validRow; }; } updateXLSXHeaders() { this.xlsxHeaders = {}; this.columnsToShow.value.forEach(c => { this.xlsxHeaders[c.columnDef] = c.columnTitle; }); } updateColumnTotals() { if (!this.dataSource) { return; } this.columns.map(col => { if ((col.total || col.average)) { this.totalsRowVisible = true; if (this.dataSource.filteredData.length > 0) { col.totalValue = this.dataSource.filteredData.map(t => get(t, col.columnDef)).reduce((acc, value) => acc + (value ? value : 0), 0); if (isNaN(col.totalValue)) { col.totalValue = 0; col.averageValue = 0; } else { col.averageValue = col.totalValue / this.dataSource.filteredData.length; } } else { col.totalValue = 0; col.averageValue = 0; } } return col; }); } applyFilter(searchTerms) { this.searchLoading = true; let filterString; searchTerms.forEach(term => { // type: Global or in a column // |searchString: the text/number searching for // :column: when type is column, this field is set // #searchType = != >= <= empty // type|searchString:column#searchType,type|searchString:column#searchType... const newTerm = `${term.type}|${term.search}${term.type === 'column' ? ':' + term.column + '#' + term.searchType : ''}`; if (filterString) filterString += `,${newTerm}`; else filterString = newTerm; }); this.filterKeyUp.next(filterString); } applyColumnFilter(element, column, columnTitle, searchType) { // if (searchType && searchType !== 'empty' && !element.value) return; this.searchLoading = true; // Send data to table-search-input and add a chip this.columnSearch$.next({ type: 'column', column: column, columnTitle: columnTitle, inputReference: element, search: element.value, searchType: searchType }); } _rowClicked(row) { if (!this.rowClick) return; this.rowClicked.emit(row); } onMatCellClick(row, column) { if (!this.cellClick) return; this.cellClicked.emit({ row: row, column: column.columnDef, data: get(row, column.columnDef) }); } /** Whether the number of selected elements matches the total number of rows. */ 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)); this.emitSelected(); } toggle(event, row) { if (event) this.selection.toggle(row); this.emitSelected(); } emitSelected() { this.selectedRows.emit(this.selection.selected); } setColumns() { this.setDefaultSorting(); // Check for saved columns in indexedDB if (this.tableId) { this.savedColumnsLoading = true; // TODO: wait here for the db if (this.columnStorageService.isDbOpen()) { this.columnStorageService.read(this.tableId).subscribe(res => { this.setSavedColumnSelection(res); }); } else { // Wait for the DB to wake up and then query setTimeout(() => { this.columnStorageService.read(this.tableId).subscribe(res => { this.setSavedColumnSelection(res); }); }, 500); } } else { this.setStaticColumns(); } } setStaticColumns() { // List of all columns to show in the view this.displayedColumns = []; // Totals row starts as false untill inspection of column definitions this.totalsRowVisible = false; // add the rest of non hidden columns to the list this.displayedColumns = this.columns.filter(c => !c.hidden).map(c => c.columnDef); // If multiple select is enabled, add select to the from of the array if (this.multiple) this.displayedColumns.unshift('select'); // set visible columns as checked in the selector this.columnsToShow.setValue(this.columns.filter(c => !c.hidden)); this.updateColumnTotals(); } displayColumnsChanged(event) { this.displayedColumns = event.value.map(c => c.columnDef); if (this.multiple) this.displayedColumns.unshift('select'); // save the current visible columns this.saveCurrentColumnSelection(this.displayedColumns); } eportToExcell() { this.updateXLSXHeaders(); this.formatExportData(); this.xlsxExportService.exportExcel(this.exportData, this.xlsxHeaders, this.fileName ? this.fileName : 'Export'); } getFilteredData() { this.cloneData(); this.filterResult.emit(this.exportData); } cloneData() { // Format dates for exporting, temp if (this.selection.hasValue()) { // Export selected data this.exportData = this.selection.selected.map(a => cloneDeep(a)); } else { // Export filtered data and sorted this.exportData = this.dataSource.sortData(this.dataSource.filteredData, this.dataSource.sort).map(a => cloneDeep(a)); } } formatExportData() { this.cloneData(); // If the columns contain a date let dateColumns = this.columnsToShow.value.filter(e => e.type === 'date'); if (dateColumns && dateColumns.length > 0) { this.exportData = this.exportData.map(td => { dateColumns.forEach(col => { if (get(td, col.columnDef)) set(td, col.columnDef, formatDate(get(td, col.columnDef), col.dateFormat ? col.dateFormat : 'dd/MM/yyyy, HH:mm', this.locale)); }); return td; }); } } nestedFilterCheck(search, data, key) { if (typeof data[key] === 'object') { for (const k in data[key]) { if (data[key][k] !== null) { search = this.nestedFilterCheck(search, data[key], k); } } } else { search += data[key]; } return search; } setDefaultSorting() { this.columns.some(col => { if (col.sort) { this.sortActive = col.columnDef; this.sortDirection = col.sort; return true; } }); } setSavedColumnSelection(saved) { this.savedColumnsLoading = false; if (!saved || saved.visibleColumns.length === 0) { this.setStaticColumns(); return; } // set the column selector with the values stored in the indexeddb this.columnsToShow.setValue(this.columns.filter(c => saved.visibleColumns.some(item => item === c.columnDef))); // If multiple is enabled but was not saved in the saved array, add it if (this.multiple && saved.visibleColumns[0] !== 'select') saved.visibleColumns.unshift('select'); this.displayedColumns = saved.visibleColumns; } saveCurrentColumnSelection(displayedColumns) { if (!this.tableId) return; let toSave = this.displayedColumns.slice(); // Do not save the selection column if (this.multiple && toSave[0] === 'select') toSave.shift(); this.columnStorageService.save({ tableId: this.tableId, visibleColumns: toSave }); } } DynamicTableComponent.decorators = [ { type: Component, args: [{ selector: 'ngx-mat-dynamic-table', template: "<div fxLayout=\"row wrap\" fxLayoutAlign=\"start center\" fxLayoutGap=\"10px\">\r\n <!-- Search input with chips -->\r\n <ngx-mat-table-search-input [columnSearch$]=\"columnSearch$\" (searchChange)=\"applyFilter($event)\">\r\n </ngx-mat-table-search-input>\r\n <mat-spinner *ngIf=\"searchLoading\" [diameter]=\"20\"></mat-spinner>\r\n <span fxFlex.gt-xs></span>\r\n <!-- Select enabled columns -->\r\n <mat-spinner *ngIf=\"savedColumnsLoading\" [diameter]=\"20\"></mat-spinner>\r\n <mat-form-field *ngIf=\"optionalColumns\" appearance=\"outline\">\r\n <mat-label>Columns</mat-label>\r\n <mat-select [formControl]=\"columnsToShow\" multiple (selectionChange)=\"displayColumnsChanged($event)\">\r\n <mat-option *ngFor=\"let column of columns\" [value]=\"column\">{{column.columnTitle}}</mat-option>\r\n </mat-select>\r\n </mat-form-field>\r\n <!-- Export selected or filtered data to excel -->\r\n <button *ngIf=\"export\" mat-stroked-button color=\"primary\" (click)=\"eportToExcell()\">\r\n <mat-icon>import_export</mat-icon> Export\r\n </button>\r\n <!-- Indicate number of selected items -->\r\n <span *ngIf=\"selection.selected.length > 0\" class=\"mat-caption\">{{selection.selected.length}} values selected</span>\r\n</div>\r\n<!-- Table wrapping container -->\r\n<div class=\"table-container\">\r\n <table mat-table #table [dataSource]=\"dataSource\" matSort [matSortActive]=\"sortActive\"\r\n [matSortDirection]=\"sortDirection\" style=\"width: 100%;\">\r\n <!-- Checkbox Column -->\r\n <ng-container *ngIf=\"multiple\" matColumnDef=\"select\">\r\n <th mat-header-cell *matHeaderCellDef>\r\n <mat-checkbox (change)=\"$event ? masterToggle() : null\"\r\n [checked]=\"selection.hasValue() && isAllSelected()\"\r\n [indeterminate]=\"selection.hasValue() && !isAllSelected()\">\r\n </mat-checkbox>\r\n </th>\r\n <td mat-cell *matCellDef=\"let row\">\r\n <mat-checkbox (click)=\"$event.stopPropagation()\" (change)=\"toggle($event, row)\"\r\n [checked]=\"selection.isSelected(row)\">\r\n </mat-checkbox>\r\n </td>\r\n <ng-container *ngIf=\"totalsRowVisible\">\r\n <td mat-footer-cell *matFooterCellDef></td>\r\n </ng-container>\r\n </ng-container>\r\n <!-- Table columns -->\r\n <ng-container *ngFor=\"let column of columns\" [matColumnDef]=\"column.columnDef\" [sticky]=\"column.sticky\"\r\n [stickyEnd]=\"column.stickyEnd\">\r\n <!-- Table header -->\r\n <th mat-header-cell *matHeaderCellDef>\r\n <!-- Header and search icon -->\r\n <div fxLayout=\"row\" fxLayoutAlign=\"start center\">\r\n <span mat-sort-header>\r\n {{ column.columnTitle }}\r\n </span>\r\n <button *ngIf=\"column.search\" class=\"search-button\" mat-icon-button [matMenuTriggerFor]=\"menu\"\r\n (menuOpened)=\"searchInput.focus()\">\r\n <mat-icon [ngClass]=\"{'search-active': searchInput.value}\">search</mat-icon>\r\n </button>\r\n </div>\r\n <!-- Search Menu -->\r\n <mat-menu #menu=\"matMenu\">\r\n <div (click)=\"$event.stopPropagation()\" fxLayout=\"column\"\r\n style=\"padding-right: 8px; padding-left: 8px;\">\r\n <!-- Search input -->\r\n <mat-form-field>\r\n <input matInput #searchInput\r\n (keyup)=\"applyColumnFilter($event.target, column.columnDef, column.columnTitle, searchType.value);\"\r\n [placeholder]=\"column.columnTitle\" [disabled]=\"searchType.value === 'empty'\">\r\n <mat-hint>Search</mat-hint>\r\n <button mat-button *ngIf=\"searchInput.value\" matSuffix mat-icon-button aria-label=\"Clear\"\r\n (click)=\"searchInput.value=''; applyColumnFilter(searchInput, column.columnDef, column.columnTitle, searchType.value)\">\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n </mat-form-field>\r\n <!-- Search options -->\r\n <div fxLayout=\"row\" fxLayoutAlign=\"end end\">\r\n <mat-button-toggle-group name=\"search type\" aria-label=\"Search Type\"\r\n #searchType=\"matButtonToggleGroup\" value=\"=\"\r\n (change)=\"applyColumnFilter(searchInput, column.columnDef, column.columnTitle, searchType.value)\">\r\n <mat-button-toggle value=\"=\" matTooltip=\"Equals\">=</mat-button-toggle>\r\n <mat-button-toggle value=\"!=\" matTooltip=\"Not Equals\">\u2260</mat-button-toggle>\r\n <!-- <mat-button-toggle value=\">=\">&#62;=</mat-button-toggle>\r\n <mat-button-toggle value=\"<=\">&#60;=</mat-button-toggle> -->\r\n <mat-button-toggle value=\"empty\" matTooltip=\"Empty Cell\">\" \"</mat-button-toggle>\r\n </mat-button-toggle-group>\r\n </div>\r\n </div>\r\n </mat-menu>\r\n </th>\r\n <!-- Table Cell -->\r\n <td mat-cell *matCellDef=\"let row\" [class]=\"row[column.cellClassKey]\"\r\n [attr.data-label]=\"column.columnTitle\" (click)=\"onMatCellClick(row, column);\">\r\n <ng-container [ngSwitch]=\"column.type\">\r\n <!-- Number type columns -->\r\n <span *ngSwitchCase=\"'number'\"\r\n [ngClass]=\"column.unit?.position === 'before' ? 'unit-before' : 'unit-after'\"\r\n [attr.data-unit]=\"row | lodashGet: column.unit?.key\">\r\n {{ row | lodashGet: column.columnDef | number }}\r\n </span>\r\n <!-- Date Type columns -->\r\n <span *ngSwitchCase=\"'date'\">\r\n {{ row | lodashGet: column.columnDef | date: column.dateFormat ? column.dateFormat :\r\n 'dd/MM/yyyy, HH:mm' }}\r\n </span>\r\n <!-- Icon Columns -->\r\n <span *ngSwitchCase=\"'icon'\">\r\n <ng-container *ngFor=\"let icon of column.icons\">\r\n <mat-icon *ngIf=\"icon.value === (row | lodashGet: column.columnDef)\"\r\n [style.color]=\"icon.color\" [matTooltip]=\"icon.matTooltip\">{{icon.matIcon}}\r\n </mat-icon>\r\n </ng-container>\r\n </span>\r\n <!-- Text Columns -->\r\n <span *ngSwitchDefault>\r\n {{ row | lodashGet: column.columnDef }}\r\n </span>\r\n </ng-container>\r\n </td>\r\n\r\n <ng-container *ngIf=\"totalsRowVisible\">\r\n <td mat-footer-cell *matFooterCellDef [attr.data-label]=\"column.columnTitle\">\r\n <ng-container *ngIf=\"column.total\">\r\n {{column.totalValue | number}}\r\n </ng-container>\r\n <ng-container *ngIf=\"column.total && column.average\">\r\n &nbsp;|&nbsp;\r\n </ng-container>\r\n <ng-container *ngIf=\"column.average\">\r\n &mu;&nbsp;{{column.averageValue | number}}\r\n </ng-container>\r\n </td>\r\n </ng-container>\r\n\r\n </ng-container>\r\n\r\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns; sticky: true;\"></tr>\r\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\" (click)=\"_rowClicked(row)\"\r\n [ngClass]=\"{'row-click': rowClick}\"></tr>\r\n\r\n <tr class=\"mat-row\" *matNoDataRow>\r\n <td class=\"mat-cell\" [attr.colspan]=\"displayedColumns.length\">No data</td>\r\n </tr>\r\n\r\n <ng-container *ngIf=\"totalsRowVisible\">\r\n <tr mat-footer-row *matFooterRowDef=\"displayedColumns; sticky: true;\"></tr>\r\n </ng-container>\r\n\r\n </table>\r\n</div>\r\n<mat-paginator class=\"mat-paginator-sticky\" [pageSizeOptions]=\"pageSizeOptions ? pageSizeOptions: [50, 100, 200]\">\r\n</mat-paginator>", changeDetection: ChangeDetectionStrategy.OnPush, styles: [".row-click{cursor:pointer}.search-button{height:24px!important;line-height:24px!important;width:24px!important}.search-button .mat-icon{height:18px!important;line-height:18px!important;width:18px!important}.search-button .material-icons{font-size:18px!important}.search-active{color:#00f}.unit-after:after,.unit-before:before{content:attr(data-unit)}.mat-cell{padding-left:5px;padding-right:5px}.mat-cell,.mat-header-row{text-overflow:ellipsis;white-space:nowrap}@media screen and (min-width:550px){.table-container{max-height:calc(100vh - 150px);overflow:auto}}@media screen and (max-width:550px){.mat-table .mat-row{border-bottom:5px solid #ddd;padding-bottom:5px}.mat-table .mat-header-row{display:flex;flex-wrap:wrap;height:auto;margin-bottom:4%;position:-webkit-sticky;position:sticky;top:0;z-index:2}.mat-table .mat-header-row th.mat-header-cell{align-items:center;display:flex;flex:1;justify-content:space-around;padding:1rem .5rem}.mat-table .mat-header-row th.mat-header-cell>.mat-sort-header-container{padding-left:15px}.mat-table .mat-cell{display:block;font-size:1em;font-weight:700;height:30px;margin-bottom:4%;padding-left:8px;padding-right:8px;text-align:right}.mat-table .mat-cell:before{content:attr(data-label);float:left;font-size:.85em;font-weight:400;text-transform:uppercase}.mat-table .mat-footer-cell{display:block;font-size:1em;font-weight:700;height:30px;margin-bottom:4%;padding-left:8px;padding-right:8px;text-align:right}.mat-table .mat-footer-cell:before{content:attr(data-label);float:left;font-size:.85em;font-weight:400;text-transform:uppercase}.mat-table .mat-cell:last-child{border-bottom:5px solid #ddd}}.mat-button-toggle-label-content{line-height:32px!important}"] },] } ]; DynamicTableComponent.ctorParameters = () => [ { type: String, decorators: [{ type: Inject, args: [LOCALE_ID,] }] }, { type: XlsxExportService }, { type: ColumnStorageService } ]; DynamicTableComponent.propDecorators = { tableId: [{ type: Input }], tableData: [{ type: Input }], rowClick: [{ type: Input }], cellClick: [{ type: Input }], fileName: [{ type: Input }], columns: [{ type: Input }], pageSizeOptions: [{ type: Input }], export: [{ type: Input }], filter: [{ type: Input }], multiple: [{ type: Input }], optionalColumns: [{ type: Input }], emitFilteredData: [{ type: Input }], rowClicked: [{ type: Output }], cellClicked: [{ type: Output }], selectedRows: [{ type: Output }], filterResult: [{ type: Output }], sort: [{ type: ViewChild, args: [MatSort,] }], paginator: [{ type: ViewChild, args: [MatPaginator,] }] }; class DynamicTableContainerComponent { constructor() { this.export = false; // Enable export for this table this.filter = false; // Enable filter for this table this.multiple = false; // Allow multiple select for this table this.optionalColumns = false; // Show or hide columns this.emitFilteredData = false; // Show or hide columns this.rowClicked = new EventEmitter(); this.cellClicked = new EventEmitter(); this.selectedRows = new EventEmitter(); this.filterResult = new EventEmitter(); } ngOnInit() { } } DynamicTableContainerComponent.decorators = [ { type: Component, args: [{ selector: 'ngx-mat-dynamic-table-container', template: "<ng-container *ngIf=\"tableData$\">\n <div *ngIf=\"(tableData$ | async) as tableData; else loading\">\n <ngx-mat-dynamic-table [tableId]=\"tableId\" [columns]=\"columns\" [tableData]=\"tableData\" [rowClick]=\"rowClick\"\n (rowClicked)=\"rowClicked.emit($event)\" [cellClick]=\"cellClick\" (cellClicked)=\"cellClicked.emit($event)\"\n [fileName]=\"fileName\" [export]=\"export\" [filter]=\"filter\" [multiple]=\"multiple\"\n (selectedRows)=\"selectedRows.emit($event)\" [optionalColumns]=\"optionalColumns\"\n [pageSizeOptions]=\"pageSizeOptions\" [emitFilteredData]=\"emitFilteredData\"\n (filterResult)=\"filterResult.emit($event)\">\n </ngx-mat-dynamic-table>\n </div>\n <ng-template #loading>\n <mat-progress-bar mode=\"indeterminate\"></mat-progress-bar>\n </ng-template>\n</ng-container>", changeDetection: ChangeDetectionStrategy.OnPush, styles: [""] },] } ]; DynamicTableContainerComponent.ctorParameters = () => []; DynamicTableContainerComponent.propDecorators = { tableId: [{ type: Input }], tableData$: [{ type: Input }], columns: [{ type: Input }], rowClick: [{ type: Input }], cellClick: [{ type: Input }], fileName: [{ type: Input }], export: [{ type: Input }], filter: [{ type: Input }], multiple: [{ type: Input }], optionalColumns: [{ type: Input }], pageSizeOptions: [{ type: Input }], emitFilteredData: [{ type: Input }], rowClicked: [{ type: Output }], cellClicked: [{ type: Output }], selectedRows: [{ type: Output }], filterResult: [{ type: Output }] }; class XlsxTableExportComponent { constructor(xlsxExportService, matSnackBar) { this.xlsxExportService = xlsxExportService; this.matSnackBar = matSnackBar; } ngOnInit() { } eportToExcell() { if (!this.data || !this.headers) { this.matSnackBar.open('Provide data and headers.', 'Ok', { duration: 4000 }); return; } this.xlsxExportService.exportExcel(this.data, this.headers, this.fileName ? this.fileName : 'Export'); } } XlsxTableExportComponent.decorators = [ { type: Component, args: [{ selector: 'ngx-mat-xlsx-table-export', template: "<button mat-stroked-button color=\"primary\" (click)=\"eportToExcell()\"><mat-icon>import_export</mat-icon> Export</button>", styles: [""] },] } ]; XlsxTableExportComponent.ctorParameters = () => [ { type: XlsxExportService }, { type: MatSnackBar } ]; XlsxTableExportComponent.propDecorators = { data: [{ type: Input }], fileName: [{ type: Input }], headers: [{ type: Input }] }; class LodashGetPipe { transform(value, key) { if (!key) return ''; return get(value, key); } } LodashGetPipe.decorators = [ { type: Pipe, args: [{ name: 'lodashGet' },] } ]; class TableSearchInputComponent { constructor() { this.visible = true; this.selectable = true; this.separatorKeysCodes = [ENTER, COMMA]; this.searchCtrl = new FormControl(); this.searchTerms = []; this.searchChange = new EventEmitter(); } ngOnInit() { this.columnSearchSubscription = this.columnSearch$.subscribe(searchTerm => { if ((searchTerm.search || '').trim() || searchTerm.searchType === 'empty') { // See if there are existing global search let columnSearch = this.searchTerms.find(st => st.column === searchTerm.column); // If there are, update if (columnSearch) { columnSearch.search = searchTerm.search.trim(); columnSearch.searchType = searchTerm.searchType; } else // Else add this.searchTerms.push(searchTerm); this.emitNewSearch(); } else { this.remove(searchTerm); } }); } add(event) { const value = event.target.value; // See if there are existing global search let globalSearch = this.searchTerms.find(st => st.type === 'global'); // Add global search term if ((value || '').trim()) { // If there are, update if (globalSearch) globalSearch.search = value.trim(); else // Else add this.searchTerms.push({ type: 'global', search: value.trim() }); this.emitNewSearch(); } else { this.remove(globalSearch); } } remove(searchTerm) { let index; // if column is cleared if (searchTerm.type === 'column') { index = this.searchTerms.findIndex(i => i.column === searchTerm.column); } else { index = this.searchTerms.findIndex(i => i.search === searchTerm.search && i.type === searchTerm.type); if (index >= 0) this.searchInput.nativeElement.value = null; } if (searchTerm.inputReference) searchTerm.inputReference.value = ''; if (index >= 0) { this.searchTerms.splice(index, 1); this.emitNewSearch(); } } orderChanged(event) { moveItemInArray(this.searchTerms, event.previousIndex, event.currentIndex); this.emitNewSearch(); } emitNewSearch() { this.searchChange.emit(this.searchTerms); } ngOnDestroy() { this.columnSearchSubscription.unsubscribe(); } } TableSearchInputComponent.decorators = [ { type: Component, args: [{ selector: 'ngx-mat-table-search-input', template: "<div fxLayout=\"column\" fxLayout.gt-xs=\"row wrap\" fxLayoutAlign=\"start start\" fxLayoutGap.gt-xs=\"15px\">\n <mat-form-field appearance=\"outline\">\n <mat-label>Search</mat-label>\n <input matInput placeholder=\"All Columns\" #searchInput (keyup)=\"add($event)\">\n </mat-form-field>\n <div fxLayout=\"column\">\n <span *ngIf=\"searchTerms.length > 0\" class=\"mat-caption\">Filters</span>\n <mat-chip-list cdkDropList cdkDropListOrientation=\"horizontal\" (cdkDropListDropped)=\"orderChanged($event)\">\n <mat-chip class=\"example-box\" cdkDrag *ngFor=\"let term of searchTerms\"\n [color]=\"term.type === 'global' ? 'primary' : 'accent'\" selected\n [matTooltip]=\"term.type === 'global' ? 'All':term.columnTitle\">\n <span *ngIf=\"term.searchType === 'empty'; else not_empty\">\n {{term.columnTitle}} (empty)\n </span>\n <ng-template #not_empty>\n {{term.search}} {{term.searchType && term.searchType === '!=' ?\n '(\u2260)' : ''}}\n </ng-template>\n <mat-icon (click)=\"remove(term)\" matChipRemove>cancel</mat-icon>\n </mat-chip>\n </mat-chip-list>\n </div>\n</div>", styles: [""] },] } ]; TableSearchInputComponent.ctorParameters = () => []; TableSearchInputComponent.propDecorators = { columnSearch$: [{ type: Input }], searchInput: [{ type: ViewChild, args: ['searchInput',] }], searchChange: [{ type: Output }] }; class NgxMatDynamicTableModule { } NgxMatDynamicTableModule.decorators = [ { type: NgModule, args: [{ declarations: [ NgxMatDynamicTableComponent, DynamicTableComponent, DynamicTableContainerComponent, XlsxTableExportComponent, LodashGetPipe, TableSearchInputComponent ], imports: [ CommonModule, FormsModule, ReactiveFormsModule, FlexLayoutModule, MatTableModule, MatButtonModule, MatFormFieldModule, MatInputModule, MatIconModule, MatCheckboxModule, MatPaginatorModule, MatSortModule, MatProgressBarModule, MatTooltipModule, MatSelectModule, MatMenuModule, MatProgressSpinnerModule, MatChipsModule, DragDropModule, MatButtonToggleModule ], providers: [XlsxExportService], exports: [ DynamicTableComponent, DynamicTableContainerComponent, XlsxTableExportComponent ] },] } ]; /* * Public API Surface of ngx-mat-dynamic-table */ /** * Generated bundle index. Do not edit. */ export { DynamicTableComponent, DynamicTableContainerComponent, NgxMatDynamicTableComponent, NgxMatDynamicTableModule, NgxMatDynamicTableService, XlsxTableExportComponent, XlsxExportService as ɵa, ColumnStorageService as ɵb, LodashGetPipe as ɵc, TableSearchInputComponent as ɵd }; //# sourceMappingURL=ngx-mat-dynamic-table.js.map