@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
JavaScript
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