在 Web 部件属性中使用级联下拉列表

为 SharePoint 客户端 Web 部件设计属性窗格时,可能有一个 Web 部件属性,该属性根据另一个属性中选择的值显示其选项。 实现级联下拉列表控件时通常会遇到这种情况。 本文将介绍如何在 Web 部件属性窗格中创建级联下拉列表控件,而无需开发自定义属性窗格控件。

“列表项”下拉列表处于禁用状态,Web 部件占位符提示正在加载更新后的一组列表项

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

注意

请务必先设置 SharePoint 客户端 Web 部件开发环境,再执行本文中的步骤。

新建项目

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

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

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

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

    • 解决方案名称是什么?:react-cascadingdropdowns
    • 要创建哪种类型的客户端组件?:WebPart
    • Web 部件的名称是什么?:列表项
    • 要使用哪个模板?:React
  5. 在代码编辑器中,打开项目文件夹。 本文的步骤和屏幕截图中使用 Visual Studio Code,但你可以使用你喜欢的任何编辑器。

定义用于存储选定列表的 Web 部件属性

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

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

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

    export interface IListItemsWebPartProps {
      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
        });
    
        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.ListNameFieldLabel
                    })
                  ]
                }
              ]
            }
          ]
        };
      }
    
      // ...
    }
    
  5. src/webparts/listItems/loc/mystrings.d.ts 文件中,将 IListItemsStrings 接口更改为:

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

    define([], function() {
      return {
        "PropertyPaneDescription": "Description",
        "BasicGroupName": "Group Name",
        "ListNameFieldLabel": "List"
      }
    });
    
  7. src/webparts/listItems/components/ListItems.tsx 文件中,将 render() 方法的内容更改为:

    public render(): JSX.Element {
      const {
        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>
      );
    }
    
  8. src/webparts/listItems/components/IListItemsProps.ts 文件中,将 IListItemsProps 接口更改为:

    export interface IListItemsProps {
      listName: string;
    }
    
  9. 运行以下命令,确认项目是否正在运行:

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

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

在下拉列表中填充可供选择的 SharePoint 列表

此时,用户通过手动输入列表名称,指定 Web 部件应使用的列表。 此操作容易出错。理想情况下,不妨让用户选择当前 SharePoint 网站中的现有列表之一。

使用下拉列表控件呈现 listName 属性

  1. ListItemsWebPart 类中,在 Web 部件顶部添加对 PropertyPaneDropdown 类的引用。 将加载 PropertyPaneTextField 类的导入子句替换为:

    import {
      IPropertyPaneConfiguration,
      PropertyPaneTextField,
      PropertyPaneDropdown,
      IPropertyPaneDropdownOption
    } from '@microsoft/sp-property-pane';
    
  2. ListItemsWebPart 类中,添加名为 lists 的新变量,用于存储当前网站中所有可用列表的相关信息:

    export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> {
      private lists: IPropertyPaneDropdownOption[];
      // ...
    }
    
  3. 添加一个名为 listsDropdownDisabled 的新类变量: 此变量决定了是否启用“列表”下拉列表。 在 Web 部件检索到当前网站中可用列表的相关信息前,此下拉列表应处于禁用状态。

    export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> {
      // ...
      private listsDropdownDisabled: boolean = true;
      // ...
    }
    
  4. 更改 getPropertyPaneConfiguration() 方法以使用下拉列表控件呈现 listName 属性:

    export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> {
      // ...
    
      protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
        return {
          pages: [
            {
              header: {
                description: strings.PropertyPaneDescription
              },
              groups: [
                {
                  groupName: strings.BasicGroupName,
                  groupFields: [
                    PropertyPaneDropdown('listName', {
                      label: strings.ListNameFieldLabel,
                      options: this.lists,
                      disabled: this.listsDropdownDisabled
                    })
                  ]
                }
              ]
            }
          ]
        };
      }
    
    }
    
  5. 运行以下命令,验证它能否正常运行:

    gulp serve
    

    使用下拉列表控件在 Web 部件属性窗格中呈现的 listName 属性

在“列表”下拉列表中显示可用列表

