UNPKG

@adaptabletools/adaptable

Version:

Powerful data-agnostic HTML5 AG Grid extension which provides advanced, cutting-edge functionality to meet all DataGrid requirements

728 lines (727 loc) 32.3 kB
import { ColumnApiModule, } from 'ag-grid-enterprise'; import { ACTION_COLUMN_TYPE, CALCULATED_COLUMN_TYPE, FDC3_COLUMN_TYPE, FREE_TEXT_COLUMN_TYPE, } from '../AdaptableState/Common/AdaptableColumn'; import { ADAPTABLE_FDC3_ACTION_COLUMN_FRIENDLY_NAME, } from '../Utilities/Constants/GeneralConstants'; import { createUuid } from '../AdaptableState/Uuid'; import ArrayExtensions from '../Utilities/Extensions/ArrayExtensions'; import * as ModuleConstants from '../Utilities/Constants/ModuleConstants'; import { ALL_AG_GRID_MODULES } from './agGridModules'; import { agGridDataTypeDefinitions, ALL_ADAPTABLE_DATA_TYPES } from './agGridDataTypeDefinitions'; // AG GRID obfuscates its internals, this is (currently) the best way to get hold of its internal services const DANGER_AG_GRID_BEANS_MAP = {}; const getColumnApiModule = () => ColumnApiModule; export class AgGridAdapter { constructor(_adaptableInstance, config) { this._adaptableInstance = _adaptableInstance; const columnApiModuleReference = config?.getAgGridColumnApiModuleReference?.() ?? getColumnApiModule(); const ColumnDefFactory_Prototype = columnApiModuleReference?.beans?.[0]?.prototype; if (!ColumnDefFactory_Prototype) { console.error(`CRITICAL: could not get hold of AG Grid beans, this should never happen!`); } else { const ColumnDefFactory_Prototye_preWireBeans = ColumnDefFactory_Prototype.preWireBeans; ColumnDefFactory_Prototype.preWireBeans = function (beans) { ColumnDefFactory_Prototye_preWireBeans?.apply(this, arguments); const gridId = beans?.context?.getGridId(); if (!gridId) { console.error('CRITICAL: No gridId found in beans, this should never happen!'); } DANGER_AG_GRID_BEANS_MAP[gridId] = beans; }; } } destroy() { delete DANGER_AG_GRID_BEANS_MAP[this._agGridId]; this.initialGridOptions = null; this.DANGER_gridApi_from_args = null; this.DANGER_USE_GETTER_gridApi = null; this.DANGER_updateGridOptionsMonkeyPatcher = null; this._adaptableInstance = null; } get adaptableOptions() { return this._adaptableInstance.adaptableOptions; } get adaptableApi() { return this._adaptableInstance.api; } get logger() { return this._adaptableInstance.logger; } get agGridOptionsService() { return this._adaptableInstance.agGridOptionsService; } setAgGridId(agGridId) { this._agGridId = agGridId; } setAgGridApi(gridApi) { this.DANGER_USE_GETTER_gridApi = gridApi; } getAgGridApi(skipLogging) { if (this.DANGER_USE_GETTER_gridApi) { return this.DANGER_USE_GETTER_gridApi; } if (this.DANGER_gridApi_from_args) { return this.DANGER_gridApi_from_args; } if (!skipLogging) { console.error('AgGridApi is not available yet'); } } // #gridOpts_monkey_patch // we need to intercept some of the GridOptions updates and refresh the Adaptable state monkeyPatchingGridOptionsUpdates(agGridApi) { const agGridOptionsService = this.DANGER_getPrivateAgGridBeans()?.gos; if (!agGridOptionsService) { return; } const agGridOptionsServicePrototype = Object.getPrototypeOf(agGridOptionsService); const GridOptionsService_updateGridOptions = agGridOptionsServicePrototype.updateGridOptions; const self = this; this.DANGER_updateGridOptionsMonkeyPatcher = function ({ options, force, source = 'api', }) { const passedColumnDefs = options.columnDefs; if (passedColumnDefs) { const colDefsWithSpecialColumns = self.getColumnDefinitionsInclSpecialColumns(passedColumnDefs); const allDisplayedColIds = self .getAgGridApi() .getAllDisplayedColumns() .map((col) => col.getColId()); // mark as hidden the colDefs of special columns which are not visible self.patchColDefs(colDefsWithSpecialColumns, (colDef) => { if (self.adaptableApi.columnApi.internalApi.isSpecialColumn(colDef.colId)) { colDef.hide = !allDisplayedColIds.includes(colDef.colId); } }); options['columnDefs'] = colDefsWithSpecialColumns; self.logger.info(`Added SpecialColumns on GridOptions.columnDefs update (source=${source})`); } // `context` const passedContext = options.context; if (passedContext) { passedContext['__adaptable'] = self._adaptableInstance; passedContext['adaptableApi'] = self.adaptableApi; } // we mutated the options array, so it's OK to use the 'arguments' object GridOptionsService_updateGridOptions.apply(this, arguments); }; agGridOptionsService.updateGridOptions = this.DANGER_updateGridOptionsMonkeyPatcher; } DANGER_getPrivateAgGridBeans() { const beans = DANGER_AG_GRID_BEANS_MAP[this._agGridId]; if (!beans) { this.logger.consoleError('Could not get hold of AgGridBeans! This is a critical error and will prevent Adaptable from working correctly.'); } return beans; } DANGER_getLiveGridOptions() { return this.DANGER_getPrivateAgGridBeans()?.gridOptions; } isAgGridModuleRegistered(moduleName) { const agGridOptionsService = this.DANGER_getPrivateAgGridBeans()?.gos; if (!agGridOptionsService) { this.logger.consoleError('Could not get hold of GridOptionsService! This is a critical error and will prevent Adaptable from working correctly.'); return false; } return agGridOptionsService.isModuleRegistered(moduleName); } getAgGridRegisteredModules() { const allModulesSet = ALL_AG_GRID_MODULES; const registeredModules = []; allModulesSet.forEach((module) => { if (this.isAgGridModuleRegistered(module.moduleName)) { registeredModules.push(module); } }); return registeredModules; } getAgGridRegisteredModuleNames() { return this.getAgGridRegisteredModules().map((module) => module.moduleName); } getAgGridRootElement() { return this.DANGER_getPrivateAgGridBeans()?.eGridDiv; } /** * When AG Grid is rendered the first time, the AG GridApi is not yet set in the Adaptable context (as it's set only AFTER the grid is fully initialised) * yet we need it when evaluating custom GridOptions properties on the first render. * to handle this edge case, we try to extract the AG GridApi from the invocation arguments */ grabAgGridApiOnTheFly(args) { if (this.DANGER_USE_GETTER_gridApi || this.DANGER_gridApi_from_args) { return; } if (Array.isArray(args) && args[0] && typeof args[0].api === 'object' && // can't ise instanceof operator because gridApi is exported as interface typeof args[0].api?.getGridId === 'function') { this.DANGER_gridApi_from_args = args[0].api; } } updateGridOptions(options) { this.getAgGridApi()?.updateGridOptions(options); } getGridOption(key) { return this.getAgGridApi()?.getGridOption(key); } setGridOption(key, value) { this.getAgGridApi()?.setGridOption(key, value); } getUserGridOptionsProperty(propertyName) { return this.agGridOptionsService.getUserGridOptionsProperty(propertyName); } updateColumnFilterActiveState() { const filteredCols = new Set(); const columnFilters = this.adaptableApi.filterApi.columnFilterApi.getActiveColumnFilters(); columnFilters?.forEach?.((columnFilter) => { if (this.adaptableApi.filterApi.columnFilterApi.isColumnFilterActive(columnFilter)) { filteredCols.add(columnFilter.ColumnId); } }); const agGridApi = this.getAgGridApi(); (agGridApi.getColumns() || []).forEach((col) => { col.filterActive = filteredCols.has(col.getColId()); }); } getColumnDefinitionsInclSpecialColumns(agGridColDefs) { const allColDefs = this.enhanceColDefsWithSpecialColumns(agGridColDefs ?? this.getAgGridApi().getColumnDefs()); return allColDefs; } enhanceColDefsWithSpecialColumns(agGridColDefs) { this.assignColumnIdsToColDefs(agGridColDefs); const specialColDefs = this.getSpecialColDefs(); const isSpecialColDef = (colDef) => { const { type } = colDef; const colTypes = Array.isArray(type) ? type : [type]; return colTypes.some((colType) => [ ACTION_COLUMN_TYPE, CALCULATED_COLUMN_TYPE, FREE_TEXT_COLUMN_TYPE, FDC3_COLUMN_TYPE, ].includes(colType)); }; const processedSpecialColDefIds = []; const mapColDefs = (colDefs) => { return colDefs.map((colDef) => { if (this._adaptableInstance.agGridColumnAdapter.isColGroupDef(colDef)) { // if it's a group column, recursively map its children colDef.children = mapColDefs(colDef.children); return colDef; } else { if (!isSpecialColDef(colDef)) { // if it's not a special column, return it as is // without a minWidth, columns in details grid are VERY wide // so the line below fixes https://github.com/AdaptableTools/adaptable/issues/2559 return { minWidth: 10, ...colDef }; } const newlyCreatedSpecialColDef = specialColDefs.find((specialColDef) => specialColDef.colId === colDef.colId); if (newlyCreatedSpecialColDef) { // if it's a special column and we have a special col def for it, return the special col def processedSpecialColDefIds.push(colDef.colId); // merge the user defined colDef with the special col def // this way the user may provide some custom settings for the special col def (tooltip, etc) const mergedColDef = { // see above comment for minWidth minWidth: 10, ...colDef, ...newlyCreatedSpecialColDef, }; return mergedColDef; } else { // otherwise, return the original col def return colDef; } } }); }; let resultColDefs = mapColDefs(agGridColDefs); // check if there are any special colDefs that were not processed // in that case, add them to the end of the colDefs specialColDefs.forEach((specialColDef) => { if (!processedSpecialColDefIds.includes(specialColDef.colId)) { resultColDefs.push(specialColDef); } }); // remove special column that are no longer defined resultColDefs = resultColDefs.filter((colDef) => { if (isSpecialColDef(colDef)) { // must be in specialColDefs return specialColDefs.some((specialColDef) => specialColDef.colId === colDef.colId); } return true; }); return resultColDefs; } getSpecialColDefs() { const specialColDefs = [ ...this.adaptableApi.calculatedColumnApi.internalApi.getColDefsForCalculatedColumns(), ...this.adaptableApi.actionColumnApi.internalApi.getColDefsForActionColumns(), ...this.adaptableApi.freeTextColumnApi.internalApi.getColDefsForFreeTextColumns(), ...this.adaptableApi.fdc3Api.internalApi.getFdc3ActionColDefs(), ]; this.assignColumnIdsToColDefs(specialColDefs); return specialColDefs; } deriveSelectedCellInfoFromAgGrid() { const selected = this.getAgGridApi().getCellRanges(); const columns = []; const gridCells = []; // we iterate for each ranges selected.forEach((rangeSelection) => { let shouldIncludeRange = true; if (rangeSelection.startRow && rangeSelection.endRow) { let isStartRowPin = rangeSelection.startRow.rowPinned != null; let isEndRowPin = rangeSelection.endRow.rowPinned != null; // Warn user if trying to select pinned rows and if only selecting them, stop if (isStartRowPin) { if (isEndRowPin) { shouldIncludeRange = false; } this.logger.consoleWarn('Cannot select pinned rows in AG Grid.'); } if (shouldIncludeRange) { const y1 = Math.min(rangeSelection.startRow.rowIndex, rangeSelection.endRow.rowIndex); const y2 = Math.max(rangeSelection.startRow.rowIndex, rangeSelection.endRow.rowIndex); for (const column of rangeSelection.columns) { if (column != null) { const colId = column.getColId(); const selectedColumn = this.adaptableApi.columnApi.getColumnWithColumnId(colId); if (selectedColumn && !columns.includes(selectedColumn)) { columns.push(selectedColumn); } for (let rowIndex = y1; rowIndex <= y2; rowIndex++) { const rowNode = this.getAgGridApi().getDisplayedRowAtIndex(rowIndex); // we used NOT to return grouped rows but I think that was wrong - if someone wants to return them then that is up to them... // we definitely dont return pinned rows as they cannot be selected if (rowNode && !this.isPinnedRowNode(rowNode)) { const selectedCell = this.adaptableApi.gridApi.getGridCellFromRowNode(rowNode, colId); gridCells.push(selectedCell); } } } } } } }); const selectedCellInfo = { columns, gridCells, }; return selectedCellInfo; } deriveSelectedRowInfoFromAgGrid() { const nodes = this.getAgGridApi().getSelectedNodes(); const selectedRows = []; if (this.getAgGridApi().isPivotMode()) { // dont perform row selection in pivot mode return undefined; } if (ArrayExtensions.IsNotNullOrEmpty(nodes)) { nodes.forEach((node) => { const rowInfo = { isMaster: !!(node.master != null && node.master == true), isExpanded: !!(node.expanded != null && node.expanded == true), isGroup: !!(node.group != null && node.group == true), isSelected: true, isDisplayed: node.displayed == true, rowGroupLevel: node.level, }; const gridRow = { primaryKeyValue: this.adaptableApi.gridApi.getPrimaryKeyValueForRowNode(node), rowData: node.data, rowNode: node, rowInfo, }; selectedRows.push(gridRow); }); } return { gridRows: selectedRows }; } isPinnedRowNode(rowNode) { if (!rowNode) { return false; } if (rowNode.isRowPinned()) { return true; } return false; } createAdaptableColumnFromAgGridColumn(agGridColumn, colsToGroups) { const colId = agGridColumn.getColId(); const colDef = agGridColumn.getColDef(); const { columnApi } = this.adaptableApi; const ColumnId = colId; const pkColumn = this.adaptableOptions.primaryKey; const ColumnGroup = colsToGroups?.[ColumnId]; const isRealColumnGroup = ColumnGroup ? ColumnGroup.columnGroupId !== ColumnGroup.friendlyName : false; const isFdc3MainActionColumn = this.adaptableApi.fdc3Api.internalApi.isFdc3MainActionColumn(colId); let friendlyName; const isGeneratedRowGroupColumn = columnApi.isAutoRowGroupColumn(ColumnId); const isGeneratedPivotResultColumn = columnApi.isPivotResultColumn(ColumnId) && !agGridColumn.isPrimary(); const colExists = columnApi.doesColumnExist(ColumnId) || isGeneratedRowGroupColumn || isGeneratedPivotResultColumn; if (colExists) { friendlyName = columnApi.getFriendlyNameForColumnId(ColumnId); } else { const displayName = this.getAgGridApi().getDisplayNameForColumn(agGridColumn, 'header'); const columnFriendlyName = this.adaptableOptions.columnOptions.columnFriendlyName; const customFriendlyName = typeof columnFriendlyName === 'function' ? columnFriendlyName({ colId: colId, agColumn: agGridColumn, columnGroup: isRealColumnGroup ? ColumnGroup : undefined, displayName: displayName, }) : null; friendlyName = customFriendlyName ?? (isFdc3MainActionColumn ? ADAPTABLE_FDC3_ACTION_COLUMN_FRIENDLY_NAME : displayName); // Add Column Group;s friendlyname to the Column Friendly Name if its in a legitimate Column Group if (this.adaptableOptions.columnOptions.addColumnGroupToColumnFriendlyName && ColumnGroup && ColumnGroup.columnGroupId !== ColumnGroup.friendlyName) { friendlyName += ' [' + ColumnGroup.friendlyName + ']'; } } const dataType = isGeneratedPivotResultColumn ? 'number' : this.deriveAdaptableColumnDataType(agGridColumn, false); const isTreeColumn = this.isTreeColumn(isGeneratedRowGroupColumn); const visible = agGridColumn.isVisible(); const alwaysHidden = !visible && !isTreeColumn && colDef.lockVisible === true && colDef.suppressColumnsToolPanel === true && colDef.suppressFiltersToolPanel === true; const isGenerated = isGeneratedRowGroupColumn || isGeneratedPivotResultColumn; const abColumn = { Uuid: createUuid(), isTreeColumn, columnId: ColumnId, field: colDef.field, friendlyName: friendlyName, isPrimaryKey: ColumnId === pkColumn, dataType: dataType, visible, alwaysHidden, readOnly: this.isColumnReadonly(colDef), columnGroup: ColumnGroup, fieldOnly: isGenerated ? false : this.isColumnFieldonly(colDef), sortable: isGenerated ? false : this.isColumnSortable(colDef), filterable: this.isColumnFilterable(colDef), queryable: true, // override later when we have the column object exportable: true, // override later when we have the column object groupable: isGenerated ? false : this.isColumnRowGroupable(colDef), pivotable: isGenerated ? false : this.isColumnPivotable(colDef), aggregatable: isGenerated ? false : this.isColumnAggregetable(colDef), availableAggregationFunctions: null, aggregationFunction: null, moveable: this.isColumnMoveable(colDef), hideable: this.isColumnHideable(colDef), isGrouped: isGenerated ? false : this.isColumnRowGrouped(colDef), isGeneratedRowGroupColumn, isGeneratedPivotResultColumn, isFixed: this.isColumnFixed(colDef), pinned: this.getColumnPinnedPosition(colDef), columnTypes: this.getColumnTypes(colDef), isSparkline: this.isColumnSparkline(colDef), isCalculatedColumn: this.isCalculatedColumn(colDef), isFreeTextColumn: this.isFreeTextColumn(colDef), isActionColumn: this.isActionColumn(colDef), }; abColumn.queryable = this.isColumnQueryable(abColumn); abColumn.exportable = this.isColumnExportable(abColumn); if (abColumn.aggregatable) { abColumn.availableAggregationFunctions = this.getColumnAggregationFunctions(colDef); if (typeof colDef.aggFunc === 'string') { abColumn.aggregationFunction = colDef.aggFunc; } } return abColumn; } deriveAdaptableColumnDataType(agColumn, logWarning = true) { // Some columns can have no ID or Title. we return string as a consequence but it needs testing if (!agColumn) { this.logger.warn(`Column is undefined, returning 'text' for Type`); return 'text'; } const colDefType = [].concat(agColumn.getColDef()?.type || []).filter(Boolean); const skippedSpecialCols = ['actionColumn', 'fdc3Column']; if (skippedSpecialCols.some((specialColType) => colDefType.includes(specialColType))) { return 'unknown'; } let dataType = 'unknown'; // get the column type if already in store (and not unknown) const existingColumn = this.adaptableApi.columnApi.getColumnWithColumnId(agColumn.getId(), logWarning); if (existingColumn && existingColumn.dataType !== 'unknown') { return existingColumn.dataType; } // check for colDef dataType const colDefDataType = agColumn.getColDef().cellDataType; if (typeof colDefDataType === 'string' && ALL_ADAPTABLE_DATA_TYPES.includes(colDefDataType)) { return colDefDataType; } // see #agGridDataTypeDefinitions // theoretically, if AG Grid was not able to infer the type, we should not be able to either // but we give it a try, AG Grid is limited when the colDef has a valueGetter, valueParser, etc // see https://www.ag-grid.com/javascript-data-grid/cell-data-types/#inferring-data-types let row = this.getAgGridApi().getDisplayedRowAtIndex(0); if (row == null) { // possible that there will be no data. this.logger.consoleError(`No data in grid, returning type "unknown" for Column: "${agColumn.getColId()}". This will impact several Adaptable features, such as Filters and ColumnFormats.`); return 'unknown'; } // if it's a group we need the content of the group if (row.group) { const childNodes = row.childrenAfterGroup; if (ArrayExtensions.IsNullOrEmpty(childNodes)) { this.logger.consoleError(`No data in grid, returning type "unknown" for Column: "${agColumn.getColId()}". This will impact several Adaptable features, such as Filters and ColumnFormats.`); return 'unknown'; } row = childNodes[0]; } const value = this._agGridApi_getValue(agColumn, row); switch (typeof value) { case 'string': dataType = 'text'; break; case 'number': dataType = 'number'; break; case 'boolean': dataType = 'boolean'; break; case 'object': dataType = 'object'; break; } if (value instanceof Date) { dataType = 'date'; } else if (Array.isArray(value)) { const arrayDataType = ALL_ADAPTABLE_DATA_TYPES.find((arrayType) => { const dataTypeDefinition = agGridDataTypeDefinitions[arrayType]; const dataTypeMatching = dataTypeDefinition?.dataTypeMatcher?.(value); return dataTypeMatching; }); if (arrayDataType) { dataType = arrayDataType; } } this.logger.consoleWarn(`No defined type for column '${agColumn.getColId()}'. Defaulting to type of first row value: ${dataType}`); return dataType; } isColumnReadonly(colDef) { if (!colDef) { return true; } // if the column has conditional/dynamic editability, we assume some rows may be editable if (typeof colDef.editable === 'function') { return false; } // otherwise we evaluate the colDef.editable property (columns are NOT editable by default) return !colDef.editable; } isColumnFieldonly(colDef) { if (colDef.hide == true && colDef.initialHide == true && colDef.lockVisible == true) { return true; } return false; } isColumnSortable(colDef) { if (colDef && colDef.sortable != null) { return colDef.sortable; } return false; } isColumnRowGroupable(colDef) { if (colDef && colDef.enableRowGroup != null) { return colDef.enableRowGroup; } return false; } isColumnPivotable(colDef) { if (colDef && colDef.enablePivot != null) { return colDef.enablePivot; } return false; } isColumnAggregetable(colDef) { if (colDef && colDef.enableValue != null) { return colDef.enableValue; } return false; } getColumnAggregationFunctions(colDef) { return colDef.allowedAggFuncs || ['sum', 'min', 'max', 'count', 'avg', 'first', 'last']; // those are the default fns aggrid supports out-of-the-box } isTreeColumn(isGeneratedRowGroupColumn) { return this.adaptableApi.gridApi.isTreeDataGrid() ? isGeneratedRowGroupColumn : false; } isColumnMoveable(colDef) { if (!colDef) { return false; } if (colDef.suppressMovable != null && colDef.suppressMovable == true) { return false; } if (this.isColumnFixed(colDef)) { return false; } return true; } isColumnQueryable(abColumn) { return ( // !this.adaptableApi.columnApi.isPivotResultColumn(abColumn.columnId) && (this.adaptableApi.expressionApi.isColumnQueryable(abColumn)) ); } isColumnExportable(abColumn) { return this.adaptableApi.exportApi.isColumnExportable(abColumn); } isColumnHideable(colDef) { if (!colDef) { return false; } if (this.adaptableApi.gridApi.isTreeDataGrid() && this.adaptableApi.columnApi.isAutoRowGroupColumn(colDef.colId)) { return false; } if (colDef.lockVisible != null && colDef.lockVisible == true) { return false; } return true; } isCalculatedColumn(colDef) { return (this.adaptableApi.calculatedColumnApi.getCalculatedColumnForColumnId(colDef.colId) != null); } isFreeTextColumn(colDef) { return this.adaptableApi.freeTextColumnApi.getFreeTextColumnForColumnId(colDef.colId) != null; } isActionColumn(colDef) { return this.adaptableApi.actionColumnApi.getActionColumnForColumnId(colDef.colId) != null; } isColumnFilterable(colDef) { // follow agGrid logic which is that ONLY filterable if explicitly set if (this.adaptableApi.entitlementApi.getEntitlementAccessLevelForModule(ModuleConstants.ColumnFilterModuleId) == 'Hidden') { return false; } return colDef != null && colDef.filter != null && colDef.filter != false; } getColumnTypes(colDef) { if (!colDef.type) { return []; } const allTypes = typeof colDef.type === 'string' ? [colDef.type] : colDef.type; return allTypes; } getColumnPinnedPosition(colDef) { return colDef.pinned ? colDef.pinned === 'left' || colDef.pinned === true ? 'left' : 'right' : false; } // used for AG Grid when the column is FixedPinned, meaning it should never be unpinned isColumnFixed(colDef) { if (!colDef) { return false; } if (colDef.lockPosition != null && colDef.lockPosition == true) { return true; } if (colDef.lockPinned != null && colDef.lockPinned == true) { return true; } return false; } isColumnRowGrouped(colDef) { if (!colDef) { return false; } if (colDef.rowGroup != null && colDef.rowGroup == true) { return true; } if (colDef.rowGroupIndex != null) { return true; } return false; } isColumnSparkline(colDef) { // see https://www.ag-grid.com/javascript-data-grid/sparklines-overview/#enabling-sparklines return colDef?.cellRenderer === 'agSparklineCellRenderer'; } isVisibleNode(rowNode) { const foundNode = this.getAgGridApi() ?.getRenderedNodes() .find((n) => n.id == rowNode.id); return foundNode != null; } getFlattenedColDefs(colDefs = []) { const flattenedColDefs = []; colDefs.forEach((colDef) => { if (colDef.children) { flattenedColDefs.push(...this.getFlattenedColDefs(colDef.children)); } else { flattenedColDefs.push(colDef); } }); this.assignColumnIdsToColDefs(flattenedColDefs); return flattenedColDefs; } /** * Mutates the colDefs to ensure that each column has a colId */ assignColumnIdsToColDefs(colDefs = []) { const assignColId = (colDef) => { if (!colDef) { return; } if (colDef.field && !colDef.colId) { colDef.colId = colDef.field; } if (!colDef.colId) { this.logger.warn('A column is missing the colId - please check ', colDef, 'Either pass a "field" property or a "colId" property.'); } }; this.patchColDefs(colDefs, assignColId); } patchColDefs(colDefs = [], patchFn) { const applyPatch = (colDef) => { if (!colDef) { return; } if (!colDef.children) { patchFn(colDef); } if (colDef.children) { colDef.children.forEach((childColDef) => applyPatch(childColDef)); } }; colDefs.forEach((colDef) => applyPatch(colDef)); } traverseColDefs(colDefs, modifyFn) { const applyModification = (colDef) => { if ('children' in colDef) { const updatedChildren = colDef.children.map(applyModification).filter(Boolean); return { ...colDef, children: updatedChildren }; } else { return modifyFn(colDef); } }; return colDefs.map(applyModification).filter(Boolean); } getDefaultColumnDefinition() { // for early init phase, gridApi might not be ready yet return this.getAgGridApi(true)?.getGridOption('defaultColDef') ?? {}; } _agGridApi_getValue(colKey, rowNode, gridApi) { gridApi = gridApi || this.getAgGridApi(); return gridApi.getCellValue({ colKey, rowNode }); } }