Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,49 +2,56 @@ import { errors } from '@js/common/data/errors';
import { aggregators } from '@js/common/data/utils';
import { compileGetter } from '@js/core/utils/data';
import { isFunction } from '@js/core/utils/type';
import type { GroupData } from '@js/ui/data_grid';

import type { RawItemData } from '../grid_core/data_source_adapter/types';
import type { Aggregate } from './summary/types';

function depthFirstSearch(i, depth, root, callback) {
// NOTE: only the deepest groups hold rows in items; the groups above hold groups
interface AggregateNode {
items: (AggregateNode | RawItemData)[];
aggregates?: unknown[];
}

interface Aggregator {
seed?: number | unknown[] | ((groupIndex?: number) => unknown);
step: (accumulator: unknown, value: unknown) => unknown;
finalize?: (accumulator: unknown) => unknown;
}

interface NormalizedAggregate {
selector: (data: RawItemData) => unknown;
aggregator: Aggregator;
skipEmptyValues: boolean | undefined;
}

const depthFirstSearch = (
i: number,
depth: number,
root: AggregateNode,
callback: (node: AggregateNode) => void,
): void => {
let j = 0;
if (i < depth) {
for (; j < root.items.length; j++) {
depthFirstSearch(i + 1, depth, root.items[j], callback);
for (; j < root.items.length; j += 1) {
// NOTE: above the given depth, items are groups
depthFirstSearch(i + 1, depth, root.items[j] as AggregateNode, callback);
}
}

if (i === depth) {
callback(root);
}
}
};

// NOTE: https://github.com/jquery/jquery/blame/master/src/core.js#L392
function map(array, callback) {
let i;
const isEmpty = (x: unknown): boolean => Number.isNaN(x) || (x === '') || (x === null) || (x === undefined);

if ('map' in array) {
return array.map(callback);
}
const isCount = (aggregator: Aggregator): boolean => aggregator === aggregators.count;

const result = new Array(array.length);
// eslint-disable-next-line guard-for-in
for (i in array) {
result[i] = callback(array[i], i);
}
const normalizeAggregate = (aggregate: Aggregate): NormalizedAggregate => {
// @ts-expect-error badly typed compileGetter
const selector = compileGetter(aggregate.selector) as (data: RawItemData) => unknown;

return result;
}

function isEmpty(x) {
return (x !== x) || (x === '') || (x === null) || (x === undefined);
}

function isCount(aggregator) {
return aggregator === aggregators.count;
}

function normalizeAggregate(aggregate) {
const selector = compileGetter(aggregate.selector);
const skipEmptyValues = 'skipEmptyValues' in aggregate
? aggregate.skipEmptyValues
: true;
Expand All @@ -59,32 +66,33 @@ function normalizeAggregate(aggregate) {

return {
selector,
// @ts-expect-error aggregators[name] is untyped; local aggregates always have one
aggregator,
skipEmptyValues,
};
}
};

export default class AggregateCalculator {
private readonly _data: any;
private readonly _data: RawItemData[] | GroupData<RawItemData>[];

private readonly _groupLevel: number;

private readonly _totalAggregates: Aggregate[];
private readonly _totalAggregates: NormalizedAggregate[];

private readonly _groupAggregates: Aggregate[];
private readonly _groupAggregates: NormalizedAggregate[];

private _totals: unknown[];

constructor(options: {
data: any;
data: RawItemData[] | GroupData<RawItemData>[];
Comment thread
Tucchhaa marked this conversation as resolved.
groupLevel: number;
totalAggregates: Aggregate[];
groupAggregates: Aggregate[];
}) {
this._data = options.data;
this._groupLevel = options.groupLevel;
this._totalAggregates = map(options.totalAggregates, normalizeAggregate);
this._groupAggregates = map(options.groupAggregates, normalizeAggregate);
this._totalAggregates = options.totalAggregates.map(normalizeAggregate);
this._groupAggregates = options.groupAggregates.map(normalizeAggregate);
this._totals = [];
}

Expand All @@ -102,31 +110,38 @@ export default class AggregateCalculator {
return this._totals;
}

private _aggregate(aggregates, data, container) {
private _aggregate(
aggregates: NormalizedAggregate[],
data: AggregateNode,
container: unknown[],
): void {
const length = data.items ? data.items.length : 0;

for (let i = 0; i < aggregates.length; i++) {
for (let i = 0; i < aggregates.length; i += 1) {
if (isCount(aggregates[i].aggregator)) {
container[i] = (container[i] || 0) + length;
container[i] = (container[i] as number | undefined ?? 0) + length;
// eslint-disable-next-line no-continue
continue;
}

for (let j = 0; j < length; j++) {
this._accumulate(i, aggregates[i], container, data.items[j]);
for (let j = 0; j < length; j += 1) {
// NOTE: at the group level, items are rows
this._accumulate(i, aggregates[i], container, data.items[j] as RawItemData);
}
}
}

private _calculateTotals(level, data) {
private _calculateTotals(level: number, root: AggregateNode): void {
if (level === 0) {
this._totals = this._seed(this._totalAggregates);
}

if (level === this._groupLevel) {
this._aggregate(this._totalAggregates, data, this._totals);
this._aggregate(this._totalAggregates, root, this._totals);
} else {
for (let i = 0; i < data.items.length; i++) {
this._calculateTotals(level + 1, data.items[i]);
for (const item of root.items) {
// NOTE: above the group level, items are groups
this._calculateTotals(level + 1, item as AggregateNode);
}
}

Expand All @@ -135,45 +150,54 @@ export default class AggregateCalculator {
}
}

private _calculateGroups(root) {
private _calculateGroups(root: AggregateNode): void {
const maxLevel = this._groupLevel;
let currentLevel = maxLevel + 1;

const seedFn = this._seed.bind(this, this._groupAggregates);
const stepFn = this._aggregate.bind(this, this._groupAggregates);
const finalizeFn = this._finalize.bind(this, this._groupAggregates);

function aggregator(node) {
node.aggregates = seedFn(currentLevel - 1);
const aggregator = (node: AggregateNode): void => {
const aggregates = seedFn(currentLevel - 1);
node.aggregates = aggregates;

if (currentLevel === maxLevel) {
stepFn(node, node.aggregates);
stepFn(node, aggregates);
} else {
depthFirstSearch(currentLevel, maxLevel, node, (innerNode) => {
stepFn(innerNode, node.aggregates);
stepFn(innerNode, aggregates);
});
}

node.aggregates = finalizeFn(node.aggregates);
}
node.aggregates = finalizeFn(aggregates);
};

while (--currentLevel > 0) {
currentLevel -= 1;
while (currentLevel > 0) {
depthFirstSearch(0, currentLevel, root, aggregator);
currentLevel -= 1;
}
}

private _seed(aggregates, groupIndex?) {
return map(aggregates, (aggregate) => {
private _seed(aggregates: NormalizedAggregate[], groupIndex?: number): unknown[] {
return aggregates.map((aggregate) => {
const { aggregator } = aggregate;
const seed = 'seed' in aggregator
? isFunction(aggregator.seed) ? aggregator.seed(groupIndex) : aggregator.seed
: NaN;

return seed;
if (!('seed' in aggregator)) {
return NaN;
}

return isFunction(aggregator.seed) ? aggregator.seed(groupIndex) : aggregator.seed;
});
}

private _accumulate(aggregateIndex, aggregate, results, item) {
private _accumulate(
aggregateIndex: number,
aggregate: NormalizedAggregate,
results: unknown[],
item: RawItemData,
): void {
const value = aggregate.selector(item);
const { aggregator } = aggregate;
const { skipEmptyValues } = aggregate;
Expand All @@ -182,15 +206,15 @@ export default class AggregateCalculator {
return;
}

if (results[aggregateIndex] !== results[aggregateIndex]) {
if (Number.isNaN(results[aggregateIndex])) {
results[aggregateIndex] = value;
} else {
results[aggregateIndex] = aggregator.step(results[aggregateIndex], value);
}
}

private _finalize(aggregates, results) {
return map(aggregates, (aggregate, index) => {
private _finalize(aggregates: NormalizedAggregate[], results: unknown[]): unknown[] {
return aggregates.map((aggregate, index) => {
const fin = aggregate.aggregator.finalize;
return fin
? fin(results[index])
Expand Down
11 changes: 8 additions & 3 deletions packages/devextreme/js/__internal/grids/data_grid/m_utils.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
import { normalizeSortingInfo } from '@js/common/data/utils';
import { isDefined } from '@ts/core/utils/m_type';
import type { StoreLoadOptions } from '@ts/data/data_source/types';
import type { DataFilter } from '@ts/grids/grid_core/filter/types';
import { combineFilters } from '@ts/grids/grid_core/filter/utils';

import type { Column } from './types';

export function createGroupFilter(path, storeLoadOptions) {
export function createGroupFilter(
path: unknown[],
storeLoadOptions: Pick<StoreLoadOptions, 'group' | 'filter'>,
): DataFilter {
Comment thread
Tucchhaa marked this conversation as resolved.
const groups = normalizeSortingInfo(storeLoadOptions.group);

const filter: any = [];
const filter: unknown[] = [];

for (let i = 0; i < path.length; i++) {
for (let i = 0; i < path.length; i += 1) {
filter.push([groups[i].selector, '=', path[i]]);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ import $ from '@js/core/renderer';
import { extend } from '@js/core/utils/extend';
import { each } from '@js/core/utils/iterator';
import { isDefined, isPlainObject } from '@js/core/utils/type';
import type { GroupData } from '@js/ui/data_grid';
import type { DataSource } from '@ts/data/data_source/data_source';
import type { ColumnsController } from '@ts/grids/grid_core/columns_controller/m_columns_controller';
import type DataSourceAdapter from '@ts/grids/grid_core/data_source_adapter/m_data_source_adapter';
import type { RemoteOperationsOptions } from '@ts/grids/grid_core/data_source_adapter/types';
import type { RawItemData, RemoteOperationsOptions } from '@ts/grids/grid_core/data_source_adapter/types';
import type { EditingControllerRequired, ModuleType } from '@ts/grids/grid_core/m_types';
import { ColumnsView } from '@ts/grids/grid_core/views/m_columns_view';

Expand Down Expand Up @@ -349,7 +350,7 @@ export const summaryDataSourceAdapterExtender = (
}

private calculateTotalAggregates(
data,
data: RawItemData[] | GroupData<RawItemData>[],
Comment thread
Tucchhaa marked this conversation as resolved.
totalAggregates: Aggregate[],
groupAggregates: Aggregate[],
groupLevel: number,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export interface SortInfo {
export type SortByGroups = (SortInfo[] | undefined)[];

export interface CustomAggregator {
seed: (groupIndex: number) => unknown;
seed: (groupIndex?: number) => unknown;
step: (totalValue: unknown, value: unknown) => unknown;
finalize: (totalValue: unknown) => unknown;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,46 @@
import type { dxElementWrapper } from '@js/core/renderer';
import type { DxEvent } from '@js/events';
import * as accessibility from '@js/ui/shared/accessibility';

export const registerKeyboardAction = function (viewName, instance, $element, selector, action) {
import type { View } from './m_modules';

interface KeyDownArgs {
event: unknown;
handled: boolean;
}

export const registerKeyboardAction = (
viewName: string,
instance: View,
$element: dxElementWrapper,
selector: string | undefined,
action: (args: { event: DxEvent }) => void,
): void => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
let executeKeyDown = (args) => {};
let executeKeyDown = (args: KeyDownArgs): void => {};

const keyboardController = instance.getController('keyboardNavigation');
if (instance.option('useLegacyKeyboardNavigation') || (keyboardController && !keyboardController.isKeyboardEnabled())) {
if (
instance.option('useLegacyKeyboardNavigation')
|| (keyboardController && !keyboardController.isKeyboardEnabled())
) {
return;
}

if (viewName === 'filterPanel') {
executeKeyDown = (args) => {
executeKeyDown = (args: KeyDownArgs): void => {
instance.executeAction('onKeyDown', args);
};

instance.createAction('onKeyDown');
}

accessibility.registerKeyboardAction(viewName, instance, $element, selector, action, executeKeyDown);
accessibility.registerKeyboardAction(
viewName,
instance,
$element,
selector,
action,
executeKeyDown,
);
};
Loading
Loading