最初の Form カスタマイザー拡張機能をビルドする

フォーム カスタマイザーはコンポーネントSharePoint Frameworkであり、コンポーネントを使用するコンテンツ タイプに関連付けることにより、リスト レベルまたはライブラリ レベルのフォーム エクスペリエンスをオーバーライドするオプションを提供します。 フォーム カスタマイザー コンポーネントは SharePoint Online で使用でき、最新の JavaScript ツールとライブラリを使用してビルドできます。

重要

フォーム カスタマイザーは、SharePoint Framework 1.15 の一部としてリリースされているため、環境内で適切なバージョンを使用していることを確認してください。 詳細については 、v1.15 リリース ノート を参照してください。

ヒント

このチュートリアルの出力は 、GitHub から確認できます。

拡張機能のプロジェクトを作成する

  1. 自分の好みの場所に新しいプロジェクト ディレクトリを作成します。

    md form-customizer
    
  2. プロジェクト ディレクトリへ移動します。

    cd form-customizer
    
  3. Yeoman の SharePoint ジェネレーターを実行して、新しい HelloWorld の拡張機能を作成します。

    yo @microsoft/sharepoint
    
  4. プロンプトが表示されたら、以下の値を入力します (以下で省略されたすべてのプロンプトに対して既定のオプションを選択します)。

    • ソリューション名は何ですか?: フォーム カスタマイザー
    • どの種類のクライアント側コンポーネントを作成しますか?: 拡張機能
    • どの種類のクライアント側拡張機能を作成しますか? フォーム カスタマイザー
    • フォーム カスタマイザーの名前は何ですか? HelloWorld
    • どのテンプレートを使用しますか?: JavaScript Framework なし

    この時点で、Yeoman は必要な依存関係をインストールし、ソリューション ファイルを HelloWorld の拡張機能とともにスキャフォールディングします。 これには数分かかる場合があります。

  5. コンソールに次のように入力して Visual Studio Code を起動します。

    code .
    

    注:

    SharePoint のクライアント側ソリューションは HTML や TypeScript に基づいているため、クライアント側の開発をサポートしている任意のコード エディターを使用して拡張機能を構築できます。

  6. ./src/extensions/helloWorld/HelloWorldFormCustomizer.manifest.json ファイルを開きます。

    このファイルは、拡張機能の種類と、このコンポーネントでカスタム レンダリングを有効にするためにコンテンツ タイプ レベルで使用するように設定するために使用できる拡張機能の一意の識別子 id を定義します。

フォーム カスタマイザーをコーディングする

./src/extensions/helloWorld/HelloWorldFormCustomizer.ts ファイルを開きます。

フォーム カスタマイザーの基本クラスが sp-listview-extensibility パッケージからインポートされていることに注意してください。このパッケージには、フォーム カスタマイザーで必要なSharePoint Framework コードが含まれています。

import { Log } from '@microsoft/sp-core-library';
import {
  BaseFormCustomizer
} from '@microsoft/sp-listview-extensibility';

フォーム カスタマイザーのロジックは、、render()、および onDispose() メソッドにonInit()含まれています。

  • onInit() は、拡張機能に必要なセットアップを実行する場所です。 このイベントは、this.contextthis.properties が割り当てられた後、ページ DOM の準備ができる前に発生します。 Web パーツと同様に、 onInit() 非同期操作 render() を実行するために使用できる promise を返します。promise が解決されるまで呼び出されません。 それが必要なければ、Promise.resolve<void>(); だけを返します。
  • render() は、コンポーネントがレンダリングされるときに発生します。 これは、コードがその内容を書き込むことができる event.domElement HTML 要素を提供します。
  • onDispose() は、フォーム ホスト要素が削除される直前に発生します。 フォームのレンダリング中に割り当てられたリソースを解放するために使用できます。 たとえば、render() が React 要素をマウントしている場合は、onDispose() を使用して解放しなければなりません。解放しないと、リソース リークが発生します。

既定の render() ソリューションの および onDispose() の内容を次に示します。

  public render(): void {
    // Use this method to perform your custom rendering.
    this.domElement.innerHTML = `<div class="${ styles.helloWorld }"></div>`;
  }

  public onDispose(): void {
    // This method should be used to free any resources that were allocated during rendering.
    super.onDispose();
  }

