使用动态数据连接 SharePoint 框架组件

可将两个或更多 SharePoint 框架组件结合在一起,并使用动态数据在它们之间交换数据。 借助此功能,你可积累丰富体验并构建极具吸引力的最终用户解决方案。

三个 SharePoint 框架 Web 部件相互连接,显示有关事件的信息

注意

推荐在 SharePoint 框架组件之间(包括客户端 Web 部件和扩展)共享数据时使用动态数据模式。 该功能已在 SharePoint 框架 v1.7 中引入。

使用动态数据源公开数据

SharePoint 框架中的动态数据基于源通知模型。 已命名为动态数据源的组件可提供数据,并在数据交换时通知 SharePoint 框架。

页面上的其他组件可订阅由动态数据源发出的通知。 SharePoint 框架会通知使用者组件:源已通知其数据进行了更改。 然后,使用者组件会从源组件中请求数据。

每个动态数据源均会实现 IDynamicDataCallables 接口。

以下代码展示了一个显示即将发生的事件列表的 Web 部件。 事件 Web 部件通过以下两种方式向页面上的其他组件公开选定事件的相关信息:完整的事件信息和位置地址。

import {
  IDynamicDataPropertyDefinition,
  IDynamicDataCallables
} from '@microsoft/sp-dynamic-data';

export default class EventsWebPart extends BaseClientSideWebPart<IEventsWebPartProps> implements IDynamicDataCallables {
  /**
   * Currently selected event
   */
  private _selectedEvent: IEvent;

  /**
   * Event handler for selecting an event in the list
   */
  private _eventSelected = (event: IEvent): void => {
    // store the currently selected event in the class variable. Required
    // so that connected component will be able to retrieve its value
    this._selectedEvent = event;
    // notify subscribers that the selected event has changed
    this.context.dynamicDataSourceManager.notifyPropertyChanged('event');
    // notify subscribers that the selected location has changed
    this.context.dynamicDataSourceManager.notifyPropertyChanged('location');
  }

  protected onInit(): Promise<void> {
    // register this web part as dynamic data source
    this.context.dynamicDataSourceManager.initializeSource(this);

    return Promise.resolve();
  }

  /**
   * Return list of dynamic data properties that this dynamic data source
   * returns
   */
  public getPropertyDefinitions(): ReadonlyArray<IDynamicDataPropertyDefinition> {
    return [
      { id: 'event', title: 'Event' },
      { id: 'location', title: 'Location' }
    ];
  }

  /**
   * Return the current value of the specified dynamic data set
   * @param propertyId ID of the dynamic data set to retrieve the value for
   */
  public getPropertyValue(propertyId: string): IEvent | ILocation {
    switch (propertyId) {
      case 'event':
        return this._selectedEvent;
      case 'location':
        return this._selectedEvent
            ? {
                city: this._selectedEvent.city,
                address: this._selectedEvent.address
              }
            : undefined;
    }

    throw new Error('Bad property id');
  }

  public render(): void {
    const element: React.ReactElement<IEventsProps> = React.createElement(
      Events,
      {
        displayMode: this.displayMode,
        onEventSelected: this._eventSelected,
        title: this.properties.title,
        updateProperty: (value: string): void => {
          this.properties.title = value;
        },
        siteUrl: this.context.pageContext.web.serverRelativeUrl
      }
    );

    ReactDom.render(element, this.domElement);
  }

  // ... omitted for brevity
}

重要

IDynamicDataCallables 接口可以通过任何类(而不仅仅是 Web 部件和扩展)来实现。 如果动态数据源需要复杂的逻辑,则需考虑将其移动到单独的类,而不是直接在 Web 部件或扩展内进行实现。

实现 IDynamicDataCallables 接口的类必须定义以下两种方法:getPropertyDefinitions()getPropertyValue()

getPropertyDefinitions() 方法将返回一组由特定动态数据源返回的数据类型。 在上一示例中,Web 部件公开了有关事件及其位置的详细信息。 即使采用两种不同形式公开来自单个对象 (_selectedEvent) 的信息,Web 部件也会更易于重复使用并且可以与不特定于事件的其他 Web 部件(例如,可显示指定地址地图的地图 Web 部件)结合使用。 将 Web 部件连接到数据源时,会向最终用户显示数据源公开的数据类型列表。

重要

getPropertyValue() 方法返回的对象应该是扁平的,例如:

