生成属性窗格的自定义控件

SharePoint 框架包含一组属性窗格的标准控件。 不过,有时除了这组基本控件之外,还需要其他功能。 可能需要异步更新控件或特定用户界面上的数据。 生成属性窗格的自定义控件可以实现所需的功能。

在本文中,你将生成自定义下拉列表控件,在不阻止 Web 部件的用户界面的情况下,从外部服务异步加载数据。

当用户从“列表”下拉列表中选择一个列表后,“列表项”下拉列表正在加载可用列表项

可以从 GitHub (sp-dev-fx-webparts/samples/react-custompropertypanecontrols/) 获取有效 Web 部件的源。

注意

请务必先设置开发环境,以生成 SharePoint 框架解决方案,再执行本文中的步骤。

新建项目

  1. 首先,为项目新建文件夹:

    md react-custompropertypanecontrol
    
  2. 转到项目文件夹:

    cd react-custompropertypanecontrol
    
  3. 在项目文件夹中,运行 SharePoint Framework Yeoman 生成器,以搭建新的 SharePoint Framework 项目:

    yo @microsoft/sharepoint
    
  4. 出现提示时,请输入以下值(为下面省略的所有提示选择默认选项):

    • 要创建哪种类型的客户端组件? WebPart
    • Web 部件名称是什么? 列表项目
    • 你想要使用哪个模板? React
  5. 在代码编辑器中,打开项目文件夹。

定义 Web 部件属性以存储选定列表

正在生成的 Web 部件显示了选定 SharePoint 列表中的列表项。 用户可以在 Web 部件属性中选择列表。 要存储选定列表,请新建 Web 部件属性,名为 listName

  1. 在代码编辑器中,打开 src/webparts/listItems/ListItemsWebPartManifest.json 文件。 将默认的 description 属性替换为名为 listName 的新属性:

    {
      ...
      "preconfiguredEntries": [{
        ...
        "properties": {
          "listName": ""
        }
      }]
    }
    
  2. 打开 src/webparts/listItems/ListItemsWebPart.ts 文件,并将 IListItemsWebPartProps 接口更新为以下内容:

    export interface IListItemsWebPartProps {
      description: string;
      listName: string;
    }
    
  3. src/webparts/listItems/ListItemsWebPart.ts 文件中,将 render() 方法更改为:

    export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> {
      // ...
    
      public render(): void {
        const element: React.ReactElement<IListItemsProps> = React.createElement(
          ListItems,
          {
            listName: this.properties.listName,
            description: this.properties.description,
            isDarkTheme: this._isDarkTheme,
            environmentMessage: this._environmentMessage,
            hasTeamsContext: !!this.context.sdks.microsoftTeams,
            userDisplayName: this.context.pageContext.user.displayName
          }
        );
    
        ReactDom.render(element, this.domElement);
      }
    
      // ...
    }
    
  4. getPropertyPaneConfiguration() 方法更新为:

    export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> {
    
      // ...
    
      protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
        return {
          pages: [
            {
              header: {
                description: strings.PropertyPaneDescription
              },
              groups: [
                {
                  groupName: strings.BasicGroupName,
                  groupFields: [
                    PropertyPaneTextField('listName', {
                      label: strings.ListFieldLabel
                    })
                  ]
                }
              ]
            }
          ]
        };
      }
    
      // ...
    }
    
  5. src/webparts/listItems/loc/mystrings.d.ts 文件中, 向现有的 IListItemsWebPartStrings 接口添加类型 string 的新属性 ListFieldLabel

    declare interface IListItemsWebPartStrings {
      PropertyPaneDescription: string;
      BasicGroupName: string;
      ..
      ListFieldLabel: string;
    }
    
  6. src/webparts/listItems/loc/en-us.js 文件中,向返回的对象添加新属性 ListFieldLabel

    define([], function() {
      return {
        "PropertyPaneDescription": "Description",
        "BasicGroupName": "Group Name",
        ...
        "ListFieldLabel": "List"
      }
    });
    
  7. 打开 src/webparts/listItems/components/IListItemsProps.ts 文件,将 listName 属性添加到列表接口:

    export interface IListItemsProps {
      description: string;
      isDarkTheme: boolean;
      environmentMessage: string;
      hasTeamsContext: boolean;
      userDisplayName: string;
      listName: string;
    }
    
  8. src/webparts/listItems/components/ListItems.tsx 文件中,将 render() 方法的内容更改为:

    export default class ListItems extends React.Component<IListItemsProps, {}> {
      public render(): React.ReactElement<IListItemsProps> {
        const {
          description,
          isDarkTheme,
          environmentMessage,
          hasTeamsContext,
          userDisplayName,
          listName
        } = this.props;
    
        return (
          <section className={`${styles.listItems} ${hasTeamsContext ? styles.teams : ''}`}>
            <div className={styles.welcome}>
              <img alt="" src={isDarkTheme ? require('../assets/welcome-dark.png') : require('../assets/welcome-light.png')} className={styles.welcomeImage} />
              <h2>Well done, {escape(userDisplayName)}!</h2>
              <div>{environmentMessage}</div>
              <div>List name: <strong>{escape(listName)}</strong></div>
            </div>
          </section>
        );
      }
    }
    
  9. 运行以下命令,确认项目是否正在运行:

    gulp serve
    
  10. 在 Web 浏览器中,将列表项 Web 部件添加到画布,然后打开其属性。 验证为 List 属性设置的值是否显示在 Web 部件主体中。

    Web 部件显示“listName”属性的值

