Are you willing to submit a PR to fix? No
Requested priority: Normal
When using a DetailsList with filtering as per the Office Fabric React components page examples, the selection of an item does not "stick" when the list is filtered. Ideally I would like my users to be able to filter the list one way, select item(s), apply another filter, select item(s), etc. and have all of the items selected after each filter returned.
Would it be possible to get example code demonstrating this?
Every filter action clears selected items
+1! I've had to code this and it wasn't easy. It's working reasonably well except for issues with the search textbox losing focus when I restore a previous selection after the filter changes to adjust for the list item's new index. So if the user wants to keep backspacing their search filter they have to stop after each key press and put the cursor back in the textbox. Setting focus on the element isn't working and this only seems to happen after I call focusIndex() to scroll a restored selection into view if it's far down the list. Perhaps there's an event I can capture after this to restore the cursor, but I don't think the TextField.setSelection* methods are relevant and there's nothing else AFAIK that can set the cursor position at a certain index (the end).
Regardless, I'm also now afraid my selection state management code will break completely if this feature is ever completed, but I suppose I can control the deployed Fabric version or redo my code to accommodate.
I recently stumbled upon on a similar feature request and came up with the following solution which allows to preserve the selection in DetailsList
while the data is getting filtered.
First a separate component is introduced which implements the logic to preserve the selection:
export interface IViewSelection {}
export interface IViewSelectionProps
extends React.HTMLAttributes<HTMLDivElement> {
componentRef?: IRefObject<IViewSelection>;
/**
* The selection object to interact with when updating selection changes.
*/
selection: ISelection;
items: any[];
}
export interface IViewSelectionState {}
export class ViewSelection extends BaseComponent<
IViewSelectionProps,
IViewSelectionState
> {
private items: any[];
private selectedIndices: any[];
constructor(props: IViewSelectionProps) {
super(props);
this.state = {};
this.items = this.props.items;
this.selectedIndices = [];
}
public render() {
const { children } = this.props;
return <div>{children}</div>;
}
public componentWillUpdate(
nextProps: IViewSelectionProps,
nextState: IViewSelectionState
) {
this.saveSelection();
}
public componentDidUpdate(
prevProps: IViewSelectionProps,
prevState: IViewSelectionState
) {
this.restoreSelection();
}
private toListIndex(index: number) {
const viewItems = this.props.selection.getItems();
const viewItem = viewItems[index];
return this.items.findIndex(listItem => listItem === viewItem);
}
private toViewIndex(index: number) {
const listItem = this.items[index];
const viewIndex = this.props.selection
.getItems()
.findIndex(viewItem => viewItem === listItem);
return viewIndex;
}
private saveSelection(): void {
const newIndices = this.props.selection
.getSelectedIndices()
.map(index => this.toListIndex(index))
.filter(index => this.selectedIndices.indexOf(index) === -1);
const unselectedIndices = this.props.selection
.getItems()
.map((item, index) => index)
.filter(index => this.props.selection.isIndexSelected(index) === false)
.map(index => this.toListIndex(index));
this.selectedIndices = this.selectedIndices.filter(
index => unselectedIndices.indexOf(index) === -1
);
this.selectedIndices = [...this.selectedIndices, ...newIndices];
}
private restoreSelection(): void {
const indices = this.selectedIndices
.map(index => this.toViewIndex(index))
.filter(index => index !== -1);
for (const index of indices) {
this.props.selection.setIndexSelected(index, true, false);
}
}
}
Now DetailsList
component needs to be wrapped with ViewSelection
component to save and restore the selection while filtering is applied:
const items = generateItems(20);
export default class DetailsListBasicExample extends React.Component<
{},
{
viewItems: any[];
}
> {
private selection: Selection;
private detailsList = React.createRef<IDetailsList>();
constructor(props: {}) {
super(props);
this.selection = new Selection({
});
this.state = {
viewItems: items
};
this.handleChange = this.handleChange.bind(this);
}
public render(): JSX.Element {
return (
<div>
<TextField label="Filter by name:" onChange={this.handleChange} />
<ViewSelection selection={this.selection} items={this.state.viewItems} >
<DetailsList
componentRef={this.detailsList}
items={this.state.viewItems}
columns={columns}
setKey="set"
layoutMode={DetailsListLayoutMode.fixedColumns}
selection={this.selection}
selectionMode={SelectionMode.multiple}
selectionPreservedOnEmptyClick={true}
/>
</ViewSelection>
</div>
);
}
private handleChange = (
ev: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
text: string
): void => {
const viewItems = text
? items.filter(item => item.name.toLowerCase().indexOf(text.toLocaleLowerCase()) > -1)
: items;
this.setState({ viewItems });
};
}
Here is a demo
Cross posted as answer on StackOverflow
Running into the same issue. Could we add an option to Selection to retain memory of previously seen, selected items, and then choose when we want to clear that memory?
I recently stumbled upon on a similar feature request and came up with the following solution which _allows to preserve the selection_ in
DetailsList
while the data is getting filtered.First a separate component is introduced which implements the logic to preserve the selection...
...
Here is a demo
Cross posted as answer on StackOverflow
This is really a good answer, @vgrem. I used that for a while, but I noticed that the problem is elsewhere. The problem doesn't appears if the key of the viewItems is named exactly 'key'.
I use the version "6.202.0" of Fabric.
In my implementation, the constructor contain this:
...
this._allItems = this.props.listItems.map((item, index) => ({ key: index, ...item }));
...
In the render() now I don't use ViewSelection nor MarqueeSelection:
<DetailsList
componentRef={this.detailsList}
items={this.state.viewItems}
compact={true}
columns={columns}
selectionMode={this.props.multiselect ? SelectionMode.multiple : SelectionMode.single}
setKey="set"
getKey={this.getKey}
layoutMode={DetailsListLayoutMode.justified}
isHeaderVisible={true}
selection={this._selection}
selectionPreservedOnEmptyClick={true}
/>
The getKey:
private getKey = (item: any, index?: number) => {
return item.key;
}
And finally my handleChange (this mantain the items already selected when I filter and starts only after the third char insered, or on a reset of the filter):
private handleChange = (text: string): void => {
if (text.length > 2 || text.length == 0) {
let selection = this._selection.getSelection();
let filtered_items = this._allItems.filter(i => i.title.toLowerCase().indexOf(text.toLocaleLowerCase()) > -1);
let newItems = text ? [...selection, ...filtered_items].filter(distinct) : this._allItems;
this.setState({
viewItems: newItems
});
}
}
This works great.
But if I change the name of 'key' with 'uniqueKey' or other... it doesn't.
Saddly, also your implementation doesn't works well with keys named differently to 'key' (or so it seemed to me to notice). I think is a bug of the DetailsList.
Having the same problem. Any updates on this ?
I recently stumbled upon on a similar feature request and came up with the following solution which _allows to preserve the selection_ in
DetailsList
while the data is getting filtered.
First a separate component is introduced which implements the logic to preserve the selection...
...
Here is a demo
Cross posted as answer on StackOverflowThis is really a good answer, @vgrem. I used that for a while, but I noticed that the problem is elsewhere. The problem doesn't appears if the key of the viewItems is named exactly 'key'.
I use the version "6.202.0" of Fabric.In my implementation, the constructor contain this:
... this._allItems = this.props.listItems.map((item, index) => ({ key: index, ...item })); ...
In the render() now I don't use ViewSelection nor MarqueeSelection:
<DetailsList componentRef={this.detailsList} items={this.state.viewItems} compact={true} columns={columns} selectionMode={this.props.multiselect ? SelectionMode.multiple : SelectionMode.single} setKey="set" getKey={this.getKey} layoutMode={DetailsListLayoutMode.justified} isHeaderVisible={true} selection={this._selection} selectionPreservedOnEmptyClick={true} />
The getKey:
private getKey = (item: any, index?: number) => { return item.key; }
And finally my handleChange (this mantain the items already selected when I filter and starts only after the third char insered, or on a reset of the filter):
private handleChange = (text: string): void => { if (text.length > 2 || text.length == 0) { let selection = this._selection.getSelection(); let filtered_items = this._allItems.filter(i => i.title.toLowerCase().indexOf(text.toLocaleLowerCase()) > -1); let newItems = text ? [...selection, ...filtered_items].filter(distinct) : this._allItems; this.setState({ viewItems: newItems }); } }
This works great.
But if I change the name of 'key' with 'uniqueKey' or other... it doesn't.Saddly, also your implementation doesn't works well with keys named differently to 'key' (or so it seemed to me to notice). I think is a bug of the DetailsList.
hi im just having trouble replicating this, it persist when the selected items are in filter items but when the filter doesnt return any item selecteds when i go back the selection is lost, do you use any selection methos as setKeySelected and when you used it , thanks!
Having the same problem, im doing setKeySelected inside useEffect in any changes of filterItems, i get the current selectedITems with selection.getSelection() but no changes happens in list.
Due to the complexity and dependencies of our List components, we are not able to take new feature requests at this time.
Most helpful comment
This is really a good answer, @vgrem. I used that for a while, but I noticed that the problem is elsewhere. The problem doesn't appears if the key of the viewItems is named exactly 'key'.
I use the version "6.202.0" of Fabric.
In my implementation, the constructor contain this:
In the render() now I don't use ViewSelection nor MarqueeSelection:
The getKey:
And finally my handleChange (this mantain the items already selected when I filter and starts only after the third char insered, or on a reset of the filter):
This works great.
But if I change the name of 'key' with 'uniqueKey' or other... it doesn't.
Saddly, also your implementation doesn't works well with keys named differently to 'key' (or so it seemed to me to notice). I think is a bug of the DetailsList.