import { CdkDragDrop } from '@angular/cdk/drag-drop'; import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling'; import { AfterContentInit, Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChanges, ViewChild, AfterViewInit, ChangeDetectorRef } from '@angular/core'; import { MatCheckboxChange } from '@angular/material/checkbox'; import { MatMenuTrigger } from '@angular/material/menu'; import { Subject } from 'rxjs'; import { ActionColumn, SelectionType, SortDirection, TableColumn, TableFilter, TableSort, TableToggleEvent } from '../../models'; import { CsvExportService } from '../../services/export/csv-export.service'; import { TableColumnService, TableColumnSettings } from '../../services/table/table-column.service'; import { TableFilterResults, TableFilterService } from '../../services/table/table-filter.service'; import { TableResizeService } from '../../services/table/table-resize.service'; import { TableRowSelectService, TableSelectResults } from '../../services/table/table-row-select.service'; import { TableSortService } from '../../services/table/table-sort.service'; import { MatSlideToggleChange } from '@angular/material/slide-toggle'; @Component({ selector: 'fss-table', templateUrl: './fss-table.component.html', styleUrls: ['./fss-table.component.scss', './styles/fss-table-header.scss', './styles/fss-table-body.scss', './styles/fss-table-footer.scss'], providers: [TableColumnService, TableFilterService, TableSortService, TableResizeService, TableRowSelectService, CsvExportService] }) export class FssTableComponent implements OnInit, OnChanges, OnDestroy, AfterContentInit, AfterViewInit { @Input() rows: any[] = []; @Input() columns: TableColumn[] = []; @Input() defaultColumns?: string; @Input() initialColumns?: string; @Input() leftActionColumns: ActionColumn[] = []; @Input() selectionType: SelectionType = SelectionType.checkbox; @Input() hasSelectAll: boolean = true; @Input() trackByProp: string = 'key'; @Input() rowHeight: number = 30; @Input() itemSize?: number; @Input() sorts: TableSort[] = []; @Input() defaultSorts: TableSort[] = []; @Input() loading: boolean = false; @Input() selectEnabled: boolean = true; @Input() rowSelectionDisabledProp?: string; @Input() minColumnsForHScroll: number = 10; @Input() defaultMinWidthHScroll: string = '150px'; @Input() hasExport: boolean = true; @Input() exportFileName: string = ''; @Input() hasAvailableColumnsMenu: boolean = true; @Input() displayRowCount: boolean = true; @Input() rowName: string = ''; @Input() showToggleColumn = false; @Input() toggleEnabledProp: string = 'isEnabled'; @Input() disableToggles = false; @Input() disableToggleProp: string = 'disableToggle'; @Input() toggleTooltipProp: string = 'toggleTooltip'; @Output() displayedColumnsChanged: EventEmitter = new EventEmitter(); @Output() selectedRowsChanged: EventEmitter = new EventEmitter(); @Output() actionClicked: EventEmitter<{ row: any, actionName: string }> = new EventEmitter<{ row: any, actionName: string }>(); @Output() toggleChanged: EventEmitter = new EventEmitter(); _selectionType = SelectionType; @ViewChild(CdkVirtualScrollViewport, { static: true }) viewPort?: CdkVirtualScrollViewport; @ViewChild('menuTrigger', { static: false }) columnMenu?: MatMenuTrigger; displayedColumns: TableColumn[] = []; displayedColumnsStrings: string[] = []; visibleColumnsMenu: TableColumn[] = []; hasColumnHeaders: boolean = true; hasColumnFilters: boolean = true; tableColspan: number = 0; sortedColumns: Object = Object.create(null); _sortDirection = SortDirection; pos?: { x: string, y: string, width: string }; displayedRows: any[] = []; selectedRows: Object = Object.create(null); selected: any[] = []; minBuffer: number = 600; maxBuffer: number = 1200; ngUnsubscribe: Subject = new Subject(); selectAllChecked: boolean = false; selectAllIndeterminate: boolean = false; filters: TableFilter[] = []; headerTop = '-0px'; filterTop = '-34px' constructor(private tableColumnService: TableColumnService, private tableFilterService: TableFilterService, private tableSortService: TableSortService, private tableResizeService: TableResizeService, private tableRowSelectService: TableRowSelectService, private csvExportService: CsvExportService, private cdr: ChangeDetectorRef) { } ngAfterViewInit(): void { if (!!this.viewPort) { this.viewPort['_scrollStrategy'].onRenderedOffsetChanged = () => { this.headerTop = this.setInverseTranslationHeaderRow(); this.filterTop = this.setInverseTranslationFilterRow(); this.cdr.detectChanges(); } } } ngOnChanges(changes: SimpleChanges): void { for (const propName in changes) { if (changes.hasOwnProperty(propName)) { switch (propName) { case 'columns': case 'leftActionColumns': case 'selectionType': case 'initialColumns': if (!changes[propName].firstChange) { this.setColumns(); } break; case 'defaultSorts': this.setDefaultSorts(); break; case 'rows': this.displayedRows = this.rows; this.setSelectedRows(); break; } } } } ngOnInit(): void { } ngOnDestroy(): void { this.ngUnsubscribe.next(undefined); this.ngUnsubscribe.complete(); } ngAfterContentInit(): void { this.setItemAndBufferSizes(); this.setColumns(); this.displayedRows = this.rows; this.setSelectedRows(); this.setDefaultSorts(); } /***** VIRTUAL SCROLLING *****/ setInverseTranslationHeaderRow(): string { if (!this.viewPort || !this.viewPort['_renderedContentOffset']) { return '-0px'; } return `-${this.viewPort['_renderedContentOffset']}px`; } setInverseTranslationFilterRow(): string { if (!this.viewPort || !this.viewPort['_renderedContentOffset']) { return this.hasColumnHeaders ? '34px' : '-0px'; } let offset = this.viewPort['_renderedContentOffset']; if (this.hasColumnHeaders) { offset -= 34; } return `-${offset}px`; } setItemAndBufferSizes() { if (this.itemSize == null) { this.itemSize = this.rowHeight; } this.minBuffer = this.itemSize * 20; this.maxBuffer = this.itemSize * 40; } scrollToIndex(index: number, scrollBehavior: ScrollBehavior = 'auto') { if (this.viewPort) { this.viewPort.scrollToIndex(index, scrollBehavior); } } /***** COLUMNS *****/ setColumns() { const columnSettings = this.tableColumnService.getInitialColumnSettings(this.columns, this.leftActionColumns, this.selectionType, this.initialColumns); this.setColumnSettings(columnSettings); } onDisplayedColumnsChanged(displayedColumns: TableColumn[]) { const updatedTableSettings = this.tableColumnService.getUpdatedColumnSettings(displayedColumns, this.visibleColumnsMenu, this.leftActionColumns, this.selectionType); this.setColumnSettings(updatedTableSettings); } setColumnSettings(settings: TableColumnSettings) { this.displayedColumns = settings.displayedColumns; this.visibleColumnsMenu = settings.availableMenuColumns; this.hasColumnFilters = settings.hasColumnFilters; this.hasColumnHeaders = settings.hasColumnHeaders; this.tableColspan = settings.tableColspan; this.displayedColumnsStrings = this.tableColumnService.getDisplayedColumnsStrings(this.displayedColumns); } dropColumn(event: CdkDragDrop) { this.displayedColumns = this.tableColumnService.dropColumn(event, this.displayedColumns); const updatedTableSettings = this.tableColumnService.getUpdatedColumnSettings(this.displayedColumns, this.visibleColumnsMenu, this.leftActionColumns, this.selectionType); this.setColumnSettings(updatedTableSettings); this.displayedColumnsChanged.emit(this.displayedColumns); } resizeStart(event: any, handleSide: string, column: string) { this.tableResizeService.resizeStart(event, handleSide, column, this.displayedColumns); } onMouseDown(event: any, el: any = null) { this.pos = this.tableColumnService.onMouseDown(event, el); } onColumnMenuClosed() { this.displayedColumnsChanged.emit(this.displayedColumns); this.updateFilter(undefined, 'undefined'); } onCloseMenuClicked() { this.columnMenu?.closeMenu(); } /***** FILTERING *****/ updateFilter(column: TableColumn | undefined, searchText: string) { const filterResults: TableFilterResults = this.tableFilterService.updateFilter(this.rows, this.displayedColumns, column, this.filters, searchText); this.displayedRows = filterResults.filteredRows; this.filters = filterResults.filters; this.sortColumns(); this.scrollToIndex(0); } /***** SORTING *****/ setDefaultSorts() { this.sorts = this.defaultSorts; this.sortedColumns = this.tableSortService.getSortedColumnsObject(this.sorts); this.sortColumns(); } toggleSort(column: string) { const tableSortSettings = this.tableSortService.toggleSort(column, this.sorts); this.sorts = tableSortSettings.sorts; this.sortedColumns = tableSortSettings.sortedColumns; this.sortColumns(); } sortColumns() { const sortResults = this.tableSortService.sortColumns(this.sorts, this.displayedColumns, this.displayedRows, this.trackByProp); this.displayedRows = sortResults.displayedRows; this.sorts = sortResults.sorts; this.sortedColumns = this.tableSortService.getSortedColumnsObject(this.sorts); } /***** ROW SELECTION *****/ setSelectedRows() { this.selectedRows = this.tableRowSelectService.getSelectedRowsObject(this.rows, this.displayedRows, this.selected, this.trackByProp); this.setSelectAllState(); } setSelectAllState() { const selectAllState = this.tableRowSelectService.getSelectAllState(this.displayedRows, this.selected, this.rowSelectionDisabledProp); this.selectAllChecked = selectAllState.checked; this.selectAllIndeterminate = selectAllState.indeterminate; } onSelectAllChange(event: MatCheckboxChange) { this.setSelectResults(this.tableRowSelectService.selectAllChanged(event, this.rows, this.displayedRows, this.trackByProp, this.rowSelectionDisabledProp)); } toggleSelectRow(row: any) { this.setSelectResults(this.tableRowSelectService.toggleSelectRow(row, this.rows, this.displayedRows, this.selected, this.selectedRows, this.selectAllChecked, this.selectAllIndeterminate, this.trackByProp, this.selectionType, this.rowSelectionDisabledProp)); this.cdr.detectChanges(); } setSelectResults(selectResults: TableSelectResults) { this.selected = selectResults.selected; this.selectedRows = selectResults.selectedRows; this.selectAllChecked = selectResults.selectAllChecked; this.selectAllIndeterminate = selectResults.selectAllIndeterminate; this.selectedRowsChanged.emit(this.selected); } /***** EXPORT TO CSV *****/ exportToCsv() { this.csvExportService.exportTableToCsv(this.displayedColumns, this.displayedRows, this.exportFileName); } /***** ACTION COLUMNS *****/ action(event: { row: any, actionName: string }) { this.actionClicked.emit(event); } /***** TRACK BY FUNCTIONS *****/ trackByFunction = (index: number, row: any) => { return row[this.trackByProp]; } columnTrackByFunction = (index: number, column: TableColumn) => { return column.prop; } /***** TOGGLE COLUMNS *****/ toggleChange($event: MatSlideToggleChange, row: any) { if (this.toggleEnabledProp != undefined) { const fssTableToggleEvent: TableToggleEvent = { enabled: $event.checked, row: row }; row[this.toggleEnabledProp] = $event.checked; this.cdr.detectChanges(); this.toggleChanged.emit(fssTableToggleEvent); } } }