{
   "date": "2018-06-01T11:21:59.446Z",
   "name": "Tampa Home Show",
   "organizerEmail": "GradyA@contoso.onmicrosoft.com",
   "organizerName": "Grady Archie"
}

在序列化过程中,复杂的对象将经过扁平化处理。 这可能会导致出现意外结果。 例如,如下所示的对象:

{
   "date": "2018-06-01T11:21:59.446Z",
   "name": "Tampa Home Show",
   "organizer": {
      "email": "GradyA@contoso.onmicrosoft.com",
      "name": "Grady Archie"
   }
}

...会按以下方式进行序列化:

{
   "date": "2018-06-01T11:21:59.446Z",
   "name": "Tampa Home Show",
   "organizer.email": "GradyA@contoso.onmicrosoft.com",
   "organizer.name": "Grady Archie"
}

getPropertyValue() 方法将返回特定数据类型的值。 propertyId 参数的值对应于 getPropertyDefinitions() 方法中指定的定义的 id

要将组件注册为动态数据源,请调用 this.context.dynamicDataSourceManager.initializeSource() 方法,并将动态数据源的实例作为参数传递。 在上一示例中,Web 部件本身实现了 IDynamicDataCallables 接口,这就是为何将 this 作为其参数调用 initializeSource() 方法。

在示例代码中,Web 部件将即将发生的事件显示在列表中。 用户每次从列表中选择事件时,Web 部件都会调用 _eventSelected() 方法。 使用该方法,Web 部件会将选定的事件分配到 _selectedEvent 类变量并通过调用传递表示已更改数据集的定义的 idthis.context.dynamicDataSourceManager.notifyPropertyChanged() 方法发出选定事件和位置相关信息已更改的通知。

使用 Web 部件中的动态数据

Web 部件可以使用页面上显示的动态数据源公开的数据。 下面是 Web 部件的代码,在地图上显示 Web 部件之前显示的事件列表中所选事件的位置。

import { DynamicProperty } from '@microsoft/sp-component-base';
import {
  DynamicDataSharedDepth,
  IWebPartPropertiesMetadata,
  PropertyPaneDynamicFieldSet,
  PropertyPaneDynamicField
} from '@microsoft/sp-webpart-base';

/**
 * Map web part properties
 */
export interface IMapWebPartProps {
  /**
   * The address to display on the map
   */
  address: DynamicProperty<string>;
  /**
   * Bing maps API key to use with the Bing maps API
   */
  bingMapsApiKey: string;
  /**
   * The city where the address is located
   */
  city: DynamicProperty<string>;
  /**
   * Web part title
   */
  title: string;
}

/**
 * Map web part. Shows the map of the specified location. The location can be
 * specified either directly in the web part properties or via a dynamic data
 * source connection.
 */
export default class MapWebPart extends BaseClientSideWebPart<IMapWebPartProps> {
  /**
   * Event handler for clicking the Configure button on the Placeholder
   */
  private _onConfigure = (): void => {
    this.context.propertyPane.open();
  }

  public render(): void {
    // Get the location to show on the map. The location will be retrieved
    // either from the event selected in the connected data source or from the
    // address entered in web part properties
    const address: string | undefined = this.properties.address.tryGetValue();
    const city: string | undefined = this.properties.city.tryGetValue();
    const needsConfiguration: boolean = !this.properties.bingMapsApiKey || (!address && !this.properties.address.tryGetSource()) ||
    (!city && !this.properties.city.tryGetSource());

    const element: React.ReactElement<IMapProps> = React.createElement(
      Map,
      {
        needsConfiguration: needsConfiguration,
        httpClient: this.context.httpClient,
        bingMapsApiKey: this.properties.bingMapsApiKey,
        dynamicAddress: !!this.properties.address.tryGetSource(),
        address: `${address} ${city}`,
        onConfigure: this._onConfigure,
        width: this.domElement.clientWidth,
        height: this.domElement.clientHeight,
        title: this.properties.title,
        displayMode: this.displayMode,
        updateProperty: (value: string): void => {
          this.properties.title = value;
        }
      }
    );

    ReactDom.render(element, this.domElement);
  }

  protected get dataVersion(): Version {
    return Version.parse('1.0');
  }

  protected get propertiesMetadata(): IWebPartPropertiesMetadata {
    return {
      // Specify the web part properties data type to allow the address
      // information to be serialized by the SharePoint Framework.
      'address': {
        dynamicPropertyType: 'string'
      },
      'city': {
        dynamicPropertyType: 'string'
      }
    };
  }

  protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
    return {
      pages: [
        {
          groups: [
            {
              groupName: strings.BingMapsGroupName,
              groupFields: [
                PropertyPaneTextField('bingMapsApiKey', {
                  label: strings.BingMapsApiKeyFieldLabel
                }),
                PropertyPaneLink('', {
                  href: 'https://www.bingmapsportal.com/',
                  text: strings.GetBingMapsApiKeyLinkText,
                  target: '_blank'
                })
              ]
            },
            // Web part properties group for specifying the information about
            // the address to show on the map.
            {
              // Primary group is used to provide the address to show on the map
              // in a text field in the web part properties
              primaryGroup: {
                groupName: strings.DataGroupName,
                groupFields: [
                  PropertyPaneTextField('address', {
                    label: strings.AddressFieldLabel
                  }),
                  PropertyPaneTextField('city', {
                    label: strings.CityFieldLabel
                  })
                ]
              },
              // Secondary group is used to retrieve the address from the
              // connected dynamic data source
              secondaryGroup: {
                groupName: strings.DataGroupName,
                groupFields: [
                  PropertyPaneDynamicFieldSet({
                    label: 'Address',
                    fields: [
                      PropertyPaneDynamicField('address', {
                        label: strings.AddressFieldLabel
                      }),
                      PropertyPaneDynamicField('city', {
                        label: strings.CityFieldLabel
                      })
                    ],
                    sharedConfiguration: {
                      depth: DynamicDataSharedDepth.Property
                    }
                  })
                ]
              },
              // Show the secondary group only if the web part has been
              // connected to a dynamic data source
              showSecondaryGroup: !!this.properties.address.tryGetSource()
            } as IPropertyPaneConditionalGroup
          ]
        }
      ]
    };
  }

  protected get disableReactivePropertyChanges(): boolean {
    // set property changes mode to reactive, so that the Bing Maps API is not
    // called on each keystroke when typing in the address to show on the map
    // in web part properties
    return true;
  }
}

SharePoint 框架包含一个用户体验 (UX),它用于将两个 Web 部件连接到动态数据源。 这个 UX 通过发现可用的动态数据源及其属性并保持已配置的连接,简化了使用动态数据的过程。 若要使用此 UX,使用动态数据的 Web 部件必须包含几个特定构建块。

定义动态 Web 部件属性

可以从动态数据源中检索其数据的每个 Web 部件属性应定义为 DynamicProperty<T>,其中 T 表示属性中存储的数据类型,例如:

/**
 * Map web part properties
 */
export interface IMapWebPartProps {
  /**
   * The address to display on the map
   */
  address: DynamicProperty<string>;

  // ... omitted for brevity
}

在此示例中,地址是字符串,但还支持其他数据类型,如布尔、数值或对象。 定义为 DynamicProperty 的 Web 部件属性可以从动态数据源或直接在 Web 部件属性中提供的值中检索其数据。 由于相同的 Web 部件属性可以同时用于动态数据源和 Web 部件属性中配置的静态值,因此不需要定义可简化生成多个 Web 部件的过程的多个 Web 部件属性。

定义动态属性中存储的数据类型

对于每个动态属性,必须指定其包含的数据类型。 这是必要步骤,以便可以正确序列化添加到页面的 Web 部件的实例。 对于 propertiesMetadata getter 中的每个动态 Web 部件属性,指定 dynamicPropertyType 值:

protected get propertiesMetadata(): IWebPartPropertiesMetadata {
  return {
    // Specify the web part properties data type to allow the address
    // information to be serialized by the SharePoint Framework.
    'address': {
      dynamicPropertyType: 'string'
    },
    'city': {
      dynamicPropertyType: 'string'
    }
  };
}

dynamicPropertyType 属性的可能值包括:booleannumberstringarrayobject

将标准 UX 用于将 Web 部件连接到动态数据源

若要允许用户将 Web 部件连接到页面上可用的动态数据源,SharePoint 框架需提供可从 Web 部件属性窗格访问的标准 UX。

用于将 SharePoint 框架 Web 部件连接到页面上可用的动态数据源的标准 UX

重要

使用标准 UX 将 Web 部件连接到动态数据源时,确保动态数据源返回给定动态属性的值(在上一示例中,已在事件列表中选择一个事件)。 如果未返回,该 UX 将无法确定数据源返回的数据类型,并且设置连接将失败。

