Skip to content

Group By Specification

Zdravko Kolev edited this page Dec 18, 2020 · 59 revisions

Group By Specification

Contents

  1. Overview
  2. User Stories
  3. Functionality
  4. Test Scenarios
  5. Accessibility
  6. Assumptions and Limitations
  7. References

Owned by

Team Name

Developer Name

Yoanna Ivanova

Requires approval from

  • Peer Developer Name | Date:
  • Stefan Ivanov | Date: 18 Nov 2020

Signed off by

  • Product Owner: Radoslav Mirchev | Date: 08 Sep 2020
  • Platform Architect Name | Date:

Revision History

Version User Date Notes
0.1 Stamen Stoychev May 9, 2018 Initial draft
0.2 Stamen Stoychev May 10, 2018 API, keyboard nav, ARIA
0.3 Stamen Stoychev May 14, 2018 Grouping/sorting sync rules
0.4 Maya Kirova May 15, 2018 Initial test scenarios
0.5 Stamen Stoychev May 18, 2018 GroupBy pipe spec
0.6 Deyan Kamburov May 29, 2018 GroupBy toggle groups button
0.7 Martin Pavlov May 31, 2018 Adding visual designs
0.8 Stamen Stoychev June 11, 2018 Final updates
1.0 Stamen Stoychev June 22, 2018 Pipe and implementation amendments
1.1 Stamen Stoychev June 27, 2019 Paging integration changes
1.2 Zdravko Kolev July 2, 2020 Add showSummaryOnCollapse option
1.3 Radoslav Mirchev September 8, 2020 "Igx-grid: Select all rows in a group #7344" - spec is updated and signed off. Changes are only in the design of the feature.

igxGrid Group By allows developers to enable grouping of records based on the equality of specified properties.

Objectives

The feature includes the following:

  • Integration with sorting as grouping requires sorting to be performed on grouped columns
  • UI for choosing the property (column) to group by
  • UI for ungrouping columns
  • UI for expanding and collapsing of groups
  • Persistence of the groups expand/collapse states
  • UI for changing the sort order
  • Templating for the group records
  • Per-group summaries

Developer stories:

  • Story 1: As a developer, I want to configure the columns that can be grouped and their sort direction.
  • Story 2: As a developer, I want to initially group columns.
  • Story 3: As a developer, I want to group or ungroup columns at run-time.
  • Story 4: As a developer, I want to configure the group rows' initial expand/collapse state.
  • Story 5: As a developer, I want to expand/collapse group rows at run-time.
  • Story 6: As a developer, I expect that group rows can trigger selection/deselection of all elements in the group out of the box with row selection.

End-user stories:

  • Story 1: As an end-user, I want to group columns, so that I can see similar data.
  • Story 2: As an end-user, I want to ungroup columns.
  • Story 3: As an end-user, I want to collapse grouped data, so that I can hide the data that I'm not interested in.
  • Story 4: As an end-user, I want to expand the grouped data, so that I can see the data that I'm interested in.
  • Story 5: As an end-user, I want to see summary information of the grouped data.
  • Story 6: As an end-user, I want to sort the grouped data.
  • Story 7: As an end-user, I want to navigate through the data using Paging functionality.
  • Story 8: As an end-user, I want to modify the grouped data.
  • Story 9: As an end-user, I want to re-order the grouped columns.
  • Story 10: As an end-user, I want to select/deselect all the rows in a group at once, so that I don't waste time selecting them one by one.

3.1. End-User Experience

Default view of the Group By Bar

00-grouping_default

Dragging a header changes the look of the group by bar

01-grouping_draggingacolumn

When all groups are minimized

02-grouping_allgroupsminimized

When the groups are expanded (Note there is an icon in the header that collapses/expands groups)

02-grouping_allgroupsexpanded

While a second column is dragged

03-grouping_dragasecondcolumn

Two groups

04-grouping_twogroups

Two groups expanded

04-grouping_twogroups_expanded

Pinning Included

05-grouping_withpinning

Expanded grouped flat grid with showSummaryOnCollapse

Collapsed grouped flat grid with showSummaryOnCollapse

Unselected row in a group

One selected row in a group

All rows selected

None selection mode + row on focus

Single selection mode + row on focus (disabled state of the checkboxes in the group by rows)

Single selection mode with one child data row selected makes the immediate group by row selected and disabled (group by row state is determined by the state of data record/records in the group)

Multiple selection mode + group header on focus

Handoff

One Group

02-grouping_allgroupsexpanded

Two Groups