创建异步下拉列表属性窗格控件

SharePoint Framework 提供了标准的下拉列表控件,使用户可以选择一个特定的值。 该下拉列表控件的构建方式要求提前知道其所有值。 如果要动态加载值,或者从外部服务异步加载值,并且不想阻止整个 Web 部件,则生成自定义下拉列表控件是一个选项。

当在 SharePoint Framework 中创建一个使用 React 的自定义属性窗格控件时,该控件包含可在 Web 部件中注册该控件的类和会呈现下拉列表并管理其数据的 React 组件。

添加异步下拉列表属性窗格控件 React 组件

  1. 创建“components”文件夹。 在项目“src”文件夹中,创建包含三个新文件夹的层次结构,这样文件夹结构显示为“src/controls/PropertyPaneAsyncDropdown/components”

    Components 文件夹在 Visual Studio Code 中突出显示

  2. 定义异步下拉列表 React 组件属性。 在“src/controls/PropertyPaneAsyncDropdown/components”文件夹中,新建“IAsyncDropdownProps.ts”文件,并输入以下代码:

    import { IDropdownOption } from 'office-ui-fabric-react/lib/components/Dropdown';
    
    export interface IAsyncDropdownProps {
      label: string;
      loadOptions: () => Promise<IDropdownOption[]>;
      onChanged: (option: IDropdownOption, index?: number) => void;
      selectedKey: string | number;
      disabled: boolean;
      stateKey: string;
    }
    

    IAsyncDropdownProps 类定义可在“回应”组件(由自定义属性窗格控件使用)上设置的属性:

    • label 属性指定下拉列表控件的标签。
    • loadOptions 委托关联的函数由控件调用,以加载可用选项。
    • 用户在下拉列表中选择选项后,系统会调用与 onChanged 委托关联的函数。
    • selectedKey 属性指定选定值,该值可以为字符串或数字。
    • disabled 属性指定是否禁用下拉列表控件。
    • stateKey 属性用于强制“回应”组件重新呈现。
  3. 定义异步下拉列表 React 组件接口。 在“src/controls/PropertyPaneAsyncDropdown/components”文件夹中,新建“IAsyncDropdownState.ts”文件,并输入以下代码:

    import { IDropdownOption } from 'office-ui-fabric-react/lib/components/Dropdown';
    
    export interface IAsyncDropdownState {
      loading: boolean;
      options: IDropdownOption[];
      error: string;
    }
    

    IAsyncDropdownState 接口描述“回应”组件的状态:

    • loading 属性确定组件是否正在给定时刻加载其选项。
    • options 属性包含所有可用选项。
    • 如果发生错误,则系统会将其分配给其与用户通信的 error 属性中。
  4. 定义异步下拉列表 React 组件。 在“src/controls/PropertyPaneAsyncDropdown/components”文件夹中,新建“AsyncDropdown.tsx”文件,并输入以下代码:

    import * as React from 'react';
    import { Dropdown, IDropdownOption } from 'office-ui-fabric-react/lib/components/Dropdown';
    import { Spinner } from 'office-ui-fabric-react/lib/components/Spinner';
    import { IAsyncDropdownProps } from './IAsyncDropdownProps';
    import { IAsyncDropdownState } from './IAsyncDropdownState';
    
    export default class AsyncDropdown extends React.Component<IAsyncDropdownProps, IAsyncDropdownState> {
      private selectedKey: React.ReactText;
    
      constructor(props: IAsyncDropdownProps, state: IAsyncDropdownState) {
        super(props);
        this.selectedKey = props.selectedKey;
    
        this.state = {
          loading: false,
          options: undefined,
          error: undefined
        };
      }
    
      public componentDidMount(): void {
        this.loadOptions();
      }
    
      public componentDidUpdate(prevProps: IAsyncDropdownProps, prevState: IAsyncDropdownState): void {
        if (this.props.disabled !== prevProps.disabled ||
          this.props.stateKey !== prevProps.stateKey) {
          this.loadOptions();
        }
      }
    
      private loadOptions(): void {
        this.setState({
          loading: true,
          error: undefined,
          options: undefined
        });
    
        this.props.loadOptions()
          .then((options: IDropdownOption[]): void => {
            this.setState({
              loading: false,
              error: undefined,
              options: options
            });
          }, (error: any): void => {
            this.setState((prevState: IAsyncDropdownState, props: IAsyncDropdownProps): IAsyncDropdownState => {
              prevState.loading = false;
              prevState.error = error;
              return prevState;
            });
          });
      }
    
      public render(): JSX.Element {
        const loading: JSX.Element = this.state.loading ? <div><Spinner label={'Loading options...'} /></div> : <div />;
        const error: JSX.Element = this.state.error !== undefined ? <div className={'ms-TextField-errorMessage ms-u-slideDownIn20'}>Error while loading items: {this.state.error}</div> : <div />;
    
        return (
          <div>
            <Dropdown label={this.props.label}
              disabled={this.props.disabled || this.state.loading || this.state.error !== undefined}
              onChanged={this.onChanged.bind(this)}
              selectedKey={this.selectedKey}
              options={this.state.options} />
            {loading}
            {error}
          </div>
        );
      }
    
      private onChanged(option: IDropdownOption, index?: number): void {
        this.selectedKey = option.key;
        // reset previously selected options
        const options: IDropdownOption[] = this.state.options;
        options.forEach((o: IDropdownOption): void => {
          if (o.key !== option.key) {
            o.selected = false;
          }
        });
        this.setState((prevState: IAsyncDropdownState, props: IAsyncDropdownProps): IAsyncDropdownState => {
          prevState.options = options;
          return prevState;
        });
        if (this.props.onChanged) {
          this.props.onChanged(option, index);
        }
      }
    }
    

    AsyncDropdown 类代表用于呈现异步下拉列表属性窗格控件的“回应”组件:

    • 当组件首次加载时,componentDidMount() 方法或其 disabledstateKey 属性发生更改,并调用通过属性传递的 loadOptions() 方法以加载可用选项。
    • 加载选项后,组件更新它的状态,同时显示可用选项。
    • 使用 Office UI Fabric React 下拉列表组件呈现下拉列表本身。
    • 组件加载可用选项时,它使用 Office UI Fabric React 旋转图标组件显示旋转图标。