既定では、フォーム customzier コンポーネントは情報をレンダリングしないため、次のように render メソッドを更新しましょう。

  public render(): void {
    // Use this method to perform your custom rendering.
    this.domElement.innerHTML = `<div class="${ styles.helloWorld }"><h1>Super cool custom form component</h1></div>`;
  }

フォーム カスタマイザーをデバッグする

ライブ SharePoint Online サイト内でフォーム カスタマイザーをテストおよびデバッグできます。 これを行うために、カスタマイズをテナント アプリ カタログにデプロイする必要はありません。これにより、デバッグ エクスペリエンスがシンプルで効率的になります。

  1. 拡張機能をテストするには、まず、カスタマイザーをテストするリストを作成する必要があります。 そのため、フォーム カスタマイザーをテストする SharePoint Online テナント内のサイトに移動します。

  2. ツールバーで [新規] を選択し、[リスト] を選択します。

    新しいリストを作成する

  3. 新しいリスト作成エクスペリエンスから [空のリスト ] を選択します

    空のリストを選択する

  4. Business という名前の新しいリストを作成し、[作成] を選択します。

    「Orders」という名前の新しいリストを作成する

  5. Visual Studio Code 内で、 ./config/serve.json ファイルを開きます。

    プレビュー手順で pageUrl 作成したリストの URL と一致するように属性を更新します。 変更後、 serve.json は次のコードのようになります。

    {
      "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/spfx-serve.schema.json",
      "port": 4321,
      "https": true,
      "serveConfigurations": {
        "default": {
          "pageUrl": "https://yourtenant.sharepoint.com/sites/demo/_layouts/15/SPListForm.aspx",
          "formCustomizer": {
            "componentId": "fb3e7dee-a6fa-4e80-add1-e06081167bb5",
            "PageType": 8,
            "RootFolder": "/sites/demo/Lists/business",
            "properties": {
              "sampleText": "Value"
            }
          }
    

    serve.json ファイルからいくつかの特定のトピックを呼び出してみましょう

    • 特定のクエリ パラメーターの違いを使用して、新しいフォームのデバッグ、編集、表示に使用できる複数の異なる構成を確認できます。 gulp serve コマンドで使用される構成を定義できます。たとえば、 gulp serve --config=helloWorld_EditForm
    • componentId は、ソリューションの最初のリスト書式設定コンポーネントに自動的に関連付けられています (複数のコンポーネントがある場合)
    • デバッグを簡略化するために、コンポーネントが関連付けられているターゲット コンテンツ タイプ ID を定義する必要はありませんが、ランタイムでは、コンテンツ タイプ内の次のプロパティの少なくとも 1 つを更新することで、関連付けがコンテンツ タイプ レベルで実行されます。
      • Contenttype。NewFormClientSideComponentId - 新しいフォームのコンポーネント ID
      • Contenttype。NewFormClientSideComponentProperties - オプションの構成の詳細
      • Contenttype。DispFormClientSideComponentId - 編集フォームのコンポーネント ID
      • Contenttype。DispFormClientSideComponentProperties - オプションの構成の詳細
      • Contenttype。EditFormClientSideComponentId - コンポーネント ID 表示フォーム
      • Contenttype。EditFormClientSideComponentProperties - オプションの構成の詳細
  6. このコマンドを実行して、コードをコンパイルし、コンパイルしたファイルをローカル コンピューターからホストします。

    gulp serve
    

    コードがエラーなしでコンパイルされると、https://localhost:4321 から結果のマニフェストが提供されます。

    gulp serve

    これにより、既定のブラウザーが起動し、 serve.json ファイルで定義されているページが読み込まれます。

  7. ダイアログが表示されたら、[デバッグ スクリプトを読み込む] を選択して、デバッグ マニフェストの読み込みを承諾します。

    デバッグ スクリプトの読み込みを承諾する

    render メソッドに更新したカスタム コンテンツに基づいて、カスタム コンポーネントがページでどのようにレンダリングされるかに注目してください。

    既定の outpu でレンダリングされたカスタマイザーからのリスト ビュー

フォーム項目の編集機能をサンプルに追加する

これでベースライン コンポーネントを作成し、正常に動作することをテストしました。 表示、編集、および新しいフォーム用の個別のレンダリング ロジックを作成し、リストへの新しい項目の保存をサポートします。

  1. ./src/extensions/helloWorld/loc/myStrings.d.ts ファイルを開き、IHelloWorldFormCustomizerStrings インターフェイスに新しい Title を追加します。 インターフェイスは、編集後に次のようにする必要があります。.

    declare interface IHelloWorldFormCustomizerStrings {
      Save: string;
      Cancel: string;
      Close: string;
      Title: string;
    }
    
  2. ./src/extensions/helloWorld/loc/en-us.jsファイルを開き、新しい Title 文字列をファイルに追加します。 ファイルの内容は、編集後に次のようになります。

    define([], function() {
      return {
        "Save": "Save",
        "Cancel": "Cancel",
        "Close": "Close",
        "Title": "Title"
      }
    });
    
  3. ./src/extensions/helloWorld/HelloWorldFormCustomizer.module.scss ファイルを開き、スタイル定義を次のように更新します。 コンポーネントのエラー スタイルを追加しています。

    .helloWorld {
      background-color: "[theme:white, default:#ffffff]";
      color: "[theme:themePrimary, default:#0078d4]";
      padding: 0.5rem;
    
      .error {
        color: red;
      }
    }
    
  4. HelloWorldFormCustomizer.ts ファイルの先頭に移動します。

  5. import styles from './HelloWorldFormCustomizer.module.scss'; を見つけて、その直後に次の行を追加します。

    import { FormDisplayMode } from '@microsoft/sp-core-library';
    import {
      SPHttpClient,
      SPHttpClientResponse
    } from '@microsoft/sp-http';
    
  6. このコード スニペット示すように、HelloWorldFormCustomizer クラス内に_itemと_etagプライベート型を含めます。 クラス定義がコードに既に存在していることに注意してください。

    // This already exists in YOUR code
    export default class HelloWorldFormCustomizer
      extends BaseFormCustomizer<IHelloWorldFormCustomizerProperties> {
    
    // Added for the item to show in the form; use with edit and view form
    private _item: {
      Title?: string;
    };
    // Added for item's etag to ensure integrity of the update; used with edit form
    private _etag?: string;
    
  7. onInit() メソッドを次のように更新します。 このコードでは 、this.displayMode を使用してレンダリングの状態を判断し、必要に応じて選択したリスト アイテムをフェッチします。

    public onInit(): Promise<void> {
      if (this.displayMode === FormDisplayMode.New) {
        // we're creating a new item so nothing to load
        return Promise.resolve();
      }
    
      // load item to display on the form
      return this.context.spHttpClient
        .get(this.context.pageContext.web.absoluteUrl + `/_api/web/lists/getbytitle('${this.context.list.title}')/items(${this.context.itemId})`, SPHttpClient.configurations.v1, {
          headers: {
            accept: 'application/json;odata.metadata=none'
          }
        })
        .then(res => {
          if (res.ok) {
            // store etag in case we'll need to update the item
            this._etag = res.headers.get('ETag');
            return res.json();
          }
          else {
            return Promise.reject(res.statusText);
          }
        })
        .then(item => {
          this._item = item;
          return Promise.resolve();
        });
    }
    
  8. 以下のようにして render() メソッドを更新します。 フォームの表示モードに応じて、表示のみまたは編集モードでフォームをレンダリングします。 この場合、新しいエクスペリエンスと編集エクスペリエンスに同じ renderig を使用しますが、必要に応じて専用オプションを簡単に使用できます。

      public render(): void {
        // render view form
        if (this.displayMode === FormDisplayMode.Display) {
    
          this.domElement.innerHTML =
                        `<div class="${styles.basics}">
                          <label for="title">${strings.Title}</label>
                          <br />
                            ${this._item?.Title}
                          <br />
                          <br />
                          <input type="button" id="cancel" value="${strings.Close}" />
                        </div>`;
    
          document.getElementById('cancel').addEventListener('click', this._onClose.bind(this));
        }
        // render new/edit form
        else {
          this.domElement.innerHTML =
                      `<div class="${styles.basics}">
                        <label for="title">${strings.Title}</label><br />
                        <input type="text" id="title" value="${this._item?.Title || ''}"/>
                        <br />
                        <br />
                        <input type="button" id="save" value="${strings.Save}" />
                        <input type="button" id="cancel" value="${strings.Cancel}" />
                        <br />
                        <br />
                        <div class="${styles.error}"></div>
                      </div>`;
    
          document.getElementById('save').addEventListener('click', this._onSave.bind(this));
          document.getElementById('cancel').addEventListener('click', this._onClose.bind(this));
        }
      }
    
  9. HelloWorldFormCustomizer クラスの _onSave メソッドを次のように更新します。

    private _onSave = async (): Promise<void> => {
      // disable all input elements while we're saving the item
      this.domElement.querySelectorAll('input').forEach(el => el.setAttribute('disabled', 'disabled'));
      // reset previous error message if any
      this.domElement.querySelector(`.${styles.error}`).innerHTML = '';
    
      let request: Promise<SPHttpClientResponse>;
      const title: string = (document.getElementById('title') as HTMLInputElement).value;
    
      switch (this.displayMode) {
        case FormDisplayMode.New:
          request = this._createItem(title);
          break;
        case FormDisplayMode.Edit:
          request = this._updateItem(title);
      }
    
      const res: SPHttpClientResponse = await request;
    
      if (res.ok) {
        // You MUST call this.formSaved() after you save the form.
        this.formSaved();
      }
      else {
        const error: { error: { message: string } } = await res.json();
    
        this.domElement.querySelector(`.${styles.error}`).innerHTML = `An error has occurred while saving the item. Please try again. Error: ${error.error.message}`;
        this.domElement.querySelectorAll('input').forEach(el => el.removeAttribute('disabled'));
      }
    }
    
  10. HelloWorldFormCustomizer クラスに新しいメソッド _createItemを追加します。

    private _createItem(title: string): Promise<SPHttpClientResponse> {
      return this.context.spHttpClient
        .post(this.context.pageContext.web.absoluteUrl + `/_api/web/lists/getByTitle('${this.context.list.title}')/items`, SPHttpClient.configurations.v1, {
          headers: {
            'content-type': 'application/json;odata.metadata=none'
          },
          body: JSON.stringify({
            Title: title
          })
        });
    }
    
  11. HelloWorldFormCustomizer クラスに新しいメソッド _updateItemを追加します。

    private _updateItem(title: string): Promise<SPHttpClientResponse> {
      return this.context.spHttpClient
        .post(this.context.pageContext.web.absoluteUrl + `/_api/web/lists/getByTitle('${this.context.list.title}')/items(${this.context.itemId})`, SPHttpClient.configurations.v1, {
          headers: {
            'content-type': 'application/json;odata.metadata=none',
            'if-match': this._etag,
            'x-http-method': 'MERGE'
          },
          body: JSON.stringify({
            Title: title
          })
        });
    }
    

これで、最小限の New、Edit、Display エクスペリエンスをサポートするコードが完成し、デバッグ用のさまざまな構成を使用してさまざまなエクスペリエンスをテストできます。

SharePoint のコンテキストでのカスタム フォーム

拡張機能のデプロイ

コンポーネントの使用を開始する準備ができたら、コンテンツ タイプへのコンポーネントの関連付けに関して考慮すべき手順がいくつかあります。 デプロイの手順は次のとおりです。

  1. ソリューションを SharePoint アプリ カタログにデプロイする
  2. テナント スコープ展開を使用していない場合は、拡張機能を使用するサイト コレクションにソリューションをインストールします
  3. ContentType オブジェクトの特定のプロパティを使用して、カスタム コンポーネントをコンテンツ タイプに関連付けます。 これを行うには、いくつかのオプションがあります。
    1. サイト スコープ展開オプションを使用している場合は、ソリューションから使用済みのリストとコンテンツ タイプをプロビジョニングできます
    2. REST API または CSOM API を使用して、コンポーネントをコンテンツ タイプに関連付けることができます。 サイト コレクションまたはコンテンツ タイプ ハブ レベルでコンポーネントを関連付けた場合、そのコンポーネントは新しいコンテンツ タイプ インスタンスすべてに自動的に継承されます