Skip to content

Commit 22856c1

Browse files
committed
Added grouping to the listview control
1 parent 7408bc6 commit 22856c1

File tree

5 files changed

+105
-6
lines changed

5 files changed

+105
-6
lines changed

src/controls/listView/IListView.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Selection, SelectionMode } from 'office-ui-fabric-react/lib/DetailsList';
2-
import { IColumn } from 'office-ui-fabric-react/lib/components/DetailsList';
2+
import { IColumn ,IGroup} from 'office-ui-fabric-react/lib/components/DetailsList';
33

44
export { SelectionMode };
55

@@ -17,6 +17,10 @@ export interface IListViewProps {
1717
* The fields you want to view in your list view
1818
*/
1919
viewFields?: IViewField[];
20+
/**
21+
* The fields you want to group your list view by
22+
*/
23+
groupByFields?: string[];
2024
/**
2125
* Boolean value to indicate if the component should render in compact mode.
2226
* Set to false by default
@@ -44,6 +48,8 @@ export interface IListViewState {
4448
* If none are provided, default columns will be created based on the item's properties.
4549
*/
4650
columns?: IColumn[];
51+
52+
groups?: IGroup[];
4753
}
4854

4955
export interface IViewField {

src/controls/listView/ListView.tsx

Lines changed: 88 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import * as React from 'react';
2-
import { DetailsList, DetailsListLayoutMode, Selection, SelectionMode } from 'office-ui-fabric-react/lib/DetailsList';
2+
import { DetailsList, DetailsListLayoutMode, Selection, SelectionMode, IGroup } from 'office-ui-fabric-react/lib/DetailsList';
33
import { IListViewProps, IListViewState, IViewField } from './IListView';
44
import { IColumn } from 'office-ui-fabric-react/lib/components/DetailsList';
55
import { findIndex, has, sortBy, isEqual, cloneDeep } from '@microsoft/sp-lodash-subset';
66
import { FileTypeIcon, IconType } from '../fileTypeIcon/index';
7+
import * as strings from 'ControlStrings';
78

89
/**
910
* File type icon component
@@ -49,6 +50,74 @@ export class ListView extends React.Component<IListViewProps, IListViewState> {
4950
}
5051
}
5152

53+
/**
54+
* Specify result grouping for the list rendering
55+
* @param items
56+
* @param groupByFields
57+
*/
58+
private _getGroups(items: any[], groupByFields: string[], level: number = 0, startIndex: number = 0): {items: any[], groups: IGroup[]} {
59+
// Group array which stores the configured grouping
60+
let groups: IGroup[] = [];
61+
let updatedItemsOrder: any[] = [];
62+
// Check if there are groupby fields set
63+
if (groupByFields) {
64+
const groupField = groupByFields[level];
65+
// Check if grouping is configured
66+
if (groupByFields && groupByFields.length > 0) {
67+
// Create grouped items object
68+
const groupedItems = {};
69+
items.forEach((item: any) => {
70+
let groupName = item[groupField];
71+
// Check if the group name exists
72+
if (typeof groupName === "undefined") {
73+
// Set the default empty label for the field
74+
groupName = strings.ListViewGroupEmptyLabel;
75+
}
76+
// Check if group name is a number, this can cause sorting issues
77+
if (typeof groupName === "number") {
78+
groupName = `${groupName}.`;
79+
}
80+
81+
// Check if current group already exists
82+
if (typeof groupedItems[groupName] === "undefined") {
83+
// Create a new group of items
84+
groupedItems[groupName] = [];
85+
}
86+
groupedItems[groupName].push(item);
87+
});
88+
89+
// Loop over all the groups
90+
for (const groupItems in groupedItems) {
91+
// Add the items to the updated items order array
92+
groupedItems[groupItems].forEach((item) => {
93+
updatedItemsOrder.push(item);
94+
});
95+
// Retrieve the total number of items per group
96+
const totalItems = groupedItems[groupItems].length;
97+
// Create the new group
98+
const group: IGroup = {
99+
name: groupItems === "undefined" ? strings.ListViewGroupEmptyLabel : groupItems,
100+
key: groupItems === "undefined" ? strings.ListViewGroupEmptyLabel : groupItems,
101+
startIndex: startIndex,
102+
count: totalItems,
103+
};
104+
// Check if child grouping available
105+
if (groupByFields[level + 1]) {
106+
// Get the child groups
107+
group.children = this._getGroups(groupedItems[groupItems], groupByFields, (level + 1), startIndex).groups;
108+
}
109+
// Increase the start index for the next group
110+
startIndex = startIndex + totalItems;
111+
groups.push(group);
112+
}
113+
}
114+
}
115+
return {
116+
items: updatedItemsOrder,
117+
groups
118+
};
119+
}
120+
52121
/**
53122
* Process all the component properties
54123
*/
@@ -75,6 +144,17 @@ export class ListView extends React.Component<IListViewProps, IListViewState> {
75144

76145
// Add the columns to the temporary state
77146
tempState.columns = columns;
147+
148+
// Add grouping to the list view
149+
const grouping = this._getGroups(tempState.items, this.props.groupByFields);
150+
if (grouping.groups.length > 0) {
151+
tempState.groups = grouping.groups;
152+
// Update the items
153+
tempState.items = grouping.items;
154+
} else {
155+
tempState.groups = null;
156+
}
157+
78158
// Update the current component state with the new values
79159
this.setState(tempState);
80160
}
@@ -203,10 +283,13 @@ export class ListView extends React.Component<IListViewProps, IListViewState> {
203283
}
204284
return c;
205285
});
286+
// Update the grouping
287+
const groupedItems = this._getGroups(sortedItems, this.props.groupByFields);
206288
// Update the items and columns
207289
this.setState({
208-
items: sortedItems,
209-
columns: sortedColumns
290+
items: groupedItems.groups.length > 0 ? groupedItems.items : sortedItems,
291+
columns: sortedColumns,
292+
groups: groupedItems.groups.length > 0 ? groupedItems.groups : null,
210293
});
211294
}
212295
}
@@ -228,11 +311,13 @@ export class ListView extends React.Component<IListViewProps, IListViewState> {
228311
* Default React component render method
229312
*/
230313
public render(): React.ReactElement<IListViewProps> {
314+
231315
return (
232316
<div>
233317
<DetailsList
234318
items={this.state.items}
235319
columns={this.state.columns}
320+
groups={this.state.groups}
236321
selectionMode={this.props.selectionMode || SelectionMode.none}
237322
selection={this._selection}
238323
layoutMode={DetailsListLayoutMode.justified}

src/loc/en-us.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
define([], function () {
22
return {
3-
'SiteBreadcrumbLabel': 'Website breadcrumb'
3+
'SiteBreadcrumbLabel': 'Website breadcrumb',
4+
5+
'ListViewGroupEmptyLabel': 'Empty'
46
}
57
});

src/loc/mystrings.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
declare interface IControlStrings {
22
SiteBreadcrumbLabel: string;
3+
ListViewGroupEmptyLabel: string;
34
}
45

56
declare module 'ControlStrings' {

src/webparts/controlsTest/components/ControlsTest.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ export default class ControlsTest extends React.Component<IControlsTestProps, IC
9292
{
9393
name: 'ListItemAllFields.Id',
9494
displayName: 'ID',
95-
maxWidth: 20,
95+
maxWidth: 40,
9696
sorting: true
9797
},
9898
{
@@ -112,6 +112,10 @@ export default class ControlsTest extends React.Component<IControlsTestProps, IC
112112
}
113113
];
114114

115+
// Specify the fields on which you want to group your items
116+
// Grouping is takes the field order into account from the array
117+
const groupByFields: string[] = ["ListItemAllFields.Title"];
118+
115119
return (
116120
<div className={styles.controlsTest}>
117121
<div className={styles.container}>
@@ -162,6 +166,7 @@ export default class ControlsTest extends React.Component<IControlsTestProps, IC
162166
items={this.state.items}
163167
viewFields={viewFields}
164168
iconFieldName='ServerRelativeUrl'
169+
groupByFields={groupByFields}
165170
compact={true}
166171
selectionMode={SelectionMode.multiple}
167172
selection={this._getSelection} />

0 commit comments

Comments
 (0)