UNPKG

ms-data-grid

Version:

A powerful, customizable Angular data grid component with advanced features like sorting, filtering, pagination, column pinning, and taskbar actions. Perfect for enterprise applications.

1,171 lines 1.55 MB
import { Component, Input, Output, ViewChild, HostListener, EventEmitter, } from '@angular/core'; import { moveItemInArray, } from '@angular/cdk/drag-drop'; import { trigger, state, style, transition, animate, } from '@angular/animations'; import { STATUSES_BADGE_MAP } from './statuses'; import * as i0 from "@angular/core"; import * as i1 from "../services/split-columns.service"; import * as i2 from "../services/common.service"; import * as i3 from "../services/swap-columns.service"; import * as i4 from "../services/copy-service.service"; import * as i5 from "@angular/common"; import * as i6 from "@angular/forms"; import * as i7 from "@angular/cdk/drag-drop"; import * as i8 from "ng-inline-svg"; import * as i9 from "../pipes/filter.pipe"; export class DataGridComponent { constructor(columnService, cdr, commonSevice, swapColumnService, elementRef, ngZone, copyService) { this.columnService = columnService; this.cdr = cdr; this.commonSevice = commonSevice; this.swapColumnService = swapColumnService; this.elementRef = elementRef; this.ngZone = ngZone; this.copyService = copyService; // **************************************// // **************************************// // ********** Input Goes Here *********** // // **************************************// // **************************************// // Pagination Config Store Here this.paginationConfig = {}; // The dataset store here; this.dataSet = []; // The columns Store Here this.columns = []; // Row Height; this.rowHeight = 44; // Header Row Height; this.headerRowHeight = 44; // Show Vertical Borders; this.showVerticalBorder = true; // Even Rows Background Color; this.evenRowsBackgroundColor = undefined; // Even Rows Background Color; this.oddRowsBackgroundColor = undefined; // Header Rows Background Color; this.headerBackgroundColor = '#eaeaea'; // Header Rows Background Color; this.checkboxesBackgroundColor = '#eaeaea'; // Show Columns Grouping; this.showColumnsGrouping = false; // Row Hovered Background color; this.rowHoverColor = 'rgba(0, 123, 255, 0.1)'; // Left PinnedBackground color; this.leftPinnedBackgroundColor = '#000'; // Body Background color; this.bodyBackgroundColor = '#fff'; // Right Pinned Background color; this.rightPinnedBackgroundColor = '#000'; // Side Menu Background color; this.sidemenuBackgroundColor = '#000'; // Body text color; this.bodyTextColor = '#000'; // Header text color; this.headerTextColor = '#000'; // Header text size; this.headerTextFontsSize = 16; // Body text color; this.bodyTextFontsSize = 16; // Header font weight; this.headerFontWeight = 500; // Body Font Weight; this.bodyFontWeight = 400; // Checked Row Background Color; this.checkedRowBackgroundColor = ''; // dropdowns Background Color; this.dropdownsBackgroundColor = ''; // Footer row Height; this.footerRowHeight = 46; // Footer row Height; this.topGroupedBadgesBackgroundColor = ''; // Show Row wise grouping; this.showRowsGrouping = false; // Show Row wise grouping; this.showFilterRow = false; // Show Row wise grouping; this.fontFaimly = 'sans-serif'; // Show SideColumn; this.showSideMenu = false; // Footer Padding this.footerPadding = 3; // Footer Padding this.topFilterRowHeight = 50; // Show Rows shading this.rowShadingEnabled = false; // Show Rows shading this.showSerialNumber = false; // Single Spa Url Attach to icons this.singleSpaAssetsPath = 'assets/'; // Applied Filters this.filtersConfig = []; // Applied Filters this.loading = false; //Vertical Scrollbar style this.verticalScrollbarWidth = 'auto'; //Horizintal Scrollbar style this.horizintalScrollbarWidth = 'auto'; // Show Cell details box this.showCellDetailsBox = false; //Date format this.dateFormat = 'dd/MM/yyyy'; //Date format this.tableSearch = ''; //Date format this.actions = ['edit', 'delete']; // Selection task bar this.showTaskbar = false; // Table Name for state manage this.tableName = true; // Listing type this.listingType = ''; // Listing type this.checkboxState = { reset: true }; // Taskbar actions this.taskbarActions = []; // Sorting Config to show sort icons this.sortingConfig = null; this.tableFilterViewId = ''; this.selectedTableLayout = 'medium'; this.closeDropdown = { preset: { closed: false, loading: false } }; // Table View // GlobalSearch this.globalSearchText = ''; // Nested Table Row Fontsize this.nestedTablerowFontsize = 14; // Nested table row Header row Height this.nestedTableHeaderRowHeight = 40; // Nested Table row height this.nestedTablerowHeight = 36; this.gridType = 'Assets'; this.leftPinnedBoxshadow = '#4b4b4b 1px 1px 5px 0px'; this.rightPinnedBoxshadow = '#4b4b4b 4px 1px 6px 0px'; // GlobalSearch this.selectedRowsBackgroundColor = '#8ac5ff'; // GlobalSearch this.nestedTableHeaderBAckgroundColor = '#eaeaea'; this.tableView = []; this.columnThreedotsMunuConfig = { showPinleft: true, showPinright: true, showAscending: true, showDescending: true, showFilter: true, showRowsGrouping: this.showRowsGrouping, showAutosizeAllColumns: false, showAutosizeThisColumn: false, showChoseColumns: false, showResetColumns: false, }; // /////////////////////////////////////////////////////////////////////////////////////////// // /////////////////////////////////////////////////////////////////////////////////////////// // Out Put Events // /////////////////////////////////////////////////////////////////////////////////////////// // /////////////////////////////////////////////////////////////////////////////////////////// //Change Table Layout this.changeLayout = new EventEmitter(); // Filter Apply event; this.filterOptions = new EventEmitter(); this.genericEvent = new EventEmitter(); this.tablePresetConfig = new EventEmitter(); this.sortingOrderOptions = new EventEmitter(); this.createUpdateConfigListing = new EventEmitter(); this.groupedColumns = []; this.activeCol = null; this.activeFilterCell = null; this.showActionsDropDown = false; this.sideMenuVisible = false; this.pivotMode = false; this.columnSearch = ''; this.expandAllAccordians = true; this.currentOpenedSideMenue = null; this.originalColumns = []; this.originalDataSet = []; this.activeTopButton = ''; this.filterColumnsList = []; this.groupBoxPadding = 200; this.presetName = ''; this.presetFilter = ''; this.searchTextPresetTable = ''; this.addFilterColumnInput = ''; this.searchInDropdown = ''; this.addFilterDropdownSearch = ''; this.topShowHideColumns = ''; this.choseColumnsSearch = ''; this.editinDropdownSearch = ''; this.isThreeDotsFilterOpen = false; this.confirmDelete = false; this.fontFamilies = [ // Common Excel fonts 'Arial', 'Arial Black', 'Bahnschrift', 'Calibri', 'Cambria', 'Cambria Math', 'Candara', 'Comic Sans MS', 'Consolas', 'Constantia', 'Corbel', 'Courier New', 'Ebrima', 'Franklin Gothic Medium', 'Gabriola', 'Gadugi', 'Georgia', 'Impact', 'Ink Free', 'Javanese Text', 'Leelawadee UI', 'Lucida Console', 'Lucida Sans Unicode', 'Malgun Gothic', 'Microsoft Himalaya', 'Microsoft JhengHei', 'Microsoft New Tai Lue', 'Microsoft PhagsPa', 'Microsoft Sans Serif', 'Microsoft Tai Le', 'Microsoft YaHei', 'Microsoft Yi Baiti', 'Mongolian Baiti', 'MS Gothic', 'MS PGothic', 'MS UI Gothic', 'MV Boli', 'Nirmala UI', 'Palatino Linotype', 'Segoe Print', 'Segoe Script', 'Segoe UI', 'Segoe UI Historic', 'Segoe UI Emoji', 'Segoe UI Symbol', 'SimSun', 'Sitka Small', 'Sylfaen', 'Symbol', 'Tahoma', 'Times New Roman', 'Trebuchet MS', 'Verdana', 'Webdings', 'Wingdings', 'Yu Gothic', 'sans-serif', 'serif', 'monospace' ]; this.fontSizes = [ '8', '9', '10', '11', '12', '14', '16', '18', '20', '24', '28', '32' ]; this.leftPinnedColumns = []; this.centerColumns = []; this.rightPinnedColumns = []; this.previewLeftPinnedColumns = []; this.previewCenterColumns = []; this.previewRightPinnedColumns = []; this.fakeScrollbarScrollLeft = 0; // Row Hover Logic Here this.hoveredRowId = null; this.isMenueHidden = false; // Rows Selection Logic Goes Here this.selectedRows = new Set(); this.showColumnPanel = false; this.draggingInGroupArea = false; this.visibleRowCount = 25; this.startIndex = 0; this.visibleRows = []; this.trackByRenderedIndex = (i, _row) => this.renderStart + i; this.isSyncingFromMain = false; this.isSyncingFromFake = false; this.mainScrollRaf = null; this.flattenedData = []; // requestAnimationFrame IDs this.fakeScrollRaf = null; // onMainScroll(event: Event) { // this.expandedCells.clear(); // const scrollTop = (event.target as HTMLElement).scrollTop; // if (this.groupedColumns?.length > 0) { // this.startIndex = 0; // this.cdr.detectChanges(); // return; // } // const overscan = this.overscan * 2; // this.startIndex = Math.floor(scrollTop / this.rowHeight); // const start = Math.max(0, this.startIndex - overscan); // const end = Math.min(this.flattenedData.length, this.startIndex + this.viewportRows + overscan); // this.visibleRows = this.flattenedData.slice(start, end); // this.cdr.detectChanges(); // } this.translateY = 0; this.viewportRows = 0; // how many rows fit in viewport (computed) this.firstIndex = 0; // index of the first visible row (clamped) this.renderStart = 0; // where the slice actually starts (firstIndex - overscan, clamped) this.scrollRaf = null; this.pendingScrollTop = 0; this.overscan = 10; // buffer rows above and below this.dragStartIndex = 0; // onDragStart(data: any, index: number) { // this.draggingColumn = data.column; // this.dragStartIndex = index; // } // onDragMove(data: any) { // const { clientX, clientY } = data.event; // const headers = Array.from( // document.querySelectorAll('.one-row-header-cells') // ) as HTMLElement[]; // headers.forEach((headerEl, idx) => { // const rect = headerEl.getBoundingClientRect(); // if ( // idx !== this.dragStartIndex && // clientX > rect.left && // clientX < rect.right && // clientY > rect.top && // clientY < rect.bottom // ) { // const otherIndex = idx; // console.log(`${this.dragStartIndex} --> ${otherIndex}`); // this.swapColumn(this.dragStartIndex, otherIndex); // console.log('Updated Columns: ', this.columns); // this.dragStartIndex = otherIndex; // update index // } // }); // } // swapColumn(previusIndex: number, currentIndex: number) { // const columns = structuredClone(this.columns); // const flattenColumns = this.flattenColumns(columns); // const visibleFlattenColumns = flattenColumns.filter( // (col) => col.is_visible // ); // const previusColumn = visibleFlattenColumns[previusIndex]; // const currentColumn = visibleFlattenColumns[currentIndex]; // console.log('Previus Column: ', previusColumn); // console.log('current clumn: ', currentColumn); // } // onDragEnd(data: any) { // this.draggingColumn = null; // this.dragStartIndex = null; // } // CDK DRAG DROP LOGIC GOES HERE this.canEnterToRowsGrouping = (drag, drop) => { // Example: Block if already pinned left const data = drag.data; if (data?.children && data?.children.length) { return data.children.some((col) => col.is_visible && col.isGroupable); } return data.is_visible && data.isGroupable; }; this.shouldDisableDroplistSorting = false; this.isDisableColumnGrouping = false; this.currentDraggingColumn = null; this.onSortGroup = async (event, section) => { const columns = this[section]; if (event.previousIndex === event.currentIndex) return; const visibleColumns = columns.filter((col) => { if (col?.children && Array.isArray(col.children)) { return col.children.some((child) => child.is_visible); } return col.is_visible; }); const previousHeader = visibleColumns[event.previousIndex].header; const currentHeader = visibleColumns[event.currentIndex].header; const visiblePrevIndex = visibleColumns.findIndex((col) => col.header === previousHeader); const visibleCurrIndex = visibleColumns.findIndex((col) => col.header === currentHeader); const getFields = (item) => { if (item?.children && Array.isArray(item.children)) { return item.children.map((child) => child.field); } return [item.field]; }; const prevFields = getFields(visibleColumns[visiblePrevIndex]); const currFields = getFields(visibleColumns[visibleCurrIndex]); const allFields = [...prevFields, ...currFields]; const cells = allFields.length ? [].concat(...allFields.map((field) => Array.from(document.querySelectorAll(`[field="${field}"]`)))) : []; const firstPositions = new Map(); cells.forEach((el) => firstPositions.set(el, el.getBoundingClientRect().left)); console.log("this .coluns", this.columns); moveItemInArray(this[section], visiblePrevIndex, visibleCurrIndex); const prevColIndexInColumns = this.columns.findIndex(item => item.header === previousHeader); const currColIndexInColumns = this.columns.findIndex(item => item.header === currentHeader); moveItemInArray(this.columns, prevColIndexInColumns, currColIndexInColumns); // await this.refreshPreviewColumns(); this.cdr.detectChanges(); allFields.forEach((field) => { const updatedCells = Array.from(document.querySelectorAll(`[field="${field}"]`)); updatedCells.forEach((el) => { const newLeft = el.getBoundingClientRect().left; const oldLeft = firstPositions.get(el); const deltaX = oldLeft - newLeft; if (deltaX !== 0) { el.style.willChange = 'transform'; el.style.transition = 'none'; el.style.transform = `translateX(${deltaX}px)`; void el.offsetWidth; el.style.transition = 'transform 400ms cubic-bezier(0.4, 0, 0.2, 1)'; el.style.transform = 'translateX(0)'; const handle = () => { el.style.transition = ''; el.style.transform = ''; el.style.willChange = ''; el.removeEventListener('transitionend', handle); }; el.addEventListener('transitionend', handle); } }); }); }; this.dropListIds = []; this.onChildDroplistSorted = async (event, section) => { if (event.previousIndex === event.currentIndex) return; const pinned = section == 'previewLeftPinnedColumns' ? 'left' : section == 'previewRightPinnedColumns' ? 'right' : ""; const column = event.item.data; let group = this.columns.find((col) => Array.isArray(col.children) && col.children.some((child) => child?.field === column?.field)); const groupIndex = this.columns.findIndex((col) => col.header === group.header); const filteredGroup = group?.children.filter((col) => { const isPinnedMatch = (pinned === "" && (!col?.pinned || col?.pinned === "")) || col?.pinned === pinned; return isPinnedMatch && col?.is_visible; }); const previousField = filteredGroup[event.previousIndex].field; const currentField = filteredGroup[event.currentIndex].field; const visiblePrevIndex = group.children.findIndex((col) => col.field == previousField); const visibleCurrIndex = group.children.findIndex((col) => col.field == currentField); const allFields = [previousField, currentField]; const cells = allFields.length ? [].concat(...allFields.map((field) => Array.from(document.querySelectorAll(`[field="${field}"]`)))) : []; const groupInSection = this[section].find((col) => Array.isArray(col.children) && col.children.some((child) => child?.field === column?.field)); const filterGroupInSection = this[section].find((col) => Array.isArray(col.children) && col.children.some((child) => child?.field === column?.field)); const groupInSectionIndex = this[section].findIndex((col) => col.header === groupInSection.header); const firstPositions = new Map(); cells.forEach((el) => firstPositions.set(el, el.getBoundingClientRect().left)); moveItemInArray(this.columns[groupIndex]?.children, visiblePrevIndex, visibleCurrIndex); moveItemInArray(this[section]?.[groupInSectionIndex].children, event.previousIndex, event.currentIndex); // await this.refreshPreviewColumns(); this.cdr.detectChanges(); allFields.forEach((field) => { const updatedCells = Array.from(document.querySelectorAll(`[field="${field}"]`)); updatedCells.forEach((el) => { const newLeft = el.getBoundingClientRect().left; const oldLeft = firstPositions.get(el); const deltaX = oldLeft - newLeft; if (deltaX !== 0) { el.style.willChange = 'transform'; el.style.transition = 'none'; el.style.transform = `translateX(${deltaX}px)`; void el.offsetWidth; el.style.transition = 'transform 400ms cubic-bezier(0.4, 0, 0.2, 1)'; el.style.transform = 'translateX(0)'; const handle = () => { el.style.transition = ''; el.style.transform = ''; el.style.willChange = ''; el.removeEventListener('transitionend', handle); }; el.addEventListener('transitionend', handle); } }); }); console.group('Group: ', group); }; this.isActiveFilterOpen = false; this.activeSubButton = ''; this.pageSizeOptions = [25, 50, 75, 100, 150, 200, 250, 300, 500]; this.searchTextForFilterDropDown = ''; this.isFilterOpen = false; this.showFilters = this.showSideMenu ? false : false; this.firstValue = ''; this.firstCondition = ''; this.secondValue = ''; this.secondCondition = ''; this.condition = ''; this.selectedFilterOptions = []; this.currentFilterSelectedIds = new Set(); // Cell Editing Work start here this.editingKey = null; this.activeCell = null; this.emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/; this.expandedCells = new Map(); this.zCounter = 21; // Picture cell Logic this.colorCombination = ['pic-comb1', 'pic-comb2', 'pic-comb4']; this.actionHide = true; this.xPos = 0; this.yPos = 0; this.isVisible = false; this.positionedYet = false; // ************************************************************************* // ************************************************************************* // ************************************************************************* // ************************************************************************* // Cell Selection Implemented Here Implemented Here // ************************************************************************* // ************************************************************************* // ************************************************************************* // ************************************************************************* this.selectedCells = []; this.selectedKeys = new Set(); this.selectionStart = null; this.isSelecting = false; this.selectionBounds = { top: null, bottom: null, left: null, right: null }; this.cellSelectionAutoScrollInterval = null; this.scrollSpeed = 100; this.scrollMargin = 150; // ************************************************************************* // ************************************************************************* // ************************************************************************* // ************************************************************************* // Row Selection from the Indexing Implemented Here // ************************************************************************* // ************************************************************************* // ************************************************************************* // ************************************************************************* this.rowSelectedIndexes = new Set(); this.rowSelecting = false; this.rowSelectionStartIndex = null; this.autoScrollInterval = null; // getSelectedDataForCopy(): any[][] { // const copiedRows: any[][] = []; // const findRowByVirtualIndex = (vIndex: number) => // this.dataSet.find((r: any) => r.__virtualIndex === vIndex); // if (this.rowSelectedIndexes && this.rowSelectedIndexes.size > 0) { // const sortedIndexes = [...this.rowSelectedIndexes].sort((a, b) => a - b); // for (const vIndex of sortedIndexes) { // const row = findRowByVirtualIndex(vIndex); // if (!row) continue; // const rowValues = this.columns.flatMap(col => // col.children && Array.isArray(col.children) // ? col.children.map((c: { field: string }) => // this.getNestedValue(row, c.field) ?? '' // ) // : this.getNestedValue(row, col.field) ?? '' // ); // copiedRows.push(rowValues); // } // } // if (this.selectedCells && this.selectedCells.length > 0) { // const rowsMap = new Map<number, any[]>(); // for (const cell of this.selectedCells) { // const row = findRowByVirtualIndex(cell.rowIndex); // if (!row) continue; // const col = this.columns[cell.colIndex]; // let fieldName = col?.field; // if (col?.children && col.children[cell.subColIndex]) { // fieldName = col.children[cell.subColIndex].field; // } // const value = this.getNestedValue(row, fieldName) ?? ''; // if (!rowsMap.has(cell.rowIndex)) rowsMap.set(cell.rowIndex, []); // rowsMap.get(cell.rowIndex)!.push(value); // } // const sortedCells = [...rowsMap.entries()] // .sort(([a], [b]) => a - b) // .map(([_, v]) => v); // copiedRows.push(...sortedCells); // } // if (copiedRows.length === 0) { // const activeCell = document.querySelector('.active-cell'); // if (activeCell) return [[activeCell.textContent?.trim() || '']]; // } // const maxCols = copiedRows.reduce((max, row) => Math.max(max, row.length), 0); // const normalized = copiedRows.map(row => { // const newRow = [...row]; // while (newRow.length < maxCols) newRow.push(''); // return newRow; // }); // return normalized; // } this.undoStack = []; this.redoStack = []; // Config flag this.keepMultipleExpandedDetails = true; // Hold expanded IDs (array for multiple, single value for single) this.expandededDetailRows = ''; this.openIndex = null; } async ngAfterViewInit() { setTimeout(async () => { // this.updateFlattenedData(); // this.computeViewportRows(); // this.updateVisibleRows(0); // this.cdr.detectChanges(); await this.SetColumnsDefaultWidth(); await this.refreshHeaders(); this.updateFlattenedData(); this.computeViewportRows(); this.updateVisibleRows(0); if (this.cellText) { const observer = new ResizeObserver(() => { this.cdr.detectChanges(); }); observer.observe(this.cellText.nativeElement); } this.cdr.detectChanges(); }, 300); } async ngOnChanges(changes) { if (changes['filtersConfig']) { await this.applyFilteroptionList(); this.checkFilterChangesEffect(); } if (changes['columns']?.currentValue?.length) { // this.columns = this.columnService.setColumnsQuery(this.columns); this.originalColumns = structuredClone(this.columns); if (this.dataGridContainer?.nativeElement?.offsetWidth) { await this.SetColumnsDefaultWidth(); await this.updateColumnWidthsAndGroups(); await this.refreshPreviewColumns(); this.setSectionsWidth(); this.cdr.detectChanges(); } this.generateDropListIds(); this.cdr.detectChanges(); requestAnimationFrame(() => { this.cdr.detectChanges(); }); } if (changes['dataSet']) { this.dataSet = this.dataSet?.map((row, i) => ({ ...row, __virtualIndex: i + 1 })); this.originalDataSet = structuredClone(this.dataSet); this.expandedCells.clear(); this.updateFlattenedData(); this.cdr.detectChanges(); setTimeout(() => { if (this.mainScroll?.nativeElement) { this.computeViewportRows(); this.updateVisibleRows(0); } }, 100); if (this.centerPinnedHeader?.nativeElement) { void this.centerPinnedHeader.nativeElement.offsetWidth; if (this.centerScrollableBody?.nativeElement) { this.centerScrollableBody.nativeElement.scrollLeft = this.centerPinnedHeader?.nativeElement?.scrollLeft; } this.cdr.detectChanges(); } } if (changes['checkboxState']?.currentValue?.reset) { this.selectedRows.clear(); this.cdr.detectChanges(); } if (changes['closeDropdown']) { if (this.closeDropdown.preset.closed) { this.activeTopButton = null; } } if (changes['oddRowsBackgroundColor']) { this.rowShadingEnabled = this.oddRowsBackgroundColor ? this.rowShadingEnabled = true : false; } } async applyFilteroptionList() { debugger; const updatedColumns = await this.commonSevice.applyFiltersToColumns(this.columns, this.filtersConfig); this.columns = [...updatedColumns]; this.cdr.detectChanges(); } async updateColumnWidthsAndGroups(columns = null) { if (!this.dataGridContainer) return; const containerWidth = this.dataGridContainer.nativeElement.offsetWidth; // Wrap in a promise so we can await const { left, center, right } = await new Promise(resolve => { const prepared = this.columnService.prepareColumns(columns ? columns : this.columns, containerWidth); resolve(prepared); }); this.leftPinnedColumns = left; this.centerColumns = center; this.rightPinnedColumns = right; await new Promise(r => setTimeout(r, 0)); this.cdr.detectChanges(); } async refreshPreviewColumns(columns = null) { if (!this.dataGridContainer) return; const containerWidth = this.dataGridContainer.nativeElement.offsetWidth; // Wrap prepareColumns in a Promise to make it awaitable const { left, center, right } = await new Promise(resolve => { const prepared = this.columnService.prepareColumns(columns ? columns : this.columns, containerWidth); resolve(prepared); }); this.previewLeftPinnedColumns = left; this.previewCenterColumns = center; this.previewRightPinnedColumns = right; await new Promise(r => setTimeout(r, 10)); this.cdr.detectChanges(); } async SetColumnsDefaultWidth() { const containerWidth = this.dataGridContainer?.nativeElement.offsetWidth; this.columns = this.columnService.assignDefaultWidths(this.columns, containerWidth); await new Promise(resolve => setTimeout(resolve, 0)); this.cdr.detectChanges(); } setSectionsWidth() { const left = document.querySelector('.left-pinned-body'); const center = document.querySelector('.center-scrollable-body'); const right = document.querySelector('.right-pinned-body'); if (left) { left.style.minWidth = `${this.leftPinnedHeader.nativeElement.offsetWidth}`; } if (center) { left.style.minWidth = `${this.centerPinnedHeader.nativeElement.offsetWidth}`; } if (right) { left.style.minWidth = `${this.rightPinnedHeader.nativeElement.offsetWidth}`; } } // onCenterBodyScroll(): void { // const scrollTop = this.centerPinnedBody.nativeElement.scrollTop; // const scrollLeft = this.centerPinnedBody.nativeElement.scrollLeft; // // Sync vertical scroll with left & right pinned bodies // this.leftPinnedBody.nativeElement.scrollTop = scrollTop; // this.rightPinnedBody.nativeElement.scrollTop = scrollTop; // // Sync horizontal scroll with center header // this.centerPinnedHeader.nativeElement.scrollLeft = scrollLeft; // this.centerFakeScrollbar.nativeElement.scrollLeft = scrollLeft; // } onLeftBodyScroll() { const scrollTop = this.leftPinnedBody.nativeElement.scrollTop; this.centerPinnedBody.nativeElement.scrollTop = scrollTop; this.rightPinnedBody.nativeElement.scrollTop = scrollTop; } onRightBodyScroll() { const scrollTop = this.rightPinnedBody.nativeElement.scrollTop; this.centerPinnedBody.nativeElement.scrollTop = scrollTop; this.leftPinnedBody.nativeElement.scrollTop = scrollTop; } onFakeScroll(event) { const target = event.target; this.fakeScrollbarScrollLeft = target.scrollLeft; this.centerPinnedBody.nativeElement.scrollLeft = target.scrollLeft; this.centerPinnedHeader.nativeElement.scrollLeft = target.scrollLeft; } getNestedValue(obj, field) { return field.split('.').reduce((acc, part) => acc && acc[part], obj); } isNestedValueArray(obj, field) { if (!obj || !field) return false; const value = field .split('.') .reduce((acc, part) => (acc && acc[part] !== undefined ? acc[part] : undefined), obj); return Array.isArray(value); } onResizeGroup(event, col, isRightPinned) { event.preventDefault(); event.stopPropagation(); const startX = event.clientX; const children = col.children || []; if (!children.length) return; const childWidths = children.map((child) => { const el = document.querySelector(`[field="${child.field}"]`); return { field: child.field, width: el?.offsetWidth || 0, }; }); const totalInitialWidth = childWidths.reduce((sum, col) => sum + col.width, 0); const onMouseMove = (moveEvent) => { let deltaX = moveEvent.clientX - startX; if (isRightPinned) { deltaX = -deltaX; } // Prevent shrinking too small if (totalInitialWidth + deltaX < children.length * 80) return; let totalNewWidth = 0; childWidths.forEach((child) => { const ratio = child.width / totalInitialWidth; const newWidth = Math.max(child.width + deltaX * ratio, 80); totalNewWidth += newWidth; const childEls = document.querySelectorAll(`[field="${child.field}"]`); childEls.forEach((el) => { const elHtml = el; elHtml.style.minWidth = `${newWidth}px`; elHtml.style.width = `${newWidth}px`; }); this.updateColumnWidthInSourceByField(child.field, newWidth); }); // ✅ Update group header width in DOM const groupHeaderEl = document.querySelector(`[group="${col.header}"]`); if (groupHeaderEl) { groupHeaderEl.style.width = `${totalNewWidth}px`; this.cdr.detectChanges(); } }; const onMouseUp = () => { if (this.tableFilterViewId) { this.savePreset('mouseUp'); } this.refreshHeaders(); window.removeEventListener('mousemove', onMouseMove); window.removeEventListener('mouseup', onMouseUp); const event = { columns: this.columns, type: 'createUpdateConfigListing', }; this.createUpdateConfigListing.emit(event); }; window.addEventListener('mousemove', onMouseMove); window.addEventListener('mouseup', onMouseUp); } updateColumnWidthInSourceByField(field, width) { const update = (columns) => { for (const col of columns) { if (col.children?.length) { update(col.children); } else if (col.field === field) { col.width = width; } } }; update(this.columns); this.cdr.detectChanges(); } onResizeColumn(event, col) { event.preventDefault(); event.stopPropagation(); const startX = event.clientX; const targetEl = document.querySelector(`[field="${col.field}"]`); const initialWidth = targetEl?.offsetWidth || 150; const matchingEls = document.querySelectorAll(`[field="${col.field}"]`); // 👉 Add highlight while resizing if (col?.pinned == 'right') { matchingEls.forEach((el) => { el.classList.add('resizing-highlight-right'); }); } else { matchingEls.forEach((el) => { el.classList.add('resizing-highlight'); }); } const onMouseMove = (moveEvent) => { let deltaX = moveEvent.clientX - startX; // 👉 Reverse if the column is pinned to the right if (col.pinned === 'right') { deltaX = -deltaX; } let newWidth = initialWidth + deltaX; if (newWidth < 80) return; matchingEls.forEach((el) => { const element = el; element.style.minWidth = `${newWidth}px`; element.style.width = `${newWidth}px`; }); this.updateColumnWidthInSourceByField(col.field, newWidth); }; const onMouseUp = () => { if (this.tableFilterViewId) { this.savePreset('mouseUp'); } this.refreshHeaders(); const event = { columns: this.columns, type: 'createUpdateConfigListing', }; this.createUpdateConfigListing.emit(event); matchingEls.forEach((el) => { el.classList.remove('resizing-highlight'); el.classList.remove('resizing-highlight-right'); }); window.removeEventListener('mousemove', onMouseMove); window.removeEventListener('mouseup', onMouseUp); }; window.addEventListener('mousemove', onMouseMove); window.addEventListener('mouseup', onMouseUp); } onResizeGroupBox(event) { event.preventDefault(); event.stopPropagation(); const startX = event.clientX; const targetEl = document.getElementById('groupBoxHeaderDiv'); const initialWidth = targetEl?.offsetWidth || 150; const onMouseMove = (moveEvent) => { const deltaX = moveEvent.clientX - startX; let newWidth = initialWidth + deltaX; if (newWidth < 100) newWidth = 100; if (targetEl) { targetEl.style.width = `${newWidth}px`; targetEl.style.minWidth = `${newWidth}px`; this.groupBoxPadding = newWidth; } }; const onMouseUp = () => { window.removeEventListener('mousemove', onMouseMove); window.removeEventListener('mouseup', onMouseUp); }; window.addEventListener('mousemove', onMouseMove); window.addEventListener('mouseup', onMouseUp); } onFilterChange(col) { const filterValue = col.filterValue?.toLowerCase() || ''; } // Get Body Height get bodyWrapperHeight() { const rows = this.showColumnsGrouping && this.showFilterRow ? 3 : (this.showColumnsGrouping || this.showFilterRow ? 2 : 1); const offset = this.headerRowHeight * rows + 17; return `calc(100% - ${offset}px)`; } onRowHover(row) { this.hoveredRowId = row._id || row.id; } onRowLeave() { this.hoveredRowId = null; } onDocumentClick(event) { const target = event.target; this.isVisible = false; if (!this.hasParentWithClass(target, [ 'three-dots', 'filter-icon-wrapper', 'column-menu', 'filter-menu', ])) { this.activeCol = null; this.activeFilterCell = null; } if (!this.elementRef.nativeElement.contains(event.target)) { this.showActionsDropDown = false; } const insideTopFilterRow = target.closest('.filter-tags') || target.closest('.add-filter-button-menu') || target.closest('.table-right-top-actions') || target.closest('.table-layout') || target.closest('.filter-row-filter-wrapper'); const isClickedInsideThreeDotsFilter = target.closest('.three-dots-filter') || target.closest('.filter-menu-container'); if (!isClickedInsideThreeDotsFilter) { this.closeThreeDotsMenuFilter(); } if (!insideTopFilterRow) { this.closeFilterDropdowns(event); } if (!target.closest('.set-default-preset')) { document.querySelector('.set-default-preset')?.classList?.remove('show'); } } closeThreeDotsMenuFilter() { this.isThreeDotsFilterOpen = false; } closeFilterDropdowns(event) { this.isFilterOpen = false; this.isActiveFilterOpen = false; this.activeTopButton = ''; this.activeFilterCell = null; } hasParentWithClass(element, classList) { let el = element; while (el !== null) { if (el.classList && classList.some((className) => el.classList.contains(className))) { return true; } el = el.parentElement; } return false; } openThreeDotsMenu(event, child) { event.preventDefault(); event.stopPropagation(); this.isMenueHidden = true; const containerWidth = this.dataGridContainer?.nativeElement?.offsetWidth; this.activeCol = child; this.activeFilterCell = null; const x = event.clientX; setTimeout(() => { const element = document.querySelector('.column-menu'); if (element) { if (x > (containerWidth / 2)) { element.style.left = `${x - 240}px`; } else { element.style.left = `${x}px`; } } this.isMenueHidden = false; this.cdr.detectChanges(); }, 0); } // //////////////////////////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////////////////////////// // Column Three Dots Actions // ////////////////////////////////////////////////////////////////////////////////////// // ////////////////////////////////////////////////////////////////////////////////////// sortAsc(col) { if (!col.is_sortable) return; col.order_by = 'asc'; const event = { order: 'asc', column: col }; this.cdr.detectChanges(); this.sortingOrderOptions.emit(event); } sortDesc(col) { if (!col.is_sortable) return; col.order_by = 'desc'; const event = { order: 'desc', column: col }; this.cdr.detectChanges(); this.sortingOrderOptions.emit(event); } resetSort(col) { this.sortingConfig = { field: '', order_by: '' }; const event = { field: null, order_by: null }; this.cdr.detectChanges(); this.sortingOrderOptions.emit(event); } async updateColumnPinInSourceByField(column, pinned) { const update = (columns) => { for (const col of columns) { if (col.children?.length) { update(col.children); } else if (col.field === column.field) { col.pinned = pinned; } } }; this.activeCol = null; update(this.columns); this.updateColumnWidthsAndGroups(); this.refreshPreviewColumns(); this.cdr.detectChanges(); const event = { columns: this.columns, type: 'createUpdateConfigListing', }; this.createUpdateConfigListing.emit(event); } autosizeColumn(cols) { this.activeCol = null; // Normalize input to an array const columnsToResize = Array.isArray(cols) ? cols : [cols]; // Helper: Flatten all visible leaf columns const getVisibleLeafColumns = (columns) => { const result = []; for (const column of columns) { if (column.children && Array.isArray(column.children)) { result.push(...getVisibleLeafColumns(column.children)); } else if (column.is_visible) { result.push(column); } } return result; }; const visibleColumns = getVisibleLeafColumns(this.columns); const visibleCount = visibleColumns.length; if (visibleCount === 0) return; const containerWidth = this.dataGridContainer?.nativeElement?.offsetWidth ?? 0; const equalWidth = Math.floor(containerWidth / visibleCount); // Resize each passed column for (const col of columnsToResize) { if (col && col.field) { col.width = equalWidth; this.updateColumnWidthInSourceByField(col.field, equalWidth); } } this.updateColumnWidthsAndGroups(); this.refreshPreviewColumns(); } getGroupWidth(group) { return (group?.children ?.filter((col) => col?.is_visible) ?.reduce((acc, col) => acc + (col?.width || 0), 0) || 0); } autosizeAllColumns() { this.activeCol = null; const getVisibleLeafColumns = (columns) => { const result = []; for (const column of columns) { if (column.children && Array.isArray(column.children)) { result.push(...getVisibleLeafColumns(column.children)); } else if (column.is_visible !== false) { result.push(column); } } return result; }; const visibleColumns = getVisibleLeafColumns(this.columns); const visibleCount = visibleColumns.length; if (visibleCount === 0) return; const containerWidth = this.dataGridContainer?.nativeElement?.offsetWidth ?? 0; const equalWidth = Math.max(Math.floor(containerWidth / visibleCount), 150); // Update widths for all visible columns visibleColumns.forEach((col) => { col.width = equalWidth; this.updateColumnWidthInSourceByField(col.field, equalWidth); }); this.refreshHeaders(); this.cdr.detectChanges(); } markColumnAsGrouped(columns, field) { for (const col of columns) { if (col.field === field) { col.isRowGrouped = true; return; } if (col.children?.length) { this.markColumnAsGrouped(col.children, field); } } } async groupBy(col) { this.activeCol = null; if (!col) return; this.loading = true; this.cdr.detectChanges(); if (col?.isGroupable) { this.groupedColumns.push(col); this.markColumnAsGrouped(this.columns, col.field); } const fields = this.groupedColumns.map((item) => item.field); this.dataSet = await this.groupDataAsync(this.originalDataSet, fields); setTimeout(() => { this.updateFlattenedData(); this.updateColumnWidthsAndGroups(); this.refreshPreviewColumns(); this.updateVisibleRows(0); this.loading = false; this.cdr.detectChanges(); }, 100); setTimeout(() => { this.groupBoxPadding = this.columnsGroupedBox?.nativeElement.offsetWidth; this.cdr.detectChanges(); }, 10); } async groupDataAsync(data, groupFields) { return new Promise(resolve => { setTimeout(() => { const result = this.groupData(data, groupFields); resolve(result); }, 0); }); } chooseColumns() { this.activeCol = null; this.show