下一步是定义自定义属性窗格控件。 在属性窗格中定义属性时,即在 Web 部件内使用此控件,它使用之前定义的 React 组件进行呈现。

添加异步下拉列表属性窗格控件

  1. 定义异步下拉列表属性窗格控件属性。 自定义属性窗格控件有两个属性集。

    第一组属性是公开的,用于定义 Web 部件内的 Web 部件属性。 这些属性是组件专用属性,如控件旁边显示的标签、旋转图标的最小值和最大值或下拉列表的可用选项。 在定义自定义属性窗格控件时,在实现 IPropertyPaneField<TProperties> 接口时,必须将描述这些属性的类型作为 TProperties 类型进行传递。

    第二组属性是专用属性,在自定义属性窗格控件内部使用。 这些属性必须遵守 SharePoint Framework API 以便正确呈现自定义控件。 这些属性必须实现 IPropertyPaneCustomFieldProps@microsoft/sp-property-pane 包中的 接口。

  2. 定义异步下拉列表属性窗格控件的公共属性。 在“src/controls/PropertyPaneAsyncDropdown”文件夹中,新建“IPropertyPaneAsyncDropdownProps.ts”文件,并输入以下代码:

    import { IDropdownOption } from 'office-ui-fabric-react/lib/components/Dropdown';
    
    export interface IPropertyPaneAsyncDropdownProps {
      label: string;
      loadOptions: () => Promise<IDropdownOption[]>;
      onPropertyChange: (propertyPath: string, newValue: any) => void;
      selectedKey: string | number;
      disabled?: boolean;
    }
    

    IPropertyPaneAsyncDropdownProps 接口中:

    • label: 定义下拉列表旁边显示的标签。
    • loadOptions: 定义被调用以加载可用下拉列表选项的方法。
    • onPropertyChange: 定义用户在下拉列表中选择值时调用的方法。
    • selectedKey: 返回选定的下拉列表值。
    • disabled: 指定是否禁用控件。
  3. 定义异步下拉列表属性窗格控件的内部属性。 在“src/controls/PropertyPaneAsyncDropdown”文件夹中,新建“IPropertyPaneAsyncDropdownInternalProps.ts”文件,并输入以下代码:

    import { IPropertyPaneCustomFieldProps } from '@microsoft/sp-property-pane';
    import { IPropertyPaneAsyncDropdownProps } from './IPropertyPaneAsyncDropdownProps';
    
    export interface IPropertyPaneAsyncDropdownInternalProps extends IPropertyPaneAsyncDropdownProps, IPropertyPaneCustomFieldProps {
    }
    

