Aggregates

The TreeList component can render aggregated data.

Getting Started

In order to render aggregated data, it's required to calculate it and add it to the TreeList data tree, along with the other data objects. Additional styling or customization of the rows showing aggregated data can be achieved using the TreeList rowRender property.

The following example shows how to render aggregated data.

import React from 'react';
import ReactDOM from 'react-dom';
import {
    TreeList,
    mapTree,
    extendDataItem
} from '@progress/kendo-react-treelist';

import employees from './data';
import { provideIntlService } from '@progress/kendo-react-intl';

const dateFormat = '{0:MMMM d, yyyy}';
const subItemsField = 'employees';
const expandField = 'expanded';
const columns = [
    { field: 'name', title: 'Name', width: '34%', expandable: true },
    { field: 'position', title: 'Position', width: '33%' },
    { field: 'hireDate', title: 'Hire Date', width: '33%', format: dateFormat }
];

class App extends React.Component {
    state = {
        data: [...employees],
        expanded: []
    };

    intl = provideIntlService(this);

    processData = () => {
        const data = this.addExpandField(this.state.data);
        const result = mapTree(data, subItemsField, this.aggregateSubItems);

        return this.aggregateSubItems({ [subItemsField]: result }, true)[subItemsField];
    }

    aggregateSubItems = (item, footer) => {
        const subItems = item[subItemsField];
        if (subItems && subItems.length) {
            const lastHireDate = subItems.reduce((acc, curr) => Math.max(acc, curr.hireDate, (curr.lastHireDate || acc)), new Date(0));
            const employeesCount = subItems.reduce((acc, curr) => acc + (curr.employeesCount || 0) + 1, 0);
            const aggregateItem = {
                footer,
                name: `${employeesCount} employee(s)`,
                hireDate: `Last employee hired on ${this.intl.format(dateFormat, new Date(lastHireDate))}`
            };
            return extendDataItem(item, subItemsField, { employeesCount, lastHireDate, [subItemsField]: [ ...subItems, aggregateItem ] });
        }
        return item;
    }

    render() {
        return (
            <TreeList
                style={{ maxHeight: '430px', overflow: 'auto', width: '100%' }}
                expandField={expandField}
                subItemsField={subItemsField}
                onExpandChange={this.onExpandChange}
                data={this.processData()}
                columns={columns}
                rowRender={this.rowRender}
            />
        );
    }

    rowRender = (row, props) => props.dataItem.footer ? <row.type {...row.props} className="k-group-footer" /> : row;

    onExpandChange = (event) => {
        this.setState({
            expanded: event.value ?
                this.state.expanded.filter(id => id !== event.dataItem.id) :
                [...this.state.expanded, event.dataItem.id]
        });
    }

    addExpandField = (dataArr) => {
        const expanded = this.state.expanded;
        return mapTree(dataArr, subItemsField, (item) =>
            extendDataItem(item, subItemsField, { expanded: expanded.includes(item.id) }));
    }
}

ReactDOM.render(
    <App />,
    document.querySelector('my-app')
);