SharePoint : クライアントサイドでの Web パーツ接続

環境 : Office SharePoint Server 2007, Visual Studio 2008, Visual Studio extension for Windows SharePoint Services (VSeWSS) 1.2

[This article’s download sample : 20090912_ClientConnectionSample ]

こんにちは。

以下、仕事の関連で話題になりましたので、方法をメモとして記載しておきます。。。

ここでは、Client サイドの Web パーツ接続 (WebParts Connection) について説明します。現在、Web パーツを AJAX, Silverlight などを使用して構築しているケースも多いと思いますが、こうした場合に有効な手段となるでしょう。

事前知識

まず、セミナーなどでもご説明しているように、Windows SharePoint Services 3.0 (Office SharePoint Server 2007) 以降の SharePoint テクノロジーは、従来の SharePoint テクノロジーとの互換性を意識しつつ、.NET の Web 開発テクノロジーである “ASP.NET” とさまざまな面で “美しく” 統合されています。「Web パーツ」もこうした ASP.NET と “美しく” 統合されたテクノロジー・コンポーネントの 1 つであり、この辺りの知識については、下記のコラムで簡単に説明していますので参考にしてください。

UI / Web パーツで見る「フィーチャー フレームワーク」 :

https://blogs.msdn.microsoft.com/tsmatsuz/2012/03/28/sharepoint-2-ui-web-12/

このため、MOSS 2007 (WSS 3.0) の Web パーツでは、ASP.NET の WebPart クラスを継承して、SharePoint の Web パーツとして開発・配置をおこなうことが可能です。こう書くと、もはや、あの面倒な “SharePoint の WebPart” はレガシー対応でしか存在していないと言った感じですが、”SharePoint の WebPart クラス” を使用すると、実は、SharePoint でしか使えない幾つかの機能を使用することができるため、こうした目的でこのクラスを使用することができます。(ただし、こうした開発は必要最小限度に留めておきましょう。。。)

そうした機能の 1 つが、今回ご紹介する Web パーツのクライアント側での接続 (Client Connection) です。

なお、”ASP.NET の WebPart クラス” を継承した標準的な Web パーツ作成と、Web パーツどうしの接続のプログラミング方法は、以前、下記の投稿で方法とコードを掲載しています。この投稿で記載している「プロバイダー」と「コンシューマ」の概念などは、”SharePoint の WebPart クラス” でも同様に必要な概念ですので基本をおさえておいてください。

SharePoint における実用的なカスタム Web パーツ (WebPart) の開発 :

https://blogs.msdn.microsoft.com/tsmatsuz/2009/09/14/sharepoint-web/

では、本題に入っていきましょう。まず、”SharePoint の WebPart クラス” を継承したプログラミング方法ですが、これについては、以下の MSDN の記事が参考になります。

[ウォークスルー] 接続可能な Windows SharePoint Services の Web パーツを作成する

http://msdn.microsoft.com/ja-jp/library/ms469765.aspx

コードの細かな点はとりあえず置いておき、ポイントのみを記載すると、このサンプル (上記のウォークスルーのサンプル) では、ICellProvider, ICellConsumer インタフェースを実装して、相互に接続可能な「プロバイダー Web パーツ」と「コンシューマー Web パーツ」を開発していますが、これら ICellProvider、ICellConsumer 以外の他のインタフェースを使用すると、前述の投稿 (SharePoint における実用的なカスタム Web パーツの開発) でも説明しているように、さまざまなタイプのプロパティ (例 : リストそのものや、リストの行、リストの特定の列の値、フィルターの値、など) と接続可能なさまざまな Web パーツをプログラミングすることができます。
実は、この ICellProvider、ICellConsumer は、”古いインタフェース” であり、標準的な ASP.NET の Web パーツ開発では、 前述の投稿 (SharePoint における実用的なカスタム Web パーツの開発) の中でも記載しているように、IWebPartField などの ASP.NET のインタフェースを使用しますので注意してください。

クライアント接続のプログラミング (Programming)

