SharePoint Framework (以下SPFx) では、Webパーツや拡張機能間でデータのやり取りをするために「動的データ」という仕組みを利用します。動的データを作り出して他のWebパーツで利用できるように公開することは簡単なのですが、動的データの利用方法はリファレンスが見当たらなかったため、GitHub上のサンプルコードを読んで色々と試す必要がありました。備忘として残しておきます。
動的データを取得するための一番簡単な方法
動的データを取得するには、SPFxのライブラリ側で予め用意されているモジュールを利用し、Webパーツのプロパティで動的データを選択することが一番簡単です。この方法は以前ご紹介しています。
www.micknabewata.com
上記の方法は非常に簡単なのですが、少し捻った要件が出てくるとうまく行きません。以下の点が問題になります。
- 利用可能な全ての動的データが選択肢に現れてしまい制限ができないこと
- 文字列や数値ではなく自作の型で動的データを公開すると型内のプロパティまで選択しなければならないこと
- Webパーツのプロパティからの選択ではなく固定の値を取得したい場合に対応できないこと
自前のコードで動的データを取得する方法
GitHubに公開したサンプルコードと併せてお読みください。
github.com
[コードの説明]
- simpleObjectProvider Webパーツを配置すると動的データが作られます。
- simpleStringViewer Webパーツを配置すると上記一番簡単な方法の問題点が実感できます。
- customObjectViewer Webパーツが今回の本題です。以下で解説します。
[画面]
simpleObjectProviderとcustomObjectViewerを貼るとこうなります。

