UNPKG

ngx-advanced-material-table

Version:

Advanced Angular Material Table component, along with many features such as filter and display/hide columns

927 lines (918 loc) 65.7 kB
import '@angular/localize/init'; import * as i0 from '@angular/core'; import { Pipe, Component, Inject, Injectable, EventEmitter, Input, Output, ViewChild, NgModule } from '@angular/core'; import * as i1 from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog'; import * as i2 from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import * as i3 from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button'; import * as i4 from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon'; import * as i5 from '@angular/material/form-field'; import * as i6 from '@angular/cdk/scrolling'; import { ScrollingModule } from '@angular/cdk/scrolling'; import * as i7 from '@angular/material/checkbox'; import { MatCheckboxModule } from '@angular/material/checkbox'; import * as i8 from '@angular/material/button-toggle'; import { MatButtonToggleModule } from '@angular/material/button-toggle'; import * as i9 from '@angular/material/input'; import { MatInputModule } from '@angular/material/input'; import * as i11 from '@angular/common'; import { CommonModule } from '@angular/common'; import * as _ from 'lodash'; import * as i10 from '@angular/material/sort'; import { MatSort, MatSortModule } from '@angular/material/sort'; import { take } from 'rxjs/operators'; import * as i7$1 from '@angular/material/paginator'; import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator'; import * as i8$1 from '@angular/material/table'; import { MatTableDataSource, MatTableModule } from '@angular/material/table'; import { SelectionModel } from '@angular/cdk/collections'; import * as i6$1 from '@angular/material/menu'; import { MatMenuModule } from '@angular/material/menu'; import * as i13 from '@angular/material/tooltip'; import { MatTooltipModule } from '@angular/material/tooltip'; import * as i14 from '@angular/cdk/drag-drop'; import { DragDropModule } from '@angular/cdk/drag-drop'; import { CdkTableModule } from '@angular/cdk/table'; import { MatRippleModule } from '@angular/material/core'; var ColumnType; (function (ColumnType) { ColumnType[ColumnType["String"] = 0] = "String"; ColumnType[ColumnType["DateTime"] = 1] = "DateTime"; ColumnType[ColumnType["Date"] = 2] = "Date"; ColumnType[ColumnType["Time"] = 3] = "Time"; ColumnType[ColumnType["Actions"] = 4] = "Actions"; ColumnType[ColumnType["DropDown"] = 5] = "DropDown"; ColumnType[ColumnType["Icon"] = 6] = "Icon"; ColumnType[ColumnType["NumberInput"] = 7] = "NumberInput"; ColumnType[ColumnType["DropDownDynamic"] = 8] = "DropDownDynamic"; ColumnType[ColumnType["Image"] = 9] = "Image"; ColumnType[ColumnType["Link"] = 10] = "Link"; })(ColumnType || (ColumnType = {})); class Value { static getDistinctItems(items) { const newArray = []; items.forEach((item) => { if (newArray.indexOf(item) === -1) { newArray.push(item); } }); return newArray; } static splitStringBySeperator(text, seperator = ',') { if (Value.isNullOrWhiteSpace(text)) { return []; } let strs = text.split(seperator); strs = strs.map((str) => str.trim()); return strs.filter((str) => Value.isNotNullOrWhiteSpace(str)); } static extractValueSplitBySeparator(value, separator, index) { return value.indexOf(separator) > -1 ? value.split('_')[index] : ''; } static isNumber(value) { if (Value.isNotNullOrUndefined(value) && /^\d+(\.\d+)?$/.test(value.toString())) { return true; } return false; } static isNumberWithPattern(value, pattern) { if (Value.isNotNullOrUndefined(value) && pattern.test(value.toString())) { return true; } return false; } static clearArray(value) { if (Value.isArray(value)) { value.splice(0, value.length); } } static isArray(value) { if (Value.isNotNullOrUndefined(value) && value instanceof Array) { return true; } return false; } static isString(value) { if (typeof value === 'string') { return true; } return false; } static isArrayWithItems(value) { if (Value.isArray(value) && value.length > 0) { return true; } return false; } static isNullOrUndefined(value) { return !Value.isNotNullOrUndefined(value); } static isNotNullOrUndefined(value) { if (value !== undefined && value !== null) { return true; } return false; } static isNotNullOrWhiteSpace(value) { if (Value.isNotNullOrUndefined(value) && value.trim() !== '') { return true; } return false; } static isNullOrWhiteSpace(value) { return !Value.isNotNullOrWhiteSpace(value); } static isStringContains(source, toBeMatched, caseSensitive = true) { if (!source || !toBeMatched) { return false; } if (caseSensitive) { if (source.indexOf(toBeMatched) !== -1) { return true; } return false; } else { if (source.toLocaleLowerCase().indexOf(toBeMatched.toLocaleLowerCase()) !== -1) { return true; } return false; } } static isArrayContains(sourceList, toBeMatched, caseSensitive = true) { if (!sourceList || !toBeMatched) { return false; } for (const source of sourceList) { if (caseSensitive) { if (source.indexOf(toBeMatched) !== -1) { return true; } } else { if (source.toLocaleLowerCase().indexOf(toBeMatched.toLocaleLowerCase()) !== -1) { return true; } } } return false; } } var DialogActionType; (function (DialogActionType) { DialogActionType["Ok"] = "Ok"; DialogActionType["Cancel"] = "Cancel"; })(DialogActionType || (DialogActionType = {})); class FilterColumnValuesPipe { transform(items, searchText) { if (!items) { return []; } if (!searchText) { return items; } searchText = searchText.toLowerCase(); return items.filter((it) => { return it.name.toString().toLowerCase().includes(searchText); }); } } FilterColumnValuesPipe.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.2.12", ngImport: i0, type: FilterColumnValuesPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); FilterColumnValuesPipe.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "12.0.0", version: "12.2.12", ngImport: i0, type: FilterColumnValuesPipe, name: "filterCriteria" }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.12", ngImport: i0, type: FilterColumnValuesPipe, decorators: [{ type: Pipe, args: [{ name: 'filterCriteria' }] }] }); class FilterColumnsComponent { constructor(dialogRef, fb, context) { this.dialogRef = dialogRef; this.fb = fb; this.context = context; this.searchFiltersValue = ''; this.distinctColumnValues = []; this.cancelResponse = { action: DialogActionType.Cancel }; this.columnType = ColumnType; this.selectedColumn = this.context.selectedColumn; this.distinctColumnValues = this.sortColumns(this.context.distinctData); this.initialSortingDirection = this.selectedColumn.SortDirection; this.filterForm = this.fb.group({ SearchFilters: [''], }); } ngOnInit() { this.filterForm.controls.SearchFilters.valueChanges.subscribe(() => this.onSearchFiltersValueChanged()); } onSearchFiltersValueChanged() { this.searchFiltersValue = this.filterForm.controls.SearchFilters.value; } onSelectFilter(change) { this.distinctColumnValues.forEach((x) => { if (x.name === change.source.value) { x.checked = change.checked; } }); } onSortingValueChange(value) { if (this.selectedColumn.SortDirection === value) { this.selectedColumn.SortDirection = undefined; } else { this.selectedColumn.SortDirection = value; } } onApplyFiltersButton() { this.selectedColumn.FilterValues = []; this.selectedColumn.FilterValues = this.distinctColumnValues.filter((x) => x.checked === true).map((x) => x.name); const response = { action: DialogActionType.Ok, sortingHasChanged: this.initialSortingDirection !== this.selectedColumn.SortDirection, selectedColumn: this.selectedColumn, }; this.dialogRef.close(response); } sortColumns(columns) { if (columns.length === 0) { return []; } if (columns[0].name instanceof Date) { columns.sort((a, b) => (a.name > b.name ? 1 : -1)); } else if (Value.isNumber(columns[0].name)) { columns.sort((a, b) => a.name - b.name); } else if (Value.isString(columns[0].name)) { columns.sort((a, b) => a.name.localeCompare(b.name)); } return columns; } } FilterColumnsComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.2.12", ngImport: i0, type: FilterColumnsComponent, deps: [{ token: i1.MatDialogRef }, { token: i2.FormBuilder }, { token: MAT_DIALOG_DATA }], target: i0.ɵɵFactoryTarget.Component }); FilterColumnsComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.2.12", type: FilterColumnsComponent, selector: "filter-columns", ngImport: i0, template: "<div class=\"filter-columns\">\n <button mat-icon-button class=\"close-icon\" [mat-dialog-close]=\"cancelResponse\">\n <mat-icon>close</mat-icon>\n </button>\n\n <h3 mat-dialog-title i18n=\"@@filter-columns-label-filterRowsHeading\">Filter rows</h3>\n\n <mat-dialog-content>\n <form id=\"formFilterRoutesPlanning\" role=\"form\" [formGroup]=\"filterForm\">\n <mat-form-field>\n <mat-label i18n=\"@@formControl-label-filterGrid\">Search filters</mat-label>\n <input matInput type=\"text\" formControlName=\"SearchFilters\" id=\"FilterString\" />\n </mat-form-field>\n </form>\n\n <cdk-virtual-scroll-viewport itemSize=\"15\" class=\"filter-column-viewport\">\n <span *ngIf=\"distinctColumnValues.length === 0\" i18n=\"@@routeplanning-filter-column-no-values\">No available values</span>\n\n <div\n class=\"filter-column-item\"\n *cdkVirtualFor=\"let value of distinctColumnValues | filterCriteria: searchFiltersValue; let i = index\"\n >\n <mat-checkbox [value]=\"value.name\" [checked]=\"value.checked\" (change)=\"onSelectFilter($event)\">\n <span> {{ value.displayedName }}</span>\n </mat-checkbox>\n </div>\n </cdk-virtual-scroll-viewport>\n\n <mat-button-toggle-group name=\"sortDirection\" value=\"{{ selectedColumn.SortDirection }}\">\n <mat-button-toggle id=\"filter-columns-sort-ascending\" value=\"asc\" (change)=\"onSortingValueChange($event.value)\">\n <span i18n=\"@@filterColumns-btn-ascsort\">Ascending</span>\n <mat-icon>arrow_upward</mat-icon>\n </mat-button-toggle>\n <mat-button-toggle id=\"filter-columns-sort-descending\" value=\"desc\" (change)=\"onSortingValueChange($event.value)\">\n <span i18n=\"@@filterColumns-btn-descsort\">Descending</span>\n <mat-icon>arrow_downward</mat-icon>\n </mat-button-toggle>\n </mat-button-toggle-group>\n </mat-dialog-content>\n\n <mat-dialog-actions>\n <button mat-raised-button id=\"filter-columns-close\" [mat-dialog-close]=\"cancelResponse\">\n <span i18n=\"@@action-btn-cancel\">Cancel</span>\n </button>\n <button mat-raised-button id=\"filter-columns-filter\" (click)=\"onApplyFiltersButton()\" color=\"accent\">\n <span i18n=\"@@action-btn-filter\">Filter</span>\n </button>\n </mat-dialog-actions>\n</div>\n", styles: [".filter-columns{position:relative!important}.filter-columns .close-icon{position:absolute;top:-20px;right:-20px}.filter-columns mat-form-field{width:100%}.filter-columns .filter-column-viewport{height:13em;border:1px solid lightgray}.filter-columns .filter-column-item{height:25px;padding:.2em .5em}.filter-columns mat-button-toggle-group{margin-top:16px;width:100%}.filter-columns mat-button-toggle-group mat-button-toggle{flex:1 1 auto}.filter-columns mat-dialog-actions button{flex:1 1 auto}\n"], components: [{ type: i3.MatButton, selector: "button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]", inputs: ["disabled", "disableRipple", "color"], exportAs: ["matButton"] }, { type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { type: i5.MatFormField, selector: "mat-form-field", inputs: ["color", "floatLabel", "appearance", "hideRequiredMarker", "hintLabel"], exportAs: ["matFormField"] }, { type: i6.CdkVirtualScrollViewport, selector: "cdk-virtual-scroll-viewport", inputs: ["orientation", "appendOnly"], outputs: ["scrolledIndexChange"] }, { type: i7.MatCheckbox, selector: "mat-checkbox", inputs: ["disableRipple", "color", "tabIndex", "aria-label", "aria-labelledby", "id", "labelPosition", "name", "required", "checked", "disabled", "indeterminate", "aria-describedby", "value"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { type: i8.MatButtonToggle, selector: "mat-button-toggle", inputs: ["disableRipple", "aria-labelledby", "tabIndex", "appearance", "checked", "disabled", "id", "name", "aria-label", "value"], outputs: ["change"], exportAs: ["matButtonToggle"] }], directives: [{ type: i1.MatDialogClose, selector: "[mat-dialog-close], [matDialogClose]", inputs: ["type", "mat-dialog-close", "aria-label", "matDialogClose"], exportAs: ["matDialogClose"] }, { type: i1.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { type: i1.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { type: i2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { type: i2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { type: i2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { type: i5.MatLabel, selector: "mat-label" }, { type: i9.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["id", "disabled", "required", "type", "value", "readonly", "placeholder", "errorStateMatcher", "aria-describedby"], exportAs: ["matInput"] }, { type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { type: i2.FormControlName, selector: "[formControlName]", inputs: ["disabled", "formControlName", "ngModel"], outputs: ["ngModelChange"] }, { type: i6.CdkFixedSizeVirtualScroll, selector: "cdk-virtual-scroll-viewport[itemSize]", inputs: ["itemSize", "minBufferPx", "maxBufferPx"] }, { type: i11.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i6.CdkVirtualForOf, selector: "[cdkVirtualFor][cdkVirtualForOf]", inputs: ["cdkVirtualForOf", "cdkVirtualForTrackBy", "cdkVirtualForTemplate", "cdkVirtualForTemplateCacheSize"] }, { type: i8.MatButtonToggleGroup, selector: "mat-button-toggle-group", inputs: ["appearance", "name", "vertical", "value", "multiple", "disabled"], outputs: ["valueChange", "change"], exportAs: ["matButtonToggleGroup"] }, { type: i1.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]" }], pipes: { "filterCriteria": FilterColumnValuesPipe } }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.12", ngImport: i0, type: FilterColumnsComponent, decorators: [{ type: Component, args: [{ selector: 'filter-columns', templateUrl: './filter-columns.component.html', styleUrls: ['./filter-columns.component.scss'], }] }], ctorParameters: function () { return [{ type: i1.MatDialogRef }, { type: i2.FormBuilder }, { type: undefined, decorators: [{ type: Inject, args: [MAT_DIALOG_DATA] }] }]; } }); class ColumnHelper { static getContent(field, element) { if (field.indexOf('.') === -1) { return element[field]; } // Activate the way to get text from Class.Element.XX.XX.XX const fieldNames = field.split('.'); let returnValue = element[fieldNames[0]]; for (let index = 1; index < fieldNames.length; index++) { returnValue = returnValue[fieldNames[index]]; } return returnValue !== null && returnValue !== void 0 ? returnValue : ''; } static getToolTip(column, element) { if (!element || !column || !element[column.Field]) { return ''; } let val; switch (column.ColumnType) { case ColumnType.Date: case ColumnType.DateTime: case ColumnType.Time: case ColumnType.Icon: case ColumnType.DropDown: case ColumnType.String: case ColumnType.Link: case ColumnType.Image: val = element[column.Field]; break; default: val = ''; break; } return val; } static isImmutableColumn(column) { // Columns types that cannot be hidden, moved or filtered return column.ColumnType === ColumnType.Actions || column.ColumnType === ColumnType.Icon; } static canColumnBeHidden(column) { return !this.isImmutableColumn(column); } static canColumnBeMoved(column) { return !this.isImmutableColumn(column); } static canColumnBeFiltered(column) { return !this.isImmutableColumn(column); } static isFilteringEnabledOnColumn(column) { if (!this.canColumnBeFiltered(column)) { return false; } if (!column.Title) { return false; } return true; } static hasFiltersOrSortingEnabled(column) { if (!column) { return false; } if (column.SortDirection) { return true; } if (column.FilterValues && column.FilterValues.length > 0) { return true; } return false; } static isCellClickable(column) { switch (column.ColumnType) { case ColumnType.Actions: case ColumnType.Icon: case ColumnType.DropDown: case ColumnType.NumberInput: case ColumnType.DropDownDynamic: return false; default: return true; } } } class TableBuilderHelper { static buildTable(dataArray, columns, columnNames) { let out = '<table><thead><tr>'; for (const h of columnNames) { out += '<th>' + h + '</th>'; } out += '</tr></thead><tbody>'; for (const data of dataArray) { out += '<tr>'; for (const j of columns) { if (j !== 'select' && j !== 'actions') { out += '<td>' + (ColumnHelper.getContent(j, data) || '-') + '</td>'; } } out += '</tr>'; } out += '</tbody></table>'; return out; } static printPageBuilderDefault(table, printedOnLabel = 'Printed On') { return ('<html><head>' + '<style type="text/css" media="print">' + ' @page { size: auto; margin: 25px 0 25px 0; }' + '</style>' + '<style type="text/css" media="all">' + 'table{border-collapse: collapse; font-size: 12px; }\n' + 'table, th, td {border: 1px solid grey}\n' + 'th, td {text-align: center; vertical-align: middle;}\n' + 'p {font-weight: bold; margin-left:20px }\n' + 'table { width:94%; margin-left:3%; margin-right:3%}\n' + 'div.bs-table-print { text-align:center;}\n' + '</style></head><title>Print Table</title><body>' + '<p>' + printedOnLabel + ': ' + new Date() + ' </p>' + '<div class="bs-table-print">' + table + '</div></body></html>'); } } /** * Comunication with the localStorage */ class LocalStorageService { set(key, value) { localStorage.setItem(key, value); } get(key) { return localStorage.getItem(key); } remove(key) { localStorage.removeItem(key); } setAsJson(localStorageKey, state, replacer) { localStorage.setItem(localStorageKey, JSON.stringify(state, replacer)); } getAsJson(localStorageKey) { let storedValue = localStorage.getItem(localStorageKey); return storedValue ? JSON.parse(storedValue) : null; } exists(localStorageKey) { if (localStorage.getItem(localStorageKey)) { return true; } else { return false; } } } LocalStorageService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.2.12", ngImport: i0, type: LocalStorageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); LocalStorageService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "12.2.12", ngImport: i0, type: LocalStorageService, providedIn: 'root' }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.2.12", ngImport: i0, type: LocalStorageService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }] }); class AdvancedMaterialTableComponent { constructor(dialog, localStorageService, cdref) { this.dialog = dialog; this.localStorageService = localStorageService; this.cdref = cdref; this.actionSelected = new EventEmitter(); this.iconClicked = new EventEmitter(); this.rowSelected = new EventEmitter(); this.numberChange = new EventEmitter(); this.hyperLinkClicked = new EventEmitter(); this.displayedColumns = []; this.noRowsDisplayed = false; this.hasHiddenColumns = false; this.columnType = ColumnType; this.selection = new SelectionModel(true, []); this.tableColumnList = []; this.mainFilter = ''; this.getContent = (column, element) => ColumnHelper.getContent(column.Field, element); this.getToolTip = (row, column) => ColumnHelper.getToolTip(column, row); //#endregion //#region Checks (Header, Cell, Column or Row) this.isImmutableColumn = ColumnHelper.isImmutableColumn; this.canColumnBeHidden = ColumnHelper.canColumnBeHidden; this.canColumnBeMoved = ColumnHelper.canColumnBeMoved; this.canColumnBeFiltered = ColumnHelper.canColumnBeFiltered; this.isFilteringEnabledOnColumn = ColumnHelper.isFilteringEnabledOnColumn; this.hasFiltersOrSortingEnabled = ColumnHelper.hasFiltersOrSortingEnabled; this.isCellClickable = ColumnHelper.isCellClickable; } set tableColumns(value) { this.tableColumnList = _.cloneDeep(value); } get tableColumns() { return this.tableColumnList; } set data(value) { this.dataset = value; this.initializeTable(); } get data() { return this.dataset; } set selectedData(initialSelection) { this.selection = new SelectionModel(true, initialSelection); } get selectedData() { return this.selection.selected; } ngOnInit() { this.loadFromStorage(); this.renderColumns(); this.localizePaginator(); } ngAfterViewInit() { this.sortColumns(); this.sort.sortChange.subscribe((col) => { if (!col.active) { return; } this.tableColumnList.forEach((column) => { if (column.Field !== col.active) { column.SortDirection = undefined; } else { column.SortDirection = col.direction; } }); }); this.initializeTable(); } initializeTable() { if (this.data) { this.noRowsDisplayed = this.data.length === 0; } this.dataSource = new MatTableDataSource(this.data); this.dataSource.paginator = this.paginator; this.dataSource.sortingDataAccessor = (item, property) => { const content = ColumnHelper.getContent(property, item); if (!content) { return ''; } return content.toString().toLowerCase(); }; this.dataSource.sort = this.sort; this.dataSource.filterPredicate = this.getFilterPredicate(); this.applyFilters(); } renderColumns() { this.displayedColumns = this.tableColumns.filter((column) => column.Display === true).map((column) => column.Field); if (this.tableConfiguration.AllowSelect) { // Add the 'select' column at the start this.displayedColumns.unshift('select'); } } onRowChecked(row) { if (this.tableConfiguration.MultipleSelect) { this.multipleRowSelection(row); } else { this.singleRowSelection(row); } this.rowSelected.emit([false, this.selection.selected]); } onDoubleClick(row) { this.rowSelected.emit([true, [row]]); } masterToggle() { this.isAllSelected() ? this.selection.clear() : this.dataSource.data.forEach((row) => this.selection.select(row)); this.rowSelected.emit([false, this.selection.selected]); } isAllSelected() { const numSelected = this.selection.selected.length; const numRows = this.dataSource.data.length; return numSelected === numRows; } getFilterPredicate() { return (row, filters) => { const filterData = JSON.parse(filters); for (const filter of filterData) { if (filter.values.length === 0) { continue; } let value = _.get(row, filter.key); if (!value || value === '') { return false; } let index = -1; if (filter.type === ColumnType.DateTime || filter.type === ColumnType.Date) { const dates = filter.values.map((x) => new Date(x)); index = dates.findIndex((x) => x.getTime() === value.getTime()); } else if (Value.isArray(value)) { value = _.join(value, ','); index = filter.values.findIndex((x) => _.isEqual(x, value)); } else { index = filter.values.findIndex((x) => _.isEqual(x, value)); } if (index === -1) { return false; } } if (this.mainFilter && this.mainFilter.length > 0) { let match = false; filterData.forEach((filter) => { const value = _.get(row, filter.key); const stringValue = value.toLowerCase(); match = match || stringValue.indexOf(this.mainFilter) !== -1; }); return match; } return true; }; } onColumnChange(index, event) { if (this.tableColumns[index].Display && this.tableColumns.filter((c) => c.Display).length <= 1) { event.preventDefault(); return; } this.tableColumns[index].Display = !this.tableColumns[index].Display; this.renderColumns(); this.saveColumnConfig(); } clearAllFilters() { this.tableColumnList.forEach((column) => { column.SortDirection = undefined; column.FilterValues = undefined; }); // Clear sort, see https://github.com/angular/components/issues/10524 this.clearSort(); this.dataSource.filter = '[]'; } /** * Return from the Action Buttons * @param action Value From the Action Buttons */ onActionSelected(action) { this.actionSelected.emit(action); } getMinValueForNumberInput(element, column) { if (!column || !column.NumberInputOptions) { return; } if (column.NumberInputOptions.MinInputNumberField) { return element[column.NumberInputOptions.MinInputNumberField]; } return column.NumberInputOptions.MinInputNumber; } getMaxValueForNumberInput(element, column) { if (!column.NumberInputOptions) { return; } if (column.NumberInputOptions.MaxInputNumberField) { return element[column.NumberInputOptions.MaxInputNumberField]; } return column.NumberInputOptions.MaxInputNumber; } iconClick(element, column) { this.iconClicked.emit([element, column]); } numberInputChange(element, column, event) { this.numberChange.emit([element, column, event.target.value]); } onHyperLinkClicked(element, column) { this.hyperLinkClicked.emit([element, column]); } multipleRowSelection(row) { this.selection.toggle(row); } singleRowSelection(row) { if (this.selection.isSelected(row)) { this.selection.clear(); } else { this.selection.clear(); this.selection.toggle(row); } } getDistinctValues(selectedColumn) { let result = []; this.data.forEach((row) => { let value = _.get(row, selectedColumn.Field); let displayedValue = value; if (Value.isArray(value)) { value = _.join(value, ','); displayedValue = value; } if (value === undefined || value === null || value === '') { return; } const isAlreadyChecked = selectedColumn.FilterValues ? selectedColumn.FilterValues.findIndex((x) => x === value) >= 0 : false; result.push({ name: value, displayedName: displayedValue, checked: isAlreadyChecked, }); }); result = _.uniqBy(result, (x) => x.displayedName); return result; } //#region Drag and Drop headerDragStarted(index) { // Purposedly in blank } headerDropListDropped(event) { if (!event) { return; } const displayedColumns = this.displayedColumns.filter((x) => x != 'select'); const previousColumnIndex = this.tableColumns.findIndex((x) => x.Field === displayedColumns[event.previousIndex]); const currentColumnIndex = this.tableColumns.findIndex((x) => x.Field === displayedColumns[event.currentIndex]); if (this.canColumnBeMoved(this.tableColumns[currentColumnIndex])) { this.moveItemInArray(this.tableColumns, previousColumnIndex, currentColumnIndex); this.renderColumns(); this.saveColumnConfig(); } } //#endregion //#region Sorting and Filtering applyMainFilter(event) { this.mainFilter = event.value.trim().toLowerCase(); this.applyFilters(); } applyFilters() { const filters = []; this.tableColumnList.forEach((column) => { if (!column.FilterValues) { column.FilterValues = []; } filters.push({ key: column.Field, type: column.ColumnType, values: column.FilterValues, }); }); if (filters.length > 0) { this.dataSource.filter = JSON.stringify(filters); } } sortColumn(id, start) { const currentColumn = this.sort.active; const currentDirection = this.sort.direction; if (id !== currentColumn || start !== currentDirection) { this.sort.sort({ id: '', start, disableClear: false }); this.sort.sort({ id, start, disableClear: false }); } } clearSort() { // Clear sort, see https://github.com/angular/components/issues/10524 let sortable = { id: null, start: null, disableClear: false }; this.sort.sort(sortable); } sortColumns() { const id = this.tableColumns.findIndex((column) => column.SortDirection); if (id === -1) { return; } const columnName = this.tableColumns[id].Field; const direction = this.tableColumns[id].SortDirection; this.clearSort(); if (direction) { this.sortColumn(columnName, direction); } // HACK(Fernando Abel): https://github.com/angular/components/issues/10242 const activeSortHeader = this.sort.sortables.get(columnName); if (activeSortHeader) { const viewState = activeSortHeader._isSorted() ? { fromState: direction, toState: 'active' } : { fromState: 'active', toState: direction }; activeSortHeader._setAnimationTransitionState(viewState); } this.cdref.detectChanges(); } //#endregion //#region Dialogs openFilterDialog(selectedColumn) { const data = { selectedColumn: _.cloneDeep(selectedColumn), distinctData: this.getDistinctValues(selectedColumn), }; const columnFilteringDialog = this.dialog.open(FilterColumnsComponent, { disableClose: false, autoFocus: false, width: '350px', panelClass: 'overlay-panel', data, }); columnFilteringDialog .afterClosed() .pipe(take(1)) .subscribe((response) => { if (response && response.action === 'Ok') { this.filterByColumn(response); this.sortByTable(response); } }); } //#endregion //#region After FilterColumns response filterByColumn(response) { if (!response || !response.selectedColumn) { return; } const column = this.tableColumnList.find((x) => x.Field === response.selectedColumn.Field); if (column) { column.FilterValues = response.selectedColumn.FilterValues; } this.applyFilters(); } sortByTable(response) { if (response.sortingHasChanged === false) { return; } const columnName = response.selectedColumn.Field; const direction = response.selectedColumn.SortDirection; this.tableColumnList.forEach((column) => { column.SortDirection = column.Field !== columnName ? undefined : direction; }); this.sortColumns(); } //#endregion //#region Print and Export sendToPrinter() { const selectedData = this.getDataToExportPrint(); const colNames = this.getDisplayedColumnNames(); const table = TableBuilderHelper.buildTable(selectedData, this.displayedColumns, colNames); if (table) { const newWin = window.open('#'); if (!newWin) return; newWin.document.write(TableBuilderHelper.printPageBuilderDefault(table)); newWin.print(); newWin.close(); } } // TODO: Create a service for that, currently being used in advanced table exportToExcel() { // const tableTitle = TableTags.Table; // const selectedData = this.getSelectedDataWithDisplayedColumnsOnly(); // const colNames = this.getDisplayedColumnNames(); // // generate a worksheet // const ws = xlsx.utils.aoa_to_sheet([colNames]); // xlsx.utils.sheet_add_json(ws, selectedData, { // header: this.displayedColumns.slice(1), // remove the 'select' column // skipHeader: true, // origin: 1, // }); // // add to workbook // const wb = xlsx.utils.book_new(); // xlsx.utils.book_append_sheet(wb, ws, tableTitle); // // write workbook and force a download // xlsx.writeFile(wb, `${tableTitle}.xls`, { // type: 'array', // bookType: 'xls', // }); } getDataToExportPrint() { if (!this.selection.isEmpty()) { return this.selection.selected; } if (!this.dataSource.sort) { return this.selection.selected; } return this.dataSource.sortData(this.dataSource.filteredData, this.dataSource.sort); } getSelectedDataWithDisplayedColumnsOnly() { const selectedData = this.getDataToExportPrint(); return _.map(selectedData, (obj) => { return _.pick(obj, this.displayedColumns); }); } getDisplayedColumnNames() { return this.tableColumns.filter((column) => column.Display === true).map((column) => column.Title); } //#endregion //#region LocalStorage loadFromStorage() { if (!this.tableConfiguration || !this.tableConfiguration.LocalStorageKey) { return; } const localStorageColumns = this.localStorageService.getAsJson(this.tableConfiguration.LocalStorageKey); if (!localStorageColumns) { return; } const tableColumnList = _.cloneDeep(this.tableColumns); for (const obj of tableColumnList) { const index = localStorageColumns.findIndex((i) => i.Field === obj.Field); if (index !== -1) { const previousIndex = this.tableColumns.findIndex((i) => i.Field === obj.Field); // update the displayed property this.tableColumns[previousIndex].Display = localStorageColumns[index].Display; // rearange columns this.moveItemInArray(this.tableColumns, previousIndex, index); } } } saveColumnConfig() { if (!this.tableConfiguration || !this.tableConfiguration.LocalStorageKey) { return; } this.localStorageService.setAsJson(this.tableConfiguration.LocalStorageKey, this.tableColumns); } moveItemInArray(array, previousIndex, index) { const temp = array[previousIndex]; array[previousIndex] = array[index]; array[index] = temp; } //#endregion //#region Paginator localizePaginator() { if (!this.tableConfiguration.AllowPagination) { return; } if (this.paginator === undefined) { return; } this.paginator._intl.firstPageLabel = 'First Page'; this.paginator._intl.previousPageLabel = 'Previous Page'; this.paginator._intl.nextPageLabel = 'Next Page'; this.paginator._intl.lastPageLabel = 'Last Page'; this.paginator._intl.itemsPerPageLabel = 'Items per Page'; this.paginator._intl.getRangeLabel = (page, pageSize, length) => { if (length === 0 || pageSize === 0) { return `0 of ${length}`; } length = Math.max(length, 0); const startIndex = page * pageSize; // If the start index exceeds the list length, do not try and fix the end index to the end. const endIndex = startIndex < length ? Math.min(startIndex + pageSize, length) : startIndex + pageSize; return `${startIndex + 1} - ${endIndex} of ${length}`; }; } //#endregion //#region ClassNames getRowClassName() { const hasImageColumn = this.tableColumns.find((c) => c.ColumnType === ColumnType.Image); if (hasImageColumn) { return 'row-with-image'; } return ''; } getColumnClassName(column) { switch (column.ColumnType) { case ColumnType.Actions: return 'actions'; case ColumnType.Icon: return 'icons'; case ColumnType.Image: return 'images'; } return ''; } } AdvancedMaterialTableComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.2.12", ngImport: i0, type: AdvancedMaterialTableComponent, deps: [{ token: i1.MatDialog }, { token: LocalStorageService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); AdvancedMaterialTableComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.2.12", type: AdvancedMaterialTableComponent, selector: "ngx-advanced-material-table", inputs: { tableConfiguration: "tableConfiguration", actionConfiguration: "actionConfiguration", tableColumns: "tableColumns", data: "data", selectedData: "selectedData" }, outputs: { actionSelected: "actionSelected", iconClicked: "iconClicked", rowSelected: "rowSelected", numberChange: "numberChange", hyperLinkClicked: "hyperLinkClicked" }, viewQueries: [{ propertyName: "paginator", first: true, predicate: MatPaginator, descendants: true }, { propertyName: "sort", first: true, predicate: MatSort, descendants: true }], ngImport: i0, template: "<div>\n <div class=\"table-top-panel\" *ngIf=\"tableConfiguration.AllowFilter || tableConfiguration.AllowActions\">\n <div class=\"table-filter\">\n <ng-container *ngIf=\"tableConfiguration.AllowFilter\">\n <mat-form-field>\n <input\n matInput\n id=\"{{ tableConfiguration.Id }}-table-filter-field\"\n (keyup)=\"applyMainFilter($event.target)\"\n i18n-placeholder=\"@@placeholder-text-filterResult\"\n placeholder=\"Filter on results...\"\n />\n </mat-form-field>\n </ng-container>\n </div>\n\n <div class=\"table-actions\" *ngIf=\"tableConfiguration.AllowActions\">\n <button\n mat-icon-button\n id=\"{{ tableConfiguration.Id }}-table-clearAllFilters\"\n onclick=\"this.blur()\"\n (click)=\"clearAllFilters()\"\n [matTooltip]=\"templateClearAllFilters.innerText\"\n >\n <mat-icon>filter_list_off</mat-icon>\n </button>\n\n <button\n mat-icon-button\n id=\"{{ tableConfiguration.Id }}-table-showColumns\"\n [matMenuTriggerFor]=\"visibleColumnsMenu\"\n [matTooltip]=\"templateShowHide.innerText\"\n >\n <mat-icon>view_column</mat-icon>\n </button>\n\n <mat-menu #visibleColumnsMenu=\"matMenu\">\n <ng-template matMenuContent>\n <div id=\"{{ tableConfiguration.Id }}-table-columns-checkbox\">\n <div\n mat-menu-item\n *ngFor=\"let column of tableColumns; let i = index\"\n (click)=\"onColumnChange(i, $event); $event.stopPropagation()\"\n >\n <mat-icon *ngIf=\"column.Display\" color=\"accent\">check_box</mat-icon>\n <mat-icon *ngIf=\"!column.Display\">check_box_outline_blank</mat-icon>\n <span>{{ column.Title }}</span>\n </div>\n </div>\n </ng-template>\n </mat-menu>\n\n <button\n mat-icon-button\n id=\"{{ tableConfiguration.Id }}-table-export-to-excel-button\"\n [matTooltip]=\"templateExportCsv.innerText\"\n (click)=\"exportToExcel()\"\n [disabled]=\"noRowsDisplayed\"\n >\n <mat-icon>file_download</mat-icon>\n </button>\n\n <button\n mat-icon-button\n id=\"{{ tableConfiguration.Id }}-table-print-button\"\n (click)=\"sendToPrinter()\"\n [matTooltip]=\"templatePrint.innerText\"\n [disabled]=\"noRowsDisplayed\"\n >\n <mat-icon>print</mat-icon>\n </button>\n\n <template #templateShowHide i18n=\"@@table-tooltip-grid-showColumns\">Select visible columns</template>\n <template #templateExportCsv i18n=\"@@table-tooltip-export-csv\">Export to Excel</template>\n <template #templateClearAllFilters i18n=\"@@table-tooltip-clear-all-filters\">Clear filters and sorting</template>\n <template #templatePrint i18n=\"@@action-btn-print\">Print</template>\n </div>\n </div>\n\n <div class=\"table-pagination\" *ngIf=\"tableConfiguration.AllowPagination\">\n <mat-paginator\n [pageSizeOptions]=\"[10, 25, 50, 100]\"\n id=\"{{ tableConfiguration.Id }}-table-paginator\"\n showFirstLastButtons\n ></mat-paginator>\n </div>\n\n <mat-table\n id=\"{{ tableConfiguration.Id }}-table\"\n [dataSource]=\"dataSource\"\n matSort\n matSortDisableClear=\"false\"\n cdkDropListGroup\n cdkDropList\n cdkDropListLockAxis=\"x\"\n cdkDropListOrientation=\"horizontal\"\n (cdkDropListDropped)=\"headerDropListDropped($event)\"\n >\n <!-- Select Check Box Column -->\n <ng-container matColumnDef=\"select\">\n <mat-header-cell *matHeaderCellDef>\n <mat-checkbox\n *ngIf=\"tableConfiguration.MultipleSelect\"\n (change)=\"$event ? masterToggle() : null\"\n [checked]=\"selection.hasValue() && isAllSelected()\"\n [indeterminate]=\"selection.hasValue() && !isAllSelected()\"\n >\n </mat-checkbox>\n </mat-header-cell>\n <mat-cell *matCellDef=\"let row\">\n <mat-checkbox\n (click)=\"$event.stopPropagation()\"\n (change)=\"$event ? onRowChecked(row) : null\"\n [checked]=\"selection.isSelected(row)\"\n >\n </mat-checkbox>\n </mat-cell>\n </ng-container>\n\n <ng-container *ngFor=\"let column of tableColumns; let i = index\" matColumnDef=\"{{ column.Field }}\">\n <mat-header-cell\n *matHeaderCellDef\n cdkDrag\n (cdkDragStarted)=\"headerDragStarted(i)\"\n [cdkDragData]=\"{ name: column.Field }\"\n [cdkDragDisabled]=\"!canColumnBeMoved(column)\"\n [ngClass]=\"getColumnClassName(column)\"\n [matTooltip]=\"column.Title\"\n >\n <ng-container *ngIf=\"isFilteringEnabledOnColumn(column)\">\n <span mat-sort-header [class.selected]=\"hasFiltersOrSortingEnabled(column)\">{{ column.Title }}</span>\n <button\n mat-icon-button\n disableRipple\n onclick=\"this.blur()\"\n (click)=\"openFilterDialog(column)\"\n [disabled]=\"noRowsDisplayed\"\n [class.selected]=\"hasFiltersOrSortingEnabled(column)\"\n >\n <mat-icon>filter_list</mat-icon>\n </button>\n </ng-container>\n </mat-header-cell>\n <mat-cell\n *matCellDef=\"let element; let rowIndex = index\"\n [ngClass]=\"getColumnClassName(column)\"\n (click)=\"isCellClickable(column) ? onRowChecked(element) : null\"\n >\n <div [matTooltip]=\"getToolTip(element, column)\">\n <ng-container [ngSwitch]=\"column.ColumnType\">\n <!-- DateTime -->\n <span *ngSwitchCase=\"columnType.DateTime\"> {{ getContent(column, element) }}</span>\n <!-- Date -->\n <span *ngSwitchCase=\"columnType.Date\"> {{ getContent(column, element) }}</span>\n <!-- Time -->\n <span *ngSwitchCase=\"columnType.Time\"> {{ getContent(column, element) }}</span>\n <!-- String -->\n <span *ngSwitchCase=\"columnType.String\"> {{ getContent(column, element) }}</span>\n <!-- Link -->\n <span *ngSwitchCas