さて、上記のウォークスルーのサンプルの中でも、実は、既にクライアント接続用のコードが記載 (サーバー接続用のコードと混在) されていますが、今回は話を簡単にするため、このクライアント接続の部分のコードのみにフォーカスを当て、より簡単なサンプルで説明します。

まずは、いきなりですが、プロバイダー (Provider) Web パーツ側のサンプルコードを以下に記載します。

. . . . .
using Microsoft.SharePoint.WebPartPages.Communication;

namespace ClientConnectionWebPart
{
  [Guid("9ddf3991-37ad-49db-9993-e874b65893b6")]
  public class SampleProviderWebPart : Microsoft.SharePoint.WebPartPages.WebPart, ICellProvider
  {
    [Obsolete]
    public override void EnsureInterfaces()
    {
      RegisterInterface("MyCellProvider_WPQ_",
         InterfaceTypes.ICellProvider,
         Microsoft.SharePoint.WebPartPages.WebPart.LimitOneConnection,
         ConnectionRunAt.Client,
         this,
         "CellProvider_WPQ_",
         "Send to MyCellConsumer",
         "this is test");
    }

    [Obsolete]
    public override ConnectionRunAt CanRunAt()
    {
      return ConnectionRunAt.Client;
    }

    [Obsolete]
    public override void PartCommunicationConnect(string interfaceName, Microsoft.SharePoint.WebPartPages.WebPart connectedPart, string connectedInterfaceName, ConnectionRunAt runAt)
    {
      if (runAt == ConnectionRunAt.Client)
      {
        // 接続時に呼ばれるため、接続時の処理はここに書きます。
        // 今回は、何もしない . . .
      }
      EnsureChildControls(); // <-- 今回は、なくても良い
    }

    [Obsolete]
    public override void PartCommunicationInit()
    {
      // クライアント側の接続では、ここは呼ばれません
      // (サーバー側の接続の場合のみ、実装)
    }

    [Obsolete]
    public override void PartCommunicationMain()
    {
      // クライアント側の接続では、ここは呼ばれません
      // (サーバー側の接続の場合のみ、実装)
    }

    protected override void RenderWebPart(HtmlTextWriter output)
    {
      EnsureChildControls(); // <-- 今回は、なくても良い

      output.Write(ReplaceTokens(
        "<select id='MySelect_WPQ_' onchange='MenuChange_WPQ_()'>n" +
        "<option value='1'>1番選択</option>n" +
        "<option value='2'>2番選択</option>n" +
        "</select>"));

      output.Write(ReplaceTokens(
        "n" +

        "var CellProvider_WPQ_ = new funcCellProvider_WPQ_();n" +
        "function funcCellProvider_WPQ_() {n" +

        "  this.PartCommunicationInit = myInit;n" +
        "  this.PartCommunicationMain = myMain;n" +
        "  this.CellConsumerInit = myCellConsumerInit;n" +

        "  function myInit() {n" +
        "  var cellProviderInitArgs = new Object();n" +
        "  cellProviderInitArgs.FieldName = 'CellName';n" +
        "  WPSC.RaiseConnectionEvent('MyCellProvider_WPQ_', 'CellProviderInit', cellProviderInitArgs);n" +
        "  }n" +

        "  function myMain() {n" +
        "  var cellReadyArgs = new Object();n" +
        "  cellReadyArgs.Cell = '';n" +
        "  WPSC.RaiseConnectionEvent('MyCellProvider_WPQ_', 'CellReady', cellReadyArgs);n" +
        "  }n" +

        "  function myCellConsumerInit(sender, cellConsumerInitArgs) { }n" +

        "}n" +

        "function MenuChange_WPQ_() {n" +
        "  var cellReadyArgs = new Object();" +
        "  cellReadyArgs.Cell = document.all('MySelect_WPQ_').value;n" +
        "  WPSC.RaiseConnectionEvent('MyCellProvider_WPQ_', 'CellReady', cellReadyArgs);n" +
        "}n" +

        "n"));
    }

    #region ICellProvider メンバ

    // 以下はいずれも、クライアント側の接続では呼ばれません
    // (サーバー側の接続の場合のみ、実装)
    public void CellConsumerInit(object sender, CellConsumerInitEventArgs cellConsumerInitEventArgs)
    {
    }
    public event CellProviderInitEventHandler CellProviderInit;
    public event CellReadyEventHandler CellReady;