虽然 IPropertyPaneAsyncDropdownInternalProps 接口不定义任何新属性,但其将之前定义的 IPropertyPaneAsyncDropdownProps 接口中的属性与标准 SharePoint 框架 IPropertyPaneCustomFieldProps 接口(自定义控件正确运行的必需项)组合在了一起。

  1. 定义异步下拉列表属性窗格控件。 在“src/controls/PropertyPaneAsyncDropdown”文件夹中,新建“PropertyPaneAsyncDropdown.ts”文件,并输入以下代码:

    import * as React from 'react';
    import * as ReactDom from 'react-dom';
    import {
      IPropertyPaneField,
      PropertyPaneFieldType
    } from '@microsoft/sp-property-pane';
    import { IDropdownOption } from 'office-ui-fabric-react/lib/components/Dropdown';
    import { IPropertyPaneAsyncDropdownProps } from './IPropertyPaneAsyncDropdownProps';
    import { IPropertyPaneAsyncDropdownInternalProps } from './IPropertyPaneAsyncDropdownInternalProps';
    import AsyncDropdown from './components/AsyncDropdown';
    import { IAsyncDropdownProps } from './components/IAsyncDropdownProps';
    
    export class PropertyPaneAsyncDropdown implements IPropertyPaneField<IPropertyPaneAsyncDropdownProps> {
      public type: PropertyPaneFieldType = PropertyPaneFieldType.Custom;
      public targetProperty: string;
      public properties: IPropertyPaneAsyncDropdownInternalProps;
      private elem: HTMLElement;
    
      constructor(targetProperty: string, properties: IPropertyPaneAsyncDropdownProps) {
        this.targetProperty = targetProperty;
        this.properties = {
          key: properties.label,
          label: properties.label,
          loadOptions: properties.loadOptions,
          onPropertyChange: properties.onPropertyChange,
          selectedKey: properties.selectedKey,
          disabled: properties.disabled,
          onRender: this.onRender.bind(this),
          onDispose: this.onDispose.bind(this)
        };
      }
    
      public render(): void {
        if (!this.elem) {
          return;
        }
    
        this.onRender(this.elem);
      }
    
      private onDispose(element: HTMLElement): void {
        ReactDom.unmountComponentAtNode(element);
      }
    
      private onRender(elem: HTMLElement): void {
        if (!this.elem) {
          this.elem = elem;
        }
    
        const element: React.ReactElement<IAsyncDropdownProps> = React.createElement(AsyncDropdown, {
          label: this.properties.label,
          loadOptions: this.properties.loadOptions,
          onChanged: this.onChanged.bind(this),
          selectedKey: this.properties.selectedKey,
          disabled: this.properties.disabled,
          // required to allow the component to be re-rendered by calling this.render() externally
          stateKey: new Date().toString()
        });
        ReactDom.render(element, elem);
      }
    
      private onChanged(option: IDropdownOption, index?: number): void {
        this.properties.onPropertyChange(this.targetProperty, option.key);
      }
    }
    

    PropertyPaneAsyncDropdown 类将 IPropertyPaneAsyncDropdownProps 接口用作可从 Web 部件内部设置的公共属性的合同,从而实现标准的 SharePoint 框架 IPropertyPaneField 接口。 该类包含以下三个由 IPropertyPaneField 接口定义的公共属性:

    • type: 必须设置为自定义属性窗格控件的 PropertyPaneFieldType.Custom
    • targetProperty: 用于指定要与控件一同使用的 Web 部件属性的名称。
    • properties: 用于定义控件特定的属性。

    请注意,properties 属性为内部 IPropertyPaneAsyncDropdownInternalProps 类型,而非由类实现的公共 IPropertyPaneAsyncDropdownProps 接口。 这是有意为之,以便 properties 属性可以定义 SharePoint 框架所需的 onRender() 方法。 如果 onRender() 方法为公共 IPropertyPaneAsyncDropdownProps 接口的一部分,则在 Web 部件中使用异步下拉列表控件时,需要在 Web 部件内为其分配值,而这并不可取。

    PropertyPaneAsyncDropdown 类定义公共 render() 方法,该方法可用于重画控件。 在某些情况下,如果级联下拉列表中一个下拉列表内设置的值决定了另一个下拉列表中的可用选项,这就非常有用。 在选择项后,调用 render() 方法,便可以具有相关的下拉列表负载可用选项。 要实现这一点,必须让“回应”检测控件是否已更改。 为此,请将 stateKey 的值设置为当前日期。 使用此技巧后,每次调用 onRender() 方法时,不仅会重新呈现组件,而且还会更新其可用选项。 不要忘记实现 onDispose() 方法,以在每次关闭属性窗格时卸载元素。

