Office UI Fabric TextField焦点/光标问题

时间:2019-01-11 18:20:03

标签: reactjs typescript office-ui-fabric

我有一个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中失去焦点?

1 个答案:

答案 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