#はじめに
SharePointのWebパーツをReactで作れるらしいので、実際にやってみました。しばらくMSから離れていたのですが、私の知っている鎖国のような時代は終わっていて、オープンソースワールドがそこには広がっていました。MSでReactやgulpを使う日が来るなんて…。これをきっかけに、またいろいろと作ってみたくなりました。
#Office UI Fabric React
Webパーツ自体は、JavaScriptフレームワークを使っても使わなくてもよいです。が、Reactベースの素晴らしすぎるUIフレームワークがあったので、これを使います。このコンポーネントを使うだけで、簡単にそれっぽいものが作れそうです。
#今回作るWebパーツ
せっかくなので、SharePointのサイトコンテンツにアクセスするものを作ろうと思います。今回は、リストデータを取得して表示するパーツを作ります。使うコンポーネントは、DetailsListです。
#やってみた
##0. 事前準備
こちらを参照して、開発環境のセットアップをしておきます。
##1. 新しいWebパーツプロジェクトの作成
次のコマンドを実行して、自分の好きな場所に新しいプロジェクトを作成します。
mkdir detailslist-webpart
cd detailslist-webpart
yo @microsoft/sharepoint
対話形式で必要な情報をインプットします。
以下は明示的に指定して、それ以外はデフォルトの設定としました。
- What is your solution name? detailslist-webpart
- Which baseline packages do you want to target for your component(s)? SharePoint Online only (latest)
- Where do you want to place the files? Use the current folder
- Which type of client-side component to create? WebPart
- What is your Web part name? DetailsListSample
- Which framework would you like to use? React
##2. Office UI Fabricコンポーネントの追加
Office UI Fabric Reactコンポーネントを追加します。
src/webparts/detailsListSample/components/DetailsListSample.tsxを開きます。
まず、1行目から4行目までを次のように置き換えます。
import * as React from 'react';
import { IDetailsListSampleProps } from './IDetailsListSampleProps';
import { Announced } from 'office-ui-fabric-react/lib/Announced';
import { TextField, ITextFieldStyles } from 'office-ui-fabric-react/lib/TextField';
import { DetailsList, DetailsListLayoutMode, Selection, IColumn } from 'office-ui-fabric-react/lib/DetailsList';
import { MarqueeSelection } from 'office-ui-fabric-react/lib/MarqueeSelection';
import { Fabric } from 'office-ui-fabric-react/lib/Fabric';
import { mergeStyles } from 'office-ui-fabric-react/lib/Styling';
const exampleChildClass = mergeStyles({
display: 'block',
marginBottom: '10px',
});
const textFieldStyles: Partial<ITextFieldStyles> = { root: { maxWidth: '300px' } };
export interface IDetailsListItem {
key: number;
name: string;
value: number;
}
export interface IDetailsListSampleState {
items: IDetailsListItem[];
selectionDetails: string;
}
次に、DetailsListSampleクラスも編集します。
export default class DetailsListSample extends React.Component<IDetailsListSampleProps, IDetailsListSampleState> {
private _selection: Selection;
private _allItems: IDetailsListItem[];
private _columns: IColumn[];
constructor(props: IDetailsListSampleProps) {
super(props);
this._selection = new Selection({
onSelectionChanged: () => this.setState({ selectionDetails: this._getSelectionDetails() }),
});
// Populate with items for demos.
this._allItems = [];
for (let i = 0; i < 200; i++) {
this._allItems.push({
key: i,
name: 'Item ' + i,
value: i,
});
}
this._columns = [
{ key: 'column1', name: 'Name', fieldName: 'name', minWidth: 100, maxWidth: 200, isResizable: true },
{ key: 'column2', name: 'Value', fieldName: 'value', minWidth: 100, maxWidth: 200, isResizable: true },
];
this.state = {
items: this._allItems,
selectionDetails: this._getSelectionDetails(),
};
}
public render(): JSX.Element {
const { items, selectionDetails } = this.state;
return (
<Fabric>
<div className={exampleChildClass}>{selectionDetails}</div>
<Announced message={selectionDetails} />
<TextField
className={exampleChildClass}
label="Filter by name:"
onChange={this._onFilter}
styles={textFieldStyles}
/>
<Announced message={`Number of items after filter applied: ${items.length}.`} />
<MarqueeSelection selection={this._selection}>
<DetailsList
items={items}
columns={this._columns}
setKey="set"
layoutMode={DetailsListLayoutMode.justified}
selection={this._selection}
selectionPreservedOnEmptyClick={true}
ariaLabelForSelectionColumn="Toggle selection"
ariaLabelForSelectAllCheckbox="Toggle selection for all items"
checkButtonAriaLabel="Row checkbox"
onItemInvoked={this._onItemInvoked}
/>
</MarqueeSelection>
</Fabric>
);
}
private _getSelectionDetails(): string {
const selectionCount = this._selection.getSelectedCount();
switch (selectionCount) {
case 0:
return 'No items selected';
case 1:
return '1 item selected: ' + (this._selection.getSelection()[0] as IDetailsListItem).name;
default:
return `${selectionCount} items selected`;
}
}
private _onFilter = (ev: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, text: string): void => {
this.setState({
items: text ? this._allItems.filter(i => i.name.toLowerCase().indexOf(text) > -1) : this._allItems,
});
}
private _onItemInvoked = (item: IDetailsListItem): void => {
alert(`Item invoked: ${item.name}`);
}
}
ここまでできたら、いったんローカル実行をしてみます。次のコマンドを実行すると、localhost:4321のローカルワークベンチでWebパーツをプレビューすることができます。
gulp serve
無事、DetailsListコンポーネントが表示されました。
##3. モックストアの作成
今回のゴールは、SharePointサイトのリストデータを表示することですが、ローカルワークベンチでもテストできるように、モックストアを作成します。
リストデータを操作するための、リストモデルを作成します。ここは、実際のSharePointサイトでリスト定義を確認して、適宜モデルを修正してください。今回は、このようなモデルにします。
export interface IDetailsListItems {
value: IDetailsListItem[];
}
export interface IDetailsListItem {
key: number;
name: string;
value: number;
}
src/webparts/detailsListSample/components/MockHttpClient.tsという名前で、新規ファイルを作成します。
import { IDetailsListItem } from './DetailsListSample';
export default class MockHttpClient {
private static _items: IDetailsListItem[] = [{ key: 1, name: 'Mock List 1', value: 1 },
{ key: 2, name: 'Mock List 2', value: 2 },
{ key: 3, name: 'Mock List 3', value: 3 }];
public static get(): Promise<IDetailsListItem[]> {
return new Promise<IDetailsListItem[]>((resolve) => {
resolve(MockHttpClient._items);
});
}
}
src/webparts/detailsListSample/components/DetailsListSample.tsxに戻って、MockHttpClientモジュールをインポートします。
import MockHttpClient from './MockHttpClient';
クラス内でリストデータを取得するプライベートメソッドを追加します。
private _getMockListData(): Promise<IDetailsListItems> {
return MockHttpClient.get()
.then((data: IDetailsListItem[]) => {
var listData: IDetailsListItems = { value: data };
return listData;
}) as Promise<IDetailsListItems>;
}
##4. リストデータを取得
SharePointワークベンチでは、EnvironmentTypeモジュールを使って、Webパーツを実行している環境に応じて、接続先を簡単にスイッチすることができます。これを使って、ローカル環境の場合はモックストアから、SharePointサイトの場合は実際のリストを取得できるようにします。
src/webparts/detailsListSample/components/DetailsListSample.tsxで、EnvironmentTypeモジュールをインポートします。
import {
Environment,
EnvironmentType
} from '@microsoft/sp-core-library';
ワークベンチがSharePointでホストされている場合は、ページのコンテキストにアクセスして、サイトのURLを取得する必要があります。また、SharePoint Frameworkには、spHttpClientというSharePoint APIを呼び出すためのヘルパークラスが含まれており、これを使ってリストにアクセスします。
PropsでサイトのURLとspHttpClientをコンポーネントに与えてあげます。
src/webparts/detailsListSample/components/IDetailsListSampleProps.tsを次のように書き換えます。
import { SPHttpClient } from "@microsoft/sp-http";
export interface IDetailsListSampleProps {
description: string;
spHttpClient: SPHttpClient;
siteUrl: string;
}
src/webparts/detailsListSample/DetailsListSampleWebPart.tsで、値をセットします。
public render(): void {
const element: React.ReactElement<IDetailsListSampleProps> = React.createElement(
DetailsListSample,
{
description: this.properties.description,
spHttpClient: this.context.spHttpClient,
siteUrl: this.context.pageContext.web.absoluteUrl,
}
);
ReactDom.render(element, this.domElement);
}
src/webparts/detailsListSample/components/DetailsListSample.tsxを開いて、ヘルパークラスをインポートします。
import {
SPHttpClient,
SPHttpClientResponse
} from '@microsoft/sp-http';
では、Propsに持たせたspHttpClientを使って、SharePointからリストを取得するプライベートメソッドを追加します。
private _getListData(): Promise<IDetailsListItems> {
return this.props.spHttpClient.get(this.props.siteUrl + `/_api/web/lists/getbytitle('リスト名')/items?$top=10`, SPHttpClient.configurations.v1)
.then((response: SPHttpClientResponse) => {
return response.json();
});
}
最後に、モックストアとSharePointを切り替えてデータを取得するためのプライベートメソッドを追加して、これを初期化のタイミングあたりでキックしてあげます。
private _getListItemAsync(): void {
// Local environment
if (Environment.type === EnvironmentType.Local) {
this._getMockListData().then((response) => {
this._allItems = response.value;
this.setState({
items: this._allItems,
});
});
}
else if (Environment.type == EnvironmentType.SharePoint ||
Environment.type == EnvironmentType.ClassicSharePoint) {
this._getListData()
.then((response) => {
this._allItems = response.value;
this.setState({
items: this._allItems,
});
});
}
}
ローカルでプレビューすると、こんな感じになります。モックストアでセットしたデータがちゃんと表示されています。
##5. SharePointにWebパーツをデプロイする
次のコマンドでプロジェクトをビルドして、ソリューションをパッケージ化します。
gulp bundle --ship
gulp package-solution --ship
sharepoint/solution/detailslist-webpart.sppkgというファイルが生成されると思います。あとは、これをサイトにアップロードして展開すると、実際にWebパーツとして利用できるようになります。
ちょっと今回は適当なサイトがなかったので、キャプチャなどは割愛します。実際に動かしてみた感じでは、とてもサクサク動くのでびっくりしました。
#おわりに
ReactベースでSharePointのWebパーツを作成しました。
これを応用すると、結構いろんなパーツが作れるので、めちゃくちゃ活用できそうです。何でもっと早くやらなかったんだろうと思うくらい、控えめに言って最強なソリューションでした。