在 Web 部件中使用异步下拉列表属性窗格控件

异步下拉列表属性窗格控件就绪后,下一步是在 Web 部件中使用它,以便用户能够选择列表。

添加列表信息接口

若要以一致的方式传递可用列表的相关信息,请定义表示列表相关信息的接口。 在“src/webparts/listItems”文件夹中,新建“IListInfo.ts”文件,并输入以下代码:

export interface IListInfo {
  Id: string;
  Title: string;
}

使用异步下拉列表属性窗格控件呈现 listName Web 部件属性

  1. 引用所需的类型。 在 src/webparts/listItems/ListItemsWebPart.ts 文件的顶部部分,添加以下内容,以导入之前创建的 PropertyPaneAsyncDropdown 类:

    import { PropertyPaneAsyncDropdown } from '../../controls/PropertyPaneAsyncDropdown/PropertyPaneAsyncDropdown';
    
  2. 在该代码之后,添加对 IDropdownOption 接口的引用以及处理 Web 部件属性所需的两个帮助程序函数。

    import { IDropdownOption } from 'office-ui-fabric-react/lib/components/Dropdown';
    import { update, get } from '@microsoft/sp-lodash-subset';
    
  3. 添加用于加载可用列表的方法。 在 ListItemsWebPart 类中,添加以下 loadLists() 方法以加载可用列表。 在本文中,你使用模拟数据,但仍然可以调用 SharePoint REST API 以从当前网站检索可用列表的列表。 为了模拟从外部服务加载选项,此方法使用两秒延迟。

    private loadLists(): Promise<IDropdownOption[]> {
      return new Promise<IDropdownOption[]>((resolve: (options: IDropdownOption[]) => void, reject: (error: any) => void) => {
        setTimeout(() => {
          resolve([{
            key: 'sharedDocuments',
            text: 'Shared Documents'
          },
            {
              key: 'myDocuments',
              text: 'My Documents'
            }]);
        }, 2000);
      });
    }
    
  4. 添加处理下拉列表中值更改的方法。 在 ListItemsWebPart 类中,添加名为 onListChange() 的新方法。

    private onListChange(propertyPath: string, newValue: any): void {
      const oldValue: any = get(this.properties, propertyPath);
      // store new value in web part properties
      update(this.properties, propertyPath, (): any => { return newValue; });
      // refresh web part
      this.render();
    }
    

    当用户在下拉列表中选择一个列表后,选定值应暂留在 Web 部件属性中,且应重新呈现 Web 部件以反映选定属性。

  5. 使用异步下拉列表属性窗格控件呈现“列表”Web 部件属性。 在 ListItemsWebPart 类中,将 getPropertyPaneConfiguration() 方法更改为使用异步下拉列表属性窗格控件,以呈现 listName Web 部件属性。

    export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> {
      // ...
    
      protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
        return {
          pages: [
            {
              header: {
                description: strings.PropertyPaneDescription
              },
              groups: [
                {
                  groupName: strings.BasicGroupName,
                  groupFields: [
                    new PropertyPaneAsyncDropdown('listName', {
                      label: strings.ListFieldLabel,
                      loadOptions: this.loadLists.bind(this),
                      onPropertyChange: this.onListChange.bind(this),
                      selectedKey: this.properties.listName
                    })
                  ]
                }
              ]
            }
          ]
        };
      }
    
      // ...
    }
    
  6. 此时,可以使用新建的异步下拉列表属性窗格控件以选择列表。 要验证控件是否按预期工作,请打开控制台并运行:

    gulp serve
    

    异步下拉列表属性窗格控件加载其选项而不会阻止 Web 部件用户界面

    在异步下拉列表属性窗格控件中选择其中一个选项

