@adaptabletools/adaptable
Version:
Powerful data-agnostic HTML5 AG Grid extension which provides advanced, cutting-edge functionality to meet all DataGrid requirements
174 lines (173 loc) • 8.04 kB
JavaScript
import { ROW_SUMMARY_ROW_ID, WEIGHTED_AVERAGE_AGGREGATED_FUNCTION, } from '../../AdaptableState/Common/RowSummary';
import { RowSummarySet } from '../../Redux/ActionsReducers/InternalRedux';
import * as ModuleConstants from '../../Utilities/Constants/ModuleConstants';
import Helper from '../Helpers/Helper';
import { AggregatedScalarLiveValue } from './AggregatedScalarLiveValue';
import { isObjectEmpty } from '../Extensions/ObjectExtensions';
import throttle from 'lodash/throttle';
import isEqual from 'lodash/isEqual';
/**
* The logic is extracted here to make it easier to follow
*/
export class RowSummaryService {
constructor(api) {
this.api = api;
this.cachedCellSummary = new Map();
this._throttleAcumulatedColumnsThatChanged = new Set();
/**
*
* @param colId optional to evaluate only one column
*/
this._throttledEvaluateRowSummary = throttle(this.evaluateRowSummary, 300);
}
onAdaptableReady() {
this.rowSummariesSubscriptions();
}
rowSummariesSubscriptions() {
// return;
if (this.api.isDestroyed()) {
return;
}
// Currently not available for serverside model
if (!this.api.layoutApi.internalApi.getLayoutSupportedFeatures().RowSummaries) {
return;
}
this.throttledEvaluateRowSummary();
this.api.eventApi.on('AdaptableStateReloaded', () => {
this.throttledEvaluateRowSummary();
});
this.api.internalApi
.getDataService()
.on('RowDataChanged', (rowDataChangedInfo) => {
this.throttledEvaluateRowSummary();
});
this.api.eventApi.on('CellChanged', (event) => {
const columnId = event.cellDataChange.column.columnId;
this.throttledEvaluateRowSummary({
columnIds: [columnId],
});
});
this.api.eventApi.on('LayoutChanged', (event) => {
// exclude filter events, those are handled in another event
if (event.actionName.includes('FILTER')) {
return;
}
setTimeout(() => {
// the timeout is added so the grid has time to repond to the layout changed
this.throttledEvaluateRowSummary();
}, 16);
});
const adaptable = this.api.internalApi.getAdaptableInstance();
adaptable._on('AdapTableFiltersApplied', () => {
// we need to use this instead of layout changed so the rows have time to update
this.throttledEvaluateRowSummary();
});
adaptable._on('FirstDataRendered', () => {
this.throttledEvaluateRowSummary();
});
}
throttledEvaluateRowSummary(reason) {
if (reason) {
reason.columnIds.forEach((col) => this._throttleAcumulatedColumnsThatChanged.add(col));
}
this._throttledEvaluateRowSummary(reason);
}
evaluateRowSummary(reason) {
if (this._throttleAcumulatedColumnsThatChanged.size > 0) {
const columnIds = Array.from(this._throttleAcumulatedColumnsThatChanged.values());
reason = {
columnIds,
};
this._throttleAcumulatedColumnsThatChanged.clear();
}
if (this.api.isDestroyed() || this.api.layoutApi.isCurrentLayoutPivot()) {
return;
}
const currentLayout = this.api.layoutApi.getCurrentLayout();
let previousLayout = this.previousLayout;
// it is added here to be sure it is saved
this.previousLayout = currentLayout;
/**
* If the previous & current layout does not have row summaries, it is safe to exit
*/
if (isObjectEmpty(currentLayout?.RowSummaries) && isObjectEmpty(previousLayout?.RowSummaries)) {
return;
}
const rowSummaries = currentLayout.RowSummaries ?? [];
const aggColsMap = (currentLayout.TableAggregationColumns || []).reduce((acc, { ColumnId, AggFunc }) => {
acc[ColumnId] = AggFunc;
return acc;
}, {});
const rowSummariesResults = rowSummaries
.filter((rowSummary) => !rowSummary.IsSuspended)
.map((rowSummary, index) => {
const { ColumnsMap, Position,
// it defaults to true
IncludeOnlyFilteredRows = true, } = rowSummary;
return {
Position,
RowData: Object.entries(ColumnsMap ?? {}).reduce((acc, [columnId, expression]) => {
if (columnId === 'Uuid' || columnId === 'Source' || columnId === 'AdaptableVersion') {
return acc;
}
const key = `${columnId}-${expression}-IncludeOnlyFilteredRows=${IncludeOnlyFilteredRows ? 'filtered' : 'all'}`;
let expressionLiveValue = this.cachedCellSummary.get(key);
if (expressionLiveValue) {
if (!reason) {
// refresh all of them
expressionLiveValue.refresh();
}
else if ('columnIds' in reason && reason.columnIds.includes(columnId)) {
expressionLiveValue.refresh();
}
}
if (!expressionLiveValue) {
try {
let aggregatedScalarExpression = `${expression}([${columnId}])`;
if (aggregatedScalarExpression.includes(WEIGHTED_AVERAGE_AGGREGATED_FUNCTION) &&
aggColsMap[columnId] &&
typeof aggColsMap[columnId] === 'object') {
const weight = aggColsMap[columnId].weightedColumnId;
if (weight) {
aggregatedScalarExpression = `AVG([${columnId}], WEIGHT([${weight}]))`;
}
}
expressionLiveValue = new AggregatedScalarLiveValue({
aggregatedScalarExpression,
}, ModuleConstants.LayoutModuleId, this.api, () => {
if (rowSummary.IncludeOnlyFilteredRows ?? true) {
return this.api.gridApi.getVisibleRowNodes();
}
else {
return this.api.gridApi.getAllRowNodes();
}
});
}
catch (e) {
this.api.logError('Error evaluating row summary', e);
}
this.cachedCellSummary.set(key, expressionLiveValue);
}
let value = null;
if (expressionLiveValue) {
value = expressionLiveValue.getGlobalAggregatedValue();
if (typeof value === 'number' && !isNaN(value)) {
value = Helper.roundNumber(value, 2);
}
}
const column = this.api.columnApi.getColumnWithColumnId(columnId);
const fieldName = column?.field ?? column?.columnId ?? columnId;
acc = this.api.internalApi.setValueUsingField(acc, fieldName, value);
return acc;
}, {
[ROW_SUMMARY_ROW_ID]: `row-summary-${index}`,
}),
};
});
if (this.previousRowSummaries && isEqual(rowSummariesResults, this.previousRowSummaries)) {
return;
}
this.api.internalApi.dispatchReduxAction(RowSummarySet(rowSummariesResults));
this.previousRowSummaries = rowSummariesResults;
}
}