松崎 剛 Blog

This Blog's theme : エンタープライズ開発 (Server side)、Office サーバ開発

SharePoint 2013 Apps : List の開発と Client Side Rendering (CSR)

SharePoint 2013 Apps : List の開発と Client Side Rendering (CSR)

  • Comments 0

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

SharePoint 2013 Apps 開発

こんにちは。

このシリーズをすっかり放置していたところ、「.NET 技術の断捨離」の集まりで参加者の方からツッコミをいただきましたので、再開したいと思います。。。(すみません、今年の初ポストです。)

予定では Remote Event Receiver の解説が先でしたが、その前に、カスタム リスト定義 (List Definition) とリスト インスタンス (List Instance) を理解しておいたほうが良いので、順番を変更しました。

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

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

 

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

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

Visual Studio 2012 を起動し、[Apps for SharePoint 2013] のプロジェクトを新規作成します。
リスト定義 (List Definition) のような SharePoint の artifacts の場合には、SharePoint-hosted (SharePoint ホスト型) でホストされます。(SharePoint-hosted については、「Apps for SharePoint 2013 の動作と概要」参照。) このため、今回は、ウィザードで、[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 の App (SharePoint-hosted の Apps for SharePoint 2013) を追加した場合、以下のサブ サイトが作成され、ここにリスト定義やリスト インスタンスが追加されます。(「Apps for SharePoint 2013 の動作と概要」の解説を参照。)

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

アプリがインストールされた際のハンドラーが、Remote App (App とは別プロジェクト) として作成されます。(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;
  }
  . . .

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

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

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

また、[サイト コンテンツ] から、この App をクリックした場合に、リスト (上記の 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 は、App のサイトを表すトークンです。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 を作成します。「Apps for SharePoint 2013」の 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 の変更が反映されないことがあります。)

 

Leave a Comment
  • Please add 1 and 6 and type the answer here:
  • Post