    #endregion
  }
}

上記のコードを説明します。

まず、”SharePoint の Web パーツ” ですから、SharePoint の WebPart クラスを継承し、今回は、ICellProvider インタフェース (特定の列の値と接続可能なインタフェース) を実装しています。

EnsureInterfaces オーバーライドメソッドでは、この Web パーツがいったい何者であるかを返しています。EnsureInterfaces の 6 番目の引数の “CellProvider_WPQ_” は、イベントが発生した場合に処理されるクライアント側 (JavaScript 側) のクラスを示しており、今回は、コンシューマ側がイベントを投げると、このプロバイダーの CellProvider_WPQ_ のクラス (JavaScript のオブジェクト) でイベントが処理されることになります。

上記で、_WPQ_ は、非常に重要なキーワードです。このキーワードは、SharePoint により一意な ID に書き換えられます。これにより、同一の Web パーツが複数存在しているケースなど、さまざまなケースで矛盾がなく処理されるようになりますので、関数名、オブジェクト名など、一意性が求められる箇所にはこのキーワードを入れておきましょう。(上記の RenderWebPart オーバーライドメソッドにおける ReplaceTokens では、この書き換えを明示的に指定しています。)

Obsolete 属性は、”古いメソッド” などを使用している場合に警告を表示しないために使用しているオマジナイだと思ってください。前述したように、ICellProvider 自体も古いインタフェースであり、Obsolete でビルドメッセージを指定しないと、ビルド時に、”廃棄予定のメソッドである” などのメッセージが表示されることになるでしょう。

RenderWebPart オーバーライドメソッドに記載されているコードは最も重要です。(というか、今回はクライアント側の処理のため、ここに処理のほとんどが書かれています。) 通常のサーバー側の Web パーツでは、Web パーツの初期化がおこなわれた際に呼ばれる PartCommunicationInit メソッドや、選択項目が変更された際などに呼ばれるメイン処理の PartCommunicationMain メソッドなどのオーバーライドメソッドをプログラミングしますが、クライアントサイドの接続では、こうした処理はすべて JavaScript で記述することになります (このフレームワークの JavaScript ライブラリが SharePoint によって内部で提供されます)。このため、このフレームワークと連携して動作する処理を  “JavaScript として”  RenderWebPart メソッドで出力しています。

この JavaScript の処理についてですが、this.PartCommunicationInit は Web パーツの初期化がおこなわれた際に呼ばれる処理で、this.PartCommunicationMain は初期化後の状態の変更通知などで呼ばれるメイン処理です。いずれのメソッドも、それをコンシューマー Web パーツに通知するため、WPSC.RaiseConnectionEvent (SharePoint の組み込みの JavaScript 関数) を呼び出しています。
また、CellConsumerInit メソッドは、逆に、コンシューマ側から通知されるイベント処理で、今回、ここでは特に処理はおこなっていません。
メニュー (select タグの項目) が変更されると、RadioOnClick_WPQ_ 関数により、PartCommunicationMain の処理と同様、CellReady のイベントをコンシューマ Web パーツに通知し、この際、引数としてメニューで選択された値を送信しています。
(なお、ここではクライアントのコードで説明していますが、サーバー側の実装でも、上記と同様の概念で、PartCommunicationInit、PartCommunicationMain などを使用するので、この動作概念はおぼえておくと良いでしょう。サーバー側の実装の場合、コントロールの再描画時などに自動的に PartCommunicationMain が呼ばれるので、コントロール側では、単にポストバックをおこなうように実装すれば良いでしょう。。。)

つぎに、コンシューマ (Consumer) 側のコードを記載してみましょう。

. . . . .
using Microsoft.SharePoint.WebPartPages.Communication;