使用异步下拉列表属性窗格控件实现级联下拉列表

生成 SharePoint 框架 Web 部件时,可能需要实现可用选项取决于之前选择的其他选项的配置。 一个常见例子是先让用户选择列表,再选择此列表中的列表项。 一组可用列表项视选定列表而定。 下面介绍了如何使用先前步骤中实现的异步下拉列表属性窗格控件实现此类方案。

添加“列表项”Web 部件属性

  1. 在代码编辑器中,打开“src/webparts/listItems/ListItemsWebPart.manifest.json”文件。 向 properties 部分添加名为 item 的新属性,如下所示:

    {
      ...
      "preconfiguredEntries": [{
        ...
        "properties": {
          "description": "List items",
          "listName": "",
          "item": ""
        }
      }]
    }
    
  2. src/webparts/listItems/ListItemsWebPart.ts 文件中的 IListItemsWebPartProps 接口中的代码更改为:

    export interface IListItemsWebPartProps {
      description: string;
      listName: string;
      item: string;
    }
    
  3. 更新 src/webparts/listItems/components/IListItemsProps.ts 文件的内容,添加 item 属性:

    export interface IListItemsProps {
      ...
      listName: string;
      itemName: string;
    }
    
  4. src/webparts/listItems/ListItemsWebPart.ts 文件中,更改 render() 方法的代码,使其包含 item 属性:

    export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> {
      // ...
    
      public render(): void {
        const element: React.ReactElement<IListItemsProps> = React.createElement(ListItems, {
          listName: this.properties.listName,
          itemName: this.properties.item,
          ...
        });
    
        ReactDom.render(element, this.domElement);
      }
    
      // ...
    }
    
  5. src/webparts/listItems/loc/mystrings.d.ts 文件中,更改 IListItemsWebPartStrings 接口,使其包含 ItemFieldLabel 属性:

    declare interface IListItemsWebPartStrings {
      ...
      PropertyPaneDescription: string;
      BasicGroupName: string;
      ListFieldLabel: string;
      ItemFieldLabel: string;
    }
    
  6. src/webparts/listItems/loc/en-us.js 文件中,添加 ItemFieldLabel 字符串缺少的定义:

    define([], function() {
      return {
        ...
        "PropertyPaneDescription": "Description",
        "BasicGroupName": "Group Name",
        "ListFieldLabel": "List",
        "ItemFieldLabel": "Item"
      }
    });
    

