UNPKG

@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
/**----------------------------------------------------------------------------------------- * 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 }]; } });