@adaptabletools/adaptable
Version:
Powerful data-agnostic HTML5 AG Grid extension which provides advanced, cutting-edge functionality to meet all DataGrid requirements
389 lines (388 loc) • 17.3 kB
JavaScript
import { ApiBase } from '../Implementation/ApiBase';
import StringExtensions from '../../Utilities/Extensions/StringExtensions';
import FormatHelper from '../../Utilities/Helpers/FormatHelper';
import ObjectFactory from '../../Utilities/ObjectFactory';
import * as ModuleConstants from '../../Utilities/Constants/ModuleConstants';
import { errorOnce } from '../../agGrid/AdaptableLogger';
function getParentGroup(source) {
if (!source) {
return undefined;
}
let current = source;
while (current.getParent()) {
current = current.getParent();
if (!current) {
return undefined;
}
const currentProvidedColumnGroup = current.getProvidedColumnGroup();
// for columns that don't belong to groups or belong to a group that doesn't have full depth
// there are artificial parent groups created by ag-grid
// so for the current column group, lets grab the leaf columns
// and if the only leaf column is the source column, then we can continue
// and do another iteration where we go up one level
if (currentProvidedColumnGroup) {
const leafColumns = current.getLeafColumns();
if (leafColumns.length === 1 && leafColumns[0] === source) {
continue;
}
// otherwise, we have found the parent group corresponding to the source column
return currentProvidedColumnGroup;
}
}
return undefined;
}
export class FormatColumnInternalApi extends ApiBase {
/**
* Retrieves all Format Columns in Adaptable State with the `Style` property set
* @returns format columns
*/
getAllFormatColumnWithStyle() {
return this.getFormatColumnApi()
.getFormatColumns()
.filter((fc) => fc.Style);
}
/**
* Retrieves all Format Columns in Adaptable State with the `Style` or the `CellAlignment` property set
* @returns format columns
*/
getAllFormatColumnWithStyleAndCellAlignment() {
return this.getFormatColumnApi()
.getFormatColumns()
.filter((fc) => fc.Style || fc.CellAlignment);
}
/**
* Retrieves all Format Columns in Adaptable State with `DisplayFormat` property set
* @returns format columns
*/
getAllFormatColumnWithDisplayFormat() {
return this.getFormatColumnApi()
.getFormatColumns()
.filter((fc) => fc.DisplayFormat);
}
/**
* Retrieves all Format Columns in Adaptable State with `CellAlignment` property set
* @returns format columns
*/
getAllFormatColumnWithCellAlignment() {
return this.getFormatColumnApi()
.getFormatColumns()
.filter((fc) => fc.CellAlignment);
}
/**
* Get all FormatColumns which are defined for this column and have a custom AdaptableStyle
* @param column
* @param config
* @returns list of FormatColumn
*/
getFormatColumnsWithStyleForColumn(column, config) {
const formatColumns = this.getAllFormatColumnWithStyleAndCellAlignment()
.filter((formatColumn) => {
// FormatColumn default target is 'cell', so if no target is specified, we assume 'cell'
const fcTarget = formatColumn.Target ?? 'cell';
return fcTarget === config.target;
})
.filter((formatColumn) => config?.includeSuspended || !formatColumn.IsSuspended);
return this.getFormatColumnWithColumnInScope(formatColumns, column);
}
/**
* Gets Format Column if any for given Column which includes Style element with ClassName
* @param column The Column for which to retrieve the Format Column
* @returns format columns
*/
getFormatColumnWithStyleClassNameForColumn(column, config) {
return this.getFormatColumnsWithStyleForColumn(column, config).filter((formatColumn) => StringExtensions.IsNotNullOrEmpty(formatColumn?.Style?.ClassName));
}
getFormatColumnWithColumnInScope(formatColumns, column) {
return this.getFormatColumnInColumnScope(formatColumns).filter((scopedFormatColumn) => this.getColumnScopeApi().isColumnInScope(column, scopedFormatColumn.Scope));
}
// TODO is this really needed, I don't think it achieves anything
getFormatColumnInColumnScope(formatColumns) {
// we need to maintain the format columns order, therefore we will extract all 3 scope types in a single iteration
return formatColumns.filter((fc) => {
return (
// this.getFormatColumnsWithColumnScope(formatColumns)
(this.getColumnScopeApi().scopeHasColumns(fc.Scope) ||
// this.getFormatColumnsWithDataTypeScope(formatColumns)
this.getColumnScopeApi().scopeHasDataType(fc.Scope) ||
// this.getFormatColumnsWithAllScope(formatColumns)
this.getColumnScopeApi().scopeIsAll(fc.Scope) || this.getColumnScopeApi().scopeHasColumnType(fc.Scope))
);
});
}
/**
* Get all FormatColumns which are defined for this column and have a custom DisplayFormat
* @param column
* @param config
* @returns list of FormatColumn
*/
getFormatColumnsWithDisplayFormatForColumn(column, config) {
const formatColumns = this.getAllFormatColumnWithDisplayFormat()
.filter((formatColumn) => {
// FormatColumn default target is 'cell', so if no target is specified, we assume 'cell'
const fcTarget = formatColumn.Target ?? 'cell';
return fcTarget === config.target;
})
.filter((formatColumn) => config?.includeSuspended || !formatColumn.IsSuspended);
return this.getFormatColumnWithColumnInScope(formatColumns, column);
}
/**
* Format value according to format options.
*
* @param customDisplayFormatterContext context that includes value to format
* @param options formatter options
*/
getNumberFormattedValue(value, node, column, options) {
const preparedValue = this.applyCustomFormatters(value, node, column, options);
return FormatHelper.NumberFormatter(preparedValue, options, node, column, this.getAdaptableApi());
}
/**
* Format value according to format options.
*
* @param value context that includes value to format
* @param options formatter options
*/
getStringFormattedValue(value, node, column, options) {
const preparedValue = this.applyCustomFormatters(value, node, column, options);
return FormatHelper.StringFormatter(preparedValue, options, node, column, this.getAdaptableApi());
}
/**
* Format value according to format options.
*
* @param customDisplayFormatterContext context that includes value to format
* @param options formatter options
*/
getDateFormattedValue(value, node, abColumn, options) {
const preparedValue = this.applyCustomFormatters(value, node, abColumn, options);
const dateFormatterOptions = options;
// only use DateFormatter if we have a pattern
return dateFormatterOptions.Pattern
? FormatHelper.DateFormatter(preparedValue, options)
: preparedValue;
}
applyCustomFormatters(value, node, abColumn, options) {
const columnCustomFormatters = options?.CustomDisplayFormats ?? [];
if (!columnCustomFormatters?.length) {
return value;
}
const customFormattersFromOptions = this.getFormatColumnOptions()?.customDisplayFormatters ?? [];
// formatters are applied in the order they are defined in the options
const customFormatters = customFormattersFromOptions.filter((customFormatterOption) => columnCustomFormatters.includes(customFormatterOption.id));
const customDisplayFormatterContext = ObjectFactory.CreateCustomDisplayFormatterContext(value, node, abColumn, this.getAdaptableApi());
return customFormatters.reduce((context, formatter) => {
if (formatter && formatter.handler) {
return formatter.handler(context);
}
return context.cellValue;
}, customDisplayFormatterContext);
}
/**
* Returns all Predicates appropriate for the given Scope
* @param scope Scope to check
*/
getFormatColumnDefsForScope(scope) {
return this.getAdaptableApi()
.predicateApi.internalApi.getFormatColumnPredicateDefs(scope)
.filter((predicateDef) => this.getColumnScopeApi().isScopeInScope(scope, predicateDef.columnScope));
}
formatColumnWithColumnGroupScopeShouldRender(formatColumn, column) {
if (!formatColumn.ColumnGroupScope) {
return true;
}
const agGridApi = this.getAdaptableApi().agGridApi;
const agGridColumn = agGridApi.isPivotMode()
? agGridApi.getPivotResultColumns().find((col) => col.getColId() === column.columnId)
: agGridApi.getColumn(column.columnId);
if (!agGridColumn) {
return false;
}
const columnGroupParentForCurrentColumn = getParentGroup(agGridColumn);
if (!columnGroupParentForCurrentColumn) {
return false;
}
const columnGroupScope = columnGroupParentForCurrentColumn.isExpanded()
? 'Expanded'
: 'Collapsed';
const columnGroupLeafColumns = columnGroupParentForCurrentColumn.getLeafColumns();
const columnIsTopLevel = columnGroupParentForCurrentColumn.getLevel() === 0 &&
columnGroupLeafColumns.length === 1 &&
columnGroupLeafColumns[0] === agGridColumn;
if (columnIsTopLevel) {
// for top-level columns don't apply formatColumns that have an explicit ColumnGroupScope defined
return false;
}
if (formatColumn.ColumnGroupScope === 'Both') {
return true;
}
return formatColumn.ColumnGroupScope === columnGroupScope;
}
/**
* Checks if format column is relevant for a given cell (intersection of given AdaptableColumn and RowNode)
*
* @param formatColumn
* @param column
* @param params
*/
formatColumnShouldRenderInCell(formatColumn, column, rowNode, cellValue) {
// suspended is important to be first
if (formatColumn.IsSuspended) {
return false;
}
const isSummaryNode = this.getGridApi().isSummaryNode(rowNode);
const isGroupedRowNode = this.getGridApi().isGroupRowNode(rowNode);
const isGrandTotalRowNode = this.getGridApi().isGrandTotalRowNode(rowNode);
if (isGrandTotalRowNode) {
if (formatColumn.RowScope?.ExcludeTotalRows) {
return false;
}
}
else if (isSummaryNode) {
if (formatColumn.RowScope?.ExcludeSummaryRows) {
return false;
}
}
else if (isGroupedRowNode) {
if (formatColumn.RowScope?.ExcludeGroupRows) {
return false;
}
}
else {
if (formatColumn.RowScope?.ExcludeDataRows) {
return false;
}
}
if (formatColumn.ColumnGroupScope &&
!this.formatColumnWithColumnGroupScopeShouldRender(formatColumn, column)) {
return false;
}
if (!formatColumn.Rule) {
return true;
}
// first run the predicate
if (formatColumn.Rule.Predicates && formatColumn.Rule?.Predicates?.length) {
const predicateDefHandlerContext = {
value: cellValue,
oldValue: null,
displayValue: cellValue,
node: rowNode,
column: column,
...this.getAdaptableInternalApi().buildBaseContext(),
};
return this.evaluatePredicate(formatColumn, predicateDefHandlerContext);
} // then run the Expression
else if (formatColumn.Rule.BooleanExpression) {
return this.evaluateExpression(formatColumn, rowNode);
}
// nothing has passed then return false
return false;
}
/**
* Checks if format column is relevant for a given Column Header
*
* @param formatColumn
* @param column
*/
formatColumnShouldRenderInHeader(formatColumn, column) {
if (formatColumn.IsSuspended) {
return false;
}
if (formatColumn.ColumnGroupScope &&
!this.getFormatColumnApi().internalApi.formatColumnWithColumnGroupScopeShouldRender(formatColumn, column)) {
return false;
}
return true;
}
evaluatePredicate(formatColumn, predicateDefHandlerContext) {
return this.getPredicateApi().handleColumnPredicates(formatColumn.Rule?.Predicates, predicateDefHandlerContext, false);
}
evaluateExpression(formatColumn, node) {
const isValidExpression = this.getExpressionApi().isValidBooleanExpression(formatColumn.Rule.BooleanExpression, ModuleConstants.FormatColumnModuleId, `Invalid format column rule '${formatColumn.Rule.BooleanExpression}'`);
try {
return (isValidExpression &&
this.getAdaptableApi()
.internalApi.getQueryLanguageService()
.evaluateBooleanExpression(formatColumn.Rule.BooleanExpression, ModuleConstants.FormatColumnModuleId, node));
}
catch (error) {
errorOnce(error.message);
return false;
}
}
/**
* Extract from the given FormatColumns only the ones which are relevant for a given cell (intersection of given AdaptableColumn and RowNode)
*
* @param formatColumns
* @param column
* @param params
*/
getFormatColumnsRelevantForColumn(formatColumns, column, params) {
return formatColumns.filter((formatColumn) => this.formatColumnShouldRenderInCell(formatColumn, column, params.node, params.value));
}
/**
* Extract from the given FormatColumns the one which is the most relevant for a given cell (intersection of given AdaptableColumn and RowNode)
*
* @param formatColumns
* @param column
* @param params
*/
getMostRelevantFormatColumnForColumn(formatColumns, column, params) {
return formatColumns.find((formatColumn) => this.formatColumnShouldRenderInCell(formatColumn, column, params.node, params.value));
}
/**
* Retrieves all Format Columns which have an Expression
* @returns Format Columns with Expression
*/
getFormatColumnsWithExpression() {
return this.getFormatColumnApi()
.getFormatColumns()
.filter((fc) => !!fc.Rule?.BooleanExpression);
}
getFormatColumnsDependentOnColumns(columnSet) {
return this.getFormatColumnApi()
.getFormatColumns()
.filter((fc) => fc.Rule?.Predicates?.some((p) => columnSet.has(p.ColumnId)));
}
/**
* Retrieves the columns that need rerendering based on format column predicates.
*/
getFormatColumnColumnsDependentOnColumnChange(column) {
const impactedColumnIds = new Set();
impactedColumnIds.add(column.columnId);
this.getCalculatedColumnApi()
.internalApi.getCalculatedColumnsDependentOnColumn(column)
.forEach((calculatedColumnId) => {
impactedColumnIds.add(calculatedColumnId);
});
const columnsThatNeedRefresh = new Set();
this.getFormatColumnsDependentOnColumns(impactedColumnIds).forEach((formatColumn) => {
this.getAdaptableApi()
.columnScopeApi.getColumnsInScope(formatColumn.Scope)
.forEach((col) => {
columnsThatNeedRefresh.add(col.columnId);
});
});
return [...columnsThatNeedRefresh];
}
formatColumnHeaderName(headerName, params) {
// currently we only format the header name for normal columns (NOT column groups)
if (!params.column) {
return headerName;
}
const abColumn = this.getColumnApi().getColumnWithColumnId(params.column.getColId());
const activeFormatColumnsWithDisplayFormat = this.getFormatColumnApi().internalApi.getFormatColumnsWithDisplayFormatForColumn(abColumn, {
target: 'columnHeader',
});
if (!activeFormatColumnsWithDisplayFormat.length) {
return headerName;
}
const mostRelevantFormatColumn = activeFormatColumnsWithDisplayFormat.find((formatColumn) => this.formatColumnShouldRenderInHeader(formatColumn, abColumn));
if (!mostRelevantFormatColumn) {
return headerName;
}
if (mostRelevantFormatColumn.DisplayFormat?.Formatter !== 'StringFormatter') {
// headers are always strings, so we can only use StringFormatter
return headerName;
}
const formattedHeaderName = this.getFormatColumnApi().internalApi.getStringFormattedValue(headerName, null, abColumn, mostRelevantFormatColumn.DisplayFormat.Options);
return formattedHeaderName;
}
}