環境 :
Office 365 (Preview)
Visual Studio 2012
Microsoft Office Developer Tools for Visual Studio 2012 (Preview2)

SharePoint Add-ins 開発

こんにちは。

今回は、SharePoint Add-ins (SharePoint アドイン, 旧 App for SharePoint) のカスタム リスト定義 (List Definition) とリスト インスタンス (List Instance) の開発 (プログラミング) を解説します。

まず、ご存じない方のために、基本事項を補足します。
リスト定義とは、例えば、SharePoint に既に含まれている「タスク」、「お知らせ」、「ディスカッション掲示板」などのリスト (ドキュメント ライブラリーを含む) のひな形 (テンプレート) であり、リスト インスタンスは、このリスト定義から実際に作成された「リスト」 (または、「ドキュメント ライブラリー」) です。(厳密には、リスト定義とは別に「リスト テンプレート」があり、SharePoint ではこれらもテンプレートとして表示されますが、ここでは細かな説明は省略します。)
SharePoint Add-ins でも、従来の SharePoint 開発同様、リスト定義とリスト インスタンスを開発 (プログラミング) でき、考え方のベースは同じですが、いくつか SharePoint 2013 独自の注意点や新機能もあるので以下に補足しながら解説します。

なお、今回から、ちゃんと日本語版を使用します !

 

基本は従来と同じ、しかし ... (App Event Receiver の活用)

では、さっそくリスト定義とリスト インスタンスを構築してみましょう。

Visual Studio 2012 を起動し、[Apps for SharePoint 2013] (SharePoint Add-ins) のプロジェクトを新規作成します。
リスト定義 (List Definition) のような SharePoint の artifacts の場合には、SharePoint-hosted (SharePoint ホスト型) でホストされます。(SharePoint-hosted については、「SharePoint Add-ins の動作と概要」参照。) このため、今回は、ウィザードで、[SharePoint ホスト型] (SharePoint-hosted) を選択してプロジェクトを作成してください。

作成されたプロジェクトをマウスで右クリックして、[追加] - [新しい項目] を選択すると、下記の通り、SharePoint の artifacts を追加する画面が表示されます。ここで、「List1」という名前のリストを追加します。
リストを追加すると、リスト定義と、そのリスト定義から派生したリスト インスタンスが自動作成されます。

以降の開発 (プログラミング) の基本は、SharePoint 2010 の頃と同様です。ですので、事前に、「SharePoint のリスト定義の作成」を読んで理解しておいてください。

例えば、作成された「List1」をダブルクリックして表示される下図で、[列] タブを選択して列の追加や変更が可能です。(下図では、「Address」という名前の列を追加しています。)

この編集内容は、下図の通り、schema.xml に反映されます。(リスト定義の schema.xml については、「SharePoint のリスト定義の作成」を参照してください。)

<?xml version="1.0" encoding="utf-8"?>
<List xmlns:ows="Microsoft SharePoint" Title="List1" ...>
  <MetaData>
    . . .

    <Fields>
      <Field ID="{fa564e0f-0c70-4ab9-b863-0177e6ddd247}"
        Type="Text"
        Name="Title"
        DisplayName="$Resources:core,Title;"
        Required="TRUE" ... />
      <Field ID="{56ec54d2-3c20-4284-9d74-564ceced0e82}"
        Type="Text"
        Name="Address"
        DisplayName="Address" />
    </Fields>
    . . .

また、上図で [リスト] タブを選択して、表示される画面で [リストをサイド リンク バーに表示する] (Display list at Quick Launch) のチェックを選択すると、リスト インスタンスは SharePoint サイトの左のバー (Quick Launch) に表示されます。

この内容は、これまで (SharePoint 2010 まで) と同様、リスト インスタンスのフィーチャーの Elements.xml に下記の通り反映されます。(Elements.xml についても、「SharePoint のリスト定義の作成」を参照してください。)

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <ListInstance
    Title="List1"
    OnQuickLaunch="TRUE"
    TemplateType="10000"
    Url="Lists/List1"
    Description="マイ リスト インスタンス">
  </ListInstance>
</Elements>