之前已将 listName 属性的下拉列表控件与 lists 类属性相关联。 由于尚未向其中加载任何值,因此 Web 部件属性窗格中的列表下拉列表仍处于禁用状态。 此部分将介绍如何把 Web 部件扩展为加载可用列表的相关信息。

  1. ListItemsWebPart 类中,添加用于加载可用列表的方法。 你将使用模拟数据,但也可以调用 SharePoint REST API 从当前网站中检索一系列可用列表。 为了模拟从外部服务加载选项,此方法使用两秒延迟。

    export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> {
      // ...
    
      private loadLists(): Promise<IPropertyPaneDropdownOption[]> {
        return new Promise<IPropertyPaneDropdownOption[]>((resolve: (options: IPropertyPaneDropdownOption[]) => void, reject: (error: any) => void) => {
          setTimeout((): void => {
            resolve([{
              key: 'sharedDocuments',
              text: 'Shared Documents'
            },
            {
              key: 'myDocuments',
              text: 'My Documents'
            }]);
          }, 2000);
        });
      }
    }
    
  2. 在“列表”下拉列表中加载可用列表的相关信息。 在 ListItemsWebPart 类中,使用以下代码重写 onPropertyPaneConfigurationStart() 方法:

    export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> {
      // ...
    
      protected onPropertyPaneConfigurationStart(): void {
        this.listsDropdownDisabled = !this.lists;
    
        if (this.lists) {
          return;
        }
    
        this.context.statusRenderer.displayLoadingIndicator(this.domElement, 'lists');
    
        this.loadLists()
          .then((listOptions: IPropertyPaneDropdownOption[]): void => {
            this.lists = listOptions;
            this.listsDropdownDisabled = false;
            this.context.propertyPane.refresh();
            this.context.statusRenderer.clearLoadingIndicator(this.domElement);
            this.render();
          });
      }
      // ...
    }
    

    在 Web 部件的 Web 部件属性窗格打开后,SharePoint 框架会调用 onPropertyPaneConfigurationStart() 方法。

    • 首先,此方法会检查是否已加载当前网站中的可用列表的相关信息。
    • 如果列表信息已加载,则会启用“列表”下拉列表。
    • 如果列表信息尚未加载,则会显示“正在加载”指示器,告诉用户 Web 部件正在加载列表信息。

    当可用列表的相关信息正在加载时,Web 部件中显示“正在加载”指示器

    在可用列表的相关信息已加载后,此方法会将检索到的数据分配给 lists 类变量,“列表”下拉列表可通过它使用这些数据。

    接下来,下拉列表会启用,允许用户选择列表。 调用 this.context.propertyPane.refresh() 可刷新 Web 部件属性窗格,反映“列表”下拉列表的最新变化。

    在列表信息已加载后,立即调用 clearLoadingIndicator() 方法,删除“正在加载”指示器。 因为调用此方法会清除 Web 部件用户界面,所以需要调用 render() 方法来强制 Web 部件重新呈现。

  3. 运行以下命令,确认一切是否都按预期正常运行:

    gulp serve
    

    将 Web 部件添加到画布并打开其属性窗格后,你应该会看到“列表”下拉列表中填充了可供用户选择的可用列表。

    Web 部件属性窗格中显示可用列表的“列表”下拉列表

允许用户选择选定列表中的列表项

生成 Web 部件时,经常需要允许用户从一组值中选择一个值,而这组值又是根据之前选择的值进行确定的。例如,根据选定的洲来选择国家/地区,或选择选定列表中的列表项。 这种用户体验通常称为“级联下拉列表”。 使用标准的 SharePoint 框架客户端 Web 部件功能,可以在 Web 部件属性窗格中生成级联下拉列表。 为此,需要将之前生成的 Web 部件扩展为,能够根据之前选择的列表选择列表项。