04-grouping_twogroups_expanded

Two Groups with Row Selection

06-grouping_rowselect

3.2. Developer Experience

The ability to group a column from the UI is enabled by setting its groupable property.

<igx-grid #grid1 [data]="data">
    <igx-column *ngFor="let c of columns" [field]="c.field" [groupable]="true">
    </igx-column>
</igx-grid>

Columns that are not set as groupable may still have grouping applied through the API, however, their group area pins are disabled for interaction and the end-user is only allowed to expand/collapse their group rows.

Both the current grouping applied and the groups' expansion states are controllable through the grid's API.

There is a group area rendered when any of the columns is groupable or there are groupingExpressions defined. It can also be rendered run-time if the developer pushes a grouping expression.

The grouping expressions collection is of type Array<ISortingExpressions> (as the grouping is the same as sorting on data level).

export enum SortingDirection {
    None = 0,
    Asc = 1,
    Desc = 2
}

export interface ISortingExpression {
   fieldName: string;
   dir: SortingDirection;
   ignoreCase?: boolean;
}

The input property groupingExpressions gets the current state and may be set to apply а new one. Similarly, users may call the groupBy method which accepts one or more expressions to add to the list:

grid.groupBy(expression: ISortingExpression | Array<ISortingExpression>);

Grouping can be cleared via the clearGrouping API method. The method can be called with no parameter, in which case all grouping in the grid will be cleared, or the name of the field for which to clear grouping.

To clear all grouping in the grid:

grid.clearGrouping();

To clear grouping from a particular column by its field name:

grid.clearGrouping(fieldName);

groupsRecords is a property that users may get a collection of groups created for the current data view. It has the following signature:

public groupsRecords: IGroupByRecord[]

Using the IGroupByRecord's records property yields the records part of this group while the groups property holds the child group records. This makes it easy to find the group row that is required and, e.g. toggles it through the API.

grid.toggleGroup(grid.groupsRecords[0]);

Group rows can be toggled all at once recursively via the grid's toggleAllGroupRows API method.

grid.toggleAllGroupRows();

Group by works in conjunction with sorting. Grouping expressions are essentially sorting ones and are processed by the sorting pipe. The grouping and sorting API ensure that passed expressions are synced between the two collections so that the following behavior is achieved:

  • The grouping expression collection contains all grouping expressions in the order in which they were grouped. They are also added in the sorting expressions collection after which the sorting collection is re-arranged so that group expressions are always first. The sorting expression pipe sorts the data in the order of the sorting expressions so that it will always sort first by the sort expressions added as a result of grouping and then by the additional sort expressions.

  • When grouping by a new expression collection each related field will be added to the grouping expression collection. If it is already available in the sorting expressions collection then the order of the sorting expressions will be re-arranged so that the sort expressions added as a result of grouping are first and are therefore applied first.

  • When sorting a new expressions collection each field from it available in the grouping expressions collection will just change its sort order and/or ignore case flag based on the new expression. If the sort order is set to None then grouping is removed.

Grouping is achieved through a pipe that must be called after the sorting one. It has the following structure:

export class IgxGridGroupingPipe implements PipeTransform {
    public transform(collection: any[], expression: ISortingExpression | ISortingExpression[],
                     expansion: IGroupByExpandState | IGroupByExpandState[], defaultExpanded: boolean,
                     id: string, pipeTrigger: number): any[] {
    }
}

It serves the purpose of processing the sorted data and creating the tree grouping structure based on the values of equality. The values are compared with the SortingStrategy's comparer which can be overridden by the user. Afterward, the tree is flattened based on the groups' expansion state producing a view that can be rendered with a structural directive (such as igxForOf).

This result of the grouping pipe is then sent to the paging one so that all group records participate in the paging process and are part of the total page size for each page. This can be observed in the following sample with a page size of 5:

1

Groups that span multiple pages are split between them. The group summary information is consistent for the whole group but the header itself is not created for each page:

2

Expanding and collapsing groups would change the paging state as it alters the total amount of items participating in paging:

3

The expand state has the following structure:

export interface IGroupByExpandState {
    expanded: boolean;
    hierarchy: Array<IGroupByKey>;
}

export interface IGroupByKey {
    fieldName: string;
    value: any;
}

It consists of a state and an identifier. The identifier is the list of groups in the grouping hierarchy that uniquely identifies it.

The expressions and expansion collections, the default expansion and the sorting strategy form the IGroupingState interface:

