Skip to content

Commit 6bd0e13

Browse files
author
Piotr Siatka
committed
Add support of filter on ListView.
Update documentation.
1 parent 79846a4 commit 6bd0e13

File tree

3 files changed

+127
-7
lines changed

3 files changed

+127
-7
lines changed

docs/documentation/docs/controls/ListView.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,12 @@ import { ListView, IViewField, SelectionMode, GroupOrder, IGrouping } from "@pnp
2727
compact={true}
2828
selectionMode={SelectionMode.multiple}
2929
selection={this._getSelection}
30+
showFilter={true}
31+
defaultFilter="John"
32+
filterPlaceHolder="Search..."
3033
groupByFields={groupByFields} />
3134
```
35+
- The control provides full text filtering through all the columns. If you want to exectue filtering on the specified columns, you can use syntax : `<ColumndName>`:`<FilterValue>`. Use `':'` as a separator between column name and value. Control support both `'fieldName'` and `'name'` properties of IColumn interface.
3236

3337
- With the `selection` property you can define a method that which gets called when the user selects one or more items in the list view:
3438

@@ -71,6 +75,9 @@ The ListView control can be configured with the following properties:
7175
| selection | function | no | Selection event that passes the selected item(s) from the list view. |
7276
| groupByFields | IGrouping[] | no | Defines the field on which you want to group the items in the list view. |
7377
| defaultSelection | number[] | no | The index of the items to be select by default |
78+
| filterPlaceHolder | string | no | Specify the placeholder for the filter text box. Default 'Search' |
79+
| showFilter | boolean | no | Specify if the filter text box should be rendered. |
80+
| defaultFilter | string | no | Specify the initial filter to be applied to the list. |
7481

7582
The `IViewField` has the following implementation:
7683

src/controls/listView/IListView.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ export enum GroupOrder {
99
}
1010

1111
export interface IListViewProps {
12-
1312
/**
1413
* Specify the name of the file URL path which will be used to show the file icon.
1514
*/
@@ -44,10 +43,25 @@ export interface IListViewProps {
4443
* The index of the items to be select by default
4544
*/
4645
defaultSelection?: number[];
46+
/**
47+
* Specify the placeholder for the filter text box. Default 'Search'
48+
*/
49+
filterPlaceHolder?: string;
50+
/**
51+
* Specify if the filter text box should be rendered.
52+
*/
53+
showFilter?: boolean;
54+
/**
55+
* Specify the initial filter to be applied to the list.
56+
*/
57+
defaultFilter?: string;
4758
}
4859

4960
export interface IListViewState {
50-
61+
/**
62+
* Current value of the filter input
63+
*/
64+
filterValue?: string;
5165
/**
5266
* The items to render.
5367
*/

src/controls/listView/ListView.tsx

Lines changed: 104 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import { FileTypeIcon, IconType } from '../fileTypeIcon/index';
77
import * as strings from 'ControlStrings';
88
import { IGroupsItems } from './IListView';
99
import * as telemetry from '../../common/telemetry';
10+
import { TextField } from 'office-ui-fabric-react/lib/components/TextField';
11+
import { filter } from 'lodash';
1012

1113
/**
1214
* File type icon component
@@ -27,11 +29,13 @@ export class ListView extends React.Component<IListViewProps, IListViewState> {
2729

2830
// Initialize state
2931
this.state = {
30-
items: []
32+
items: [],
33+
filterValue: this.props.defaultFilter
3134
};
3235

3336
// Binding the functions
3437
this._columnClick = this._columnClick.bind(this);
38+
this._updateFilterValue = this._updateFilterValue.bind(this);
3539

3640
if (typeof this.props.selection !== 'undefined' && this.props.selection !== null) {
3741
// Initialize the selection
@@ -73,9 +77,9 @@ export class ListView extends React.Component<IListViewProps, IListViewState> {
7377
*/
7478
private _setSelectedItems(): void {
7579
if (this.props.items &&
76-
this.props.items.length > 0 &&
77-
this.props.defaultSelection &&
78-
this.props.defaultSelection.length > 0) {
80+
this.props.items.length > 0 &&
81+
this.props.defaultSelection &&
82+
this.props.defaultSelection.length > 0) {
7983
for (const index of this.props.defaultSelection) {
8084
if (index > -1) {
8185
this._selection.setIndexSelected(index, true, false);
@@ -344,6 +348,16 @@ export class ListView extends React.Component<IListViewProps, IListViewState> {
344348
}
345349
}
346350

351+
/**
352+
* Method updates the controlled value of the filter field
353+
* @param newValue
354+
*/
355+
private _updateFilterValue(newValue: string) {
356+
this.setState({
357+
filterValue: newValue
358+
})
359+
}
360+
347361
/**
348362
* Sort the list of items by the clicked column
349363
* @param items
@@ -373,11 +387,90 @@ export class ListView extends React.Component<IListViewProps, IListViewState> {
373387
return sortedItems;
374388
}
375389

390+
391+
/**
392+
* Executes filtering. Method tries to indicate if filtering should be executed on a single or all columns.
393+
* @param filterValue
394+
* @param items
395+
* @param columns
396+
*/
397+
private _executeFilternig(filterValue: string, items: any[], columns: IColumn[]): any {
398+
const filterSeparator = ":";
399+
400+
let filterColumns = [...columns];
401+
if (filterValue && filterValue.indexOf(filterSeparator) >= 0) {
402+
const columnName = filterValue.split(filterSeparator)[0];
403+
filterValue = filterValue.split(filterSeparator)[1]
404+
405+
filterColumns = filter(columns, column => {
406+
return column.fieldName === columnName || column.name === columnName;
407+
});
408+
}
409+
410+
return this._getFilteredItems(filterValue, items, filterColumns);
411+
}
412+
/**
413+
* Execute filtering on the provided data set and columns
414+
* @param filterValue
415+
* @param items
416+
* @param columns
417+
*/
418+
private _getFilteredItems(filterValue: string, items: any[], columns: IColumn[]): any {
419+
if (!filterValue) {
420+
return items;
421+
}
422+
423+
let result: any[] = [];
424+
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
425+
const item: any = items[itemIndex];
426+
let addItemToResultSet: boolean = false;
427+
for (let viewFieldIndex = 0; viewFieldIndex < columns.length; viewFieldIndex++) {
428+
let viewField = columns[viewFieldIndex];
429+
if (this._doesPropertyContainsValue(item, viewField.fieldName, filterValue)) {
430+
addItemToResultSet = true;
431+
break;
432+
}
433+
if (this._doesPropertyContainsValue(item, viewField.name, filterValue)) {
434+
addItemToResultSet = true;
435+
break;
436+
}
437+
}
438+
439+
if (addItemToResultSet) {
440+
result.push(item);
441+
}
442+
};
443+
444+
return result;
445+
}
446+
447+
/**
448+
* Check if the item contains property with proper value
449+
* @param item
450+
* @param property
451+
* @param filter
452+
*/
453+
private _doesPropertyContainsValue(item: any, property: string, filter: string): boolean {
454+
const propertyValue = item[property];
455+
let result = false;
456+
if (propertyValue) {
457+
// Case insensitive
458+
result = String(propertyValue).toLowerCase().indexOf(filter.toLowerCase()) >= 0;
459+
}
460+
461+
return result;
462+
}
463+
376464
/**
377465
* Default React component render method
378466
*/
379467
public render(): React.ReactElement<IListViewProps> {
380468
let groupProps: IGroupRenderProps = {};
469+
470+
let { showFilter, filterPlaceHolder } = this.props;
471+
let { filterValue, items, columns } = this.state;
472+
473+
filterPlaceHolder = filterPlaceHolder ? filterPlaceHolder : "Search";
381474
// Check if selection mode is single selection,
382475
// if that is the case, disable the selection on grouping headers
383476
if (this.props.selectionMode === SelectionMode.single) {
@@ -388,11 +481,17 @@ export class ListView extends React.Component<IListViewProps, IListViewState> {
388481
}
389482
};
390483
}
484+
if (showFilter && filterValue && columns) {
485+
items = this._executeFilternig(filterValue, items, columns)
486+
}
391487

392488
return (
393489
<div>
490+
{
491+
showFilter && <TextField placeholder={filterPlaceHolder} onChanged={this._updateFilterValue} value={filterValue}/>
492+
}
394493
<DetailsList
395-
items={this.state.items}
494+
items={items}
396495
columns={this.state.columns}
397496
groups={this.state.groups}
398497
selectionMode={this.props.selectionMode || SelectionMode.none}

0 commit comments

Comments
 (0)