我有一个CodePen可以在这里说明问题:https://codepen.io/elegault/pen/QzZwLO
场景:一个DetailsList组件和一个搜索框(TextField组件)。可以根据用户在搜索框中输入的内容来过滤列表项。如果已在搜索结果中选择了任何项目,则仍将在搜索结果中选择该项目。如果它不在搜索结果中,并且随后的搜索确实包括该选择,则将重新选择它。 (注意:Office UI Fabric团队似乎意识到应该在本地处理此问题,但是我不确定根据此GitHub issue来添加此功能的计划。)
问题:每次按键后焦点都丢失了,这使得输入和编辑搜索条件变得困难,因为用户每次都必须重新插入光标。
什么不起作用:在TextField已经被聚焦时(isFocused = true)在textField上调用focus()不会执行任何操作。仅当isFocused = false时才调用focus()。但这仅在在筛选列表中还原选择后调用DetailsList.focusIndex()时才如此。
伪代码:
componentDidUpdate(previousProps: any, previousState: AppProjectListState) {
//Toggle off the current selection
this._selection.toggleIndexSelected(currentIdx);
//Set the new selection
this._selection.toggleIndexSelected(newIdx);
//Scroll the selection into view
this._detailsListRef.current.focusIndex(newIdx, false);
}
这是TextField或DetailsList组件中的某种错误吗?还是用我在React组件生命周期中执行此操作的方式?还是有一种方法可以确保在用户键入文本,重新计算列表项以及修改所选索引时不会从TextField中失去焦点?
答案 0 :(得分:2)
我最近偶然发现了类似的功能请求,并提出了以下解决方案,允许在过滤数据时保留DetailsList
中的选择。
首先引入一个单独的组件,该组件实现保留选择的逻辑:
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);
}
}
}
现在DetailsList
组件需要用ViewSelection
组件包装,以在应用过滤时保存和恢复选择:
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 });
};
}
这里是a demo