export interface IGroupingState {
    expressions: ISortingExpression[];
    expansion: IGroupByExpandState[];
    defaultExpanded: boolean;
    strategy?: ISortingStrategy;
}

As already mentioned the main grouping implementation is part of the sorting strategy further enforcing their connection. Users that implement custom sorting strategies can easily modify their groupBy to work in tandem.

export interface ISortingStrategy {
    sort: (data: any[], expressions: ISortingExpression[]) => any[];
    groupBy: (data: any[], expressions: ISortingExpression[],
              expansion: IGroupByExpandState[], defaultExpanded: boolean) => IGroupByResult;
    compareValues: (a: any, b: any) => number;
}

Note: As shown, the groupBy method should produce an IGroupByResult with the following signature:

export interface IGroupByResult {
    data: any[];
    metadata: IGroupByRecord[];
}

Where data is the same collection passed to the pipe and metadata holds each record's immediate IGroupByRecord for the same index.

The output of the final grouping pipe is an array that contains both data records to be templated through the IgxGridRowComponent and grouping records that are templated through the IgxGridGroupByRowComponent. The group record is an interface that has the following structure:

export class GroupedRecords extends Array<any> {}

export interface IGroupByRecord {
    expression: ISortingExpression;
    level: number;
    records: GroupedRecords;
    value: any;
    groups?: IGroupByRecord[];
    groupParent: IGroupByRecord;
}

Default or initial expand or collapse state of the groups is defined by the groupsExpanded (default is true). This means that when the end-user groups a column, the resulting groups will be expanded or collapsed depending on the value of this option.

Each row current expand/collapse state is kept in groupingExpansionState option.

Grouping persists through all grid operations and is reapplied through pipe triggers. The expansion states persist in the same way. Clearing expansions states is done automatically when ungrouping a field, for each grouping level under it in the grouping hierarchy or when changing the grouping order, for each level under the highest in the hierarchy that had its position changed.

When grouping is applied, there is a button in the header area for collapsing and expanding all groups. What this button does under the hood is to clear the groupingExpansionState and change the value of groupsExpanded. When clicking the button the groups are going to be collapse or expand depending on the current groupsExpanded value. Also note that the default expand state for the groups is changed, which means that if another grouping is applied, the expand state of the groups will be retained.

3.3. Globalization/Localization

Describe any special localization requirements such as the number of localizable strings, regional formats

3.4. Keyboard Navigation

Elements from the grouping UI participate in the document's tab sequence. Both column chips in the grid's toolbar and group rows inside the grid's body are focusable and controllable with the keyboard. Elements in the body follow the natural tab sequence of body elements and chips in the toolbar follow the toolbar's one.

  • For group rows (focus should be on the row)
  • For group igxChip components in the group by area (focus should be on the chip)
Keys Description
Alt + / Expands the group
Alt + / Collapse the group
Shift + Moves the focused chip left, changing the grouping order, if possible
Shift + Moves the focused chip right, changing the grouping order, if possible
Space (on chip in group by area) Changes the sorting direction
Delete Ungroups the field
Enter Focus on the separate elements of the chip
Space (on group by row in the body) Selects all rows in the group

3.5. API

Options

  1. IgxGridComponent

    Name Description Type Default value Valid values
    groupingExpressions A list of expressions to group by Array<ISortingExpression> null [{ fieldName: "Name", dir: SortingDirection.Asc, ignoreCase: true }]
    groupingExpansionState A list of expansions states based on a composite grouping key consisting of list of the column names and value uniquely identifying a group row Array<IGroupByExpandState> null [ [{ fieldName: "Name", value: "Angela Seamons" }], expanded: true }]
    groupsExpanded Controls whether created groups are rendered expanded or not boolean true true, false
    groupsRowList A list of visible group rows QueryList<IgxGridGroupByRowComponent> []
    groupsRecords All groups in hierarchy reflecting the current groups state. IGroupByRecord[] []
  2. IgxGridColumnComponent

    Name Description Type Default value Valid values
    groupable Controls if the column may be grouped through the UI boolean false

Methods

  1. IgxGridColumnComponent
Name Description Return type Parameters
groupBy Adds modifies a single or multiple fields for grouping void
isExpandedGroup Returns if a group is expanded or not boolean group: IGroupByRecord
clearGrouping Removes grouping for a single or all field void name?: string
toggleGroup Toggles the expansion state of a group void group: IGroupByRecord
getGroup Gets a group record by its composite key IGroupByRecord field: string, value: any
toggleAllGroupRows Toggles the expansion state of all groups recursively void void

Events

