-
Notifications
You must be signed in to change notification settings - Fork 161
Group By Specification
- Overview
- User Stories
- Functionality
- Test Scenarios
- Accessibility
- Assumptions and Limitations
- References
Team Name
Developer Name
Yoanna Ivanova
- Peer Developer Name | Date:
- Stefan Ivanov | Date: 18 Nov 2020
- Product Owner: Radoslav Mirchev | Date: 08 Sep 2020
- Platform Architect Name | Date:
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.
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
Dragging a header changes the look of the group by bar
When all groups are minimized
When the groups are expanded (Note there is an icon in the header that collapses/expands groups)
While a second column is dragged
Two groups
Two groups expanded
Pinning Included
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
One Group
Two Groups
Two Groups with Row Selection
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:
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:
Expanding and collapsing groups would change the paging state as it alters the total amount of items participating in paging:
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
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
-
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[]
[]
-
IgxGridColumnComponent
Name Description Type Default value Valid values groupable Controls if the column may be grouped through the UI boolean
false
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 |
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. IfshowSummaryOnCollapse
is set totrue
, the summary row will be visible on Grouped row collapse action. DefaultshowSummaryOnCollapse
value isfalse
.
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
-
aria-expanded
(true
|false
) for each group row -
aria-describedby
(grid id and column name) for each group row -
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