在 Web 部件属性窗格中打开的“列表项”下拉列表

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

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

    {
      // ...
      "properties": {
        "listName": "",
        "itemName": ""
      }
      // ...
    }
    
  2. src/webparts/listItems/IListItemsWebPartProps.ts 文件中的代码更改为:

    export interface IListItemsWebPartProps {
      listName: string;
      itemName: string;
    }
    
  3. src/webparts/listItems/components/IListItemsProps.ts 文件中的代码更改为:

    export interface IListItemsProps {
      listName: string;
      itemName: string;
    }
    
  4. 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,
          itemName: this.properties.itemName
        });
    
        ReactDom.render(element, this.domElement);
      }
      // ...
    }
    
  5. src/webparts/listItems/loc/mystrings.d.ts 文件中,将 IListItemsStrings 接口更改为:

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

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

呈现“列表项”Web 部件属性值

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

export default class ListItems extends React.Component<IListItemsProps, {}> {
  public render(): JSX.Element {
    const {
      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>
    );
  }
}

允许用户选择列表中的列表项

与用户通过使用下拉列表选择列表的方式类似,用户可以从一组可用列表项中选择一项。

  1. ListItemsWebPart 类中,添加名为 items 的新变量,用于存储当前选定列表中所有可用列表项的相关信息。

    export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> {
      // ...
      private items: IPropertyPaneDropdownOption[];
      // ...
    }
    
  2. 添加一个名为 itemsDropdownDisabled 的新类变量: 此变量决定了是否应启用“列表项”下拉列表。 用户只有在选择列表后才能选择列表项。

    export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> {
      // ...
      private itemsDropdownDisabled: boolean = true;
      // ...
    }
    
  3. 更改 getPropertyPaneConfiguration() 方法以使用下拉列表控件呈现 itemName 属性。

    export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> {
      // ...
      protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
        return {
          pages: [
            {
              header: {
                description: strings.PropertyPaneDescription
              },
              groups: [
                {
                  groupName: strings.BasicGroupName,
                  groupFields: [
                    PropertyPaneDropdown('listName', {
                      label: strings.ListNameFieldLabel,
                      options: this.lists,
                      disabled: this.listsDropdownDisabled
                    }),
                    PropertyPaneDropdown('itemName', {
                      label: strings.ItemNameFieldLabel,
                      options: this.items,
                      disabled: this.itemsDropdownDisabled
                    })
                  ]
                }
              ]
            }
          ]
        };
      }
    }
    
  4. 运行以下命令,确认一切是否正常运行:

    gulp serve
    

    使用下拉列表控件在 Web 部件属性窗格中呈现的 itemName 属性

在“列表项”下拉列表中显示选定列表中的可用列表项