Name Description Cancelable Parameters
onGroupingDone Fires after a new column is grouped or ungrouped no ISortingExpression

Automation

Basic

  • Scenario 1: GroupBy allows grouping by columns with different data types: Number, String, Date, and Boolean.
  • Scenario 2: GroupBy allows grouping by multiple columns.
  • Scenario 3: GroupBy allows expanding/collapsing groups.
  • Scenario 4: GroupBy allows changing the order of the groupBy columns.
  • Scenario 5: GroupBy allows setting initially expanded/collapsed state for group rows.
  • Scenario 6: onGroupingDone event fires after a column is grouped/ungrouped and event args contains correct sorting expressions.
  • Scenario 7: GroupBy allows setting a custom template for the group row content.
  • Scenario 8: ARIA attributes are properly applied to the group row elements - aria-expanded, aria-describedby.
  • Scenario 9: Expand/Collapse icons should be focusable (tabbable) and should allow toggling via Enter/Space key.
  • Scenario 10: Group area is rendered when groupBy API method is invoked.
  • Scenario 11: Group area is rendered when there is a groupable column.
  • Scenario 12: GroupBy should apply the chips correctly when there are grouping expressions applied and reordered.
  • Scenario 13: GroupBy should allow dragging headers and dropping them to group area and transforming them to chips.
  • Scenario 14: GroupBy allows reordering chips and reflect their state to the groups state accordingly.
  • Scenario 15: GroupBy should allow changing the grouping direction of the groups from the chips.
  • Scenario 16: GroupBy should render disabled non-interactable chip for a column that does not allow grouping.
  • Scenario 17: Grid should be able to collapse or expand all group rows via toggleAllGroupRows API.

Integration

Sorting

  • Scenario 1: When sorting is applied to a non-grouped field the data in the existing groups are sorted.
  • Scenario 2: When sorting is applied on an already grouped field the new sort order and/or ignore case is applied to the group. In case of sort order is set to None via the API the grouping for that field is removed.
  • Scenario 3: When grouping is applied on an already sorted field grouping is applied with the specified sort order and ignore case.
  • Scenario 4: When changing sort order for grouped column via the sorting UI (clicking the column header) only two states should be applied - ascending and descending. State "None" should be skipped.

Paging

  • Scenario 1: When paging is applied the data is split based on flat records + group records view. Page count and page size include both item types.
  • Scenario 2: Expanding/Collapsing groups affect the page count/page size.
  • Scenario 3: Group row's summary should be calculated based on all data records that belong to the group, even if they don't belong to the current page.
  • Scenario 4: If a group spans multiple pages, its group header is rendered only on the first one.

Virtualization

  • Scenario 1: Group rows are virtualized.
  • Scenario 2: Expanding/Collapsing rows recalculates the visible chunk data and scrollbar size so that there are no empty spaces.
  • Scenario 3: Group row expansion state is persisted when moving between different virtualization frames.
  • Scenario 4: Group rows remain static when scrolling horizontally so that their content is always visible.

Filtering

  • Scenario 1: Filtering filters by the data records and renders their related groups. Group expand state is kept intact i.e. if a group row is collapsed and there are data rows that match the filter the group row will still be collapsed after the filtering.

Selection

  • Scenario 1: Group rows cannot be selected, only focused, when clicked or navigated into.
  • Scenario 2: Keyboard navigation via arrow keys should focus the group rows and allow navigating to the next data cell.
  • Scenario 3: When navigating from a data cell to a group row via the arrow keys, the data cell should be deselected.
  • Scenario 4: When navigating vertically in a column between data and group cells, navigation should always continue in the same column.

