UNPKG

dynamic-mat-table

Version:

dynamic-mat-table is an Angular component for presenting large and complex data with a lightning fast performance (at least 10x faster) and excellent level of control over the presentation.

1,157 lines (1,146 loc) 163 kB
import * as i0 from '@angular/core'; import { Injectable, EventEmitter, Directive, ChangeDetectorRef, ViewChild, Input, HostBinding, Output, Component, ChangeDetectionStrategy, ContentChildren, Inject, Injector, Renderer2, Compiler, ComponentFactoryResolver, ViewContainerRef, NgModule, HostListener, forwardRef, NgZone, ContentChild, ElementRef, TemplateRef, COMPILER_OPTIONS, CompilerFactory } from '@angular/core'; import { Observable, merge, of, combineLatest, Subscription, ReplaySubject, Subject, BehaviorSubject } from 'rxjs'; import { map, delay, filter, distinctUntilChanged, tap, takeWhile, takeUntil, switchMap } from 'rxjs/operators'; import { MatTableDataSource, MatTable, MatTableModule } from '@angular/material/table'; import { CdkVirtualScrollViewport, VIRTUAL_SCROLL_STRATEGY, ScrollingModule } from '@angular/cdk/scrolling'; import { moveItemInArray, DragDropModule } from '@angular/cdk/drag-drop'; import { SelectionModel } from '@angular/cdk/collections'; import { MatSort, MatSortModule } from '@angular/material/sort'; import { MatPaginator, PageEvent, MatPaginatorIntl, MatPaginatorModule } from '@angular/material/paginator'; import { trigger, transition, query, style, stagger, animate, state } from '@angular/animations'; import { MatMenuTrigger, MatMenuModule } from '@angular/material/menu'; import { MatDialog, MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog'; import { Overlay, OverlayContainer, OverlayPositionBuilder, OverlayModule } from '@angular/cdk/overlay'; import { ComponentPortal } from '@angular/cdk/portal'; import { CommonModule } from '@angular/common'; import { MatIconModule } from '@angular/material/icon'; import { MatInputModule } from '@angular/material/input'; import { MatButtonModule } from '@angular/material/button'; import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatProgressBarModule } from '@angular/material/progress-bar'; import { MatDividerModule } from '@angular/material/divider'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { MatRadioModule } from '@angular/material/radio'; import { MatSelectModule } from '@angular/material/select'; import { JitCompilerFactory } from '@angular/platform-browser-dynamic'; import { MatTooltipModule } from '@angular/material/tooltip'; import { MatRippleModule } from '@angular/material/core'; // ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| // |||||||||||||||||||||||||||||||||||||| Utils |||||||||||||||||||||||||||||||||||||||||||||||||| /** * check object is null or undefined */ function isNullorUndefined(value) { if (value === null || value === undefined) { return true; } else { return false; } } /** * clone object but reference variable not change */ function clone(obj) { if (obj === null || obj === undefined) { return obj; } else if (Array.isArray(obj)) { const array = []; obj.forEach(item => array.push(Object.assign({}, item))); return array; } else { return Object.assign({}, obj); } } /** * clone object and all reference variable but may be there is a circle loop. */ function deepClone(obj) { if (obj === null || obj === undefined) { return obj; } else if (Array.isArray(obj)) { const array = []; obj.forEach(item => array.push(deepClone(item))); return array; } else { const c = Object.assign({}, obj); const fields = Object.getOwnPropertyNames(obj); fields.forEach(f => { const field = obj[f]; if (field !== null && typeof field === 'object') { c[f] = deepClone(field); } }); return c; } } function getObjectProp(fieldName, defaultValue, ...variable) { for (const v in variable) { if (variable[v] && !isNullorUndefined(variable[v][fieldName])) { return variable[v][fieldName]; } } return defaultValue; } function copy(from, to, forced = false, nullSkip = true, undefinedSkip = true) { if (from === null || from === undefined) { return; } if (to === null || to === undefined) { to = {}; } const f = Object.keys(from); const t = Object.keys(to); f.forEach(fi => { if (forced === true || t.includes(fi) === true) { if (!(from[fi] === null && nullSkip === true) && !(from[fi] === undefined && undefinedSkip === true)) { to[fi] = from[fi]; } } }); } /** * Simplifies a string (trims and lowerCases) */ function simplify(s) { return `${s}`.trim().toLowerCase(); } /** * Transforms a camelCase string into a readable text format * @example textify('helloWorld!') * // Hello world! */ function textify(text) { return text .replace(/([A-Z])/g, char => ` ${char.toLowerCase()}`) .replace(/^([a-z])/, char => char.toUpperCase()); } /** * Transforms a text string into a title case text format * @example titleCase('hello world!') * // Hello World! */ function titleCase(value) { const sentence = value.toLowerCase().split(' '); for (let i = 0; i < sentence.length; i++) { sentence[i] = sentence[i][0].toUpperCase() + sentence[i].slice(1); } return sentence.join(' '); } class TableVirtualScrollDataSource extends MatTableDataSource { constructor() { super(...arguments); this.filterMap = {}; this.columns = []; } get allData() { return this.data; } toTranslate() { const tranList = []; const keys = Object.keys(this.filterMap); for (const k of keys) { let fieldTotalTran = ''; for (const f of this.filterMap[k]) { fieldTotalTran += f.toPrint(); } if (fieldTotalTran !== '') { tranList.push({ key: titleCase(k), value: fieldTotalTran }); } } return tranList; } getFilter(fieldName) { return this.filterMap[fieldName]; } setFilter(fieldName, filters) { this.filterMap[fieldName] = filters; return new Observable(subscriber => { setTimeout(() => { this.refreshFilterPredicate(); subscriber.next(); subscriber.complete(); }, 200); // for show progress }); } clearFilter(fieldName = null) { if (fieldName != null) { delete this.filterMap[fieldName]; } else { this.filterMap = {}; } this.refreshFilterPredicate(); } clearData() { this.data = []; } refreshFilterPredicate() { let conditionsString = ''; Object.keys(this.filterMap).forEach(key => { let fieldCondition = ''; this.filterMap[key].forEach((fieldFilter, row, array) => { if (row < array.length - 1) { fieldCondition += fieldFilter.toString(key) + (fieldFilter.type === 'and' ? ' && ' : ' || '); } else { fieldCondition += fieldFilter.toString(key); } }); if (fieldCondition !== '') { conditionsString += ` ${conditionsString === '' ? '' : ' && '} ( ${fieldCondition} )`; } }); if (conditionsString !== '') { const filterFunction = new Function('_a$', 'return ' + conditionsString); this.filterPredicate = (data, filter) => filterFunction(data); } else { this.filterPredicate = (data, filter) => true; } this.filter = conditionsString; } // When client paging active use for retrieve paging data pagingData(data) { const p = this._paginator; if (p && p !== null) { const end = (p.pageIndex + 1) * p.pageSize; const start = p.pageIndex * p.pageSize; return data.slice(start, end); } return data; } _updateChangeSubscription() { var _a; this.initStreams(); const sort = this._sort; const paginator = this._paginator; const internalPageChanges = this._internalPageChanges; const filter = this._filter; const renderData = this._renderData; const dataStream = this._data; const sortChange = sort ? merge(sort.sortChange, sort.initialized) : of(null); const pageChange = paginator ? merge(paginator.page, internalPageChanges, paginator.initialized) : of(null); // First Filter const filteredData = combineLatest([dataStream, filter]).pipe(map(([data]) => this._filterData(data))); // Second Order const orderedData = combineLatest([filteredData, sortChange]).pipe(map(([data, sortColumn]) => { const sc = sortColumn; if (!sc) { return data; } else if (sc.active !== '') { const column = this.columns.filter(c => c.name == sc.active)[0]; if (column.sort === 'server-side') { return data; } else if (column.sort === 'client-side') { return this._orderData(data); } } })); // Last Paging const paginatedData = combineLatest([orderedData, pageChange]).pipe(map(([data]) => this.pagingData(data))); (_a = this._renderChangesSubscription) === null || _a === void 0 ? void 0 : _a.unsubscribe(); this._renderChangesSubscription = new Subscription(); this._renderChangesSubscription.add(paginatedData.subscribe(data => this.dataToRender$.next(data))); this._renderChangesSubscription.add(this.dataOfRange$.subscribe(data => renderData.next(data))); } initStreams() { if (!this.streamsReady) { this.dataToRender$ = new ReplaySubject(1); this.dataOfRange$ = new ReplaySubject(1); this.streamsReady = true; } } } class TableService { constructor() { } /************************************* Local Export *****************************************/ static getFormattedTime() { const today = new Date(); const y = today.getFullYear(); const m = today.getMonth() + 1; const d = today.getDate(); const h = today.getHours(); const mi = today.getMinutes(); const s = today.getSeconds(); return y + "-" + m + "-" + d + "-" + h + "-" + mi + "-" + s; } // private downloadBlob(blob: any, filename: string) { // if (navigator.msSaveBlob) { // IE 10+ // navigator.msSaveBlob(blob, filename); // } else { // const link = document.createElement('a'); // if (link.download !== undefined) { // // Browsers that support HTML5 download attribute // const link = window.document.createElement('a'); // const date = new Date(); // link.className = 'download' + date.getUTCFullYear() + date.getUTCMonth() + date.getUTCSeconds(); // link.setAttribute('href', blob); // link.setAttribute('download', filename); // link.style.visibility = 'hidden'; // link.click(); // // setTimeout(() => { // // const g = document.body.getElementsByClassName(link.className); // // document.body.removeChild(link); // // }); // } // } // } downloadBlob(blob, filename) { if (navigator.msSaveBlob) { // IE 10+ navigator.msSaveBlob(blob, filename); } else { const link = document.createElement("a"); if (link.download !== undefined) { // Browsers that support HTML5 download attribute const url = URL.createObjectURL(blob); link.setAttribute("href", url); link.setAttribute("download", filename); link.style.visibility = "hidden"; document.body.appendChild(link); link.click(); document.body.removeChild(link); } } } exportToCsv(columns, rows, selectionModel, filename = "") { filename = filename === "" ? this.tableName + TableService.getFormattedTime() + ".csv" : filename; if (!rows || !rows.length) { return; } const fields = columns.filter((c) => c.exportable !== false && c.display !== 'hidden'); const separator = ","; const CR_LF = "\n"; //'\u0D0A'; const keys = fields.map(f => f.name); const headers = fields.map(f => f.header); const csvContent = headers.join(separator) + CR_LF + rows .map((row) => { return fields.map((f) => { let cell = f.toExport(row, "csv") || ""; cell = cell instanceof Date ? cell.toLocaleString() : cell.toString().replace(/"/g, '""'); if (cell.search(/("|,|\n)/g) >= 0) { cell = `"${cell}"`; } return cell; }).join(separator); }).join(CR_LF); const blob = new Blob([ new Uint8Array([0xEF, 0xBB, 0xBF]), csvContent ], { type: 'text/csv;charset=utf-8' }); this.downloadBlob(blob, filename); } exportToJson(rows, filename = "") { filename = filename === "" ? this.tableName + TableService.getFormattedTime() + ".json" : filename; const blob = new Blob([JSON.stringify(rows)], { type: "text/csv;charset=utf-8;", }); this.downloadBlob(blob, filename); } /************************************* Save Setting into storage *****************************************/ loadSavedColumnInfo(columnInfo, saveName) { // Only load if a save name is passed in if (saveName) { if (!localStorage) { return; } const loadedInfo = localStorage.getItem(`${saveName}-columns`); if (loadedInfo) { return JSON.parse(loadedInfo); } this.saveColumnInfo(columnInfo); return columnInfo; } } saveColumnInfo(columnInfo, saveName = this.tableName) { if (saveName) { if (!localStorage) { return; } localStorage.setItem(`${saveName}-columns`, JSON.stringify(columnInfo)); } } } /** @nocollapse */ TableService.ɵprov = i0.ɵɵdefineInjectable({ factory: function TableService_Factory() { return new TableService(); }, token: TableService, providedIn: "root" }); /** @type {!Array<{type: !Function, args: (undefined|!Array<?>)}>} */ TableService.decorators = [ { type: Injectable, args: [{ providedIn: "root", },] } ]; /** * @type {function(): !Array<(null|{ * type: ?, * decorators: (undefined|!Array<{type: !Function, args: (undefined|!Array<?>)}>), * })>} * @nocollapse */ TableService.ctorParameters = () => []; class TableSetting { constructor() { this.direction = 'ltr'; this.visibleActionMenu = null; } } class TableCoreDirective { constructor(tableService, cdr, config) { this.tableService = tableService; this.cdr = cdr; this.config = config; this.backgroundColor = null; this.contextMenuItems = []; this.expandColumn = []; this.defaultWidth = null; this.minWidth = 120; /*************************************** I/O parameters *********************************/ this.printConfig = {}; this.rowHeight = 48; this.headerHeight = 56; this.footerHeight = 48; this.headerEnable = true; this.footerEnable = false; // tslint:disable-next-line: no-output-on-prefix this.onTableEvent = new EventEmitter(); this.onRowEvent = new EventEmitter(); this.settingChange = new EventEmitter(); this.paginationChange = new EventEmitter(); this.noData = true; // Variables // this.progressColumn = []; this.displayedColumns = []; this.displayedFooter = []; this.tvsDataSource = new TableVirtualScrollDataSource([]); this._rowSelectionModel = new SelectionModel(true, []); this._tablePagination = { pageIndex: 0, pageSize: 10, pageSizeOptions: [5, 10, 100, 1000, 10000] }; this.tablePagingMode = "none"; this.viewportClass = "viewport-with-pagination"; this.showProgress = true; this.tableSetting = { direction: "ltr", columnSetting: null, visibleActionMenu: null, }; if (this.config) { this.tableSetting = Object.assign(Object.assign({}, this.tableSetting), this.config); } } get direction() { var _a; return (_a = this.tableSetting) === null || _a === void 0 ? void 0 : _a.direction; } set direction(value) { this.tableSetting.direction = value; } get ScrollStrategyType() { return this.tableSetting.scrollStrategy; } set ScrollStrategyType(value) { this.viewport["_scrollStrategy"].scrollStrategyMode = value; this.tableSetting.scrollStrategy = value; } get pagingMode() { return this.tablePagingMode; } set pagingMode(value) { this.tablePagingMode = value; this.updatePagination(); } get pagination() { return this._tablePagination; } set pagination(value) { if (value && value !== null) { this._tablePagination = value; if (isNullorUndefined(this._tablePagination.pageSizeOptions)) { this._tablePagination.pageSizeOptions = [5, 10, 25, 100]; } if (isNullorUndefined(this._tablePagination.pageSize)) { this._tablePagination.pageSize = this._tablePagination.pageSizeOptions[0]; } this.updatePagination(); } } get rowSelectionModel() { return this._rowSelectionModel; } set rowSelectionModel(value) { if (!isNullorUndefined(value)) { if (this._rowSelectionMode && value && this._rowSelectionMode !== "none") { this._rowSelectionMode = value.isMultipleSelection() === true ? "multi" : "single"; } this._rowSelectionModel = value; } } get rowSelectionMode() { return this._rowSelectionMode; } set rowSelectionMode(selection) { var _a, _b; selection = selection || "none"; const isSelectionColumn = selection === "single" || selection === "multi"; if (this._rowSelectionModel === null || (this._rowSelectionModel.isMultipleSelection() === true && selection === "single") || (this._rowSelectionModel.isMultipleSelection() === false && selection === "multi")) { this._rowSelectionModel = new SelectionModel(selection === "multi", []); } if (((_a = this.displayedColumns) === null || _a === void 0 ? void 0 : _a.length) > 0 && !isSelectionColumn && this.displayedColumns[0] === "row-checkbox") { this.displayedColumns.shift(); } else if (((_b = this.displayedColumns) === null || _b === void 0 ? void 0 : _b.length) > 0 && isSelectionColumn && this.displayedColumns[0] !== "row-checkbox") { this.displayedColumns.unshift("row-checkbox"); } this._rowSelectionMode = selection; } get tableName() { return this.tableService.tableName; } set tableName(value) { this.tableService.tableName = value; } get showProgress() { return this.progressColumn.length > 0; } set showProgress(value) { this.progressColumn = []; if (value === true) { this.progressColumn.push("progress"); } } initSystemField(data) { if (data) { data = data.map((item, index) => { item.id = index; item.option = item.option || {}; return item; }); } } get expandComponent() { return this._expandComponent; } set expandComponent(value) { this._expandComponent = value; if (this._expandComponent !== null && this._expandComponent !== undefined) { this.expandColumn = ["expandedDetail"]; } else { this.expandColumn = []; } } get columns() { return this.tableColumns; } set columns(fields) { (fields || []).forEach((f, i) => { // key name error // if (f.name.toLowerCase() === "id") { throw 'Field name is reserved.["id"]'; } const settingFields = (this.tableSetting.columnSetting || []).filter((s) => s.name === f.name); const settingField = settingFields.length > 0 ? settingFields[0] : null; /* default value for fields */ f.printable = f.printable || true; f.exportable = f.exportable || true; f.toExport = f.toExport || ((row, type) => (typeof row === "object" ? row[f.name] : "")); f.toPrint = (row) => (typeof row === "object" ? row[f.name] : ""); f.enableContextMenu = f.enableContextMenu || true; f.header = f.header || titleCase(f.name); f.display = getObjectProp("display", "visible", settingField, f); f.filter = getObjectProp("filter", "client-side", settingField, f); f.sort = getObjectProp("sort", "client-side", settingField, f); f.sticky = getObjectProp("sticky", "none", settingField, f); f.width = getObjectProp("width", this.defaultWidth, settingField, f); const unit = f.widthUnit || "px"; const style = unit === "px" ? f.width + "px" : `calc( ${f.widthPercentage}% )`; if (f.width) { f.style = Object.assign(Object.assign({}, f.style), { "max-width": style, "min-width": style }); } }); this.tableColumns = fields; this.updateColumn(); } updateColumn() { if (this.tableColumns) { // isNullorUndefined(this.tableSetting.columnSetting) this.tableSetting.columnSetting = clone(this.tableColumns); } this.setDisplayedColumns(); } /**************************************** Methods **********************************************/ updatePagination() { if (isNullorUndefined(this.tvsDataSource)) { return; } if (this.tablePagingMode === "client-side" || this.tablePagingMode === "server-side") { this.viewportClass = "viewport-with-pagination"; if (!isNullorUndefined(this.tvsDataSource.paginator)) { let dataLen = this.tvsDataSource.paginator.length; if (!isNullorUndefined(this._tablePagination.length) && this._tablePagination.length > dataLen) { dataLen = this._tablePagination.length; } this.tvsDataSource.paginator.length = dataLen; } } else { this.viewportClass = "viewport"; if (this.tvsDataSource._paginator !== undefined) { delete this.tvsDataSource._paginator; } } this.tvsDataSource.refreshFilterPredicate(); } clearSelection() { if (this._rowSelectionModel) { this._rowSelectionModel.clear(); } } clear() { if (!isNullorUndefined(this.tvsDataSource)) { if (this.viewport) { this.viewport.scrollTo({ top: 0, behavior: "auto" }); } this.tvsDataSource.clearData(); this.expandedElement = null; } this.clearSelection(); this.cdr.detectChanges(); } setDisplayedColumns() { if (this.columns) { this.displayedColumns.splice(0, this.displayedColumns.length); this.columns.forEach((column, index) => { column.index = index; if (column.display === undefined || column.display === "visible" || column.display === "prevent-hidden") { this.displayedColumns.push(column.name); } }); if ((this._rowSelectionMode === "multi" || this._rowSelectionMode === "single") && this.displayedColumns.indexOf("row-checkbox") === -1) { this.displayedColumns.unshift("row-checkbox"); } this.displayedFooter = this.columns .filter((item) => item.footer !== null && item.footer !== undefined) .map((item) => item.name); if (this.tableSetting.visibleTableMenu !== false) { this.displayedColumns.push("table-menu"); } } } /************************************ Drag & Drop Column *******************************************/ refreshGrid() { this.cdr.detectChanges(); this.refreshColumn(this.tableColumns); this.table.renderRows(); this.viewport.checkViewportSize(); } moveRow(from, to) { if (from >= 0 && from < this.tvsDataSource.data.length && to >= 0 && to < this.tvsDataSource.data.length) { this.tvsDataSource.data[from].id = to; this.tvsDataSource.data[to].id = from; moveItemInArray(this.tvsDataSource.data, from, to); this.tvsDataSource.data = Object.assign([], this.tvsDataSource.data); } } moveColumn(from, to) { setTimeout(() => { moveItemInArray(this.columns, from, to); this.refreshColumn(this.columns); }); } refreshColumn(columns) { if (this.viewport) { const currentOffset = this.viewport.measureScrollOffset(); this.columns = columns; // this.setDisplayedColumns(); setTimeout(() => this.viewport.scrollTo({ top: currentOffset, behavior: "auto" }), 0); } } /************************************ Selection Table Row *******************************************/ /** Whether the number of selected elements matches the total number of rows. */ isAllSelected() { const numSelected = this._rowSelectionModel.selected.length; const numRows = this.tvsDataSource.filteredData.length; return numSelected === numRows; } /** Selects all rows if they are not all selected; otherwise clear selection. */ masterToggle() { const isAllSelected = this.isAllSelected(); if (isAllSelected === false) { this.tvsDataSource.filteredData.forEach((row) => this._rowSelectionModel.select(row)); } else { this._rowSelectionModel.clear(); } this.onRowEvent.emit({ event: "MasterSelectionChange", sender: { selectionModel: this._rowSelectionModel }, }); } onRowSelectionChange(e, row) { if (e) { this._rowSelectionModel.toggle(row); this.onRowEvent.emit({ event: "RowSelectionChange", sender: { selectionModel: this._rowSelectionModel, row: row }, }); } } } /** @type {!Array<{type: !Function, args: (undefined|!Array<?>)}>} */ TableCoreDirective.decorators = [ { type: Directive, args: [{ // tslint:disable-next-line:directive-selector selector: "[core]", },] } ]; /** * @type {function(): !Array<(null|{ * type: ?, * decorators: (undefined|!Array<{type: !Function, args: (undefined|!Array<?>)}>), * })>} * @nocollapse */ TableCoreDirective.ctorParameters = () => [ { type: TableService }, { type: ChangeDetectorRef }, { type: TableSetting } ]; /** @type {!Object<string, !Array<{type: !Function, args: (undefined|!Array<?>)}>>} */ TableCoreDirective.propDecorators = { sort: [{ type: ViewChild, args: [MatSort, { static: true },] }], paginator: [{ type: ViewChild, args: [MatPaginator, { static: true },] }], dataSource: [{ type: Input }], backgroundColor: [{ type: Input }], direction: [{ type: Input }, { type: HostBinding, args: ["style.direction",] }], contextMenuItems: [{ type: Input }], ScrollStrategyType: [{ type: Input }], pagingMode: [{ type: Input }], pagination: [{ type: Input }], rowSelectionModel: [{ type: Input }], rowSelectionMode: [{ type: Input }], tableName: [{ type: Input }], showProgress: [{ type: Input }], expandComponent: [{ type: Input }], rowContextMenuItems: [{ type: Input }], defaultWidth: [{ type: Input }], minWidth: [{ type: Input }], columns: [{ type: Input }], printConfig: [{ type: Input }], sticky: [{ type: Input }], pending: [{ type: Input }], rowHeight: [{ type: Input }], headerHeight: [{ type: Input }], footerHeight: [{ type: Input }], headerEnable: [{ type: Input }], footerEnable: [{ type: Input }], showNoData: [{ type: Input }], showReload: [{ type: Input }], onTableEvent: [{ type: Output }], onRowEvent: [{ type: Output }], settingChange: [{ type: Output }], paginationChange: [{ type: Output }], table: [{ type: ViewChild, args: [MatTable, { static: true },] }], viewport: [{ type: ViewChild, args: [CdkVirtualScrollViewport, { static: true },] }] }; class AbstractFilter { hasValue() { if (this.parameters !== null) { return this.parameters.filter(p => p.value != null && p.value !== undefined && p.value.toString() !== '').length > 0; } } } const contains = 'a.toString().includes(b)'; const equals$1 = 'a.toString() === b.toString()'; const startsWith = 'a.toString().startsWith(b)'; const endsWith = 'a.toString().endsWith(b.toString())'; const empty$1 = '!a'; const notEmpty$1 = '!!a'; const operations$1 = [contains, equals$1, startsWith, endsWith, empty$1, notEmpty$1]; class TextFilter extends AbstractFilter { constructor(languagePack) { super(); this.languagePack = languagePack; // tslint:disable-next-line:variable-name this._selectedIndex = null; this._selectedIndex = 0; if (TextFilter.operationList.length === 0) { // init for first time operations$1.forEach(fn => { TextFilter.operationList.push({ predicate: fn, text: null }); }); } TextFilter.operationList[0].text = languagePack.filterLabels.TextContains; // contains // TextFilter.operationList[1].text = languagePack.filterLabels.TextEquals; // equals // TextFilter.operationList[2].text = languagePack.filterLabels.TextStartsWith; // startsWith // TextFilter.operationList[3].text = languagePack.filterLabels.TextEndsWith; // endsWith // TextFilter.operationList[4].text = languagePack.filterLabels.TextEmpty; // empty // TextFilter.operationList[5].text = languagePack.filterLabels.TextNotEmpty; // notEmpty // } get selectedIndex() { return this._selectedIndex; } set selectedIndex(value) { this._selectedIndex = value; // init filter parameters if (value === 0 || value === 1 || value === 2 || value === 3) { // contains equals startsWith endsWith this.parameters = [{ value: '', text: this.languagePack.filterLabels.Text }]; } else { // empty notEmpty this.parameters = null; } } get selectedValue() { if (this._selectedIndex !== null) { return TextFilter.operationList[this._selectedIndex]; } else { return null; } } getOperations() { return TextFilter.operationList; } toString(dynamicVariable) { const a = '_a$'; const b = '_b$'; const predicate = this.selectedValue.predicate.replace('a', a).replace('b', b); const statement = predicate.replace(a, `${a}['${dynamicVariable}']?.toString()?.toLowerCase()`); // one static parameters equals notEquals greaterThan lessThan // if (this._selectedIndex === 0 || this._selectedIndex === 1 || this._selectedIndex === 2 || this._selectedIndex === 3) { const value = '\'' + (this.parameters[0].value !== null ? this.parameters[0].value.toLowerCase() : ' null ') + '\''; return statement.replace('_b$', value); } else { // without static parameters return statement; } } toPrint() { return TextFilter.operationList[this._selectedIndex].text + ' ' + this.parameters[0].value + ' ' + (this.type || '') + ' '; } toSql() { return TextFilter.sql[this._selectedIndex].replace('[*]', (this.parameters[0].value || '')) + (this.type || '') + ' '; } } TextFilter.sql = ['LIKE "%[*]%"', '= "[*]"', 'LIKE "%[*]"', 'LIKE "[*]%"', 'IS NULL', 'IS NOT NULL']; TextFilter.operationList = []; const equals = 'a === b'; const notEquals = 'a !== b'; const greaterThan = 'a > b'; const lessThan = 'a < b'; const empty = '!a'; const notEmpty = '!!a'; const operations = [equals, notEquals, greaterThan, lessThan, empty, notEmpty]; class NumberFilter extends AbstractFilter { // private languageText: LanguagePack; constructor(languagePack) { super(); this.languagePack = languagePack; // tslint:disable-next-line:variable-name this._selectedIndex = null; if (NumberFilter.operationList.length === 0) { operations.forEach(fn => { NumberFilter.operationList.push({ predicate: fn, text: null }); }); } NumberFilter.operationList[0].text = languagePack.filterLabels.NumberEquals; // equals // NumberFilter.operationList[1].text = languagePack.filterLabels.NumberNotEquals; // notEquals // NumberFilter.operationList[2].text = languagePack.filterLabels.NumberGreaterThan; // greaterThan // NumberFilter.operationList[3].text = languagePack.filterLabels.NumberLessThan; // lessThan // NumberFilter.operationList[4].text = languagePack.filterLabels.NumberEmpty; // empty // NumberFilter.operationList[5].text = languagePack.filterLabels.NumberNotEmpty; // notEmpty // } get selectedIndex() { return this._selectedIndex; } set selectedIndex(value) { this._selectedIndex = value; // init filter parameters if (value === 0 || value === 1 || value === 2 || value === 3) { // equals notEquals greaterThan lessThan this.parameters = [{ value: null, text: this.languagePack.filterLabels.Number }]; } else { // empty notEmpty this.parameters = null; } } get selectedValue() { if (this._selectedIndex !== null) { return NumberFilter.operationList[this._selectedIndex]; } else { return null; } } getOperations() { return NumberFilter.operationList; } toString(dynamicVariable) { const a = '_a$'; const b = '_b$'; const predicate = this.selectedValue.predicate.replace('a', a).replace('b', b); const statement = predicate.replace(a, `${a}['${dynamicVariable}']`); // one static variable (equals, notEquals,greaterThan,lessThan) if (this._selectedIndex === 0 || this._selectedIndex === 1 || this._selectedIndex === 2 || this._selectedIndex === 3) { const value = this.parameters[0].value ? this.parameters[0].value.toString() : ' null '; return statement.replace(b, value); } else { // none static variable (empty, notEmpty) return statement; } } toPrint() { return NumberFilter.operationList[this._selectedIndex].text + ' ' + this.parameters[0].value + ' ' + (this.type || '') + ' '; } toSql() { return NumberFilter.sql[this._selectedIndex] + ' ' + (this.parameters[0].value || '') + ' ' + (this.type || '') + ' '; } } NumberFilter.sql = ['=', '<>', '>', '<', 'IS NULL', 'IS NOT NULL']; NumberFilter.operationList = []; class TableIntl { constructor() { this.menuLabels = { saveData: 'Save Data', newSetting: 'New Setting', defaultSetting: 'Default Setting', noSetting: 'No Setting', fullScreen: 'Full Screen', columnSetting: 'Column Setting', saveTableSetting: 'Save Table Setting', clearFilter: 'Clear Filter', jsonFile: 'Json File', csvFile: 'CSV File', printTable: 'Print Table', filterMode: 'Filter Mode:', filterLocalMode: 'Local', filterServerMode: 'Server', sortMode: 'Sort Mode:', sortLocalMode: 'Local', sortServerMode: 'Server', printMode: 'Print Mode', printYesMode: 'Yes', printNoMode: 'No', pinMode: 'Pin Mode:', pinNoneMode: 'None', pinStartMode: 'Start', pinEndMode: 'End', thereIsNoColumn: 'There is no column.' }; this.paginatorLabels = { changes: new Subject(), itemsPerPageLabel: 'Items per page:', nextPageLabel: 'Next Page:', previousPageLabel: 'Previous Page:', firstPageLabel: 'First Page:', lastPageLabel: 'Last Page:', getRangeLabel: (page, pageSize, length) => { if (length === 0 || pageSize === 0) { return `0 of ${length}`; } length = Math.max(length, 0); const startIndex = page * pageSize; const endIndex = startIndex < length ? Math.min(startIndex + pageSize, length) : startIndex + pageSize; return `${startIndex + 1} - ${endIndex} of ${length}`; } }; this.tableLabels = { NoData: 'No records found.' }; this.filterLabels = { Clear: 'Clear', Search: 'Search', And: 'And', Or: 'Or', /* Text Compare */ Text: 'Text', TextContains: 'Contains', TextEmpty: 'Empty', TextStartsWith: 'Starts With', TextEndsWith: 'Ends With', TextEquals: 'Equals', TextNotEmpty: 'Not Empty', /* Number Compare */ Number: 'Number', NumberEquals: 'Equals', NumberNotEquals: 'Not Equals', NumberGreaterThan: 'Greater Than', NumberLessThan: 'Less Than', NumberEmpty: 'Empty', NumberNotEmpty: 'Not Empty', /* Category List Compare */ CategoryContains: 'Contains', CategoryNotContains: 'Not Contains', /* Boolean Compare */ /* Date Compare */ }; } } /** @nocollapse */ TableIntl.ɵprov = i0.ɵɵdefineInjectable({ factory: function TableIntl_Factory() { return new TableIntl(); }, token: TableIntl, providedIn: "root" }); /** @type {!Array<{type: !Function, args: (undefined|!Array<?>)}>} */ TableIntl.decorators = [ { type: Injectable, args: [{ providedIn: 'root' },] } ]; const listAnimation = trigger('listAnimation', [ transition('* <=> *', [ query(':enter', [style({ opacity: 0 }), stagger('10ms', animate('400ms ease-out', style({ opacity: 1 })))], { optional: true }), ]) ]); class HeaderFilterComponent { constructor(languagePack, service, cdr) { this.languagePack = languagePack; this.service = service; this.cdr = cdr; this.filterChanged = new EventEmitter(); this.filterList = []; } get filters() { if (isNullorUndefined(this.filterList) === true || this.filterList.length === 0) { this.filterList = []; this.addNewFilter(this.field.type || 'text'); } return this.filterList; } set filters(values) { this.filterList = values; } get hasValue() { return this.filterList && this.filterList.filter(f => f.hasValue() === true).length > 0; } get showTrigger() { if (this.menu === undefined) { return false; } else { return this.menu.menuOpen || this.hasValue; } } ngOnDestroy() { if (this.eventsSubscription) { this.eventsSubscription.unsubscribe(); } } ngOnInit() { if (isNullorUndefined(this.filters)) { this.filters = []; this.addNewFilter(this.field.type); } } addNewFilter(type = 'text') { switch (type || 'text') { case 'text': { this.filterList.push(new TextFilter(this.languagePack)); break; } case 'number': { this.filterList.push(new NumberFilter(this.languagePack)); break; } case 'date': { // this.compare = new DateCompare(service); break; } case 'boolean': { // this.compare = new BooleanCompare(service); break; } default: this.filterList.push(new TextFilter(this.languagePack)); } this.filters[this.filters.length - 1].selectedIndex = 0; return this.filters[this.filters.length - 1]; } ngAfterViewInit() { if (this.menu) { this.eventsSubscription = this.menu.menuOpened.subscribe(() => this.focusToLastInput()); } } focusToLastInput() { setTimeout(() => { if (this.filterInputList.length > 0) { this.filterInputList.last.focus(); } }); } filterAction_OnClick(index, action) { if (action === 0 || action === 1) { // and or this.filters[index].type = action === 0 ? 'and' : 'or'; if (this.filters.length === index + 1) { this.addNewFilter(this.field.type); this.focusToLastInput(); } } else if (action === 2 && this.filters.length > 1) { // delete setTimeout(() => { this.filters.splice(index, 1); this.cdr.detectChanges(); this.focusToLastInput(); }); // bug for delete filter item(unwanted reaction close menu) } } clearColumn_OnClick() { this.filterList = []; this.filterChanged.emit(this.filterList); } applyFilter_OnClick() { this.filterChanged.emit(this.filterList); } } /** @type {!Array<{type: !Function, args: (undefined|!Array<?>)}>} */ HeaderFilterComponent.decorators = [ { type: Component, args: [{ // tslint:disable-next-line:component-selector selector: 'header-filter', template: "<ng-content></ng-content>\r\n\r\n<mat-menu filter-event #filterMenu=\"matMenu\" class=\"menu\">\r\n <ng-template matMenuContent>\r\n\r\n <div filter-event class=\"menu-title\">\r\n {{field?.header}}\r\n </div>\r\n <div [@listAnimation]=\"filters.length\" filter-event *ngFor=\"let filter of filters; let index = index\"\r\n class=\"filter-panel\">\r\n\r\n <mat-form-field>\r\n <mat-select [value]=\"filter.selectedIndex\" [panelClass]=\"'mat-elevation-z10'\"\r\n (selectionChange)=\"filter.selectedIndex = $event.value;\" placeholder='Conditions'\r\n (keyup.enter)=\"applyFilter_OnClick()\">\r\n <mat-option *ngFor=\"let op of filter.getOperations(); let selectedIndex=index\" [value]=\"selectedIndex\">\r\n {{ op.text }}\r\n </mat-option>\r\n </mat-select>\r\n </mat-form-field>\r\n\r\n <div *ngFor=\"let ctrl of filter?.parameters\">\r\n <mat-form-field class=\"input-field\">\r\n <mat-label>{{ctrl.text}}</mat-label>\r\n <input matInput #filterInput=\"matInput\" [(ngModel)]=\"ctrl.value\" [placeholder]=\"\"\r\n (keyup.enter)=\"applyFilter_OnClick()\" autocomplete=\"off\" />\r\n </mat-form-field>\r\n </div>\r\n\r\n <div class=\"or-and\">\r\n <span *ngIf=\"filters?.length !== index+1\" class=\"selected-filter-type\">{{ filter?.type === 'and' ?\r\n languagePack.filterLabels.And : languagePack.filterLabels.Or}}</span>\r\n <span class=\"svg\">\r\n <mat-icon (click)=\"filterAction_OnClick(index,0)\">add</mat-icon>\r\n </span>\r\n <span class=\"svg\">\r\n <mat-icon (click)=\"filterAction_OnClick(index,1)\" style=\"transform: rotate(90deg);\">drag_handle</mat-icon>\r\n </span>\r\n <span class=\"svg\">\r\n <mat-icon (click)=\"filterAction_OnClick(index,2)\">clear</mat-icon>\r\n </span>\r\n </div>\r\n\r\n </div>\r\n\r\n <div filter-event class=\"menu-action\">\r\n <button mat-raised-button type=\"button\" (click)=\"clearColumn_OnClick()\">{{ languagePack.filterLabels.Clear\r\n }}</button>\r\n <button mat-raised-button type=\"button\" color=\"primary\" (click)=\"applyFilter_OnClick()\">{{\r\n languagePack.filterLabels.Search}}</button>\r\n </div>\r\n </ng-template>\r\n</mat-menu>\r\n\r\n<span class=\"trigger\" [matMenuTriggerFor]=\"filterMenu\" *ngIf=\"field.filter !== 'none'\">\r\n <mat-icon>filter_list</mat-icon>\r\n</span>\r\n", changeDetection: ChangeDetectionStrategy.OnPush, animations: [listAnimation], styles: ["@media print{.print-preview{background-color:#fff;position:fixed;width:100%;height:auto;z-index:99999999;margin:0;padding:0;top:0;left:0;overflow:visible;display:block}}.disable-backdrop-click .cdk-overlay-backdrop.cdk-overlay-transparent-backdrop.cdk-overlay-backdrop-showing{pointer-events:none}:host{display:flex;align-items:center;width:100%;align-self:stretch}.trigger{color:#0000004d;display:flex;opacity:0;transform:translateY(-5px);cursor:pointer;transition-duration:.4s;transition-property:opacity,transform;position:sticky;right:0px;z-index:1;padding-left:0 8px}:host.has-value .trigger{opacity:1;color:#0000008a}:host:hover .trigger,:host.show-trigger .trigger{opacity:1;transform:translateY(-1px)}::ng-deep .mat-menu-content:not(:empty){padding:0!important}.mat-menu-item-highlighted:not([disabled]),.mat-menu-item.cdk-keyboard-focused:not([disabled]),.mat-menu-item.cdk-program-focused:not([disabled]),.mat-menu-item:hover:not([disabled]){background-color:inherit}.input-field{margin-top:-15px}.menu-title{font-weight:bolder;top:-8px;position:sticky;background-color:#fff;z-index:1}.menu-action{position:sticky;bottom:-8px;padding-top:10px;padding-bottom:0;background-color:#fff}.menu-action button{width:calc(50% - 10px);margin:5px;border-radius:10px}.filter-panel{border-radius:5px;background-color:#fdfbfb;border:solid 1px #efefef;transition:all .5s;padding:5px;overflow:hidden;font-size:14px;margin-top:10px;display:flex;flex-direction:column}.filter-panel:nth-child(2){margin-top:0!important}.filter-panel:hover{border:solid 1px #d1d1d1}.filter-panel:hover .svg{opacity:1;transform:translateY(-1px)}.or-and{display:inherit!important;text-align:right;margin:-12px 0;height:35px;cursor:inherit;font-size:12px}.svg{color:#0000004d;display:flex;opacity:0;transform:translateY(-5px);transition-duration:.4s;transition-property:opacity,transform;margin-left:5px;padding:2px;border-radius:5px;color:#4c4c4c;cursor:pointer;display:inline-block!important;height:24px}.svg mat-icon{margin:0;vertical-align:top;border-radius:5px}.svg mat-icon:hover{color:#fff;background-color:#89898a}.svg:hover{background-color:#f8f8f8}.selected-filter-type{float:left;color:#fff;background-color:#898