呈现项 Web 部件属性的值

src/webparts/listItems/components/ListItems.tsx 文件中,将 render() 方法更改为:

export default class ListItems extends React.Component<IListItemsProps, {}> {
  public render(): React.ReactElement<IListItemsProps> {
    const {
      description,
      isDarkTheme,
      environmentMessage,
      hasTeamsContext,
      userDisplayName,
      listName,
      itemName
    } = this.props;

    return (
      <section className={`${styles.listItems} ${hasTeamsContext ? styles.teams : ''}`}>
        <div className={styles.welcome}>
          <img alt="" src={isDarkTheme ? require('../assets/welcome-dark.png') : require('../assets/welcome-light.png')} className={styles.welcomeImage} />
          <h2>Well done, {escape(userDisplayName)}!</h2>
          <div>{environmentMessage}</div>
          <div>List name: <strong>{escape(listName)}</strong></div>
          <div>Item name: <strong>{escape(itemName)}</strong></div>
        </div>
      </section>
    );
  }
}

添加加载列表项的方法

src/webparts/listItems/ListItemsWebPart.ts 文件中,在 ListItemsWebPart 类中添加新方法,以从选定列表加载可用的列表项。 与加载可用列表的方法一样,使用的是模拟数据。 根据之前选择的列表, loadItems() 方法会返回模拟列表项。 没有选择列表时,此方法可解决这一承诺,而不包含任何数据。

private loadItems(): Promise<IDropdownOption[]> {
  if (!this.properties.listName) {
    // resolve to empty options since no list has been selected
    return Promise.resolve([]);
  }

  const wp: ListItemsWebPart = this;

  return new Promise<IDropdownOption[]>((resolve: (options: IDropdownOption[]) => void, reject: (error: any) => void) => {
    setTimeout(() => {
      const items = {
        sharedDocuments: [
          {
            key: 'spfx_presentation.pptx',
            text: 'SPFx for the masses'
          },
          {
            key: 'hello-world.spapp',
            text: 'hello-world.spapp'
          }
        ],
        myDocuments: [
          {
            key: 'isaiah_cv.docx',
            text: 'Isaiah CV'
          },
          {
            key: 'isaiah_expenses.xlsx',
            text: 'Isaiah Expenses'
          }
        ]
      };
      resolve(items[wp.properties.listName]);
    }, 2000);
  });
}

添加用于处理列表项选择的方法

ListItemsWebPart 类中,添加名为 onListItemChange() 的新方法。 在项下拉列表中选择一项后,Web 部件应在 Web 部件中存储新值并重新呈现 Web 部件以反映用户界面中的更改。

