@adaptabletools/adaptable
Version:
Powerful AG Grid extension which provides advanced, cutting-edge functionality to meet all DataGrid requirements
247 lines (246 loc) • 11.4 kB
JavaScript
import { CalculatedColumnModuleId } from '../Constants/ModuleConstants';
import { AggregatedScalarLiveValue } from './AggregatedScalarLiveValue';
import * as parser from '../../parser/src';
import Helper from '../Helpers/Helper';
export class CalculatedColumnExpressionService {
adaptableApi;
aggregatedScalarLiveValuesMap = new Map();
constructor(adaptableApi) {
this.adaptableApi = adaptableApi;
this.adaptableApi = adaptableApi;
}
buildSyntheticCellDataChangedInfosForCalcColumns(cellDataChangedInfo) {
return buildSyntheticCellDataChangedInfosForCalcColumns(this.adaptableApi, cellDataChangedInfo);
}
listentoCellDataChange(cellDataChangedInfo) {
if (cellDataChangedInfo.trigger === 'calculatedColumnChange') {
return;
}
const refreshedCalculatedColumns = [];
this.aggregatedScalarLiveValuesMap.forEach((aggregatedScalarLiveValue, calculatedColumnId) => {
const calculatedColumn = this.adaptableApi.calculatedColumnApi.getCalculatedColumnById(calculatedColumnId);
const columnDependencies = this.adaptableApi.calculatedColumnApi.internalApi.getReferencedColumnIdsForCalculatedColumn(calculatedColumn);
if (columnDependencies.includes(cellDataChangedInfo.column.columnId)) {
aggregatedScalarLiveValue.refresh();
refreshedCalculatedColumns.push(calculatedColumn.ColumnId);
}
});
if (refreshedCalculatedColumns.length) {
this.adaptableApi.gridApi.refreshColumns(refreshedCalculatedColumns);
}
}
listentoRowDataChange(rowDataChangedInfo) {
const refreshedCalculatedColumns = [];
this.aggregatedScalarLiveValuesMap.forEach((aggregatedScalarLiveValue, calculatedColumnId) => {
const calculatedColumn = this.adaptableApi.calculatedColumnApi.getCalculatedColumnById(calculatedColumnId);
aggregatedScalarLiveValue.refresh();
refreshedCalculatedColumns.push(calculatedColumn.ColumnId);
});
if (refreshedCalculatedColumns.length) {
this.adaptableApi.gridApi.refreshColumns(refreshedCalculatedColumns);
}
}
destroy() {
this.aggregatedScalarLiveValuesMap.clear();
this.aggregatedScalarLiveValuesMap = null;
}
getCalculatedColumnDataType(calculatedColumnQuery) {
try {
if (calculatedColumnQuery.AggregatedScalarExpression) {
return 'number';
}
let firstRowNode = this.adaptableApi.gridApi.getFirstRowNode();
let firstRowValue = this.adaptableApi.internalApi
.getQueryLanguageService()
.evaluateScalarExpression(calculatedColumnQuery.ScalarExpression, CalculatedColumnModuleId, firstRowNode);
if (firstRowValue instanceof Date) {
return 'date';
}
if (typeof firstRowValue === 'boolean') {
return 'boolean';
}
if (typeof firstRowValue === 'string') {
return 'text';
}
if (typeof firstRowValue === 'number') {
return 'number';
}
if (Array.isArray(firstRowValue)) {
return 'numberArray';
}
}
catch (e) {
this.adaptableApi.logError(`Invalid CalculatedColumn expression ${this.adaptableApi.expressionApi.getAdaptableQueryExpression(calculatedColumnQuery)} :: ${e}`);
return 'number';
}
}
isCalculatedColumnQueryValid(calculatedColumnQuery) {
if (calculatedColumnQuery.ScalarExpression) {
try {
let firstRowNode = this.adaptableApi.gridApi.getFirstRowNode();
this.adaptableApi.internalApi
.getQueryLanguageService()
.evaluateScalarExpression(calculatedColumnQuery.ScalarExpression, CalculatedColumnModuleId, firstRowNode);
return true;
}
catch (e) {
return false;
}
}
if (calculatedColumnQuery.AggregatedScalarExpression) {
const validationResult = this.adaptableApi.expressionApi.isValidAggregatedScalarExpression(calculatedColumnQuery.AggregatedScalarExpression, CalculatedColumnModuleId);
return validationResult;
}
return false;
}
evaluateCalculatedColumnQuery(calculatedColumn, rowNode) {
try {
if (this.adaptableApi.gridApi.isGroupRowNode(rowNode)) {
return undefined;
}
if (calculatedColumn?.Query?.ScalarExpression) {
return this.adaptableApi.internalApi
.getQueryLanguageService()
.evaluateScalarExpression(calculatedColumn.Query.ScalarExpression, CalculatedColumnModuleId, rowNode);
}
if (calculatedColumn?.Query?.AggregatedScalarExpression) {
const aggregatedScalarLiveValue = this.aggregatedScalarLiveValuesMap.get(calculatedColumn.Uuid);
return aggregatedScalarLiveValue?.getAggregatedValueForRow(rowNode);
}
}
catch (error) {
this.adaptableApi.logError(`Invalid CalculatedColumn expression ${calculatedColumn?.Query?.ScalarExpression ??
calculatedColumn?.Query?.AggregatedScalarExpression} :: ${error}`);
return null;
}
}
createAggregatedScalarLiveValue(calculatedColumn) {
this.destroyAggregatedScalarLiveValue(calculatedColumn);
if (calculatedColumn.Query?.AggregatedScalarExpression) {
try {
const aggregatedScalarLiveValue = new AggregatedScalarLiveValue({
aggregatedScalarExpression: calculatedColumn.Query?.AggregatedScalarExpression,
}, CalculatedColumnModuleId, this.adaptableApi);
this.aggregatedScalarLiveValuesMap.set(calculatedColumn.Uuid, aggregatedScalarLiveValue);
}
catch (e) {
this.adaptableApi.logError(`Creating CalculatedColumn ('${calculatedColumn.Query?.AggregatedScalarExpression}') failed!`, e.message);
}
}
}
destroyAggregatedScalarLiveValue(calculatedColumn) {
this.aggregatedScalarLiveValuesMap.delete(calculatedColumn.Uuid);
}
}
function isScalarCalculatedColumn(calculatedColumn) {
return !!calculatedColumn.Query?.ScalarExpression;
}
function collectDependentScalarCalculatedColumns(adaptableApi, rootColumnId) {
const collected = [];
const seenCalcColumnIds = new Set();
const queue = [rootColumnId];
while (queue.length > 0) {
const columnId = queue.shift();
const adaptableColumn = adaptableApi.columnApi.getColumnWithColumnId(columnId);
if (!adaptableColumn) {
continue;
}
const dependentColumnIds = adaptableApi.calculatedColumnApi.internalApi.getCalculatedColumnsDependentOnColumn(adaptableColumn);
dependentColumnIds.forEach((dependentColumnId) => {
if (seenCalcColumnIds.has(dependentColumnId)) {
return;
}
const calculatedColumn = adaptableApi.calculatedColumnApi.getCalculatedColumnForColumnId(dependentColumnId);
if (!calculatedColumn || !isScalarCalculatedColumn(calculatedColumn)) {
return;
}
seenCalcColumnIds.add(dependentColumnId);
collected.push(calculatedColumn);
queue.push(dependentColumnId);
});
}
return collected;
}
function buildShadowRowData(adaptableApi, info) {
const shadowData = Helper.cloneObject(info.rowNode?.data ?? info.rowData ?? {});
const fieldName = info.column.field ?? info.column.columnId;
adaptableApi.internalApi.setValueUsingField(shadowData, fieldName, info.oldValue);
return shadowData;
}
function evaluateScalarExpressionWithShadowData(adaptableApi, calculatedColumn, rowNode, shadowData) {
const internalApi = adaptableApi.internalApi;
const moduleFns = internalApi
.getQueryLanguageService()
.getModuleExpressionFunctionsMap(CalculatedColumnModuleId);
const baseFunctions = { ...moduleFns.booleanFunctions, ...moduleFns.scalarFunctions };
const resolveColumnValue = (columnId) => {
const childCalculatedColumn = adaptableApi.calculatedColumnApi.getCalculatedColumnForColumnId(columnId);
if (childCalculatedColumn?.Query?.ScalarExpression) {
return evaluateScalarExpressionWithShadowData(adaptableApi, childCalculatedColumn, rowNode, shadowData);
}
const adaptableColumn = adaptableApi.columnApi.getColumnWithColumnId(columnId);
return internalApi.getValueUsingField(shadowData, adaptableColumn?.field ?? columnId);
};
const customFunctions = {
...baseFunctions,
COL: {
...baseFunctions.COL,
handler: (args) => resolveColumnValue(args[0]),
},
FIELD: {
...baseFunctions.FIELD,
handler: (args) => internalApi.getValueUsingField(shadowData, args[0]),
},
};
return parser.evaluate(calculatedColumn.Query.ScalarExpression, {
node: { ...rowNode, data: shadowData },
functions: customFunctions,
...internalApi.buildBaseContext(),
});
}
export function buildSyntheticCellDataChangedInfosForCalcColumns(adaptableApi, info) {
if (!info.rowNode ||
info.preventEdit ||
info.trigger === 'undo' ||
info.trigger === 'aggChange' ||
info.trigger === 'calculatedColumnChange') {
return [];
}
const calculatedColumns = collectDependentScalarCalculatedColumns(adaptableApi, info.column.columnId);
if (!calculatedColumns.length) {
return [];
}
const shadowData = buildShadowRowData(adaptableApi, info);
const expressionService = adaptableApi.internalApi.getCalculatedColumnExpressionService();
const syntheticEvents = [];
calculatedColumns.forEach((calculatedColumn) => {
const oldValue = evaluateScalarExpressionWithShadowData(adaptableApi, calculatedColumn, info.rowNode, shadowData);
const newValue = expressionService.evaluateCalculatedColumnQuery(calculatedColumn, info.rowNode);
if (oldValue == newValue) {
return;
}
const column = adaptableApi.columnApi.getColumnWithColumnId(calculatedColumn.ColumnId);
if (!column) {
return;
}
syntheticEvents.push(adaptableApi.internalApi.buildCellDataChangedInfo({
oldValue,
newValue,
column,
primaryKeyValue: info.primaryKeyValue,
rowNode: info.rowNode,
trigger: 'calculatedColumnChange',
}));
});
return syntheticEvents;
}
export function expandCellDataChangedInfosWithCalculatedColumns(adaptableApi, cellDataChangedInfos) {
if (!cellDataChangedInfos.length) {
return cellDataChangedInfos;
}
const syntheticEvents = cellDataChangedInfos.flatMap((info) => buildSyntheticCellDataChangedInfosForCalcColumns(adaptableApi, info));
if (!syntheticEvents.length) {
return cellDataChangedInfos;
}
return [...cellDataChangedInfos, ...syntheticEvents];
}