在最简单的形式中,可以按如下所示定义 UI:

protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
  return {
    pages: [
      {
        groups: [
          {
            groupFields: [
              PropertyPaneDynamicFieldSet({
                label: 'Select event source',
                fields: [
                  PropertyPaneDynamicField('event', {
                    label: 'Event source'
                  })
                ]
              })
            ]
          }
        ]
      }
    ]
  };
}

对于每个动态属性集,使用 PropertyPaneDynamicFieldSet() 方法添加新组。 对于每个动态 Web 部件属性,添加允许用户从中选择属性的 PropertyPaneDynamicFieldPropertyPaneDynamicField 中指定的 Web 部件属性应检索其数据。

如果动态数据字段集包含多个动态属性,则可以使用 sharedConfiguration.depth 属性指定如何共享连接数据:

groupFields: [
  PropertyPaneDynamicFieldSet({
    label: 'Address',
    fields: [
      PropertyPaneDynamicField('address', {
        label: strings.AddressFieldLabel
      }),
      PropertyPaneDynamicField('city', {
        label: strings.CityFieldLabel
      })
    ],
    sharedConfiguration: {
      depth: DynamicDataSharedDepth.Property
    }
  })
]

在此示例中,所有动态属性会共享选定的连接和属性。 这在以下情况下非常有用:数据源公开的选定属性是一个对象,并且你希望将动态属性连接到选定对象的不同属性。 如果希望使用相同的数据源,但要将每个 Web 部件属性连接到选定数据源公开的不同属性,则可以改为使用 DynamicDataSharedDepth.Source。 最后,如果希望每个属性从不同数据源中检索其数据,则可以将 sharedConfiguration.depth 属性设置为 DynamicDataSharedDepth.None

允许用户选择是否要使用动态数据或自行指定值

生成 Web 部件时,可以允许用户将 Web 部件连接到页面上的其他组件,或自行指定 Web 部件属性的值。 只需做少许额外工作即可生成更全面且适用于更多用例的 Web 部件。

若要允许用户选择是否要加载动态属性中的数据或在 Web 部件属性中自行输入值,可以将 Web 部件属性组定义为 IPropertyPaneConditionalGroup 组。

protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
  return {
    pages: [
      {
        groups: [
          // Web part properties group for specifying the information about
          // the address to show on the map.
          {
            // Primary group is used to provide the address to show on the map
            // in a text field in the web part properties
            primaryGroup: {
              groupName: strings.DataGroupName,
              groupFields: [
                PropertyPaneTextField('address', {
                  label: strings.AddressFieldLabel
                }),
                PropertyPaneTextField('city', {
                  label: strings.CityFieldLabel
                })
              ]
            },
            // Secondary group is used to retrieve the address from the
            // connected dynamic data source
            secondaryGroup: {
              groupName: strings.DataGroupName,
              groupFields: [
                PropertyPaneDynamicFieldSet({
                  label: 'Address',
                  fields: [
                    PropertyPaneDynamicField('address', {
                      label: strings.AddressFieldLabel
                    }),
                    PropertyPaneDynamicField('city', {
                      label: strings.CityFieldLabel
                    })
                  ],
                  sharedConfiguration: {
                    depth: DynamicDataSharedDepth.Property
                  }
                })
              ]
            },
            // Show the secondary group only if the web part has been
            // connected to a dynamic data source
            showSecondaryGroup: !!this.properties.address.tryGetSource()
          } as IPropertyPaneConditionalGroup
        ]
      }
    ]
  };
}

条件 Web 部件属性窗格组包含主要次要组。 使用 showSecondaryGroup 属性,你可以指定应何时显示次要组,以及何时隐藏主要组。

在上述示例中,用户通过单击省略号 (...) 选择将 Web 部件连接到动态数据源时,将显示用于将 Web 部件连接到动态数据源的次要组。

在用于将 SharePoint 框架 Web 部件连接到动态数据源的 Web 部件属性窗格中将鼠标指针悬停在省略号上

注意事项

SharePoint 框架的动态数据功能具有以下特征:

  • 每个页面可以有多个动态数据源和使用者
  • 每个组件可以向其他组件提供动态数据,也可以使用其他组件中的动态数据
  • 组件可以使用多个动态数据源中的数据
  • SharePoint 框架提供用于将 Web 部件连接到数据源的标准 UI 并自动保留连接信息

另请参阅