LoginSignup
5
9

More than 3 years have passed since last update.

SharePointのWebパーツをReactベースで作る

Posted at

はじめに

SharePointのWebパーツをReactで作れるらしいので、実際にやってみました。しばらくMSから離れていたのですが、私の知っている鎖国のような時代は終わっていて、オープンソースワールドがそこには広がっていました。MSでReactやgulpを使う日が来るなんて…。これをきっかけに、またいろいろと作ってみたくなりました。

Office UI Fabric React

Webパーツ自体は、JavaScriptフレームワークを使っても使わなくてもよいです。が、Reactベースの素晴らしすぎるUIフレームワークがあったので、これを使います。このコンポーネントを使うだけで、簡単にそれっぽいものが作れそうです。
office-ui-fabric-sample.png

今回作るWebパーツ

せっかくなので、SharePointのサイトコンテンツにアクセスするものを作ろうと思います。今回は、リストデータを取得して表示するパーツを作ります。使うコンポーネントは、DetailsListです。

detailslist-sample.png

やってみた

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 create-new-project-1.png このようになれば、プロジェクトの作成は完了です。 create-new-project-2.png

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コンポーネントが表示されました。
office-ui-fabric-1.png

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,
        });
      });
  }
}

ローカルでプレビューすると、こんな感じになります。モックストアでセットしたデータがちゃんと表示されています。
mock-store-1.png

5. SharePointにWebパーツをデプロイする

次のコマンドでプロジェクトをビルドして、ソリューションをパッケージ化します。

gulp bundle --ship
gulp package-solution --ship

sharepoint/solution/detailslist-webpart.sppkgというファイルが生成されると思います。あとは、これをサイトにアップロードして展開すると、実際にWebパーツとして利用できるようになります。

ちょっと今回は適当なサイトがなかったので、キャプチャなどは割愛します。実際に動かしてみた感じでは、とてもサクサク動くのでびっくりしました。

おわりに

ReactベースでSharePointのWebパーツを作成しました。
これを応用すると、結構いろんなパーツが作れるので、めちゃくちゃ活用できそうです。何でもっと早くやらなかったんだろうと思うくらい、控えめに言って最強なソリューションでした。

5
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
9