之前,定义了下拉列表控件,用于在 Web 部件属性窗格中呈现 itemName 属性。 接下来,将 Web 部件扩展为,能够加载选定列表中的可用列表项的相关信息,并在“列表项”下拉列表中显示列表项。

  1. 添加用于加载列表项的方法。 在 src/webparts/listItems/ListItemsWebPart.ts 文件中,向 ListItemsWebPart 类添加新方法,用于加载选定列表中的可用列表项。 (就像用于加载可用列表的方法一样,将使用模拟数据。)

    export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> {
      // ...
      private loadItems(): Promise<IPropertyPaneDropdownOption[]> {
        if (!this.properties.listName) {
          // resolve to empty options since no list has been selected
          return Promise.resolve();
        }
    
        const wp: ListItemsWebPart = this;
    
        return new Promise<IPropertyPaneDropdownOption[]>((resolve: (options: IPropertyPaneDropdownOption[]) => 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);
        });
      }
    }
    

    loadItems() 方法返回之前选定的列表的模拟列表项。 没有选择列表时,此方法可解决这一承诺,而不包含任何数据。

  2. 在“列表项”下拉列表中加载可用列表项的相关信息。 在 ListItemsWebPart 类中,扩展 onPropertyPaneConfigurationStart() 方法以加载所选列表的项:

    export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> {
      // ...
      protected onPropertyPaneConfigurationStart(): void {
        this.listsDropdownDisabled = !this.lists;
        this.itemsDropdownDisabled = !this.properties.listName || !this.items;
    
        if (this.lists) {
          return;
        }
    
        this.context.statusRenderer.displayLoadingIndicator(this.domElement, 'options');
    
        this.loadLists()
          .then((listOptions: IPropertyPaneDropdownOption[]): Promise<IPropertyPaneDropdownOption[]> => {
            this.lists = listOptions;
            this.listsDropdownDisabled = false;
            this.context.propertyPane.refresh();
            return this.loadItems();
          })
          .then((itemOptions: IPropertyPaneDropdownOption[]): void => {
            this.items = itemOptions;
            this.itemsDropdownDisabled = !this.properties.listName;
            this.context.propertyPane.refresh();
            this.context.statusRenderer.clearLoadingIndicator(this.domElement);
            this.render();
          });
      }
      // ...
    }
    

    初始化时,Web 部件会先确定是否应启用“列表项”下拉列表。 如果用户之前选择了列表,便可以选择相应列表中的列表项。 如果未选择任何列表,那么“列表项”下拉列表处于禁用状态。

    你扩展了之前为了加载可用列表的相关信息而定义的代码,使其能够加载选定列表中的可用列表项的相关信息。 然后,此代码会将检索到的信息分配给 items 类变量,以供“列表项”下拉列表使用。 最后,此代码会清除“正在加载”指示器,允许用户开始使用 Web 部件。

  3. 运行以下命令,确认一切是否正常运行:

    gulp serve
    

    根据要求,“列表项”下拉列表最初处于禁用状态,迫使用户先选择列表。 但此时,即使用户已选择列表,“列表项”下拉列表仍处于禁用状态。

    即使用户已选择列表,“列表项”下拉列表仍处于禁用状态

  4. 在用户选择列表后更新 Web 部件属性窗格。 当用户在属性窗格中选择一个列表后,Web 部件应进行更新,以启用“列表项”下拉列表,并显示选定列表中的一组列表项。

    ListItemsWebPart.ts 文件中,使用以下代码替代 ListItemsWebPart 类中的 onPropertyPaneFieldChanged() 方法:

    export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> {
      // ...
      protected onPropertyPaneFieldChanged(propertyPath: string, oldValue: any, newValue: any): void {
        if (propertyPath === 'listName' &&
            newValue) {
          // push new list value
          super.onPropertyPaneFieldChanged(propertyPath, oldValue, newValue);
          // get previously selected item
          const previousItem: string = this.properties.itemName;
          // reset selected item
          this.properties.itemName = undefined;
          // push new item value
          this.onPropertyPaneFieldChanged('itemName', previousItem, this.properties.itemName);
          // disable item selector until new items are loaded
          this.itemsDropdownDisabled = true;
          // refresh the item selector control by repainting the property pane
          this.context.propertyPane.refresh();
          // communicate loading items
          this.context.statusRenderer.displayLoadingIndicator(this.domElement, 'items');
    
          this.loadItems()
            .then((itemOptions: IPropertyPaneDropdownOption[]): void => {
              // store items
              this.items = itemOptions;
              // enable item selector
              this.itemsDropdownDisabled = false;
              // clear status indicator
              this.context.statusRenderer.clearLoadingIndicator(this.domElement);
              // re-render the web part as clearing the loading indicator removes the web part body
              this.render();
              // refresh the item selector control by repainting the property pane
              this.context.propertyPane.refresh();
            });
        }
        else {
          super.onPropertyPaneFieldChanged(propertyPath, oldValue, newValue);
        }
      }
      // ...
    }
    

    在用户选择列表后,Web 部件会保留新选择的值。 由于选定列表已更改,因此 Web 部件会重置之前选择的列表项。 至此,用户选择了一个列表,Web 部件属性窗格会加载这一特定列表的列表项。 加载项时,用户无法选择项。

    在选定列表的列表项加载后,它们会分配给 items 类变量,“列表项”下拉列表可通过它引用这些列表项。 至此,可用列表项的相关信息已加载,“列表项”下拉列表处于启用状态,允许用户选择列表项。 “正在加载”指示器已删除,这会清除 Web 部件主体,这也就是为什么应重新呈现 Web 部件的原因所在。 最后,刷新 Web 部件属性窗格,以反映最新变化。

    Web 部件属性窗格中显示选定列表的可用列表项的“列表项”下拉列表

另请参阅