group()
groups data in a collection that has a plain tree-like structure according to the specified order and additional configuration
Data grouping isn't intended for working with lazyDataProxy
Grouped data can be serialized. After serialization data is available for rendering and editing as a plain tree-like structure
Usage
type TGroupOrderFunc = (item: IDataItem) => string;
type TAggregate = "sum" | "count" | "min" | "max" | "avg" | string;
interface IGroupOrder {
by: string | TGroupOrderFunc;
map?: {
[field: string]: [string, TAggregate] | ((item: IDataItem[]) => string | number)
};
summary?: "top" | "bottom";
}
type TGroupOrder = string | TGroupOrderFunc | IGroupOrder;
interface IGroupConfig {
showMissed?: boolean | string; // true by default
showEmptyGroups?: boolean; // false by default
field?: string; // "group" by default
}
group(order: TGroupOrder[], config?: IGroupConfig): void;
Parameters
| order | (array) an array that defines the order and configuration for data grouping. Each element in the array can be:
|
| config | (object) optional, the configuration of data grouping. The configuration object may include the following properties:
|
Examples
- simple grouping with the use of a callback function and a string field value
const grid = new dhx.Grid("grid_container", {
columns: [
{ id: "salary", header: [{ text: "Salary" }] },
{ id: "experience", header: [{ text: "Experience (years)" }] },
{ id: "city", header: [{ text: "City" }] }
],
group: true,
groupable: true,
data: dataset
});
grid.data.group([
function(row) {
if (row.salary < 30000) return "Low income";
if (row.salary >= 30000 && row.salary < 70000) return "Medium income";
return "High income";
},
"city"
]);
- grouping with the use of a configuration object and aggregation settings
const grid = new dhx.Grid("grid_container", {
columns: [
{ id: "department", header: [{ text: "Department" }] },
{ id: "employees", header: [{ text: "Number of Employees" }] },
{ id: "location", header: [{ text: "Location" }] }
],
group: true,
groupable: true,
data: dataset
});
grid.data.group([{
by: "department", // grouping by the `department` field
map: {
employees: ["employees", "sum"], // aggregation: sums up the number of employees
location: (rows) => {
// a custom function for calculating unique locations
const uniqueLocations = [...new Set(rows.map(r => r.location))];
return uniqueLocations.join(", ");
}
},
summary: "top" // the total row is rendered at the top of the group
}]);
- grouping with the use of the
showMissedproperty
const grid = new dhx.Grid("grid_container", {
columns: [
{ id: "name", header: [{ text: "Name" }] },
{ id: "age", header: [{ text: "Age" }] },
{ id: "city", header: [{ text: "City" }] }
],
group: true,
groupable: true,
data: dataset
});
grid.data.group(["city"], {
// the group elements that don't have the "city" value
// will be rendered in the "Unknown City" group
showMissed: "Unknown City"
});
Group counters and aggregates
Group headers follow the data they hold. DataCollection recalculates them after every change of the collection content, that is after the filter(), resetFilter(), add(), remove(), update() and parse() methods.
The same applies to TreeCollection, and thus to Grid in the TreeGrid mode: a counter covers the whole subtree of a header row, and an emptied header row disappears together with everything below it.
Recalculation needs no configuration, it happens on every data change while the collection is grouped:
const data = new dhx.DataCollection();
data.parse(dataset);
data.group([{ by: "status", map: { total: ["price", "sum"] }, summary: "bottom" }]);
// the header row of the "wip" group, which holds two items with the total of 50
const wip = data.map(item => item).find(item => item.$group);
wip.$count; // 2
wip.$totalCount; // 2
wip.total; // 50
data.filter({
by: "price",
match: 30,
compare: (value, match) => Number(value) >= Number(match)
});
wip.$count; // 1
wip.$totalCount; // 2, the unfiltered number of items
wip.total; // 30, recomputed over the items that are left
data.getItem(`${wip.id}:summary`).total; // 30, the summary row follows
data.resetFilter();
wip.$count; // 2
Counters of a group
A group header row carries the following service properties:
$count- the number of data items that the group currently holds. For a nested grouping it is the size of the whole subtree of the group. Nested headers and summary rows don't count as data$totalCount- the number of data items that the group holds ignoring the active filters. It equals$countwhen no filter is active
Aggregated fields
DataCollection recomputes every field listed in the map object of a grouping level over the items that are left, on the header row and on the group summary row that the summary property adds alike, so both rows show the same values.
Filtering grouped data
DataCollection matches a filtering rule against the data items only, so a filtering function never receives a $group or a $groupSummary item. A group stays as long as any of its items match the rule, and a group that loses all of them leaves the collection together with its summary row and its nested groups. map() skips such a group and getLength() leaves it out, unless you pass the showEmptyGroups: true config to the method, and resetFilter() brings it back either way.
Removing a group
A group emptied by remove() has no filter to bring it back, so it leaves the collection for good, its summary row included, and getItem() returns undefined for the id of its header. If the data is grouped with the showEmptyGroups: true config, the group stays in the collection with the $count: 0 value instead.
Related sample: Grid. Group counter after filtering
Change log:
- As of v9.4, DataCollection recalculates the counters and aggregated values of group headers after every change of the collection content
- The
showEmptyGroupsproperty of theconfigparameter is added in v9.4 - Added in v9.0