サンプルWebパーツ
上の色が付いているのがsimpleObjectProvider、下のJSON文字列がcustomObjectViewerです。simpleObjectProviderでの入力に従って、customObjectViewerの表示が動的に変わります。
当サンプルで扱う動的データの型
src/dynamicData/objectData.tsで定義しています。単純な文字列や数値ではなく、プロパティをいくつも持つ自作の型を2つ公開します。
import { IDynamicDataPropertyDefinition, IDynamicDataCallables } from '@microsoft/sp-dynamic-data';
export const propertyId1 = 'objectData';
export const propertyId2 = 'objectData2';
export interface ObjectType {
name : string;
ammount : number;
date : Date;
obj : SubObjectType;
}
export interface SubObjectType {
subName : string;
subAmmount : number;
}
export default class ObjectData implements IDynamicDataCallables {
private _value1 : ObjectType;
private _value2 : ObjectType;
public getPropertyDefinitions(): ReadonlyArray<IDynamicDataPropertyDefinition> {
return [
{
id: propertyId1,
title: 'オブジェクト1'
},
{
id: propertyId2,
title: 'オブジェクト2'
}
];
}
public getPropertyValue(propId: string): ObjectType {
switch (propId) {
case propertyId1:
return this._value1;
case propertyId2:
return this._value2;
}
throw new Error('プロパティIDが不正です。');
}
public setPropertyValue(propId: string, value : ObjectType)
{
switch (propId) {
case propertyId1:
this._value1 = value;
break;
case propertyId2:
this._value2 = value;
break;
default :
throw new Error('プロパティIDが不正です。');
}
}
}
動的データを提供するWebパーツ
/src/webparts/simpleObjectProvider/SimpleObjectProviderWebPart.tsです。入力欄を用意する段階でプロパティが多すぎたかと後悔しましたが(笑、コードとしては特に捻りはありません。
動的データを取得するWebパーツ
ここが本題です。
/src/webparts/customObjectViewer/CustomObjectViewerWebPart.tsです。
/src/webparts/simpleStringViewer/SimpleStringViewerWebPart.tsでやっているようにPropertyPaneDynamicFieldSetを利用する簡単なやり方ではなく、自前で動的データの一覧を取得・フィルタリングし、ドロップダウンの選択肢を作っています。
利用可能な動的データソースの取得
getAvailableDataSourcesメソッドで動的データソースの一覧をthis.context.dynamicDataProvider.getAvailableSources()の命令で取得します。全量としては標準のページコンテキストに加えて動的データを公開するWebパーツの分だけ動的データソースが取得できますが、今回のサンプルではその一部に絞り込んで取得できるようにしています。
protected getAvailableDataSources(webpartNames: string[] = []): IDynamicDataSource[] {
let ret: IDynamicDataSource[] = [];
this.context.dynamicDataProvider.getAvailableSources().forEach((source) => {
if (webpartNames && webpartNames.length > 0) {
webpartNames.forEach((webpartName) => {
if (source.metadata.title == webpartName) ret.push(source);
});
}
else {
ret.push(source);
}
});
return ret;
}
今回のサンプルでは、上記メソッドで絞り込んだ動的データソースをWebパーツのプロパティで選択できるようにしています。以下のようにドロップダウン選択肢を作ります。
protected getDynamicDataSourceDropdownOptions() : IPropertyPaneDropdownOption[] {
let ret: IPropertyPaneDropdownOption[] = [];
let webpartName = ['SimpleStringProvider', 'SimpleObjectProvider' ];
let sources = this.getAvailableDataSources(webpartName);
if (sources)
{
sources.forEach((source, i) => {
ret.push({
index: i,
key: source.id,
text: source.metadata.title,
type: PropertyPaneDropdownOptionType.Normal
});
});
}
return ret;
}
で、Webパーツのプロパティを以下のように定義します。
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
groups: [
{
groupName: '動的データ接続',
groupFields: [
PropertyPaneDropdown('dynamicDataSource', {
options: this.getDynamicDataSourceDropdownOptions(),
label: 'データソース (Webパーツ)'
})
]
}
],
displayGroupsAsAccordion : false
}
]
};
}
このプロパティで選択された値を受け取ってくれるように、プロパティ定義もお忘れなく。/src/webparts/customObjectViewer/ICustomObjectViewerWebPartProps.tsです。
export default interface ICustomObjectViewerWebPartProps {
dynamicDataSource: string;
}
選択した動的データソース内の動的プロパティを取得
上記までで、最終的に取得するべき動的データソースのIDが得られました。「当サンプルで扱う動的データの型」の章でご紹介したように、動的データソース内では複数の動的プロパティを公開することが可能です。今回のサンプルではsrc/dynamicData/objectData.tsで定義したobjectDataとobjectData2が動的プロパティに当たります。これをユーザーが選択できるようにWebパーツのプロパティを構成します。
まず、以下のように動的データソース内の動的プロパティを列挙してドロップダウン選択肢として返します。
protected getDynamicPropertyDropdownOptions(): IPropertyPaneDropdownOption[] {
let ret: IPropertyPaneDropdownOption[] = [];
if (this.properties.dynamicDataSource && this.properties.dynamicDataSource.length > 0) {
let source = this.context.dynamicDataProvider.tryGetSource(this.properties.dynamicDataSource);
if (source) {
source.getPropertyDefinitions().forEach((property, i) => {
ret.push({
index: i,
key: property.id,
text: property.title,
type: PropertyPaneDropdownOptionType.Normal
});
});
}
}
return ret;
}
で、Webパーツのプロパティにドロップダウンを追加します。
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
groups: [
{
groupName: '動的データ接続',
groupFields: [
PropertyPaneDropdown('dynamicDataSource', {
options: this.getDynamicDataSourceDropdownOptions(),
label: 'データソース (Webパーツ)'
}),
PropertyPaneDropdown('dynamicDataProperty', {
options: this.getDynamicPropertyDropdownOptions(),
label: 'プロパティ',
disabled: !(this.properties.dynamicDataSource && this.properties.dynamicDataSource.length > 0)
})
]
}
],
displayGroupsAsAccordion : false
}
]
};
}
選択された値を受け取るプロパティも/src/webparts/customObjectViewer/ICustomObjectViewerWebPartProps.tsに追加します。
export default interface ICustomObjectViewerWebPartProps {
dynamicDataSource: string;
dynamicDataProperty: string;
}
動的プロパティの値を取得
ここまでで、動的データソースのIDと動的プロパティのIDが取得できるようになります。次は動的プロパティから値を取得します。
protected getDynamicPropertyValues(): any {
let ret: any;
if (this.properties.dynamicDataSource && this.properties.dynamicDataSource.length > 0 && this.properties.dynamicDataProperty && this.properties.dynamicDataProperty.length > 0) {
let source = this.context.dynamicDataProvider.tryGetSource(this.properties.dynamicDataSource);
if (source) {
ret = source.getPropertyValue(this.properties.dynamicDataProperty);
}
}
return ret;
}
これをrenderメソッドから呼び出して描画用のコンポーネントに渡してあげます。
public render(): void {
ReactDom.render(
React.createElement(
CustomObjectViewer,
{
dynamicData: this.getDynamicPropertyValues()
}
),
this.domElement);
}
今回のサンプルでは渡された値をJSON.stringifyして表示しているだけなので、整形も何もしていません。anyのまま渡しています。雑!
/src/webparts/customObjectViewer/components/CustomObjectViewer.tsx
import * as React from 'react';
import styles from './CustomObjectViewer.module.scss';
import { ICustomObjectViewerProps } from './ICustomObjectViewerProps';
export default class CustomObjectViewer extends React.Component<ICustomObjectViewerProps, {}> {
public render(): React.ReactElement<ICustomObjectViewerProps> {
return (
<div className={ styles.customObjectViewer }>
{
(this.props.dynamicData)?
JSON.stringify(this.props.dynamicData) :
''
}
</div>
);
}
}
動的プロパティの値の変更をキャッチするリスナーを登録
さて上記までのコードで動的プロパティの値を表示できるようになりましたが、このままだと動的プロパティの値が変わったときにWebパーツの描画を更新してくれません。というわけでリスナーを登録します。登録後、別の動的プロパティを選んだりWebパーツを削除した時にはリスナーの解除も行う必要があります。(やらないとJavaScriptエラーが起きて画面が固まります)
まず、登録と登録解除は以下のように行います。
private dynamicData: DynamicProperty<any>;
protected registerDynamicDataReference() {
if (this.properties.dynamicDataSource
&& this.properties.dynamicDataSource.length > 0
&& this.properties.dynamicDataProperty
&& this.properties.dynamicDataProperty.length > 0)
{
if (!this.dynamicData) {
this.dynamicData = new DynamicProperty<any>(this.context.dynamicDataProvider, this.render);
this.dynamicData.register(this.render);
}
this.dynamicData.setReference(`${this.properties.dynamicDataSource}:${this.properties.dynamicDataProperty}`);
}
else
{
this.unregisterDynamicDataReference();
}
}
protected unregisterDynamicDataReference() {
if (this.dynamicData) this.dynamicData.unregister(this.render);
}
で、renderメソッドとonDisposeメソッドでそれぞれ呼び出します。
public render(): void {
this.registerDynamicDataReference();
ReactDom.render(
React.createElement(
CustomObjectViewer,
{
dynamicData: this.getDynamicPropertyValues()
}
),
this.domElement);
}
protected onDispose(): void {
this.unregisterDynamicDataReference();
ReactDom.unmountComponentAtNode(this.domElement);
}
これで動的データが更新された時にWebパーツが再描画されるようになります。
今回はシンプルにほとんどの処理をWebパーツのクラス内で実施しましたが、実運用に回すコードでは可読性を考慮してクラスを分けるなど工夫してください。
以上、参考になれば幸いです。