UNPKG

@transunion-ui/tablejs

Version:
1,061 lines 358 kB
import { Directive, ElementRef, EventEmitter, Inject, InjectionToken, Injector, Input, Output } from '@angular/core'; import { DragAndDropGhostComponent } from './../../components/drag-and-drop-ghost/drag-and-drop-ghost.component'; import { DOCUMENT } from '@angular/common'; import { TablejsGridProxy } from './../../shared/classes/tablejs-grid-proxy'; import { ColumnReorderEvent } from './../../shared/classes/events/column-reorder-event'; import { ColumnResizeEvent } from './../../shared/classes/events/column-resize-event'; import { GridEvent } from './../../shared/classes/events/grid-event'; import { ScrollViewportDirective } from './../../directives/scroll-viewport/scroll-viewport.directive'; import { ResizeSensor } from 'css-element-queries'; import { Subject } from 'rxjs'; import { OverlayConfig } from '@angular/cdk/overlay'; import { ComponentPortal } from '@angular/cdk/portal'; import * as i0 from "@angular/core"; import * as i1 from "./../../services/grid/grid.service"; import * as i2 from "./../../services/directive-registration/directive-registration.service"; import * as i3 from "@angular/cdk/overlay"; import * as i4 from "./../../services/scroll-dispatcher/scroll-dispatcher.service"; import * as i5 from "./../../services/operating-system/operating-system.service"; export class GridDirective extends TablejsGridProxy { constructor(elementRef, resolver, gridService, directiveRegistrationService, document, overlay, scrollDispatcherService, operatingSystem, rendererFactory) { super(); this.elementRef = elementRef; this.resolver = resolver; this.gridService = gridService; this.directiveRegistrationService = directiveRegistrationService; this.document = document; this.overlay = overlay; this.scrollDispatcherService = scrollDispatcherService; this.operatingSystem = operatingSystem; this.rendererFactory = rendererFactory; this.dragging = false; this.reordering = false; this.startX = 0; this.startY = 0; this.stylesByClass = []; this.id = null; this.viewport = null; this.viewportID = null; this.currentClassesToResize = []; this.startingWidths = []; this.minWidths = []; this.totalComputedMinWidth = 0; this.totalComputedWidth = 0; this.defaultTableMinWidth = 25; this.gridTemplateClasses = []; this.gridOrder = []; this.classWidths = []; this.gridTemplateTypes = []; this.draggingColumn = null; this.colRangeGroups = []; this.lastDraggedOverElement = null; this.lastDraggedGroupIndex = -1; this.lastDraggedOverRect = null; this.lastDraggedGroupBoundingRects = null; this.lastMoveDirection = -1; this.resizableColumns = []; this.resizableGrips = []; this.reorderGrips = []; this.reorderableColumns = []; this.columnsWithDataClasses = []; this.rows = []; this.infiniteScrollViewports = []; this.mutationResizableColumns = []; this.mutationResizableGrips = []; this.mutationReorderGrips = []; this.mutationReorderableColumns = []; this.mutationColumnsWithDataClasses = []; this.mutationRows = []; this.mutationInfiniteScrollViewports = []; this.headTag = this.document.getElementsByTagName('head')[0]; this.styleContent = ''; this.headStyle = null; this.styleList = []; this.initialWidths = []; this.initialWidthsAreSet = undefined; this.lastColumns = []; this.contentResizeSensor = null; this.observer = null; this.isCustomElement = false; this.parentGroups = []; this.colData = null; this.colDataGroups = []; this.elementsWithHighlight = []; this.dragAndDropGhostComponent = null; this.dragOffsetX = 0; this.dragOffsetY = 0; this.reorderHandleColOffset = 0; this.scrollbarWidth = 0; // class used for setting order this.reorderableClass = 'reorderable-table-row'; // fragments this.widthStyle = null; this.widthStyleFragment = null; this.reorderHighlightStyle = null; this.reorderHighlightStyleFragment = null; this.subGroupStyles = []; this.subGroupFragments = []; this.gridOrderStyles = []; this.gridOrderFragments = []; this.subGroupStyleObjs = {}; this.scrollbarAdjustmentFragment = null; this.scrollbarAdjustmentStyle = null; this.resizeMakeUpPercent = 0; this.resizeMakeUpPerColPercent = 0; this.scrollViewportDirective = null; this.hiddenColumnIndices = []; this.hiddenColumnChanges = new Subject(); this.HIDDEN_COLUMN_CLASS = 'column-is-hidden'; this.DRAG_AND_DROP_GHOST_OVERLAY_DATA = new InjectionToken('DRAG_AND_DROP_GHOST_OVERLAY_DATA'); this.animationFrameIDs = []; this.linkClass = undefined; this.resizeColumnWidthByPercent = false; this.columnResizeStart = new EventEmitter(); this.columnResize = new EventEmitter(); this.columnResizeEnd = new EventEmitter(); this.columnReorder = new EventEmitter(); this.columnReorderStart = new EventEmitter(); this.dragOver = new EventEmitter(); this.columnReorderEnd = new EventEmitter(); this.preGridInitialize = new EventEmitter(true); this.gridInitialize = new EventEmitter(true); console.log('TableJS has been moved! Please install the newest versions from https://www.npmjs.com/package/@tablejs/community (npm install @tablejs/community).'); this.registerDirectiveToElement(); this.attachMutationObserver(); } registerDirectiveToElement() { this.elementRef.nativeElement.gridDirective = this; this.elementRef.nativeElement.parentElement.gridDirective = this; } attachMutationObserver() { if (!this.observer) { const ths = this; this.observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { ths.updateMutations(mutation); }); }); this.observer.observe(this.elementRef.nativeElement, { // configure it to listen to attribute changes attributes: true, subtree: true, childList: true, characterData: false }); } } updateMutations(mutation) { if (mutation.type === 'childList') { const addedNodes = Array.from(mutation.addedNodes); addedNodes.forEach(node => { this.directiveRegistrationService.registerNodeAttributes(node); this.getChildNodes(node); }); } } getChildNodes(node) { node.childNodes.forEach((childNode) => { this.directiveRegistrationService.registerNodeAttributes(childNode); if (childNode.getAttribute) { this.getChildNodes(childNode); } }); } ngAfterViewInit() { const viewport = this.elementRef.nativeElement.querySelector('*[tablejsScrollViewport]'); if (viewport !== null && (viewport.scrollViewportDirective === null || viewport.scrollViewportDirective === undefined)) { // attach directive const viewportRef = new ElementRef(viewport); this.scrollViewportDirective = new ScrollViewportDirective(viewportRef, this.gridService, this.document, this.directiveRegistrationService, this.scrollDispatcherService, this.operatingSystem, null, this.rendererFactory); this.scrollViewportDirective.registerCustomElementsInputs(viewport); this.scrollViewportDirective.ngOnInit(); this.scrollViewportDirective.ngAfterViewInit(); } // Close observer if directives are registering this.elementRef.nativeElement.directive = this; if (!this.document['hasPointerDownListener']) { this.pointerListenerFunc = (e) => { let el = e.target; if (el) { while (el !== null && el.getAttribute('tablejsGrid') === null) { el = el.parentElement; } if (el) { el['directive'].onPointerDown(e); } } }; this.document.addEventListener('pointerdown', this.pointerListenerFunc); this.document['hasPointerDownListener'] = true; } const animationFrameID = window.requestAnimationFrame((timestamp) => { this.onEnterFrame(this, timestamp); }); this.animationFrameIDs.push(animationFrameID); } onEnterFrame(ths, timestamp) { if (this.columnsWithDataClasses.length > 0) { this.observer?.disconnect(); } if (this.columnsWithDataClasses.length === 0 && this.mutationColumnsWithDataClasses.length === 0) { const animationFrameID = window.requestAnimationFrame((tmstamp) => { ths.onEnterFrame(ths, tmstamp); }); this.animationFrameIDs.push(animationFrameID); return; } if (this.columnsWithDataClasses.length === 0 && this.mutationColumnsWithDataClasses.length !== 0) { this.isCustomElement = true; this.resizableColumns = this.mutationResizableColumns.concat(); this.resizableGrips = this.mutationResizableGrips.concat(); this.reorderGrips = this.mutationReorderGrips.concat(); this.reorderableColumns = this.mutationReorderableColumns.concat(); this.columnsWithDataClasses = this.mutationColumnsWithDataClasses.concat(); this.rows = this.mutationRows.concat(); this.infiniteScrollViewports = this.mutationInfiniteScrollViewports.concat(); this.mutationResizableColumns = []; this.mutationResizableGrips = []; this.mutationReorderGrips = []; this.mutationReorderableColumns = []; this.mutationColumnsWithDataClasses = []; this.mutationRows = []; this.mutationInfiniteScrollViewports = []; } const allElementsWithDataResizable = this.columnsWithDataClasses; const el = allElementsWithDataResizable[0]; const resizeClasses = this.getResizableClasses(el); const resizeCls = resizeClasses[0]; const firstEl = this.elementRef.nativeElement.getElementsByClassName(resizeCls)[0]; if (!this.initialWidthSettingsSubscription$) { this.initialWidthSettingsSubscription$ = this.gridService.containsInitialWidthSettings.subscribe(hasWidths => { this.initialWidthsAreSet = hasWidths; }); } if (!this.hiddenColumnChangesSubscription$) { this.hiddenColumnChangesSubscription$ = this.hiddenColumnChanges.subscribe((change) => { if (change) { const relatedHeader = this.getRelatedHeader(change.hierarchyColumn.element); relatedHeader.hideColumn = change.hidden; if (change.wasTriggeredByThisColumn) { this.updateHiddenColumnIndices(); const hideColumnIf = change.hierarchyColumn.element.hideColumnIf; hideColumnIf.updateHeadersThatCanHide(); } if (!change.hidden) { if (change.wasTriggeredByThisColumn) { this.currentClassesToResize = this.getResizableClasses(relatedHeader); const avgWidthPerColumn = this.getAverageColumnWidth(); this.setMinimumWidths(); const totalTableWidth = this.viewport.clientWidth; let newWidth = avgWidthPerColumn * this.currentClassesToResize.length; this.currentClassesToResize.forEach(className => { const classIndex = this.gridTemplateClasses.indexOf(className); if (this.resizeColumnWidthByPercent) { this.classWidths[classIndex] = (avgWidthPerColumn / totalTableWidth * 100).toString() + '%'; // average all percentages } else { this.classWidths[classIndex] = Math.max(avgWidthPerColumn, this.minWidths[classIndex]); } }); if (this.resizeColumnWidthByPercent) { this.fitWidthsToOneHundredPercent(); } this.updateWidths(newWidth); } } this.setGridOrder(); } }); } if (this.parentGroups.length === 0) { this.setParentGroups(allElementsWithDataResizable); } const maxColumnsPerRow = this.parentGroups[this.parentGroups.length - 1].length; if (firstEl === undefined || firstEl === null) { const animationFrameID = window.requestAnimationFrame((tmstamp) => { ths.onEnterFrame(ths, tmstamp); }); this.animationFrameIDs.push(animationFrameID); } else { const keys = Object.keys(this.initialWidths); if (this.initialWidthsAreSet === true && keys.length < maxColumnsPerRow) { const animationFrameID = window.requestAnimationFrame((tmstamp) => { ths.awaitWidths(ths, tmstamp); }); this.animationFrameIDs.push(animationFrameID); } else { this.checkForGridInitReady(); } } } canHideColumn(column) { return column.hideColumnIf.canHide; } getFlattenedHierarchy() { const hierarchy = this.getColumnHierarchy(); return hierarchy.columnGroups.reduce((prev, curr) => { let arr = [curr]; if (curr.subColumns) { arr = arr.concat(this.getSubColumns(curr)); } return prev.concat(arr); }, []); } getSubColumns(item) { if (item.subColumns.length === 0) { return []; } let arr = []; for (let i = 0; i < item.subColumns.length; i++) { const subItem = item.subColumns[i]; arr = arr.concat(subItem); if (subItem.subColumns.length > 0) { arr = arr.concat(this.getSubColumns(subItem)); } } return arr; } getColumnHierarchy() { const hierarchy = { columnGroups: [] }; const highestLevelGroup = this.colDataGroups[0]; const hierarchyGroup = highestLevelGroup.map((item) => { let levelCount = 0; return { level: levelCount, element: item.child, parent: item.parent, parentColumn: null, subColumns: this.getHierarchySubColumns(item, levelCount) }; }); hierarchy.columnGroups = hierarchyGroup; return hierarchy; } getHierarchySubColumns(item, levelCount) { levelCount++; if (!item.subGroups || item.subGroups.length === 0) { return []; } const subColumns = item.subGroups.map((subItem) => { return { level: levelCount, element: subItem.child, parent: subItem.parent, parentColumn: item.child, subColumns: this.getHierarchySubColumns(subItem, levelCount) }; }); return subColumns; } checkForGridInitReady() { const allElementsWithDataResizable = this.columnsWithDataClasses; const el = allElementsWithDataResizable[0]; const resizeClasses = this.getResizableClasses(el); const resizeCls = resizeClasses[0]; const keys = Object.keys(this.initialWidths); const maxColumnsPerRow = this.parentGroups[this.parentGroups.length - 1].length; if (this.initialWidthsAreSet === true && (keys.length < maxColumnsPerRow || !this.initialWidths[resizeCls])) { const animationFrameID = window.requestAnimationFrame((tmstamp) => { this.awaitWidths(this, tmstamp); }); this.animationFrameIDs.push(animationFrameID); } else if (this.initialWidthsAreSet === undefined) { const animationFrameID = window.requestAnimationFrame((tmstamp) => { this.awaitWidths(this, tmstamp); }); this.animationFrameIDs.push(animationFrameID); } else { if (!this.linkClass) { this.initGrid(); } else { const animationFrameID = window.requestAnimationFrame((tmstamp) => { this.awaitSingleFrame(this, tmstamp); }); this.animationFrameIDs.push(animationFrameID); } } } awaitWidths(ths, timestamp) { this.checkForGridInitReady(); } awaitSingleFrame(ths, timestamp) { this.initGrid(); } onPointerDown(event) { this.addPointerListeners(); if (!this.getResizeGripUnderPoint(event)) { return; } // only drag on left mouse button if (event.button !== 0) { return; } // disables unwanted drag and drop functionality for selected text in browsers this.clearSelection(); const el = this.elementRef.nativeElement; let resizeHandles; if (this.elementRef.nativeElement.reordering) { return; } const reorderHandlesUnderPoint = this.getReorderHandlesUnderPoint(event); const colsUnderPoint = this.getReorderColsUnderPoint(event); if (reorderHandlesUnderPoint.length > 0 && colsUnderPoint.length > 0) { this.elementRef.nativeElement.reordering = true; this.draggingColumn = colsUnderPoint[0]; this.columnReorderStart.emit({ pointerEvent: event, columnDragged: this.draggingColumn, columnHovered: this.draggingColumn }); const customReorderStartEvent = new CustomEvent(ColumnReorderEvent.ON_REORDER_START, { detail: { pointerEvent: event, columnDragged: this.draggingColumn, columnHovered: this.draggingColumn } }); this.elementRef.nativeElement.parentElement.dispatchEvent(customReorderStartEvent); const elRect = this.draggingColumn.getBoundingClientRect(); this.dragOffsetX = (event.pageX - elRect.left) - window.scrollX; this.dragOffsetY = (event.pageY - elRect.top) - window.scrollY; this.removeDragAndDropComponent(); this.createDragAndDropComponent(); const dragNDropX = event.pageX - this.dragOffsetX; const dragNDropY = event.pageY - this.dragOffsetY; this.setDragAndDropPosition(dragNDropX, dragNDropY); this.attachReorderGhost(this.draggingColumn); this.setReorderHighlightHeight(this.draggingColumn); this.lastDraggedOverElement = this.draggingColumn; this.parentGroups.forEach((arr, index) => { if (arr.indexOf(this.lastDraggedOverElement) !== -1) { this.lastDraggedGroupIndex = index; } }); this.reorderHandleColOffset = reorderHandlesUnderPoint[0].getBoundingClientRect().left - this.draggingColumn.getBoundingClientRect().left; this.lastDraggedGroupBoundingRects = this.parentGroups[this.lastDraggedGroupIndex].map(item => { const boundingRect = item.getBoundingClientRect(); const rect = { left: item.getBoundingClientRect().left + this.getContainerScrollCount(item), right: boundingRect.right + window.scrollX, top: boundingRect.top, bottom: boundingRect.bottom, width: boundingRect.width, height: boundingRect.height }; rect.x = rect.left; rect.y = rect.top; rect.toJSON = {}; return rect; }); } resizeHandles = this.resizableGrips; if (resizeHandles.length === 0) { return; } // if no handle exists, allow whole row to be resizable if (resizeHandles.length > 0) { const resizableElements = document.elementsFromPoint(event.clientX, event.clientY); const els = resizableElements.filter(item => { let handleItem = null; resizeHandles.forEach(resizeHandle => { if (item === resizeHandle) { handleItem = resizeHandle; } }); return handleItem !== null; }); if (els.length === 0) { return; } } this.dragging = true; const elements = this.getResizableElements(event); if (elements.length === 0) { return; } this.totalComputedMinWidth = 0; this.totalComputedWidth = 0; this.minWidths = []; this.startingWidths = []; this.currentClassesToResize = this.getResizableClasses(elements[0]); // disallow resizing the rightmost column with percent sizing if (this.resizeColumnWidthByPercent) { const lastColumnClass = this.getLastVisibleColumnClass(); if (this.currentClassesToResize.indexOf(lastColumnClass) !== -1) { this.dragging = false; } } this.currentClassesToResize.forEach((className) => { const wdth = this.getClassWidthInPixels(className); if (!this.columnIsHiddenWithClass(className)) { this.totalComputedWidth += wdth; } this.startingWidths.push(wdth); }); this.setMinimumWidths(); this.startX = event.clientX; this.startY = event.clientY; this.columnResizeStart.emit({ pointerEvent: event, columnWidth: this.totalComputedWidth, columnMinWidth: this.totalComputedMinWidth, classesBeingResized: this.currentClassesToResize }); const customResizeStartEvent = new CustomEvent(ColumnResizeEvent.ON_RESIZE_START, { detail: { pointerEvent: event, columnWidth: this.totalComputedWidth, columnMinWidth: this.totalComputedMinWidth, classesBeingResized: this.currentClassesToResize } }); this.elementRef.nativeElement.parentElement.dispatchEvent(customResizeStartEvent); // stop interference with reordering columns event.preventDefault(); event.stopImmediatePropagation(); } getClassWidthInPixels(className) { const classIndex = this.gridTemplateClasses.indexOf(className); let wdth = this.classWidths[classIndex]; if (this.resizeColumnWidthByPercent) { wdth = wdth.replace('%', ''); // remove px let totalTableWidth = this.viewport.clientWidth; wdth = (Number(wdth) / 100 * totalTableWidth).toString(); } return Number(wdth); } setMinimumWidths() { this.gridTemplateClasses.forEach(className => { const firstEl = this.elementRef.nativeElement.querySelector('.' + className); const minWidth = window.getComputedStyle(firstEl).getPropertyValue('min-width'); let wdth = Number(minWidth.substring(0, minWidth.length - 2)); // remove px wdth = Number(wdth) < this.defaultTableMinWidth ? this.defaultTableMinWidth : wdth; // account for minimum TD size in tables if (this.currentClassesToResize.indexOf(className) !== -1 && !this.columnIsHiddenWithClass(className)) { this.totalComputedMinWidth += wdth; } this.minWidths.push(wdth); }); } attachReorderGhost(column) { this.dragAndDropGhostComponent?.updateView(column.reorderGhost, column.reorderGhostContext); } getContainerScrollCount(el) { if (!el) { return 0; } let scrollXCount = el.scrollLeft; while (el !== document.body) { el = el.parentElement; scrollXCount += el.scrollLeft; } // include scrolling on tablejs-grid component scrollXCount += el.parentElement.scrollLeft; return scrollXCount; } onPointerMove(event) { const ths = document['currentGridDirective']; if (ths.elementRef.nativeElement.reordering) { ths.clearSelection(); const dragNDropX = event.pageX - ths.dragOffsetX; const dragNDropY = event.pageY - ths.dragOffsetY; ths.setDragAndDropPosition(dragNDropX, dragNDropY); const trueMouseX = event.pageX - ths.reorderHandleColOffset + ths.getContainerScrollCount(ths.draggingColumn); if (!ths.lastDraggedOverElement) { return; } ths.columnReorder.emit({ pointerEvent: event, columnDragged: ths.draggingColumn, columnHovered: ths.lastDraggedOverElement }); const customReorderEvent = new CustomEvent(ColumnReorderEvent.ON_REORDER, { detail: { pointerEvent: event, columnDragged: ths.draggingColumn, columnHovered: ths.lastDraggedOverElement } }); ths.elementRef.nativeElement.parentElement.dispatchEvent(customReorderEvent); let moveDirection = 0; let currentRect; let currentColIndex; for (const rect of ths.lastDraggedGroupBoundingRects) { if (trueMouseX > rect.left && trueMouseX < rect.left + rect.width) { const elX = rect.left; const elW = rect.width; if ((trueMouseX - elX) >= elW / 2) { moveDirection = 1; } else { moveDirection = 0; } currentRect = rect; currentColIndex = ths.lastDraggedGroupBoundingRects.indexOf(rect); break; } } if (currentColIndex === undefined) { return; } if (ths.lastDraggedOverRect === currentRect && ths.lastMoveDirection === moveDirection) { return; } ths.lastMoveDirection = moveDirection; ths.lastDraggedOverRect = currentRect; ths.removeElementHighlight(ths.lastDraggedOverElement); ths.removeHighlights(ths.lastDraggedOverElement, moveDirection); const draggableInColumn = ths.parentGroups[ths.lastDraggedGroupIndex][currentColIndex]; ths.lastDraggedOverElement = draggableInColumn; let colRangeDraggedParentInd = -1; let colRangeDraggedChildInd = -1; let colRangeDroppedParentInd = -1; let colRangeDroppedChildInd = -1; let draggedInd = -1; let droppedInd = -1; let draggedGroup = null; const pGroup = ths.colDataGroups.forEach((group, groupInd) => group.forEach((columnData, index) => { const item = columnData.child; if (item === ths.getRelatedHeader(ths.draggingColumn)) { colRangeDraggedParentInd = groupInd; colRangeDraggedChildInd = ths.getRangePosition(columnData); // index; draggedInd = index; draggedGroup = group; } if (item === ths.getRelatedHeader(ths.lastDraggedOverElement)) { colRangeDroppedParentInd = groupInd; colRangeDroppedChildInd = ths.getRangePosition(columnData); // index; droppedInd = index; } })); if (ths.draggingColumn === ths.lastDraggedOverElement) { return; } let parentRanges = null; const tempRanges = ths.colRangeGroups.concat(); let parentRangeIndex = -1; tempRanges.sort((a, b) => b.length - a.length); tempRanges.forEach((item, index) => { if (!parentRanges && item.length < draggedGroup.length) { parentRanges = item; parentRangeIndex = ths.colRangeGroups.indexOf(item); } }); const fromOrder = (colRangeDraggedChildInd + 1); const toOrder = (colRangeDroppedChildInd + 1); // if has to stay within ranges, get ranges and swap if (parentRanges !== null) { ths.colRangeGroups[parentRangeIndex].forEach(range => { const lowRange = range[0]; const highRange = range[1]; if (fromOrder >= lowRange && fromOrder < highRange && toOrder >= lowRange && toOrder < highRange) { if (colRangeDraggedParentInd === colRangeDroppedParentInd) { if (moveDirection === 1) { ths.lastDraggedOverElement.classList.add('highlight-right'); } else { ths.lastDraggedOverElement.classList.add('highlight-left'); } ths.elementsWithHighlight.push({ el: ths.lastDraggedOverElement, moveDirection }); } } }); } else { if (colRangeDraggedParentInd === colRangeDroppedParentInd) { if (moveDirection === 1) { ths.lastDraggedOverElement.classList.add('highlight-right'); } else { ths.lastDraggedOverElement.classList.add('highlight-left'); } ths.elementsWithHighlight.push({ el: ths.lastDraggedOverElement, moveDirection }); } } } if (!ths.dragging) { return; } let mouseOffset = Math.round(event.clientX) - Math.round(ths.startX); const widthsNeedUpdating = Math.round(event.clientX) !== ths.startX; ths.startX = Math.round(event.clientX); // reset starting X let newWidth = ths.totalComputedWidth + mouseOffset; const allowableWidthChange = newWidth - ths.totalComputedMinWidth; if (allowableWidthChange <= 0) { return; } if (widthsNeedUpdating) { ths.updateWidths(newWidth); } ths.columnResize.emit({ pointerEvent: event, columnWidth: ths.totalComputedWidth, columnMinWidth: ths.totalComputedMinWidth }); const customResizeEvent = new CustomEvent(ColumnResizeEvent.ON_RESIZE, { detail: { pointerEvent: event, columnWidth: ths.totalComputedWidth, columnMinWidth: ths.totalComputedMinWidth } }); ths.elementRef.nativeElement.parentElement.dispatchEvent(customResizeEvent); } getLastVisibleColumnClass() { let highestOrderIndex = 0; let lastVisibleColumnClass = ''; this.gridTemplateClasses.forEach(className => { const classNameIndex = this.gridTemplateClasses.indexOf(className); const gridOrderIndex = this.gridOrder.indexOf(classNameIndex + 1); if (this.hiddenColumnIndices.indexOf(gridOrderIndex + 1) === -1) { if (gridOrderIndex > highestOrderIndex) { highestOrderIndex = gridOrderIndex; lastVisibleColumnClass = className; } } }); return lastVisibleColumnClass; } getRangePosition(columnData) { let subGroups = columnData.subGroups; let child = columnData; while (subGroups.length > 0) { child = subGroups[0]; subGroups = child.subGroups; } return child.nthChild - 1; } columnIsHiddenWithClass(className) { const classNameIndex = this.gridTemplateClasses.indexOf(className); const gridOrderIndex = this.gridOrder.indexOf(classNameIndex + 1); return this.hiddenColumnIndices.indexOf(gridOrderIndex + 1) !== -1; } getTotalGroupedColumnsVisible(sortableWidths) { const len = sortableWidths.length; let totalGroupedColumnsVisible = 0; for (let i = 0; i < len; i++) { const item = sortableWidths[i]; if (!this.columnIsHiddenWithClass(item.className)) { totalGroupedColumnsVisible++; } } return totalGroupedColumnsVisible; } getFirstGridOrderIndexAfterColumnGroup(sortableWidthGroup) { let maxIndex = -1; sortableWidthGroup.forEach(classItem => { const columnIndx = this.gridTemplateClasses.indexOf(classItem.className); const gridOrderIndex = this.gridOrder.indexOf(columnIndx + 1); if (maxIndex < gridOrderIndex) { maxIndex = gridOrderIndex; } }); return maxIndex + 1; } // returns a number in percent moved two decimal places over (10.245 is equal to 10.245%) getPostColumnWidthTotal(startingIndex) { let count = 0; for (let i = startingIndex; i < this.gridOrder.length; i++) { const clsIndex = this.gridOrder[i] - 1; let perc = Number(this.classWidths[clsIndex].toString().replace('%', '')); if (this.hiddenColumnIndices.indexOf(i + 1) === -1) { count += perc; } } return count; } // returns a number in percent moved two decimal places over (10.245 is equal to 10.245%) getPostColumnMinimumWidthTotal(startingIndex) { let count = 0; for (let i = startingIndex; i < this.gridOrder.length; i++) { const clsIndex = this.gridOrder[i] - 1; let perc = Number(this.minWidths[clsIndex].toString().replace('%', '')); if (this.hiddenColumnIndices.indexOf(i + 1) === -1) { count += perc; } } return count; } // returns a number in percent moved two decimal places over (10.245 is equal to 10.245%) getPreviousColumnWidthTotal(sortableWidthGroup) { let count = 0; let minIndex = Infinity; sortableWidthGroup.forEach(classItem => { const columnIndx = this.gridTemplateClasses.indexOf(classItem.className); const gridOrderIndex = this.gridOrder.indexOf(columnIndx + 1); if (minIndex > gridOrderIndex) { minIndex = gridOrderIndex; } }); for (let i = 0; i < minIndex; i++) { const classIndx = this.gridOrder[i] - 1; const wdth = Number(this.classWidths[classIndx].toString().replace('%', '')); count += wdth; } return count; } updateWidthsInPercent(newWidth, sortableWidths, totalGroupedColumnsVisible, sortableWidthGroup) { let totalTableWidth = this.viewport.clientWidth; let newWidthInPercent = newWidth / totalTableWidth * 100; const classMinWidths = sortableWidths.map((item) => item.minWidth); const groupMinWidthCalc = classMinWidths.reduce((prev, curr) => prev + curr); const firstGridOrderIndexAfterColumnGroup = this.getFirstGridOrderIndexAfterColumnGroup(sortableWidthGroup); const colsPastMinWidthCalc = this.getPostColumnMinimumWidthTotal(firstGridOrderIndexAfterColumnGroup); const colsPastMinWidthInPercent = colsPastMinWidthCalc / totalTableWidth * 100; const colsPastWidthPerc = this.getPostColumnWidthTotal(firstGridOrderIndexAfterColumnGroup); let prevColPercentTotal = 0; prevColPercentTotal = this.getPreviousColumnWidthTotal(sortableWidthGroup); const percentMoved = (prevColPercentTotal + newWidthInPercent + colsPastWidthPerc) - 100; if (prevColPercentTotal + newWidthInPercent + colsPastMinWidthInPercent > 100) { const actualPerCanMove = 100 - (prevColPercentTotal + colsPastMinWidthInPercent); newWidthInPercent = actualPerCanMove; } if (newWidth < groupMinWidthCalc) { newWidthInPercent = groupMinWidthCalc / totalTableWidth * 100; } sortableWidths.sort((item1, item2) => { const wdth1 = item1.width; const wdth2 = item2.width; if (wdth1 === wdth2) { return 0; } return wdth1 < wdth2 ? -1 : 1; }); const mappedGroupWidthsInPixels = sortableWidths.map(item => item.width); const totalPrevGroupWidths = mappedGroupWidthsInPixels.reduce((prev, curr) => prev + curr); const dispersedPercs = sortableWidths.map(item => item.width / totalPrevGroupWidths); const totalPercMoved = newWidthInPercent - (totalPrevGroupWidths / totalTableWidth * 100); let additionalPercentFromColumnsToSmall = 0; const sortableWidthsLen = sortableWidths.length; sortableWidths.forEach((item, index) => { const classIndex = this.gridTemplateClasses.indexOf(item.className); const minWidthInPercent = this.minWidths[classIndex] / totalTableWidth * 100; let calculatedPercent = dispersedPercs[index] * newWidthInPercent; if (calculatedPercent < minWidthInPercent) { additionalPercentFromColumnsToSmall += minWidthInPercent - calculatedPercent; calculatedPercent = minWidthInPercent; } else { const itemsRemaining = sortableWidthsLen - index - 1; if (itemsRemaining !== 0) { const extraAmtToRemove = additionalPercentFromColumnsToSmall / itemsRemaining; calculatedPercent -= extraAmtToRemove; additionalPercentFromColumnsToSmall -= extraAmtToRemove; } } const colWidthInPercent = calculatedPercent.toString() + '%'; this.classWidths[classIndex] = colWidthInPercent; }); let remainingPercToDisperse = totalPercMoved + additionalPercentFromColumnsToSmall; const maxPercsCanMovePerCol = []; for (let i = firstGridOrderIndexAfterColumnGroup; i < this.gridOrder.length; i++) { const clsIndex = this.gridOrder[i] - 1; let perc = Number(this.classWidths[clsIndex].toString().replace('%', '')); let minWidthPerc = (this.minWidths[clsIndex] / totalTableWidth * 100); if (this.hiddenColumnIndices.indexOf(i + 1) === -1) { maxPercsCanMovePerCol.push({ moveAmt: percentMoved > 0 ? perc - minWidthPerc : perc, classIndex: clsIndex }); } } const totalPercsCanMove = maxPercsCanMovePerCol.reduce((prev, curr) => prev + curr.moveAmt, 0.0000001); maxPercsCanMovePerCol.forEach((item) => { const percOfTotalMovementAllowed = item.moveAmt / totalPercsCanMove; const percOfRemainingDispersement = percOfTotalMovementAllowed * remainingPercToDisperse; const perc = Number(this.classWidths[item.classIndex].toString().replace('%', '')); const dispersedWidth = perc - percOfRemainingDispersement; this.classWidths[item.classIndex] = dispersedWidth + '%'; }); newWidth = newWidthInPercent / 100 * totalTableWidth; let amountMoved = newWidth - totalPrevGroupWidths; amountMoved = Math.round(amountMoved * 100) / 100; // round to 2 decimal points this.totalComputedWidth += amountMoved; const gridTemplateColumns = this.constructGridTemplateColumns(); this.gridTemplateTypes.forEach(styleObj => { styleObj.style.innerHTML = this.id + ' .' + this.reorderableClass + ' { display: grid; grid-template-columns:' + gridTemplateColumns + '; }'; this.setStyleContent(); }); } updateWidthsInPixels(newWidth, sortableWidths, totalGroupedColumnsVisible) { let remainingWidth = this.totalComputedWidth - newWidth; sortableWidths.forEach((item) => { const maxPercOfRemaining = 1 / totalGroupedColumnsVisible; let amountMoved = 0; const resizeID = this.id + ' .' + item.className; if (item.width - item.minWidth < maxPercOfRemaining * remainingWidth) { amountMoved = item.width - item.minWidth; } else { amountMoved = maxPercOfRemaining * remainingWidth; } amountMoved = Math.round(amountMoved * 100) / 100; // round to 2 decimal points const classIndex = this.gridTemplateClasses.indexOf(item.className); this.classWidths[classIndex] = (item.width - amountMoved); const markupItem = this.stylesByClass.filter(style => style.id === resizeID)[0]; let markup = resizeID + ' { width: ' + (item.width - amountMoved) + 'px }'; markupItem.markup = markup; markupItem.width = (item.width - amountMoved).toString(); this.totalComputedWidth -= amountMoved; }); const gridTemplateColumns = this.constructGridTemplateColumns(); this.gridTemplateTypes.forEach(styleObj => { styleObj.style.innerHTML = this.id + ' .' + this.reorderableClass + ' { display: grid; grid-template-columns:' + gridTemplateColumns + '; }'; this.setStyleContent(); }); } fitWidthsToOneHundredPercent() { const numericalWidths = this.classWidths.map((wdth, index) => Number(wdth.replace('%', ''))); const widthTotal = numericalWidths.reduce((prev, wdth) => { return prev + wdth; }, 0); const scaledWidths = numericalWidths.map((wdth, index) => { return { width: wdth / widthTotal * 100, index: index }; }); scaledWidths.forEach((item, index) => { this.classWidths[item.index] = scaledWidths[item.index].width.toString() + '%'; }); } updateWidths(newWidth) { const currentWidths = this.currentClassesToResize.map((resizeClass) => { return this.getClassWidthInPixels(resizeClass); }); const sortableWidths = currentWidths.map((w, index) => { return { minWidth: this.minWidths[index], width: w, className: this.currentClassesToResize[index] }; }); const visibleSortableWidths = sortableWidths.filter(item => { return !this.columnIsHiddenWithClass(item.className); }); const totalGroupedColumnsVisible = this.getTotalGroupedColumnsVisible(visibleSortableWidths); if (this.resizeColumnWidthByPercent) { this.updateWidthsInPercent(newWidth, visibleSortableWidths, totalGroupedColumnsVisible, sortableWidths); } else { this.updateWidthsInPixels(newWidth, visibleSortableWidths, totalGroupedColumnsVisible); } this.generateWidthStyle(); } generateWidthStyle() { let innerHTML = ''; this.stylesByClass.forEach(item => { innerHTML += item.markup; }); this.widthStyle.innerHTML = innerHTML; this.setStyleContent(); } getResizableClasses(el) { return el ? el['dataClasses'] : null; } setResizableStyles() { const allElementsWithDataResizable = this.columnsWithDataClasses; let el; const classesUsed = []; let fragment; let style; let styleText = ''; if (this.linkClass === undefined || this.gridService.linkedDirectiveObjs[this.linkClass] === undefined) { fragment = document.createDocumentFragment(); style = document.createElement('style'); style.type = 'text/css'; } else { fragment = this.gridService.linkedDirectiveObjs[this.linkClass].widthStyleFragment; style = this.gridService.linkedDirectiveObjs[this.linkClass].widthStyle; } let markup; if (this.linkClass === undefined || this.gridService.linkedDirectiveObjs[this.linkClass] === undefined) { for (let i = 0; i < allElementsWithDataResizable.length; i++) { el = allElementsWithDataResizable[i]; const resizeClasses = this.getResizableClasses(el); resizeClasses.forEach((resizeCls) => { if (classesUsed.indexOf(resizeCls) === -1) { const firstEl = this.elementRef.nativeElement.getElementsByClassName(resizeCls)[0]; const startingWidth = !!this.initialWidths[resizeCls] ? this.initialWidths[resizeCls] : firstEl.offsetWidth; // Override percentage if we have widthPercent enabled const startingWidthPercent = this.initialWidths[resizeCls]; const resizeID = this.id + ' .' + resizeCls; if (this.resizeColumnWidthByPercent || startingWidth.toString().includes('%')) { markup = resizeID + ' { width: ' + 100 + '%}'; this.resizeColumnWidthByPercent = true; this.attachContentResizeSensor(); } else { markup = resizeID + ' { width: ' + startingWidth + 'px }'; } styleText += markup; this.stylesByClass.push({ style, id: resizeID, resizeClass: resizeCls, markup, width: startingWidth }); classesUsed.push(resizeCls); } }); } } else { this.stylesByClass = this.gridService.linkedDirectiveObjs[this.linkClass].stylesByClass; } if (this.linkClass === undefined || this.gridService.linkedDirectiveObjs[this.linkClass] === undefined) { style.innerHTML = styleText; } fragment.appendChild(style); this.widthStyle = style; this.widthStyleFragment = fragment; this.addStyle(style, false); if (this.linkClass) { if (this.gridService.linkedDirectiveObjs[this.linkClass] === undefined) { this.gridService.linkedDirectiveObjs[this.linkClass] = {}; this.gridService.linkedDirectiveObjs[this.linkClass].gridDirective = this; this.gridService.linkedDirectiveObjs[this.linkClass].stylesByClass = this.stylesByClass; } this.gridService.linkedDirectiveObjs[this.linkClass].widthStyleFragment = fragment; this.gridService.linkedDirectiveObjs[this.linkClass].widthStyle = style; } } addStyle(style, addToContent = true) { if (this.styleList.indexOf(style) === -1) { this.styleList.push(style); } if (addToContent) { this.setStyleContent(); } } setStyleContent() { this.styleContent = ''; this.styleList.forEach(style => { this.styleContent += style.innerHTML; }); this.headStyle.innerHTML = this.styleContent; } moveStyleContentToProminent() { this.headTag.appendChild(this.headStyle); } setReorderStyles() { if (this.linkClass === undefined || (this.gridService.linkedDirectiveObjs[this.linkClass] && this.gridService.linkedDirectiveObjs[this.linkClass].reorderHighlightStyle === undefined)) { const fragment = document.createDocumentFragment(); const style = document.createElement('style'); style.type = 'text/css'; style.innerHTML = this.id + ' .highlight-left div:after, ' + this.id + ' .highlight-right div:after { height: 200px !important }'; fragment.appendChild(style); this.reorderHighlightStyle = style; this.reorderHighlightStyleFragment = fragment; this.addStyle(style, false); if (this.linkClass) { this.gridService.linkedDirectiveObjs[this.linkClass].reorderHighlightStyle = this.reorderHighlightStyle; this.gridService.linkedDirectiveObjs[this.linkClass].reorderHighlightStyleFragment = this.reorderHighlightStyleFragment; } } else { this.reorderHighlightStyle = this.gridService.linkedDirectiveObjs[this.linkClass].reorderHighlightStyle; thi