Row Selectors

  • Scenario 1: Row selectors should be rendered for the group rows when the selection mode is single or multiple and should be properly aligned.
  • Scenario 2: Row selectors should not be rendered for the group rows when the selection mode is none.
  • Scenario 3: Check whether the checkboxes in the group row get hidden when setting the hideRowSelectors to true.
  • Scenario 4: Group row checkboxes should be checked when selectAll API is called or when the header checkbox is clicked.
  • Scenario 5: Row selectors for all rows in certain group should be checked if the checkbox for this group row is checked.
  • Scenario 6: Row selectors for all rows in certain group should be unchecked if the checkbox for this group row is unchecked.
  • Scenario 7: If all records in a group are selected the group row selector state should be checked.
  • Scenario 8: If some of the records in a group but not all are selected the group row selector should be in indeterminate state.
  • Scenario 9: If selectionMode is single, the group row selectors should be disabled.
  • Scenario 10: If selectionMode is single and the groupRow is focused pressing space should not affect current row selection.
  • Scenario 11: If selectionMode is multiple and none or at least not all records within a group are selected and the groupRow is focused pressing space should select all the records for this group.
  • Scenario 12: If selectionMode is multiple and all records within a group are selected and the groupRow is focused pressing space should deselect all the records for this group.
  • Scenario 13: Filter, select all rows in group. Remove filter and check whether the previously filtered out rows appear as not selected and the group row checkbox is in the correct state.
  • Scenario 14: Select/deselect all rows in group from API either when igxGrid primaryKey is provided or not.
  • Scenario 15: Add new row with value of a currently grouped field where all records are selected. Since it is added as non-selected check whether the state of the group row checkbox is changed to indeterminate.
  • Scenario 16: Should have the correct properties in the custom groupByRow selector template.
  • Scenario 17: ARIA support should give information regarding the current groupRow field name and its value.
  • Scenario 18: Edit selected row so it goes to another group where all rows are selected as well. Check, whether the group row checkbox of the new group that the record becomes part of, is checked.
  • Scenario 19: Edit selected row so it goes to another group where all rows are not selected. Check, whether the group row checkbox of the new group that the record becomes part of, is indeterminate.
  • Scenario 20: Edit non-selected row so it goes to another group where all rows are selected. Check, whether the checkbox of the new group that the record belongs to is indeterminate.
  • Scenario 21: Edit the only non-selected row in a group so that it moves to another group and check whether the current group row checkbox becomes checked.
  • Scenario 22: Edit the only selected row in a group so that it moves to another group and check whether the current group row checkbox becomes unchecked.
  • Scenario 23: Delete the only selected row from a certain group and check whether the group checkbox state is unchecked.
  • Scenario 24: Delete row in group with all rows selected and check whether the group row checkbox state stays checked.
  • Scenario 25: Delete the only-non selected row in a group and check whether the group row checkbox becomes checked.

Resizing

  • Scenario 1: Resizing a column does not break group rows layout.

Summaries

  • Scenario 1: Summaries take into account only the data records. Group rows are disregarded.
  • Scenario 2: There is showSummaryOnCollapse property that manages summaries visibility on Group row collapse/expands action. If showSummaryOnCollapse is set to true, the summary row will be visible on Grouped row collapse action. Default showSummaryOnCollapse value is false.

Hiding

  • Scenario 1: Hiding/Showing a column does not break the group rows layout.

Pinning

  • Scenario 1: Pinning/Unpinning a column does not break group rows layout.

Updating

  • Scenario 1: After updating a cell for a column that is grouped through the UI, its record should be relocated in the correct group.
  • Scenario 2: Deleting via the API all items from a group should remove the group row as well.
  • Scenario 3: Adding new records via the API should include them in the correct group.
  • Scenario 4: Updating records via the API should move them to the correct group.

Manual

Basic

  • Scenario 1: Test with displayDensity: compact, cosy, comfortable.
  • Scenario 2: Test grouping of columns using touch gestures (column can be dragged and dropped in the grouping area using touch).
  • Scenario 3: Test expand/collapse of group rows using touch.
  • Scenario 4: Test initially grouped columns correctly displayed inside the group area
  • Scenario 5: Test changing sorting direction of the grouped columns using the group area on desktop and touch.
  • Scenario 6: Test changing grouping order by dragging chips around inside the group area on desktop and touch.
  • Scenario 7: Test group chips spanning on a new row when there are too many chips to fit in the group area on desktop and touch.
  • Scenario 8: Test grouping by dragging a column when chips are spanned on multiple rows on desktop and touch.
  • Scenario 9: Test changing grouping order by dragging chips around inside the group area when chips are spanned on multiple rows on desktop and touch.
  • Scenario 10: Test keyboard navigation inside the group area.

Integration

Column Moving

  • Scenario 1: Test drag and drop a column inside the group area that is not groupable should not group that column.
  • Scenario 2: Test drag and drop a chip from the group area over a column header should not display Column Moving drop icon and should not move columns.

ARIA Support

  1. aria-expanded (true|false) for each group row
  2. aria-describedby (grid id and column name) for each group row
  3. aria-selected for selectable elements

RTL Support

Assumptions Limitation Notes
The predefined igxGrid styles allow for up to 10 (ten) levels of grouping
Horizontal touch scrolling works only on data rows because the group rows are fixed in the view area

Specify all referenced external sources

Clone this wiki locally