namespace ClientConnectionSample
{
  [Guid("0bab1085-f5f8-42ba-a406-9c6f8619121e")]
  public class SampleConsumerWebPart : Microsoft.SharePoint.WebPartPages.WebPart, ICellConsumer
  {
    [Obsolete]
    public override void EnsureInterfaces()
    {
      RegisterInterface("MyCellConsumer_WPQ_",
        InterfaceTypes.ICellConsumer,
        Microsoft.SharePoint.WebPartPages.WebPart.LimitOneConnection,
        ConnectionRunAt.Client,
        this,
        "CellConsumer_WPQ_",
        "Receive from MyCellProvider",
        "this is test");
    }

    [Obsolete]
    public override ConnectionRunAt CanRunAt()
    {
      return ConnectionRunAt.Client;
    }

    [Obsolete]
    public override void PartCommunicationConnect(string interfaceName, Microsoft.SharePoint.WebPartPages.WebPart connectedPart, string connectedInterfaceName, ConnectionRunAt runAt)
    {
      if (runAt == ConnectionRunAt.Client)
      {
        // 接続時に呼ばれるため、接続時の処理はここに書きます。
        // 今回は、何もしない . . .
      }
      EnsureChildControls(); // <-- 今回は、なくても良い
    }

    [Obsolete]
    public override void PartCommunicationInit()
    {
      // クライアント側の接続では、ここは呼ばれません
      // (サーバー側の接続の場合のみ、実装)
    }

    protected override void RenderWebPart(HtmlTextWriter output)
    {
      EnsureChildControls(); // <-- 今回は、なくても良い

      output.Write(ReplaceTokens("<input type='text' id='MyText_WPQ_' />n"));

      output.Write(ReplaceTokens(
        "n" +

        "var CellConsumer_WPQ_ = new funcCellConsumer_WPQ_();n" +
        "function funcCellConsumer_WPQ_() {n" +

        "  this.PartCommunicationInit = myInit;n" +
        "  this.CellProviderInit = myCellProviderInit;n" +
        "  this.CellReady = myCellReady;n" +

        "  function myInit() {n" +
        "  var cellConsumerInitArgs = new Object();n" +
        "  cellConsumerInitArgs.FieldName = 'CellName';n" +
        "  WPSC.RaiseConnectionEvent('MyCellConsumer_WPQ_', 'CellConsumerInit', cellConsumerInitArgs);n" +
        "  }n" +

        "  function myCellProviderInit(sender, cellProviderInitArgs) { }n" +

        "  function myCellReady(sender, cellReadyArgs) {n" +
        "  document.all('MyText_WPQ_').value = cellReadyArgs.Cell;n" +
        "  }n" +

        "}n" +

        "n"));
    }

    #region ICellConsumer メンバ

    // 以下はいずれも、クライアント側の接続では呼ばれません
    // (サーバー側の接続の場合のみ、実装)
    public event CellConsumerInitEventHandler CellConsumerInit;
    public void CellProviderInit(object sender, CellProviderInitEventArgs cellProviderInitArgs)
    {
    }
    public void CellReady(object sender, CellReadyEventArgs cellReadyArgs)
    {
    }

    #endregion
  }
}

コンシューマ側も、考え方はまったく同様です。プロバイダー側のコードを理解できた方は、こちらのコードも簡単に理解できたのではないでしょうか。

コンシューマ側では、上述したプロバイダー側からの初期化のイベント処理 (PartCommunicationInit 関数が渡した CellProviderInit イベント) とメインのイベント処理 (PartCommunicationMain 関数が渡した CellReady イベント) が呼び出されますので、これらのイベント処理を JavaScript で記述しています。今回は、CellReady のイベント処理で、テキストボックスに、渡された値 (引数) をそのまま設定するように記載しています。

このプロバイダー Web パーツとコンシューマ Web パーツを SharePoint に配置し、相互の接続をおこなって動作をさせると、下図の通り、選択欄 (メニュー) が変更された際に、テキストボックスに選択した値が入力されます。(ページのポストバックはおこなわれません。)

なお、既存のリストビュー Web パーツ (ListViewWebPart) では、残念ながら、サーバー側での Web パーツ接続しかサポートしていないようですので (ConnectionRunAt.Server または、ConnectionRunAt.ServerAndClient のみ対応)、
この自作のクライアントサイドの Web パーツと接続することはできませんので注意してください。

 

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s