private onListItemChange(propertyPath: string, newValue: any): void {
  const oldValue: any = get(this.properties, propertyPath);
  // store new value in web part properties
  update(this.properties, propertyPath, (): any => { return newValue; });
  // refresh web part
  this.render();
}

在属性窗格中呈现“列表项”Web 部件属性

  1. ListItemsWebPart 类中,添加名为 itemsDropdown 的新类属性:

    export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> {
      private itemsDropDown: PropertyPaneAsyncDropdown;
      // ...
    }
    
  2. getPropertyPaneConfiguration() 方法的代码更改为:

    export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> {
      // ...
    
      protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
        // reference to item dropdown needed later after selecting a list
        this.itemsDropDown = new PropertyPaneAsyncDropdown('item', {
          label: strings.ItemFieldLabel,
          loadOptions: this.loadItems.bind(this),
          onPropertyChange: this.onListItemChange.bind(this),
          selectedKey: this.properties.item,
          // should be disabled if no list has been selected
          disabled: !this.properties.listName
        });
    
        return {
          pages: [
            {
              header: {
                description: strings.PropertyPaneDescription
              },
              groups: [
                {
                  groupName: strings.BasicGroupName,
                  groupFields: [
                    new PropertyPaneAsyncDropdown('listName', {
                      label: strings.ListFieldLabel,
                      loadOptions: this.loadLists.bind(this),
                      onPropertyChange: this.onListChange.bind(this),
                      selectedKey: this.properties.listName
                    }),
                    this.itemsDropDown
                  ]
                }
              ]
            }
          ]
        };
      }
      // ...
    }
    

    item 属性的下拉列表的初始化方式与 listName 属性的下拉列表类似。 唯一区别在于,必须将控件实例分配给类变量,因为在用户选择列表后必须刷新“列表项”下拉列表。

加载选定列表的列表项

开始时,如果用户未选择任何列表,“列表项”下拉列表处于禁用状态,并在用户选择列表后启用。 在用户选择列表后,“列表项”下拉列表还加载相应列表中的列表项。

  1. 要实现此逻辑,请将之前定义的 onListChange() 方法扩展到:

    private onListChange(propertyPath: string, newValue: any): void {
      const oldValue: any = get(this.properties, propertyPath);
      // store new value in web part properties
      update(this.properties, propertyPath, (): any => { return newValue; });
      // reset selected item
      this.properties.item = undefined;
      // store new value in web part properties
      update(this.properties, 'item', (): any => { return this.properties.item; });
      // refresh web part
      this.render();
      // reset selected values in item dropdown
      this.itemsDropDown.properties.selectedKey = this.properties.item;
      // allow to load items
      this.itemsDropDown.properties.disabled = false;
      // load items and re-render items dropdown
      this.itemsDropDown.render();
    }
    

    选择列表后,选定的项将重置、保持在 Web 部件属性中,并在项目下拉列表中重置。 将启用用于选择项的下拉列表,并刷新下拉列表以加载其选项。

  2. 要验证一切是否按预期正常运行,请在控制台中运行:

    gulp serve
    

    第一次向页中添加 Web 部件并打开其属性窗格后,你会看到下拉列表被禁用并加载其选项。

    Web 部件属性窗格中的下拉列表正在加载数据

    加载选项后,就会启用列表下拉列表。 因为尚未选择任何列表,项下拉列表将保持禁用状态。

    Web 部件属性窗格中的列表下拉列表已启用。项下拉列表已禁用

    在列表下拉列表中选择一个列表后,项下拉列表将加载该列表中的可用项。

    在列表下拉列表中选择一个列表后,项下拉列表加载可用项

    加载可用项后,就会启用项下拉列表。

    在 Web 部件属性窗格中上,从项下拉列表中选择列表项

    在项下拉列表中选择一项后,会刷新 Web 部件,在其主体中显示所选的项。

    Web 部件中呈现的选定列表和列表项

  3. 通过在控制台中按 CTRL+C 来停止本地 Web 服务器。

另请参阅