ただ、この設定をおこなっても、そもそもリスト定義やリスト インスタンスが別サイト (サブ サイト) に反映されるという点に注意してください。例えば、ユーザーが SharePoint Online (Office 365) を使っていて、https://contoso.sharepoint.com/sites/test1 のサイトで SharePointApp1 の Add-ins を追加した場合、以下のサブ サイトが作成され、ここにリスト定義やリスト インスタンスが追加されます。(「SharePoint Add-ins の動作と概要」の解説を参照。)

https://contoso-<atbitary identity>.sharepoint.com/sites/test1/<sub site with project name>
(例えば、https://contoso-3202b4c1a25bf7.sharepoint.com/sites/test1/SharePointApp1)

つまり、リスト インスタンスをサイド リンク バー (Quick Launch) に表示しても、ユーザーが使っている https://contoso.sharepoint.com/sites/test1 のサイド リンク バー (Quick Launch) ではなく、https://contoso-<atbitary identity>.sharepoint.com/sites/test1/<sub site with project name> のサイド リンク バー (Quick Launch) に設定されるので、意味がありません。

このような場合、従来の SharePoint 開発同様、カスタム コードを使って設定をおこなうことができます。SharePoint Add-ins では、従来使用していた Feature Receiver は使用できず、App Event Receiver を使用します。
例えば、Host Web (ユーザーが使用しているサイト) に Quick Launch を追加するには、(Visual Studio の) ソリューション エクスプローラーで Add-ins のプロジェクトをクリックし、下図の通り [アプリのハンドルがインストールされました] (Handle App Installed) を「True」に設定します。

アプリがインストールされた際のハンドラーが、Remote App (マニフェストの入っている Add-ins とは別プロジェクト) として作成されます。(Provider-hosted で作成されます。) この作成された Web アプリケーションの AppEventReceiver.svc.cs に、下記の通りコードを記述します。ここではインストール時の処理のみ作成していますが、同様に、アンインストール時の処理も記述しておきましょう。

. . .
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Net;
. . .

public class AppEventReceiver : IRemoteEventService
{
  public SPRemoteEventResult ProcessEvent(SPRemoteEventProperties properties)
  {
    SPRemoteEventResult result = new SPRemoteEventResult();

    if (properties.EventType != SPRemoteEventType.AppInstalled)
      return result;

    HttpRequestMessageProperty requestProperty =
      (HttpRequestMessageProperty)OperationContext.Current.IncomingMessageProperties[HttpRequestMessageProperty.Name];
    SharePointContextToken contextToken =
      TokenHelper.ReadAndValidateContextToken(
        properties.ContextToken,
        requestProperty.Headers[HttpRequestHeader.Host]);
    string accessToken = TokenHelper.GetAccessToken(
      contextToken,
      properties.AppEventProperties.HostWebFullUrl.Authority).AccessToken;
    using (ClientContext clientContext =
      TokenHelper.GetClientContextWithAccessToken(
        properties.AppEventProperties.HostWebFullUrl.ToString(),
        accessToken))
    {
      Web site = clientContext.Web;
      NavigationNodeCollection collQuickLaunchNode =
        site.Navigation.QuickLaunch;
      NavigationNodeCreationInformation ciNavicationNode =
        new NavigationNodeCreationInformation();
      ciNavicationNode.Title = "List1";
      Uri navUri = new Uri(
        properties.AppEventProperties.AppWebFullUrl,
        "SharePointApp1" + "/" + "Lists/List1");
      ciNavicationNode.Url = navUri.ToString();
      ciNavicationNode.AsLastNode = true;
      collQuickLaunchNode.Add(ciNavicationNode);
      clientContext.Load(collQuickLaunchNode);
      clientContext.ExecuteQuery();
    }

    return result;
  }
  . . .

なお、この場合、Add-ins に「サイト コレクション」への Manage のアクセス許可 (Permission) を設定してください。(アクセス許可の考え方については、「SharePoint Add-ins の動作と概要」を参照してください。)

補足 : Remote Event Receiver のデバッグ実行をおこなう際は注意してください。普通にデバッグ実行すると、Office 365 のサーバーから localhost のサービスへの接続に失敗します。
Remote App を Azure にホストするなどして動作させます。

この Remote Event Receiver の動作とプログラミングについては、次回、解説します。

また、[サイト コンテンツ] から、この Add-ins をクリックした場合に、リスト (上記の List1) が表示されるように、AppManifest.xml を下記の通り編集しておくと良いでしょう。

<?xml version="1.0" encoding="utf-8" ?>
<App . . .>
  <Properties>
    <Title>SharePointApp1_Remote</Title>
    <StartPage>~appWebUrl/Lists/List1</StartPage>
  </Properties>
  . . .
</App>

 

Client Side Rendering (CSR) と List View、List Form のカスタマイズ

カスタム リストを使った現実のアプリケーションでは、リスト ビューやリスト フォーム (DisplayForm, EditForm, NewForm) のカスタマイズが頻繁に必要となります。しかし、これまでは、「SharePoint 2010 : リスト定義における XSL を使用したビューのカスタマイズ」を読んでいただくとおわかりの通り、SharePoint 独自の XML (CAML) や XSL を使用して編集をおこなう必要があり、結構面倒でした。(例えば、JavaScript を埋め込む場合には、XSLT の中に埋め込み、文字列をエスケープするなど煩雑なコードの記述が必要でした。)

SharePoint 2013 では、Client Side Rendering (CSR) と呼ばれる方式で、SharePoint が作成するリスト ビューやリスト フォームの UI を JavaScript を使用してカスタマイズ (クライアント側で Overrides) できるようになっており、こうした Pain が解消されています。
ここでは、この SharePoint 2013 からの Client Side Rendering (CSR) について補足しておきます。

まず、特定のビューに CSR を適用するには、schema.xml を開いて、下記 (太字) の通り、使用する JavaScript ファイル (.js ファイル) を挿入します。(下記で、~site は、Add-ins のサイトを表すトークンです。List1Sample.js は、このあとで作成します。clienttemplates.js は CSR に必要なので、挿入しておいてください。)

. . .
<View BaseViewID="1" Type="HTML" WebPartZoneID="Main" . . .">
  <Toolbar Type="Standard" />
  <XslLink Default="TRUE">main.xsl</XslLink>
  <JSLink>clienttemplates.js|~site/Scripts/jquery-1.7.1.min.js|~site/Scripts/List1Sample.js</JSLink>
  <RowLimit Paged="TRUE">30</RowLimit>
  . . .

また、特定のフォーム (DisplayForm、EditForm、NewForm) に CSR を適用するには、schema.xml に下記の通り記述して .js ファイルを挿入します。(下記は、Display Form に適用した場合の例です。)

. . .
<Forms>
  <Form Type="DisplayForm"
    Url="DispForm.aspx"
    SetupPath="pages\form.aspx"
    WebPartZoneID="Main"
    JSLink="~site/Scripts/jquery-1.7.1.min.js|~site/Scripts/List1Sample.js" />
  <Form Type="EditForm"
    Url="EditForm.aspx"
    SetupPath="pages\form.aspx"
    WebPartZoneID="Main" />
  <Form Type="NewForm"
    Url="NewForm.aspx"
    SetupPath="pages\form.aspx"
    WebPartZoneID="Main" />
</Forms>
. . .

つぎに、この List1Sample.js を作成します。SharePoint Add-ins の SharePoint-hosted (SharePoint ホスト型) のプロジェクトでは、下図の通り、既に、Scirpts フォルダーとその下の js ファイルがモジュールとして追加されています。(SharePoint のモジュールとは、SharePoint に配置されるイメージ ファイル、スタイル シートなどのファイルやフォルダーのことです。) 今回は、ここに、List1Sample.js を追加します。
これをおこなうには、下図の「Scripts」フォルダーを右クリックして、[追加] - [新しい項目] メニューを選択して、「List1Sample.js」という名前で [JavaScript ファイル] を追加します。(下図の Elements.xml には、この追加されたファイルの設定も自動的に記述されます。)

例えば、リスト ビューで、Address 列に、Bing Maps へのハイパーリンク (href) を設定するには、List1Sample.js を以下の通りプログラミングします。

function List1Overrides() {
  var overrideCtx = {};
  overrideCtx.Templates = {};
  overrideCtx.BaseViewID = '1';
  overrideCtx.ListTemplateType = 10000;
  // Note1 : Override field rendering
  overrideCtx.Templates.Fields = {
    'Address': { 'View': FieldsOverrides_Address },
  };
  // Note2 : Register overrides
  SPClientTemplates.TemplateManager.RegisterTemplateOverrides(overrideCtx);
};

function FieldsOverrides_Address(
  ctx, field, listItem, listSchema) {
  var value = listItem[field.Name];
  return '<a href="http://www.bing.com/maps/?v=2&where1=' +
    encodeURI(value) +
    '">' +
    value +
    '</a>';
}

// Note3 : Execute override using CSR
RegisterModuleInit("List1Sample.js", List1Overrides); // for MDS
List1Overrides(); // for non-MDS

// Note4 : Needed for ScriptOnDemand
if (typeof (Sys) != "undefined" && Boolean(Sys) && Boolean(Sys.Application)) {
  Sys.Application.notifyScriptLoaded();
}
if (typeof (NotifyScriptLoadedAndExecuteWaitingJobs) == "function") {
  NotifyScriptLoadedAndExecuteWaitingJobs("List1Sample.js");
}

上記の Note1 では、「Address」という Field のリスト ビューの表示 (View) の際の動作を FieldsOverrides_Address 関数で Override しています。
また、Note3、Note4 は、削除しないでください。MDS (Minimal Download Strategey) モードで表示している場合には Note3 の前者が、それ以外の場合には Note3 の後者が呼ばれます。(RegisterModuleInit は、/_layouts/15/init.js にあります。SharePoint 2013 では、通常、MDS モードが有効ですが、SPWeb の EnableMinimalDownload を設定することで Disable にできます。)

実行結果は、下図の通りになります。Address 列のリンクをクリックすると、Bing Maps に飛び、その住所が表示されます。

また、OnPreRender、OnPostRender によって、レンダリング (描画) の前後で呼ばれる処理を作成 (Override) できます。
例えば、下記は、リスト ビューで、偶数行のみ背景色を変更するサンプル コードです。レンダリング (描画) の後の処理として PostRender_RowColoring を登録していますが、function の配列を渡して複数��処理を登録することもできます。
また、GenerateIIDForListItem は、ListItem から IID を取得する関数で、前述の clienttemplates.js で定義されています。

function List1Overrides() {
  var overrideCtx = {};
  overrideCtx.Templates = {};
  overrideCtx.BaseViewID = '1';
  overrideCtx.ListTemplateType = 10000;
  overrideCtx.Templates.Fields = {
    'Address': { 'View': FieldsOverrides_Address },
  };
  overrideCtx.OnPostRender = PostRender_RowColoring;
  SPClientTemplates.TemplateManager.RegisterTemplateOverrides(overrideCtx);
};
. . .

function PostRender_RowColoring(ctx) {
  //// without jquery ...
  //for (var i = 0; i < ctx.ListData.Row.length; ++i) {
  //  if (i % 2) {
  //    var iid = GenerateIIDForListItem(ctx, ctx.ListData.Row[i]);
  //    var row = document.getElementById(iid);
  //    row.style.backgroundColor = '#aaaaff;';
  //  }
  //}

  // with jquery ...
  $.each(ctx.ListData.Row, function (i, val) {
    if (i % 2) {
      var iid = GenerateIIDForListItem(ctx, val);
      $(selectorEscape(iid)).css('background-color', '#aaaaff');
    }
  });
}

// Escape jquery selector
// (SharePoint uses comma and dollar mark for iid and id.)
function selectorEscape(arg) {
  return arg.replace(
    new RegExp('(,|\\.|\'|\\$|&|\\/|\\\\|!|\\||\\+|\\*|~|=|>|;|:|#|"|\\^|\\(|\\)|\\[|\\])', 'g'),
    '\\$1');
  //var result = iid;
  //result = result.replace(/,/g, '\\,');
  //result = result.replace(/(\$)/g, '\\\$');
  //return result;
}

. . .

実行結果は、下記の通りになります。

また、フォームのカスタマイズをおこなうには、以下の通り記述します。
例えば、DisplayForm で、上記同様、Address の内容をハイパーリンクにして Bing Maps に飛ばすには、以下の通り記述します。

. . .

function List1FormsOverrides() {
    var overrideCtx = {};
    overrideCtx.Templates = {};
    overrideCtx.ListTemplateType = 10000;
    overrideCtx.Templates.Fields = {
        'Address': {
            'DisplayForm': DisplayFormOverrides_Address
        },
    };
    SPClientTemplates.TemplateManager.RegisterTemplateOverrides(overrideCtx);
};

function DisplayFormOverrides_Address(ctx) {
    var formCtx =
        SPClientTemplates.Utility.GetFormContextForCurrentField(ctx);
    if (formCtx.controlMode != SPClientTemplates.ClientControlMode.DisplayForm)
        return '';
    else
        return '<a href="http://www.bing.com/maps/?v=2&where1=' +
            encodeURI(formCtx.fieldValue) +
            '">' +
            formCtx.fieldValue +
            '</a>';
}
. . .

RegisterModuleInit("List1Sample.js", List1FormsOverrides); // for MDS
List1FormsOverrides(); // for non-MDS
. . .

実行結果は、下記の通りになります。(リンクをクリックすると Bing Maps に飛び、住所が表示されます。)

なお、NewForm、EditForm を Override する場合は、下記の通り、必要な Callback を登録しておいてください。特に、GetValue Callback が登録されていない場合、リストに値が設定されないので注意してください。(2013/02/25 追記)

. . .

function NewFormOverrides_Address(ctx) {
  var formCtx =
    SPClientTemplates.Utility.GetFormContextForCurrentField(ctx);
  var inputId = formCtx.fieldName + '_' + formCtx.fieldSchema.Id + '_$TextField';
  var inputObj = null;

  if (formCtx.controlMode == SPClientTemplates.ClientControlMode.NewForm) {
    // ClientValidator Callback
    var validators = new SPClientForms.ClientValidation.ValidatorSet();
    formCtx.registerClientValidator(formCtx.fieldName, validators);

    // Init Callback
    formCtx.registerInitCallback(formCtx.fieldName, function () {
      inputObj = $('#' + selectorEscape(inputId));
    });

    // Focus Callback
    formCtx.registerFocusCallback(formCtx.fieldName, function () {
      if (inputObj != null)
        inputObj.focus();
    });

    // ValidationError Callback
    formCtx.registerValidationErrorCallback(formCtx.fieldName, function (errorResult) {
    });

    // GetValue Callback
    formCtx.registerGetValueCallback(formCtx.fieldName, function () {
      if (inputObj == null)
        return '';
      else
        return inputObj.val();
    });

    formCtx.updateControlValue(formCtx.fieldName, '');

    // original : <span dir="none"><input title="Address" class="ms-long ms-spellcheck-true" id="Address_afb7686f-7d8a-4f53-9643-ad4c6e40bc6f_$TextField" type="text" maxlength="255" value="" /><br/></span>
    return '<span dir="none"><input title="' + formCtx.fieldSchema.Address + '" ' +
      'class="ms-long ms-spellcheck-true" ' +
      'id=' + STSHtmlEncode(inputId) + ' ' +
      'type="text" ' +
      'maxlength="' + formCtx.fieldSchema.MaxLength + '" ' +
      'value="" ' +
      '/><br/></span>';
  }
  return '';
}
. . .

CSR は、 従来のリスト UI のカスタマイズの手法 (「SharePoint 2010 : リスト定義における XSL を使用したビューのカスタマイズ」を参照) と組み合わせても良いでしょう。実際、SharePoint が生成する HTML は膨大なため、よりシンプルな HTML (必要最小限の HTML) を生成し、さらに高度な (動的な) UI カスタマイズは CSR を使った JavaScript で実装する方法も考えられます。

補足 : 例えば、カスタム リスト ビュー (スタイル) と CSR を併用する場合、「SharePoint 2010 : リスト定義における XSL を使用したビューのカスタマイズ」で紹介している <Xsl> を使った手法を使用してください。(「SharePoint 2010 : リスト定義における XSL を使用したビューのカスタマイズ」の CAML を使った手法は、<JSLINK> とは併用できません。)
また、CSR を呼び出し元は、/_layouts/15/vwstyles.xsl の RenderListView です。Custom XSL を使用する場合は、vwstyles.xsl に記述されている CSR の処理 (RenderListView の前後) を XSL に含めてください。

補足 : schema.xml を変更する際には、こちら に記載した通り、キャッシュ (cache) されることがあるので注意してください。(schema.xml の変更が反映されないことがあります。)

 

※ 変更履歴 :

2015/05/05  App for SharePoint (SharePoint 用アプリ) を SharePoint Add-ins (SharePoint アドイン) に名称変更