UNPKG

@adaptabletools/adaptable

Version:

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

1,000 lines 58 kB
import kebabCase from 'lodash/kebabCase'; import merge from 'lodash/merge'; import { FilterWrapperFactory } from './FilterWrapper'; import { FloatingFilterWrapperFactory } from './FloatingFilterWrapper'; import { convertAdaptableStyleToCSS, getVariableColor, normalizeStyleForAgGrid, } from '../Utilities/Helpers/StyleHelper'; import StringExtensions from '../Utilities/Extensions/StringExtensions'; import { ACTION_COLUMN_TYPE, CALCULATED_COLUMN_TYPE, FDC3_COLUMN_TYPE, FREE_TEXT_COLUMN_TYPE, } from '../AdaptableState/Common/AdaptableColumn'; import tinycolor from 'tinycolor2'; import UIHelper from '../View/UIHelper'; import { getPercentBarRendererForColumn } from './cellRenderers/PercentBarRenderer'; import { getBadgeRendererForColumn } from './cellRenderers/BadgeRenderer'; import Helper from '../Utilities/Helpers/Helper'; import { AdaptableNumberEditor, AdaptableReactNumberEditor } from './editors/AdaptableNumberEditor'; import { AdaptableDateEditor, AdaptableReactDateEditor } from './editors/AdaptableDateEditor'; import { AgGridExportAdapter } from './AgGridExportAdapter'; import { AdaptableHelper } from '../Utilities/Helpers/AdaptableHelper'; import { isProvidedByAdaptable } from '../Utilities/adaptableOverrideCheck'; export function getEditorForColumnDataType(columnDataType, variant) { if (columnDataType === 'number') { return variant === 'react' ? AdaptableReactNumberEditor : AdaptableNumberEditor; } if (columnDataType === 'date' || columnDataType === 'dateString') { return variant === 'react' ? AdaptableReactDateEditor : AdaptableDateEditor; } } export class AgGridColumnAdapter { constructor(adaptableInstance) { this.adaptableInstance = adaptableInstance; this.colDefPropertyCache = new Map(); } getVariant() { return this.adaptableInstance.variant; } destroy() { this.adaptableInstance = null; this.colDefPropertyCache.clear(); this.colDefPropertyCache = null; } get adaptableApi() { return this.adaptableInstance.api; } get adaptableOptions() { return this.adaptableInstance.adaptableOptions; } get agGridApi() { return this.adaptableApi.agGridApi; } setColDefProperty(col, propertyName, propertyGetter) { const colId = col.getColId(); const colDef = col.getColDef(); const colSetupInfo = { col, colDef, colId, }; const userKey = `user.${colId}.${propertyName}`; const adaptableKey = `adaptable.${colId}.${propertyName}`; const value = colDef[propertyName]; const isUserDefined = value !== this.colDefPropertyCache.get(adaptableKey); if (isUserDefined) { this.colDefPropertyCache.set(userKey, value); } const userValue = this.colDefPropertyCache.get(userKey); const adaptableValue = propertyGetter(userValue); if (adaptableValue != null) { this.colDefPropertyCache.set(adaptableKey, adaptableValue); } let theValue = adaptableValue ?? userValue; this.adaptableInstance.forPlugins((plugin) => { if (plugin.interceptSetupColumnProperty) { theValue = plugin.interceptSetupColumnProperty(colSetupInfo, propertyName, theValue, this.adaptableApi); } }); if (propertyName === 'aggFunc') { if (colDef[propertyName] !== (theValue ?? null)) { this.agGridApi?.setColumnAggFunc(colId, theValue ?? null); } } if (theValue === undefined && colDef[propertyName] === undefined) { // already undefined, so don't set an own property to the same undefined value return; } colDef[propertyName] = theValue; } getUserColDefProperty(columnId, propertyName) { const userKey = `user.${columnId}.${propertyName}`; return this.colDefPropertyCache.get(userKey); } shouldSkipColumn(colId) { /** * This skips special columns that ag grid will likely implement in the future * * BUT DOES NOT SKIP GROUP COLUMNS or SELECTION COLUMNS!!! * * It's probably here for historical reasons - previously it used to skip the selection column * but now it's not skipping it anymore */ return (colId.startsWith('ag-Grid-') && !this.adaptableApi.columnApi.isAutoRowGroupColumn(colId) && !this.adaptableApi.columnApi.isSelectionColumn(colId)); } setupColumns() { const pivotMode = this.agGridApi.isPivotMode(); let cols = pivotMode ? // for pivot mode, we take only the initial columns this.agGridApi.getColumns() : // but for non-pivot mode, we ask for all columns // which also includes those that are generated for row grouping this.agGridApi.getAllGridColumns(); if (pivotMode) { const pivotResultColumns = this.agGridApi.getPivotResultColumns() || []; const autoGroupColumns = this.agGridApi .getAllGridColumns() .filter((column) => this.adaptableApi.columnApi.isAutoRowGroupColumn(column.getColId())); cols = [...autoGroupColumns, ...cols, ...pivotResultColumns]; } // this needs to be here, before the other setup below // so the setup methods below reference the correct columns in adaptable store cols.forEach((col) => { const colDef = col.getColDef(); const colId = col.getColId(); if (this.shouldSkipColumn(colId)) { return; } const abColumn = this.adaptableApi.columnApi.getColumnWithColumnId(colId); const colSetupInfo = { col, colDef, colId, abColumn, }; this.setupColumnCellRenderer(colSetupInfo); this.setupColumnCellStyle(colSetupInfo); this.setupColumnCellClass(colSetupInfo); this.setupColumnHeaderStyle(colSetupInfo); this.setupColumnHeaderClass(colSetupInfo); this.setupColumnValueGetter(colSetupInfo); this.setupColumnTooltipValueGetter(colSetupInfo); this.setupColumnFilter(colSetupInfo); this.setupColumnFloatingFilter(colSetupInfo); this.setupColumnValueFormatter(colSetupInfo); this.setupColumnEditable(colSetupInfo); this.setupColumnValueSetter(colSetupInfo); this.setupColumnComparator(colSetupInfo); this.setupColumnGetFindText(colSetupInfo); this.setupColumnCellEditor(colSetupInfo); this.setupColumnHeader(colSetupInfo); this.setupColumnQuickFilterText(colSetupInfo); this.setupColumnAllowedAggFuncs(colSetupInfo); this.setupColumnType(colSetupInfo); this.setupColumnCellDataType(colSetupInfo); }); } setupColumnValueGetter({ col }) { // need this here if we want plugins to intercept this.setColDefProperty(col, 'valueGetter', (userValue) => { return userValue; }); } setupColumnCellClass({ col, colId, abColumn }) { this.setColDefProperty(col, 'cellClass', (userCellClass) => { const formatColumns = this.adaptableApi.formatColumnApi.internalApi.getFormatColumnWithStyleClassNameForColumn(abColumn, { target: 'cell' }); const quickSearchTextMatchStyle = this.getQuickSearchTextMatchStyle(); const quickSearchCurrentTextMatchStyle = this.getQuickSearchCurrentTextMatchStyle(); const cellClass = (params) => { const gridCell = this.adaptableApi.gridApi.getGridCellFromRowNode(params.node, abColumn.columnId); if (!gridCell.column) { return null; } // if a VisualExcel report format export is in progress, we are interested only in the Excel Style Class if (this.adaptableApi.exportApi.internalApi.isVisualDataExportInProgress()) { const userDefinedCellClass = typeof userCellClass === 'function' ? userCellClass(params) : userCellClass; const cellClassKey = AgGridExportAdapter.getExcelClassNameForCell(colId, gridCell.primaryKeyValue, userDefinedCellClass); return this.adaptableInstance.agGridExportAdapter.getExcelStyleIdForCellClassKey(cellClassKey); } const isQuickSearchActive = this.isQuickSearchActive(gridCell); const editableClassName = this.getEditableCellClass(gridCell, params); const readonlyClassName = this.getReadonlyCellClass(gridCell, params); const editedClassName = this.getEditedCellClass(gridCell, params); const highlightAlertClassName = this.getAlertCellClass(gridCell, params); const flashingClassName = this.getFlashingCellClass(gridCell, params); const styledColumn = this.adaptableApi.styledColumnApi.getStyledColumnForColumnId(colId); const hasStyledColumn = !!styledColumn && !styledColumn.IsSuspended; const noteClassName = this.getNoteCellClassName(gridCell, params); const commentsClassName = this.getCommentCellClassName(gridCell, params); const returnValue = [ typeof userCellClass === 'function' ? userCellClass(params) : userCellClass, !hasStyledColumn && formatColumns.length ? this.getFormatColumnCellClass(formatColumns, abColumn, params) : null, // isQuickSearchActive && hasQuickSearchStyleClassName ? quickSearchStyleClassName : null, isQuickSearchActive && (quickSearchTextMatchStyle || quickSearchCurrentTextMatchStyle) ? 'ab-QuickSearchFind' : null, editableClassName, readonlyClassName, editedClassName, highlightAlertClassName, flashingClassName, noteClassName, commentsClassName, ] // we flatten the array because some rules ('userCellClass' etc) might return a string[] .flat() .filter((x) => !!x); const result = returnValue.length ? returnValue : undefined; return result; }; return cellClass; }); } setupColumnHeaderClass({ col, colId, abColumn }) { this.setColDefProperty(col, 'headerClass', (userHeaderClass) => { const headerClass = (params) => { let baseHeaderClass = []; // inherit styles from user provided colDef property const userDefinedHeaderClass = typeof userHeaderClass === 'function' ? userHeaderClass(params) : userHeaderClass; if (userDefinedHeaderClass) { baseHeaderClass = typeof userDefinedHeaderClass === 'string' ? [userDefinedHeaderClass] : Array.isArray(userDefinedHeaderClass) ? userDefinedHeaderClass : [userDefinedHeaderClass]; } if (params.floatingFilter) { // we NEVER style floating filters return baseHeaderClass; } if (params.columnGroup) { // we TEMPORARILY do NOT style column groups // see https://github.com/AdaptableTools/adaptable/issues/2947#issuecomment-3062304655 return baseHeaderClass; } const columnId = params.column.getColId(); const adaptableColumn = this.adaptableApi.columnApi.getColumnWithColumnId(columnId); if (!adaptableColumn) { return baseHeaderClass; } const target = 'columnHeader'; const formatColumns = this.adaptableApi.formatColumnApi.internalApi.getFormatColumnWithStyleClassNameForColumn(abColumn, { target, }); if (!formatColumns.length) { return baseHeaderClass; } const formatColumnClasses = formatColumns .map((formatColumn) => { if (formatColumn.Style?.ClassName && this.adaptableApi.formatColumnApi.internalApi.formatColumnShouldRenderInHeader(formatColumn, abColumn)) { return formatColumn.Style?.ClassName; } }) .filter((x) => !!x); return [...baseHeaderClass, ...formatColumnClasses]; }; return headerClass; }); } setupColumnCellStyle({ col }) { this.setColDefProperty(col, 'cellStyle', (userCellStyle) => { const quickSearchStyle = this.getQuickSearchCellStyle(); const quickSearchTextMatchStyle = this.getQuickSearchTextMatchStyle(); const quickSearchCurrentTextMatchStyle = this.getQuickSearchCurrentTextMatchStyle(); const textMatchStyle = quickSearchTextMatchStyle ? Object.entries(quickSearchTextMatchStyle).reduce((acc, [key, value]) => { // needed as AG-Grid vanilla turns all CSS props // to kebab, while AG Grid React does not // @ts-ignore acc[`--ab-dynamic-${kebabCase(key)}`] = value; return acc; }, {}) : undefined; const currentTextMatchStyle = quickSearchCurrentTextMatchStyle ? Object.entries(quickSearchCurrentTextMatchStyle).reduce((acc, [key, value]) => { // @ts-ignore acc[`--ab-dynamic-${kebabCase(key)}`] = value; return acc; }, {}) : undefined; const hasQuickSearchStyle = quickSearchStyle != undefined || quickSearchCurrentTextMatchStyle != undefined; const cellStyle = (params) => { const columnId = params.column.getColId(); const gridCell = this.adaptableApi.gridApi.getGridCellFromRowNode(params.node, columnId); if (!gridCell || !gridCell.column) { return {}; } const isQuickSearchActive = hasQuickSearchStyle && this.isQuickSearchActive(gridCell); const isCurrentMatch = this.adaptableApi.agGridApi.findGetActiveMatch()?.node === params.node; const textStyleToApply = isCurrentMatch ? { ...textMatchStyle, ...currentTextMatchStyle } : textMatchStyle; let baseStyles = {}; // this is required because otherwise, when AG Grid filters, it refreshed the pivotResultColDef and the base styles get lost // if pivot result col: inherit styles from base column if (this.adaptableApi.columnApi.isPivotResultColumn(columnId)) { const baseColumn = params.column.getColDef()?.pivotValueColumn; if (baseColumn) { const baseColDefCellStyle = baseColumn?.getColDef()?.cellStyle; const baseColParams = { ...params, column: baseColumn }; baseStyles = typeof baseColDefCellStyle === 'function' ? baseColDefCellStyle(baseColParams) : baseColDefCellStyle; } } else { // inherit styles from user provided colDef property baseStyles = typeof userCellStyle === 'function' ? userCellStyle(params) : userCellStyle; } const result = { ...baseStyles, ...this.getReadOnlyCellStyle(gridCell, params), ...this.getEditableCellStyle(gridCell, params), ...this.getEditedCellStyle(gridCell, params), ...this.getFormatColumnAndStyledColumnCellStyle(gridCell.column, params), ...(isQuickSearchActive ? quickSearchStyle : {}), ...(isQuickSearchActive && textStyleToApply ? textStyleToApply : {}), ...this.getAlertCellStyle(gridCell, params), ...this.getFlashingCellStyle(gridCell, params), ...this.getCellHighlightStyle(gridCell, params), }; return normalizeStyleForAgGrid(result); }; return cellStyle; }); } setupColumnHeaderStyle({ col }) { this.setColDefProperty(col, 'headerStyle', (userHeaderStyle) => { const headerStyleFunc = (params) => { let baseStyles = {}; // inherit styles from user provided colDef property baseStyles = typeof userHeaderStyle === 'function' ? userHeaderStyle(params) : userHeaderStyle; if (params.floatingFilter) { // we NEVER style floating filters return baseStyles; } const columnId = params.column.getColId(); const adaptableColumn = this.adaptableApi.columnApi.getColumnWithColumnId(columnId); if (!adaptableColumn) { return baseStyles; } const result = { ...baseStyles, ...this.getFormatColumnHeaderStyle(adaptableColumn, params), }; if (result['borderColor']) { // by default, header cells don't have a border // if the user defines a border-color, we assume he wants to show a border result['borderWidth'] = `1px`; result['borderStyle'] = 'solid'; } return normalizeStyleForAgGrid(result); }; return headerStyleFunc; }); } setupColumnCellEditor({ colId, col, colDef, abColumn }) { const shouldShowSelectCellEditor = this.adaptableApi.userInterfaceApi.internalApi.shouldShowSelectCellEditor(abColumn); const hasRichSelectCellEditor = this.adaptableInstance.agGridAdapter.isAgGridModuleRegistered('RichSelect'); this.setColDefProperty(col, 'cellEditor', () => { if (shouldShowSelectCellEditor) { return hasRichSelectCellEditor ? 'agRichSelectCellEditor' : 'agSelectCellEditor'; } else { if (colDef.cellEditor) { return colDef.cellEditor; } const cellDataTypeEditor = getEditorForColumnDataType(abColumn.dataType, this.getVariant()); return cellDataTypeEditor; } }); this.setColDefProperty(col, 'cellEditorParams', (params) => { if (shouldShowSelectCellEditor) { return (params) => { const gridCell = this.adaptableApi.gridApi.getGridCellFromRowNode(params?.node, colId); const options = this.adaptableApi.gridApi.internalApi.getDistinctEditDisplayValuesForColumn({ columnId: colId, gridCell, currentSearchValue: '', }); const valueToLabelMap = new Map(); const values = options.then((options) => options.map((option) => { valueToLabelMap.set(option.value, option.label); return option.value; })); return { values, formatValue: (value) => valueToLabelMap.get(value) ?? value, }; }; } }); } setupColumnCellRenderer({ col, colId, abColumn }) { this.setColDefProperty(col, 'cellRenderer', () => { const styledColumn = this.adaptableApi.styledColumnApi.getStyledColumnForColumnId(abColumn.columnId); if (styledColumn && !styledColumn.IsSuspended) { if (styledColumn.PercentBarStyle) { return getPercentBarRendererForColumn(styledColumn, abColumn, this.adaptableApi); } if (styledColumn.BadgeStyle) { return getBadgeRendererForColumn(styledColumn.BadgeStyle, abColumn, this.adaptableApi); } if (styledColumn.SparklineStyle) { return 'agSparklineCellRenderer'; } } }); this.setColDefProperty(col, 'cellRendererParams', (userDefined) => { const styledColumn = this.adaptableApi.styledColumnApi.getStyledColumnForColumnId(abColumn.columnId); if (styledColumn && !styledColumn.IsSuspended) { if (styledColumn.SparklineStyle) { const sanitizedSparklineOptions = AdaptableHelper.removeAdaptableObjectPrimitives(styledColumn.SparklineStyle.options); const sparklineOptions = merge({}, userDefined?.sparklineOptions, sanitizedSparklineOptions); return { ...userDefined, sparklineOptions, }; } } }); } setupColumnTooltipValueGetter({ col, colId, abColumn }) { let hasTooptip = false; this.setColDefProperty(col, 'tooltipValueGetter', () => { const styledColumn = this.adaptableApi.styledColumnApi.getStyledColumnForColumnId(colId); if (styledColumn && !styledColumn.IsSuspended && styledColumn.PercentBarStyle && styledColumn.PercentBarStyle.ToolTipText) { hasTooptip = true; if (styledColumn?.PercentBarStyle) { return (params) => { const min = this.adaptableApi.styledColumnApi.internalApi.getNumericStyleMinValue(styledColumn, abColumn, params.node, params.value); const max = this.adaptableApi.styledColumnApi.internalApi.getNumericStyleMaxValue(styledColumn, abColumn, params.node, params.value); const textOptions = styledColumn.PercentBarStyle.ToolTipText; let returnValue = ''; if (textOptions.includes('CellValue')) { returnValue = params.value; } if (textOptions.includes('PercentageValue')) { const clampedValue = Helper.clamp(params.value, min, max); const percentageValue = ((clampedValue - min) / (max - min)) * 100; returnValue += ' ' + `(${percentageValue.toFixed(0)}%)`; } return returnValue ? returnValue : params.value; }; } } }); } setupColumnQuickFilterText({ col, abColumn }) { this.setColDefProperty(col, 'getQuickFilterText', (userGetQuickFilterText) => { if (userGetQuickFilterText) { return userGetQuickFilterText; } return (params) => { const visibleColumnsMap = this.adaptableApi.layoutApi.getCurrentVisibleColumnIdsMapForTableLayout(); const isVisible = visibleColumnsMap[abColumn.columnId]; if (!isVisible) { return ''; } return this.adaptableApi.gridApi.getDisplayValueFromRowNode(params.node, abColumn.columnId); }; }); } setupColumnAllowedAggFuncs({ col, abColumn }) { this.setColDefProperty(col, 'allowedAggFuncs', () => { return abColumn.availableAggregationFunctions; }); } setupColumnType(columnSetupInfo) { const { col, colId } = columnSetupInfo; // AG Grid introduced since v30.x an inferred cellDataType // the problem is that it breaks the default value formatter and/or editor (especially for Date columns) this.setColDefProperty(col, 'type', (original_columnType) => { const originalTypes = original_columnType == undefined ? [] : Array.isArray(original_columnType) ? original_columnType : [original_columnType]; const columnTypes = new Set(originalTypes); if (this.adaptableApi.columnApi.isCalculatedColumn(colId)) { columnTypes.add(CALCULATED_COLUMN_TYPE); } if (this.adaptableApi.columnApi.isFreeTextColumn(colId)) { columnTypes.add(FREE_TEXT_COLUMN_TYPE); } if (this.adaptableApi.columnApi.isActionColumn(colId)) { columnTypes.add(ACTION_COLUMN_TYPE); } if (this.adaptableApi.columnApi.isFdc3Column(colId)) { columnTypes.add(FDC3_COLUMN_TYPE); } return Array.from(columnTypes); }); } setupColumnCellDataType(columnSetupInfo) { const { col } = columnSetupInfo; this.setColDefProperty(col, 'cellDataType', (original_cellDataType) => { return original_cellDataType ?? true; }); } setupColumnHeader({ col }) { this.setColDefProperty(col, 'headerValueGetter', (original_headerValueGetter) => { // see #customize_header if (!isProvidedByAdaptable(original_headerValueGetter)) { this.adaptableApi.logWarn(`colDef.headerValueGetter is defined for column '${col.getColId()}', and overrides the Adaptable custom header mechanism! We recommend using a ColumnOptions.columnHeader instead!`); } return original_headerValueGetter; }); } setupColumnFilter({ col, colDef }) { if (!this.adaptableOptions.filterOptions.useAdaptableFiltering) { return; } // setup Auto Group Column Filter if (this.adaptableApi.columnApi.isAutoRowGroupColumn(col.getColId())) { if (this.adaptableApi.gridApi.isTreeDataGrid()) { this.setColDefProperty(col, 'filter', (original_filter) => { const autoGroupColumnDef = this.agGridApi.getGridOption('autoGroupColumnDef'); if (autoGroupColumnDef.filter != undefined) { // we plan to provide a TreeListColumnFilter // until then, it's the user's responsibility to explicitly set the filter in the provided `autoGroupColumnDef` return original_filter; } else { // if no filter is explicitly set, we do NOT provide a filter return false; } }); } else { this.setColDefProperty(col, 'filter', () => { return 'agGroupColumnFilter'; }); } return; } // setup "normal" column filter this.setColDefProperty(col, 'filter', () => { if (!colDef.filter) { return; } this.agGridApi.destroyFilter(col); return FilterWrapperFactory(this.adaptableInstance); }); } setupColumnFloatingFilterTemporarily(initialGridOptions) { if (!this.adaptableOptions.filterOptions.useAdaptableFiltering) { return; } initialGridOptions.columnDefs ?.filter((colDef) => !this.isColGroupDef(colDef)) .map((colDef) => { const isFloatingFilterEnabled = initialGridOptions.defaultColDef?.floatingFilter || colDef.floatingFilter; if (isFloatingFilterEnabled) { colDef.floatingFilterComponent = FloatingFilterWrapperFactory(this.adaptableInstance); } }); } setupColumnFloatingFilter({ col, colDef }) { const isFloatingFilterDisabled = !colDef.floatingFilter || !this.adaptableOptions.filterOptions.useAdaptableFiltering || !this.adaptableOptions.filterOptions.columnFilterOptions.showQuickFilter; if (this.adaptableApi.columnApi.isAutoRowGroupColumn(col.getColId())) { this.setColDefProperty(col, 'floatingFilter', (original_floatingFilter) => { // the floating filter for the group column is "inherited" from the base column // via the colDef.filter = 'agGroupColumnFilter' // see #group_inherit_column_filter // https://www.ag-grid.com/javascript-data-grid/grouping-single-group-column/#inherit-row-grouped-columns-filters // https://www.ag-grid.com/javascript-data-grid/grouping-multiple-group-columns/#filtering return original_floatingFilter; }); this.setColDefProperty(col, 'suppressFloatingFilterButton', () => { // hide button for multi column groups return this.adaptableApi.columnApi.isAutoRowGroupColumnForMulti(col.getColId()); }); return; } this.setColDefProperty(col, 'floatingFilterComponent', () => { if (isFloatingFilterDisabled) { return; } return FloatingFilterWrapperFactory(this.adaptableInstance); }); this.setColDefProperty(col, 'floatingFilter', (original_floatingFilter) => { if (isFloatingFilterDisabled) { return; } return FloatingFilterWrapperFactory(this.adaptableInstance); }); this.setColDefProperty(col, 'suppressFloatingFilterButton', () => { return !isFloatingFilterDisabled; }); } setupColumnValueFormatter({ col, abColumn }) { this.setColDefProperty(col, 'valueFormatter', (userPropertyValue) => { const activeFormatColumnsWithDisplayFormat = this.adaptableApi.formatColumnApi.internalApi.getFormatColumnsWithDisplayFormatForColumn(abColumn, { target: 'cell' }); if (!activeFormatColumnsWithDisplayFormat.length) { return; } return (params) => { const { node, value } = params; const mostRelevantFormatColumn = this.adaptableApi.formatColumnApi.internalApi.getMostRelevantFormatColumnForColumn(activeFormatColumnsWithDisplayFormat, abColumn, { node, value }); if (!mostRelevantFormatColumn) { // ALL FormatColumns are conditional and NONE of them are relevant for this row return value; } const formatterOptions = mostRelevantFormatColumn.DisplayFormat.Options; if (mostRelevantFormatColumn.DisplayFormat.Formatter === 'NumberFormatter') { // change the Number format - if the scope allows it if (this.adaptableApi.columnScopeApi.isColumnInNumericScope(abColumn, mostRelevantFormatColumn.Scope)) { let cellValue = params.value; if (typeof params.value?.toNumber === 'function' && typeof params.value?.toString === 'function') { // aggregation values are wrapped in an AG Grid specific object cellValue = params.value.toNumber(); } return this.adaptableApi.formatColumnApi.internalApi.getNumberFormattedValue(cellValue, params.node, abColumn, formatterOptions); } } if (mostRelevantFormatColumn.DisplayFormat.Formatter === 'DateFormatter') { // change the Date format - if the scope allows it if (this.adaptableApi.columnScopeApi.isColumnInDateScope(abColumn, mostRelevantFormatColumn.Scope)) { let cellValue = params.value; if (typeof params.value?.toNumber === 'function' && typeof params.value?.toString === 'function') { // aggregation values are wrapped in an AG Grid specific object cellValue = params.value.toString(); } return this.adaptableApi.formatColumnApi.internalApi.getDateFormattedValue(cellValue, params.node, abColumn, formatterOptions); } } if (mostRelevantFormatColumn.DisplayFormat.Formatter === 'StringFormatter') { // change the String format - if the scope allows it if (this.adaptableApi.columnScopeApi.isColumnInTextScope(abColumn, mostRelevantFormatColumn.Scope)) { let cellValue = params.value; if (typeof params.value?.toNumber === 'function' && typeof params.value?.toString === 'function') { // aggregation values are wrapped in an AG Grid specific object cellValue = params.value.toString(); } return this.adaptableApi.formatColumnApi.internalApi.getStringFormattedValue(cellValue, params.node, abColumn, formatterOptions); } } // should NEVER arrive at this line, but just to be sure return value; }; }); } setupColumnEditable({ col }) { this.setColDefProperty(col, 'editable', (original_editable) => { // cell is NOT editable by default const editableCallback = (params) => { const getOriginalColDefEditable = () => { if (typeof original_editable === 'function') { return original_editable(params); } else { return original_editable; } }; // 1. evaluate EditOptions.isCellEditable if provided if (this.adaptableApi.gridApi.internalApi.hasCellEditableAccordingToEditOptions()) { const gridCell = this.adaptableApi.gridApi.getGridCellFromRowNode(params.node, params.column.getColId()); const editOptionsEditability = this.adaptableApi.gridApi.internalApi.isCellEditableAccordingToEditOptions(gridCell, getOriginalColDefEditable()); if (editOptionsEditability) { return editOptionsEditability; } } // 2. otherwise, fallback to colDef.editable return getOriginalColDefEditable(); }; return editableCallback; }); } setupColumnValueSetter({ col, colId, abColumn }) { this.setColDefProperty(col, 'valueSetter', (userValueSetter) => { const preventEditAlertsForColumn = this.adaptableApi.alertApi.internalApi .getAlertDefinitionsWithPreventEdit() .filter((alertDefinition) => { return this.adaptableApi.columnScopeApi.isColumnInScope(abColumn, alertDefinition.Scope); }); const noValidations = !preventEditAlertsForColumn.length && !this.adaptableOptions.editOptions?.validateOnServer; if (noValidations) { return; } const valueSetter = (params) => { const field = params.column.getColDef().field; if (noValidations) { //TODO also consider the case when userValueSetter is a string if (typeof userValueSetter === 'function') { return userValueSetter(params); } // we allowed it go reach this point // just to run isCellEditable // and since this has already run and we have no other validations // just assign the new value and exit if (field) { params.data[field] = params.newValue; } return true; } const cellDataChangedInfo = this.adaptableApi.internalApi.buildCellDataChangedInfo({ oldValue: params.oldValue, newValue: params.newValue, column: this.adaptableApi.columnApi.getColumnWithColumnId(params.column.getColId()), primaryKeyValue: this.adaptableApi.gridApi.getPrimaryKeyValueForRowNode(params.node), rowNode: params.node, trigger: 'edit', }); if (cellDataChangedInfo.oldValue === cellDataChangedInfo.newValue) { return true; } /** * Validate on the future row, with the new value. * structuredClone fails, it contains functions. */ const newRow = { ...params.node, data: { ...params.node.data } }; newRow.data[field] = params.newValue; const cellDataChangeInfoForSyncValidation = { ...cellDataChangedInfo, rowNode: newRow, }; if (!this.adaptableApi.internalApi .getValidationService() .performValidation(cellDataChangeInfoForSyncValidation)) { return false; } const onServerValidationCompleted = () => { }; if (this.adaptableOptions.editOptions?.validateOnServer) { this.adaptableApi.internalApi .getValidationService() .performServerValidation(cellDataChangedInfo, { onServerValidationCompleted, })(); } //TODO also consider the case when userValueSetter is a string if (typeof userValueSetter === 'function') { return userValueSetter(params); } if (field) { params.data[field] = params.newValue; } else { throw `Cannot edit a column without a field - column id was ${colId}`; } return true; }; return valueSetter; }); } setupColumnComparator({ col, colId, abColumn }) { const customSort = this.adaptableApi.customSortApi.getCustomSortForColumn(colId); const columnSortComparer = this.adaptableApi.customSortApi.internalApi.getCustomSortComparer(abColumn.columnId); const comparatorGetter = (propName) => { return () => { return this.adaptableApi.columnApi.internalApi.getActiveColumnComparator(colId, customSort, columnSortComparer); }; }; this.setColDefProperty(col, 'comparator', comparatorGetter('comparator')); this.setColDefProperty(col, 'pivotComparator', comparatorGetter('pivotComparator')); } setupColumnGetFindText({ col, abColumn }) { this.setColDefProperty(col, 'getFindText', (userGetFindText) => { return (params) => { const gridCell = this.adaptableApi.gridApi.getGridCellFromRowNode(params.node, abColumn.columnId); if (!this.isCellSearchable(gridCell)) { return null; } const getCellSearchText = this.adaptableOptions.quickSearchOptions.getCellSearchText; if (getCellSearchText) { return this.getCellSearchText(gridCell); } return userGetFindText?.(params) ?? gridCell.displayValue; }; }); } getCellSearchText(gridCell) { const getCellSearchText = this.adaptableOptions.quickSearchOptions.getCellSearchText; if (getCellSearchText) { const quickSearchValue = this.adaptableApi.quickSearchApi.getQuickSearchValue(); const quickSearchContext = { ...this.adaptableApi.internalApi.buildBaseContext(), gridCell, quickSearchValue, }; return getCellSearchText(quickSearchContext); } return gridCell.displayValue; } isCellSearchable(gridCell) { const isCellSearchableFn = this.adaptableOptions.quickSearchOptions.isCellSearchable; if (!gridCell.column) { return false; } if (isCellSearchableFn) { const quickSearchValue = this.adaptableApi.quickSearchApi.getQuickSearchValue(); const quickSearchContext = { ...this.adaptableApi.internalApi.buildBaseContext(), gridCell, quickSearchValue, }; if (!isCellSearchableFn(quickSearchContext)) { return false; } } return true; } isQuickSearchActive(gridCell) { const quickSearchValue = this.adaptableApi.quickSearchApi.getQuickSearchValue(); if (!quickSearchValue) { return false; } if (!this.isCellSearchable(gridCell)) { return false; } let column = this.agGridApi.getColumn(gridCell.column.columnId); if (!column && this.adaptableApi.layoutApi.isCurrentLayoutPivot()) { column = this.agGridApi .getAllGridColumns() .find((col) => col.getColId() === gridCell.column.columnId); } if (!column) { return false; } const isServerSideRowModel = this.adaptableApi.gridApi.getAgGridRowModelType() === 'serverSide'; if (isServerSideRowModel) { const isCaseSensitive = this.adaptableOptions.quickSearchOptions.isQuickSearchCaseSensitive; const cellDisplayValue = String(this.getCellSearchText(gridCell)); const displayValue = isCaseSensitive ? cellDisplayValue : cellDisplayValue.toLocaleLowerCase(); const searchText = isCaseSensitive ? quickSearchValue : quickSearchValue.toLocaleLowerCase(); return displayValue.indexOf(searchText) !== -1; } return (this.agGridApi.findGetNumMatches({ column, node: gridCell.rowNode, }) > 0); } getEditableCellClass(gridCell, params) { const editableCellStyle = this.adaptableApi.userInterfaceApi.getEditableCellStyle(); if (!editableCellStyle?.ClassName) { return null; } const isCellEditable = this.adaptableApi.gridApi.isCellEditable(gridCell); return isCellEditable ? editableCellStyle.ClassName : null; } getReadonlyCellClass(gridCell, params) { const readonlyCellStyle = this.adaptableApi.userInterfaceApi.getReadOnlyCellStyle(); if (!readonlyCellStyle?.ClassName) { return null; } const isCellReadonly = !this.adaptableApi.gridApi.isCellEditable(gridCell); return isCellReadonly ? readonlyCellStyle.ClassName : null; } getEditedCellClass(gridCell, params) { const editedCellStyle = this.adaptableApi.userInterfaceApi.getEditedCellStyle(); if (!editedCellStyle?.ClassName) { return null; } const isCellEdited = this.adaptableApi.gridApi.isCellEdited(gridCell); return isCellEdited ? editedCellStyle.ClassName : null; } getAlertCellClass(gridCell, params) { const alert = this.adaptableApi.alertApi.internalApi.getAdaptableAlertWithHighlightCell(gridCell.column.columnId, params.node); const highlightCell = alert?.alertDefinition?.AlertProperties?.HighlightCell; return typeof highlightCell === 'object' && highlightCell?.ClassName ? highlightCell?.ClassName : null; } getFlashingCellClass(gridcell, params) { const primaryKey = params.node.aggData ? params.node.id : gridcell.primaryKeyValue; const flashingCell = this.adaptableApi.flashingCellApi.internalApi.getAdaptableFlashingCellFor(primaryKey, gridcell.column.columnId); if (!flashingCell) { return; } return flashingCell.direction === 'up' ? flashingCell.flashingCellDefinition.UpChangeStyle?.ClassName : flashingCell.direction === 'down' ? flashingCell.flashingCellDefinition.DownChangeStyle?.ClassName : flashingCell.direction === 'neutral' ? flashingCell.flashingCellDefinition.NeutralChangeStyle?.ClassName : undefined; } getNoteCellClassName(gridCell, params) { if (!this.adaptableApi.internalApi.getModuleService().isModuleAvailable('Note')) { return; } if (!this.adaptableApi.noteApi.internalApi.areNotesSupported()) { return; } const cellPosition = { PrimaryKeyValue: gridCell.primaryKeyValue, ColumnId: gridCell.column.columnId, }; const cellNote = this.adaptableApi.noteApi.getNoteForCell(cellPosition); if (!cellNote) { return undefined; } return 'ab-Cell-Note'; } getCommentCellClassName(gridCell, params) { if (!this.adaptableApi.internalApi.getModuleService().isModuleAvailable('Comment')) { return; } if (!this.adaptableApi.commentApi.internalApi.areCommentsSupportedInLayout()) { return; } const position = { PrimaryKeyValue: gridCell.primaryKeyValue, ColumnId: gridCell.column.columnId, }; const cellComments = this.adaptableApi.commentApi.getCommentThreadForCell(position); if (!cellComments) { return undefined; } return 'ab-Cell-Comment'; } getFormatColumnCellClass(formatColumns, abColumn, params) { const classNames = formatColumns .map((formatColumn) => { if (formatColumn.Style?.ClassName && this.adaptableApi.formatColumnApi.internalApi.formatColumnShouldRenderInCell(formatColumn, abColumn, params.node, params.value)) { return formatColumn.Style?.ClassName; } }) .filter((x) => !!x); return classNames; } getQuickSearchCellStyle() { const quickSearchStyle = this.adaptableApi.quickSearchApi.getQuickSearchCellMatchStyle(); if (!quickSearchStyle || StringExtensions.IsNotNullOrEmpty(quickSearchStyle.ClassName)) { return undefined; } return convertAdaptableStyleToCSS(quickSearchStyle); } getQuickSearchTextMatchStyle() { const quickSearchTextMatchStyle = this.adaptableApi.quickSearchApi.getQuickSearchTextMatchStyle(); if (!quickSearchTextMatchStyle) { return undefined; } return convertAdaptableStyleToCSS(quickSearchTextMatchStyle); } getQuickSearchCurrentTextMatchStyle() { const quickSearchCurrentTextMatchStyle = this.adaptableApi.quickSearchApi.getQuickSearchCurrentTextMatchStyle(); if (!quickSearchCurrentTextMatchStyle) { return undefined; } return convertAdaptableStyleToCSS(quickSearchCurrentTextMatchStyle); } getReadOnlyCellStyle(gridCell, params) { const editableCellStyle = this.adaptableApi.userInterfaceApi.getReadOnlyCellStyle(); if (!editableCellStyle) { return undefined; } if (gridCell) { if (!this.adaptableApi.gridApi.isCellEditable(gridCell)) { return convertAdaptableStyleToCSS(editableCellStyle); } } return undefined; } getEditableCellStyle(gridCell, params) { const editableCellStyle = this.adaptableApi.userInterfaceApi.getEditableCellStyle(); if (!editableCellStyle) { return undefined; } if (gridCell) { if (this.adaptableApi.gridApi.isCellEditable(gridCell)) { return convertAdaptableStyleToCSS(editableCellStyle); } } return undefined; } getEditedCellStyle(gridCell, params) { const editedCellStyle = this.adaptableApi.userInterfaceApi.getEditedCellStyle(); if (!editedCellStyle) { return undefined; } if (gridCell) { if (this.adaptableApi.gridApi.isCellEdited(gridCell)) { return convertAdaptableStyleToCSS(editedCellStyle); } } return undefined; } /** * The combination of styled column and format cells * This function decides when the two can be merged. */ getFormatColumnAndStyledColumnCellStyle(column, params) { let styledColumn = this.adaptableApi.styledColumnApi.getStyledColumnForColumnId(column.columnId); let styledColumnStyle = {}; if (styledColumn && !styledColumn?.IsSuspended) { const styledCellStyle = this.getStyle