@progress/kendo-angular-grid
Version:
Kendo UI Grid for Angular - high performance data grid with paging, filtering, virtualization, CRUD, and more.
205 lines (204 loc) • 9.67 kB
JavaScript
/**-----------------------------------------------------------------------------------------
* Copyright © 2025 Progress Software Corporation. All rights reserved.
* Licensed under commercial license. See LICENSE.md in the project root for more information
*-------------------------------------------------------------------------------------------*/
import { Injectable } from '@angular/core';
import { ContextService } from '../common/provider.service';
import { ColumnInfoService } from '../common/column-info.service';
import { LocalDataChangesService } from '../editing/local-data-changes.service';
import { recursiveFlatMap } from '../utils';
import { Subscription } from 'rxjs';
import * as i0 from "@angular/core";
import * as i1 from "../common/provider.service";
import * as i2 from "../editing/local-data-changes.service";
import * as i3 from "../common/column-info.service";
/**
* @hidden
*/
export class CellSelectionAggregateService {
ctx;
dataChanges;
columnInfoService;
selectedItems = [];
groupedAggregates = { dates: [], numbers: [], booleans: [] };
aggregates = {
sum: null,
average: null,
min: null,
max: null,
count: null,
isTrue: null,
isFalse: null,
earliest: null,
latest: null
};
sub = new Subscription();
constructor(ctx, dataChanges, columnInfoService) {
this.ctx = ctx;
this.dataChanges = dataChanges;
this.columnInfoService = columnInfoService;
}
ngOnDestroy() {
this.sub.unsubscribe();
}
isAggregateIncluded(aggregate) {
const { cellAggregates } = this.ctx.grid.selectable;
if (typeof cellAggregates !== 'boolean') {
return cellAggregates?.includes(aggregate);
}
return true;
}
init() {
this.sub.add(this.ctx.grid.dataStateChange.subscribe(() => {
// nullifies aggregates and sets default count to avoid mismatching state -
// https://github.com/telerik/kendo-angular-private/issues/2964
this.nullifyAggregates();
if (this.isAggregateIncluded('count')) {
this.aggregates['count'] = 0;
}
}));
if (this.ctx.grid.selectable.cellAggregates) {
if (this.isAggregateIncluded('count')) {
this.aggregates['count'] = 0;
}
const selectionDirective = this.ctx.grid.selectionDirective;
if (selectionDirective && !this.isRowSelection) {
this.selectedItems = selectionDirective.selectedKeys;
}
}
}
onSelectionChange(selectionArgs) {
this.handleSelectedItems(selectionArgs);
this.nullifyAggregates();
this.handleAggregateChanges();
return this.aggregates;
}
get isRowSelection() {
return typeof this.ctx.grid.selectable === 'boolean' || !this.ctx.grid.selectable.cell;
}
handleAggregateChanges() {
const lockedColumns = this.columnInfoService.lockedLeafColumns.toArray();
const nonLockedColumns = this.columnInfoService.nonLockedLeafColumns.toArray();
const selectedItemsLength = this.selectedItems.length;
const columns = [...lockedColumns, ...nonLockedColumns];
const fields = columns.map(col => col.field);
if (this.isAggregateIncluded('count')) {
this.aggregates['count'] = this.isRowSelection ? (selectedItemsLength * columns.length) : selectedItemsLength;
}
this.selectedItems.forEach((item) => {
if (this.isRowSelection) {
fields.forEach((field) => {
const cellValue = item.dataItem;
if (cellValue && cellValue.hasOwnProperty(field)) {
const cellValue = item.dataItem[field];
this.groupAggregates(cellValue);
}
});
}
else if (!this.isRowSelection) {
// Enables working with the current Grid data regardless of its form (array, GridDataResult, GroupedResult).
// Currently gets the item by index only - https://github.com/telerik/kendo-angular-private/issues/2964
const selectedItem = this.ctx.grid.flatData.flatMap(recursiveFlatMap)[item.itemKey];
const field = fields[item.columnKey];
if (selectedItem && selectedItem.hasOwnProperty(field)) {
const cellValue = selectedItem[fields[item.columnKey]];
this.groupAggregates(cellValue);
}
}
});
this.calculateAggregates();
}
groupAggregates(aggregate) {
if (typeof aggregate === 'number') {
this.groupedAggregates.numbers.push(aggregate);
}
else if (typeof aggregate === 'boolean') {
this.groupedAggregates.booleans.push(aggregate);
}
else if (aggregate instanceof Date) {
this.groupedAggregates.dates.push(aggregate);
}
}
calculateAggregates() {
if (this.groupedAggregates.numbers.length > 0) {
if (this.isAggregateIncluded('min')) {
this.aggregates['min'] = Math.min(...this.groupedAggregates.numbers);
}
if (this.isAggregateIncluded('max')) {
this.aggregates['max'] = Math.max(...this.groupedAggregates.numbers);
}
if (this.isAggregateIncluded('sum')) {
this.aggregates['sum'] = this.groupedAggregates.numbers.reduce((acc, curr) => acc += curr, 0);
}
if (this.isAggregateIncluded('average')) {
this.aggregates['average'] = this.aggregates['sum'] / this.groupedAggregates.numbers.length;
}
}
if (this.groupedAggregates.booleans.length > 0) {
if (this.isAggregateIncluded('isTrue')) {
const isTrueCount = this.groupedAggregates.booleans.filter(bool => bool).length;
this.aggregates['isTrue'] = isTrueCount > 0 ? isTrueCount : null;
}
if (this.isAggregateIncluded('isFalse')) {
const isFalseCount = this.groupedAggregates.booleans.length - this.aggregates['isTrue'];
this.aggregates['isFalse'] = isFalseCount > 0 ? isFalseCount : null;
}
}
if (this.groupedAggregates.dates.length > 0) {
if (this.isAggregateIncluded('earliest')) {
this.aggregates['earliest'] = new Date(Math.min(...this.groupedAggregates.dates));
}
if (this.isAggregateIncluded('latest')) {
this.aggregates['latest'] = new Date(Math.max(...this.groupedAggregates.dates));
}
}
}
handleSelectedItems(selectionArgs) {
const rowOrCellSelect = `${this.isRowSelection ? 'selectedRows' : 'selectedCells'}`;
const rowOrCellDeselect = `${this.isRowSelection ? 'deselectedRows' : 'deselectedCells'}`;
const selectedItems = selectionArgs[rowOrCellSelect];
const deselectedItems = selectionArgs[rowOrCellDeselect];
if (!this.isRowSelection) {
// Needed when we have column groups with cell selection, the deselected items are duplicated
deselectedItems.forEach((item, index) => {
if (index + 1 < deselectedItems.length) {
if (item.itemKey === deselectedItems[index + 1].itemKey
&& item.columnKey === deselectedItems[index + 1].columnKey) {
deselectedItems.splice(index, 1);
}
}
});
}
if (selectedItems.length > 0) {
selectedItems.forEach((item) => {
this.selectedItems = [...this.selectedItems, item];
});
}
if (deselectedItems.length > 0) {
if (this.isRowSelection) {
deselectedItems.forEach((row) => {
this.selectedItems = this.selectedItems.filter((elem) => elem.dataItem !== row.dataItem);
});
}
else {
deselectedItems.forEach((cell) => {
const index = this.selectedItems.findIndex((elem) => elem.itemKey === cell.itemKey && elem.columnKey === cell.columnKey);
this.selectedItems.splice(index, 1);
});
}
}
}
nullifyAggregates() {
this.groupedAggregates = { dates: [], numbers: [], booleans: [] };
this.aggregates['count'] = null;
this.aggregates['sum'] = this.aggregates['average'] = null;
this.aggregates['max'] = this.aggregates['min'] = null;
this.aggregates['isFalse'] = this.aggregates['isTrue'] = null;
this.aggregates['earliest'] = this.aggregates['latest'] = null;
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CellSelectionAggregateService, deps: [{ token: i1.ContextService }, { token: i2.LocalDataChangesService }, { token: i3.ColumnInfoService }], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CellSelectionAggregateService });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CellSelectionAggregateService, decorators: [{
type: Injectable
}], ctorParameters: function () { return [{ type: i1.ContextService }, { type: i2.LocalDataChangesService }, { type: i3.ColumnInfoService }]; } });