Professional Plus 2013

JavaScript API for Office の調査: データ アクセスとイベント

Stephen Oliver
Eric Schmidt

 

JavaScript API for Office の詳しいチュートリアルを示す連載の第 2 回です。第 1 回 (msdn.microsoft.com/magazine/jj891051、英語) は、オブジェクト モデルの概要を紹介しました。今回は、前回説明できなかったファイルのコンテンツへのアクセス方法に関する詳しいチュートリアルと、イベント モデルのレビューを取り上げます。

この連載を通して、JavaScript API for Office のリファレンス ドキュメントを頻繁に参照します。正式ドキュメント、コード サンプル、およびコミュニティ リソースについては、MSDN の Office/SharePoint アプリ デベロッパー センター (dev.office.com) を参照してください。

Office 用アプリから Office ファイルのコンテンツへのアクセス

JavaScript API for Office には、 Office ファイル内のデータにアクセスする基本的な方法が複数あります。現在選択しているデータを取得、設定する方法もあれば、ファイル全体を取得する方法もあります。このレベルのデータ アクセスは、文字通り、実に簡単です。ただし、どちらの手法にも優れた柔軟性があり、さまざまなカスタマイズが可能なため、アプリには多くの可能性がもたらされます。

JavaScript API for Office では、選択データやデータ全体へのアクセスだけでなく、データへのバインドや、ドキュメント内のカスタム XML パーツの操作も可能です。ここからは、Office コンテンツを操作する手法をもう少し詳しく見ていくことにしましょう。

選択したデータの取得と設定

前回説明したように、アプリは Document オブジェクトを使ってファイル内のデータにアクセスします。作業ウィンドウ アプリやコンテンツ アプリの場合は、Document.getSelectedDataAsync メソッドと Document.setSelectedDataAsync メソッドを使用して、Office ファイル内の選択したコンテンツを取得または設定できます。

この 2 つのメソッドでは、複数種類のデータ形式を操作できます。どの形式のデータを操作するかはメソッドの呼び出し時に指定します。getSelectedDataAsync メソッドと setSelectedDataAsync メソッドには、Office.CoercionType 列挙型の定数を受け取る coercionType というパラメーターがあります。この coercionType パラメーターで、取得または設定するコンテンツのデータ形式を指定します。coercionType パラメーターの値に応じて、プレーン テキスト、テーブル、マトリックス、HTML、または Office Open XML (OOXML) "そのもの" としてデータを選択できます (現時点では、HTML や OOXML としてのテキストの取得と設定は、Word 2013 でのみサポートされます)。

coercionType は、getSelectedDataAsync や setSelectedDataAsync を呼び出すたびに毎回指定する必要はありません。coercionType は、可能な限りコンテキストから推論されます。たとえば、setSelectedDataAsync の呼び出しに文字列を渡すと、既定の coercionType は "text" になります。同じデータを配列の配列として渡すと、既定の coercionType は "matrix" になります。

このようにシンプルなメソッドがどれほど効果的であるか示すため、まず、setSelectedDataAsync メソッドを使っていくつか例を示します。まず、単純なテキストを Word 文書に挿入するコードから始めましょう。

// Define some data to set in the document.
var booksToRead = "Anabasis by Xenophon; \n" +
  "Socrates' Apology by Plato; \n" +
  "The Illiad by Homer.";
// Set some data to the document as simple text.
Office.context.document.setSelectedDataAsync(
  booksToRead,
  { coercionType: Office.CoercionType.Text },
  function (result) {
    // Access the results, if necessary.
});

図 1 に、実行結果を示します。

Results of Inserting Data as Simple Text
図 1 単純なテキストとしてデータを挿入した結果

次に、この例に変更を加えて、"matrix" としてテキストが挿入されるようにします。matrix とは配列の配列で、簡単なセル範囲 (Excel) や表 (Word) として挿入されます。

以下のコードでは、Word への挿入時に、書式が設定されない 2 列の表が見出しを付けずに挿入されます。最初のレベルの配列の各項目が表の列を表し、第 2 レベルの配列の各項目がその列内のセルのデータを保持します。

// Define a matrix of data to set in the document.
var booksToRead = [["Xenophon", "Anabasis"],
  ["Plato", "Socrates' Apology"],
  ["Homer", "The Illiad"]];
// Set some data to the document as an unformatted table.
Office.context.document.setSelectedDataAsync(
  booksToRead,
  { coercionType: Office.CoercionType.Matrix },
  function (result) {
    // Access the results, if necessary.
});

図 2 に、実行結果を示します。

Results of Inserting Data as a Matrix
図 2 マトリックスとしてデータを挿入した結果

"matrix" 以外にも、TableData オブジェクトを使用して、データを表として取得および設定できます。これにより、ちょっとした書式を加えることができます。ここでは見出し行を追加します。headers プロパティと rows プロパティを使用して、見出し行と TableData オブジェクトのコンテンツにアクセスします。

また、TableData オブジェクトを使えば、startRow パラメーターと startColumn パラメーターにより、挿入するデータのサブセットを指定することもできます。たとえば、既存の 5 列の表の 1 列にデータを設定できます。startRow パラメーターと startColumn パラメーターについては、次回さらに詳しく説明します。

注: 文書内で選択しているのが表の場合、表の形状と挿入するデータが一致していなければなりません (startRow パラメーターと startColumn パラメーターを指定する場合は除きます)。つまり、挿入するデータが 2 x 2 の表で、文書内では 3 x 2 の表を選択していると、メソッドは失敗します。このことは、データをマトリックスとして挿入する場合にも当てはまります。

coercionType "matrix" と同様、headers プロパティと rows プロパティは配列の配列を返します。この配列の配列の最初の配列の各要素にはデータ行、第 2 レベルの配列の各項目にはデータのセルが含まれます (図 3 参照)。

図 3 文書に表としてデータを挿入

// Define some tabular data to set in the document,
// including a header row.
var booksToRead = new Office.TableData();
booksToRead.headers = [["Author", "Title"]];
booksToRead.rows = [["Xenophon", "Anabasis"],
  ["Plato", "Socrates' Apology"],
  ["Homer", "The Illiad"]];
// Set some data to the document as a table with a header.
Office.context.document.setSelectedDataAsync(
  booksToRead,
  { coercionType: Office.CoercionType.Table },
  function (result) {
    // Access the results, if necessary.
});

図 4 に、図 3 のコードの結果を示します。

Results of Inserting Data as a Table
図 4 表としてデータを挿入した結果

次の例では同じデータを挿入しますが、今度は書式を HTML に設定し、Office.CoercionType.HTML を使用して挿入します。これで、挿入したデータに CSS スタイルなどの新たな書式を追加できるようになります (図 5 参照)。

図 5 文書に HTML としてデータを挿入

// Define some HTML data to set in the document,
// including header row, text formatting and CSS styles.
var booksToRead =
  "<table style='font-family:Segoe UI'>" +
    "<thead style='background-color:#283E75;color:white'>" +
      "<tr><th>Authors</th><th>Books</th></tr>" +
    "</thead>" +
    "<tbody>" +
      "<tr><td>Xenophon</td><td><u>Anabasis</u></td></tr>" +
      "<tr><td>Plato</td><td><u>Socrates' Apology</u></td></tr>" +
      "<tr><td>Homer</td><td><u>The Iliad</u></td></tr>" +
    "</tbody>" +
  "</table>";
// Set some data to the document as a table with styles applied.
Office.context.document.setSelectedDataAsync(
  booksToRead,
  { coercionType: Office.CoercionType.Html },
    function (result) {
    // Access the results, if necessary.
});

図 6 に、図 5 のコードの結果を示します。

Results of Inserting Data as HTML
図 6 HTML としてデータを挿入した結果

最後に、OOXML としてテキストを文書に挿入します。これにより、データを大幅にカスタマイズして、さらに多くの高度な Word のコンテンツ タイプ (SmartArt や行内の図など) を使用できるようになります。

図 7 のコードに示すように、ここまで使用してきたデータ表を OOXML として表し、文字列リテラル内に格納します (注: 簡潔にするため、表の一部のみ示しています)。

図 7 Word の表を表す OOXML スニペット (JavaScript の文字列リテラルとして格納)

var newTable = "<w:tbl>" +
  "<w:tblPr>" +
    "<w:tblStyle w:val=\"TableGrid\"/>" +
    "<w:tblW w:w=\"0\" w:type=\"auto\"/>" +
    "<w:tblBorders>" +
      "<w:top w:val=\"single\" w:sz=\"4\" w:space=\"0\"" +
        "w:color=\"283E75\"/>" +
      "<w:left w:val=\"single\" w:sz=\"4\" w:space=\"0\"" +
        "w:color=\"283E75\"/>" +
      "<w:bottom w:val=\"single\" w:sz=\"4\" w:space=\"0\"" +
        "w:color=\"283E75\"/>" +
      "<w:right w:val=\"single\" w:sz=\"4\" w:space=\"0\"" +
        "w:color=\"283E75\"/>" +
      "<w:insideH w:val=\"single\" w:sz=\"4\" w:space=\"0\"" +
        "w:color=\"283E75\"/>" +
      "<w:insideV w:val=\"single\" w:sz=\"4\" w:space=\"0\"" +
        "w:color=\"283E75\"/>" +
      "</w:tblBorders>" +
    "<w:tblLook w:val=\"04A0\" w:firstRow=\"1\" w:lastRow=\"0\"" +
      "w:firstColumn=\"1\" w:lastColumn=\"0\""  +
      "w:noHBand=\"0\" w:noVBand=\"1\"/>" +
  "</w:tblPr>" +
  "<w:tblGrid>" +
    "<w:gridCol w:w=\"4675\"/>" +
    "<w:gridCol w:w=\"4675\"/>" +
  "</w:tblGrid>" +
  "<w:tr w:rsidR=\"00431544\" w:rsidTr=\"00620187\">" +
    "<w:tc>" +
      "<w:tcPr>" +
        "<w:tcW w:w=\"4675\" w:type=\"dxa\"/>" +
        "<w:shd w:val=\"clear\" w:color=\"auto\" w:fill=\"283E75\"/>" +
      "</w:tcPr>" +
      "<w:p w:rsidR=\"00431544\" w:rsidRPr=\"00236B94\""  +
        "w:rsidRDefault=\"00431544\" w:rsidP=\"00620187\">" +
        "<w:pPr>" +
          "<w:rPr>" +
            "<w:b/>" +
            "<w:color w:val=\"FEFEFE\"/>" +
          "</w:rPr>" +
        "</w:pPr>" +
        "<w:r w:rsidRPr=\"00236B94\">" +
          "<w:rPr>" +
            "<w:b/>" +
            "<w:color w:val=\"FEFEFE\"/>" +
          "</w:rPr>" +
          "<w:t>Authors</w:t>" +
        "</w:r>" +
      "</w:p>" +
    "</w:tc>" +
    "<w:tc>" +
      "<w:tcPr>" +
        "<w:tcW w:w=\"4675\" w:type=\"dxa\"/>" +
        "<w:shd w:val=\"clear\" w:color=\"auto\" w:fill=\"283E75\"/>" +
      "</w:tcPr>" +
      "<w:p w:rsidR=\"00431544\" w:rsidRPr=\"00236B94\"" +
        "w:rsidRDefault=\"00431544\" w:rsidP=\"00620187\">" +
        "<w:pPr>" +
          "<w:rPr>" +
            "<w:b/>" +
            "<w:color w:val=\"FEFEFE\"/>" +
          "</w:rPr>" +
        "</w:pPr>" +
        "<w:r w:rsidRPr=\"00236B94\">" +
          "<w:rPr>" +
            "<w:b/>" +
            "<w:color w:val=\"FEFEFE\"/>" +
          "</w:rPr>" +
          "<w:t>Books</w:t>" +
        "</w:r>" +
      "</w:p>" +
    "</w:tc>" +
  "</w:tr>" +
  "<w:tr w:rsidR=\"00431544\" w:rsidTr=\"00620187\">" +
    "<w:tc>" +
      "<w:tcPr>" +
        "<w:tcW w:w=\"4675\" w:type=\"dxa\"/>" +
      "</w:tcPr>" +
      "<w:p w:rsidR=\"00431544\" w:rsidRDefault=\"00431544\"" +
        "w:rsidP=\"00620187\">" +
        "<w:r>" +
          "<w:t>Xenophon</w:t>" +
        "</w:r>" +
      "</w:p>" +
    "</w:tc>" +
    "<w:tc>" +
      "<w:tcPr>" +
        "<w:tcW w:w=\"4675\" w:type=\"dxa\"/>" +
      "</w:tcPr>" +
      "<w:p w:rsidR=\"00431544\" w:rsidRDefault=\"00431544\"" +
        "w:rsidP=\"00620187\">" +
        "<w:r>" +
          "<w:t>Anabasis</w:t>" +
        "</w:r>" +
      "</w:p>" +
    "</w:tc>" +
  "</w:tr>" +
  // The rest of the code has been omitted for the sake of brevity.
"</w:tbl>";

この手法では、XML に関する高い知識と構造 (具体的には OOXML 標準 (ECMA-376) に記述されている構造) も必要です。文書に OOXML を設定する際、データは、必要な情報 (ファイル形式パッケージ内のリレーションシップや関連ドキュメント パーツなど) がすべて含まれた文字列として格納する必要があります。そのため、OOXML を使用してさらに高度なコンテンツ タイプを Word に挿入する際は、OOXML と Open Packaging Conventions の使用に関するベスト プラクティスに従って OOXML データを操作するようにしてください。

図 8 では、まず、データを OOXML として取得して、(受け取ったデータと新しいデータを文字列として操作することで) 文書内の OOXML とデータを連結し、その OOXML を文書に再挿入することでこの問題を回避しています (もっとも、このコードがうまくいく理由の 1 つは、ファイル内でリレーションシップやドキュメント パーツの追加または変更を必要とするコンテンツが追加されていないことにあります)。

図 8 OOXML を使用して、文書に表としてデータを挿入

// Get the OOXML for the data at the point of insertion
// and add a table at the beginning of the selection.
Office.context.document.getSelectedDataAsync(
  Office.CoercionType.Ooxml,
  {
    valueFormat: Office.ValueFormat.Formatted,
    filterType: Office.FilterType.All
  },
  function (result) {
    if (result.status == "succeeded") {
      // Get the OOXML returned from the getSelectedDataAsync call.
      var selectedData = result.value.toString();
      // Define the new table in OOXML.
      var newTable = "<!--Details omitted for brevity.-->";
      // Find the '<w:body>' tag in the returned data—the tag
      // that represents the body content of the selection, contained
      // within the main document package part (/word/document.xml)—
      // and then insert the new table into the OOXML at that point.
      var newString = selectedData.replace(
        "<w:body>",
        "<w:body>" + newTable,
        "gi");
        // Insert the data back into the document with the table added.
        Office.context.document.setSelectedDataAsync(
          newString,
          { coercionType: Office.CoercionType.Ooxml },
          function () {
        });
    }
});

図 9 に、図 8 のコードの結果を示します。

Results of Inserting Data as OOXML
図 9 OOXML としてデータを挿入した結果

注: アプリから OOXML を操作する方法を学ぶ優れた方法の 1 つは、UI ([挿入] タブをクリックし、[図] の [SmartArt] をクリックして SmartArt を挿入するなど) を使用して操作するコンテンツを追加し、getSelectedDataAsync を使ってそのコンテンツの OOXML を取得して、結果を読み込む方法です。詳細については、bit.ly/SeU3MS で、ブログ記事「Inserting images with apps for Office」(Office 用アプリを使用して画像を挿入する、英語) を参照してください。

ファイルの全コンテンツの取得

選択ポイントにあるデータを取得、設定できましたが、ファイルの全コンテンツを取得するシナリオもあります。たとえば、アプリで文書内の全コンテンツをテキストとして取得し、その構文を解析して、バブル チャートに表示するような場面です。ほかにも、離れた場所から印刷やファックス送信を行うために、ファイル内の全コンテンツをアプリからリモート Web サービスに送信するような場合もあります。

JavaScript API for Office では、まさにこのようなシナリオに使用できる機能が用意されています。アプリで JavaScript API を使用すると、挿入する場所にファイルのコピーを作成し、そのコピーをサイズ (最大 4 MB) を指定したデータのチャンク ("スライス") に分割して、そのスライス内にデータを読み取ることができます。

ファイルの全コンテンツを取得するプロセスでは、事実上、次の 3 つの手順が必要です。

  1. Word や PowerPoint に組み込むアプリの場合は、アプリから Document.getFileAsync メソッドを呼び出します。このメソッドは、ファイルのコピーに対応する File オブジェクトを返します。
  2. ファイルへの参照を設定したら、File.getSliceAsync メソッドを呼び出して、取得するスライスのインデックスを渡して、ファイル内の特定のスライスにアクセスできます。for ループを使用してこれを実行する場合、呼び出し側のコードでは最後のスライスの処理方法に注意が必要です。
  3. 最後に、File.closeAsync メソッドを呼び出し、使い終わった File オブジェクトを閉じます。どの時点でも、メモリ内に保持できるのは 2 ファイルのみです。Document.getFileAsync を使用して 3 つ目のファイルを開こうとすると、「内部エラーが発生しました」というエラーが発生します。

図 10 では、Word 文書を 1 KB のチャンク単位で取得し、ファイルの各チャンクを反復処理して、すべてのチャンクを処理したらファイルを閉じています。

図 10 ファイルの全コンテンツをテキストとして取得し、スライスを反復処理

// Get all of the content from a Word document in 1KB chunks of text.
function getFileData() {
  Office.context.document.getFileAsync(
  Office.FileType.Text,
  {
    sliceSize: 1000
  },
  function (asyncResult) {
    if (asyncResult.status === 'succeeded') {
      var myFile = asyncResult.value,
        state = {
          file: myFile,
          counter: 0,
          sliceCount: myFile.sliceCount
        };
      getSliceData(state);
    }
  });
}
// Get a slice from the file, as specified by
// the counter contained in the state parameter.
function getSliceData(state) {
  state.file.getSliceAsync(
    state.counter,
    function (result) {
    var slice = result.value,
      data = slice.data;
    state.counter++;
    // Do something with the data.
    // Check to see if the final slice in the file has
    // been reached—if not, get the next slice;
    // if so, close the file.
    if (state.counter < state.sliceCount) {
      getSliceData(state);
    }
    else {
      closeFile(state);
    }
  });
}
// Close the file when done with it.
function closeFile(state) {
  state.file.closeAsync(
    function (results) {
      // Inform the user that the process is complete.
  });
}

Office 用アプリからファイルの全コンテンツを取得する方法の詳細については、ドキュメント ページ「方法: PowerPoint 用アプリでドキュメント全体を取得する」(https://msdn.microsoft.com/ja-jp/library/office/apps/jj715279.aspx、英語) を参照してください。

Project のタスク データ、ビュー データ、およびリソース データの取得

JavaScript API for Office には、Project に組み込まれる作業ウィンドウ アプリ向けに、アクティブ プロジェクトや、選択したタスク、リソース、またはビューのデータを読み取るための新たなメソッドがあります。office.js を拡張した project-15.js スクリプトでは、タスク、リソース、およびビューの選択変更イベントも追加しています。たとえば、ユーザーがチーム プランナー ビュー内のタスクを選択すると、アプリでは、そのタスクでスケジュールが設定されている残りの作業、その作業に対応できる担当者、およびスケジュールに影響する可能性のある他の SharePoint タスク リストや Project Server 内の関連プロジェクトを統合して、1 か所に表示できます。

Project に組み込まれる作業ウィンドウ アプリには、Project のコンテンツに対して読み取りのアクセス許可しかありません。しかし、作業ウィンドウ アプリの本質は Web ページであるため、JavaScript や Representational State Transfer (REST) などのプロトコルを使用して、外部アプリケーションに対する読み取りおよび書き込みを行う可能性があります。たとえば、Office/SharePoint 用アプリのドキュメントには、jQuery と Project の OData レポート サービスを使用して、アクティブ プロジェクトの合計コストと作業データを、Project Web アプリ内の全プロジェクトの平均と比較する Project Professional 用のサンプル アプリが含まれています (図 11 参照)。

A Task Pane App that Uses jQuery with an OData Reporting Service
図 11 jQuery と OData のレポート サービスを使用する作業ウィンドウ アプリ

詳細については、ドキュメント ページ「方法: 社内の Project Server OData サービスで REST を使用する Project アプリを作成する」(https://msdn.microsoft.com/ja-jp/library/office/apps/jj670097.aspx) を参照してください。

ProjectDocument では Document オブジェクトを拡張するため、Office.context.document オブジェクトによってアクティブ プロジェクトへの参照がキャプチャされます (他のホスト アプリケーションに組み込まれるアプリと同様)。Project で使用できる非同期メソッドのシグネチャは、JavaScript API for Office の他のメソッドと同様です。たとえば、getProjectFieldAsync メソッドには次の 3 つのパラメーターがあります。

  • fieldId: callback パラメーターのオブジェクトで返すフィールドを指定します。Office.ProjectProjectFields 列挙型には、Project の GUID、開始日、終了日、および (ある場合は) Project Server の URL や SharePoint タスク リスト URL など、12 のフィールドが含まれています。
  • asyncContext: (省略可能) asyncResult オブジェクトで返される任意のユーザー定義型です。
  • callback: 呼び出しから戻る際に実行する関数への参照と、成功または失敗を処理するためのオプションを含みます。

図 12 に示すように、Project のアプリ固有のメソッドは、他のアプリケーションでホストされるアプリと同じように使用されます。スクリプトでは、ローカルに定義した関数から、アプリでエラー メッセージを表示するルーチンを呼び出します。asyncContext パラメーターは使用しません。

図 12 Project に組み込まれる作業ウィンドウからフィールドの GUID を取得

var _projectUid = "";
// Get the GUID of the active project.
function getProjectGuid() {
  Office.context.document.getProjectFieldAsync(
    Office.ProjectProjectFields.GUID,
    function (asyncResult) {
      if (asyncResult.status == Office.AsyncResultStatus.Succeeded) {
        _projectUid = asyncResult.value.fieldValue;
        }
      else {
        // Display error message to user.
      }
    }
  );
}

getProjectFieldAsync メソッドでは一般的な Project の 12 のフィールドしか取得できませんが、getTaskFieldAsync では、ProjectTaskFields 列挙型を使用して 1 つのタスクの 282 種類のフィールドのうち任意の 1 つを取得できます。また、getResourceFieldAsync メソッドでは、ProjectResourceFields 列挙型を使用して 1 つのリソースの 200 のフィールドのうち任意の 1 つを取得できます。ProjectDocument オブジェクトのさらに一般的なメソッドには、サポート対象のビューで選択されているテキスト データを返す getSelectedDataAsync と、選択したタスクに関する一般的なデータのうち複数の項目を返す getTaskAsync があります。作業ウィンドウ アプリでは、Project の 16 種類のビューを操作できます。

加えて、Project の作業ウィンドウ アプリでは、ユーザーがビューの変更、タスクの選択、またはリソースの選択を行うときにイベント ハンドラーを追加または削除できます。

Office 用アプリのイベント

JavaScript API for Office では、イベントを使用して応答性の高いアプリを作成できます。API のイベント モデルでは、Office 用アプリ開発の基盤となる 4 つの重要なイベント シナリオがサポートされます (後ほど説明します)。これら 4 つのシナリオを理解すると、API のイベント モデルについてしっかりと把握することができます。

Office 用アプリのイベント モデルには全体を通して一貫性があるため、イベント処理に関する共通の設計を理解すれば、この重要な概念についても完全に理解できます。

Office API 用アプリのイベントの共通の設計として、以下のオブジェクトに関連イベントが含まれています。

  • Binding
  • CustomXMLPart
  • Document
  • RoamingSettings (メール アプリ)
  • Settings

上記の各オブジェクトには、関連イベントに加えて、イベントを処理する次の 2 つのメソッドが用意されています。

  • addHandlerAsync
  • removeHandlerAsync

removeHandlerAsync メソッドは単にイベントからハンドラーのサブスクライブを解除するだけであり、シグネチャは addHandlerAsync とほぼ同じであるため、ここからは addHandlerAsync のみに重点を置いて説明します。

注: removeHandlerAsync メソッドと addHandlerAsync メソッドには、非常に重要な違いがあります。removeHandlerAsync では、handler パラメーターが省略可能です。指定がなければ、特定の種類のイベントのハンドラーがすべて削除されます。

AddHandlerAsync メソッド

addHandlerAsync メソッドは、指定したイベントにイベント ハンドラーを関連付けます。また、実装するオブジェクトごとに同じシグネチャが使用されます。

objectName.addHandlerAsync(eventType, handler [, options], callback);

では、このメソッドのパラメーターについて説明しましょう。

eventType パラメーター: 必須のパラメーターで、EventType 列挙値を受け取ります。このパラメーターでは、関連付けるイベントの種類をメソッドに指示します。

handler パラメーター: eventType パラメーターに続けて指定します。ハンドラーは、名前付き関数または匿名インライン関数のどちらかにすることができます。多くのプログラミング言語のイベント モデルと同様に、Office 用アプリ ランタイムからハンドラーが呼び出され、唯一のパラメーターとしてイベント オブジェクトの引数が渡されます。また、handler パラメーターに匿名インライン関数を指定すると、このハンドラーを削除するには、handler パラメーターを指定しないで removeHandlerAsync を呼び出して、イベントからすべてのハンドラーを削除するしかありません。

options パラメーター: Office API 用アプリのすべての非同期関数と同様に、省略可能なパラメーターを含むオブジェクトを指定できますが、すべての addHandlerAsync メソッドに指定できる省略可能なパラメーターは asyncContext だけです。options パラメーターは、コールバック内で取得できるように、非同期メソッドによってデータを渡す手段として用意されています。

callback パラメーター: Office API 用アプリ全体でまったく同じ動作をしますが、1 つの重要な例外が AsyncResult オブジェクトの値プロパティです。ここで既に説明したように、ランタイムからコールバックを呼び出す際に AsyncResult オブジェクトを渡します。この AsyncResult オブジェクトの値プロパティを使用して、非同期呼び出しの戻り値を取得します。addHandlerAsync メソッドのコールバックの場合、AsyncResult オブジェクトの値は常に未定義になります。

図 13 は、DocumentSelectionChanged イベントの addHandlerAsync メソッドのコーディング方法を示しています (このコードでは、"message" という id 属性値が含まれた <div> 要素があることを前提としています)。

図 13 Document.addHandlerAsync メソッドを使用した DocumentSelectionChanged イベントのイベント ハンドラーの関連付け

Office.initialize = function (reason) {
  $(document).ready(function () {       
    Office.context.document.addHandlerAsync(
      Office.EventType.DocumentSelectionChanged, onDocSelectionChanged,
        addHandlerCallback);
      Office.context.document.addHandlerAsync(
        Office.EventType.DocumentSelectionChanged, onDocSelectionChanged2,
        addHandlerCallback2);
  });
};
function onDocSelectionChanged(docSelectionChangedArgs) {
  write("onDocSelectionChanged invoked each event.");
}
function onDocSelectionChanged2(docSelectionChangedArgs) {
  write("onDocSelectionChanged2 invoked each event.");
}
function addHandlerCallback(asyncResult) {
  write("addHandlerCallback only called once on app initialize.");
}
function addHandlerCallback2(asyncResult) {
  write("addHandlerCallback2 only called once on app initialize.");
}
function write(message) {$('#message').append(message + "\n");

アプリが初期化されるときに、図 13 のコードによって onDocSelectionChanged ハンドラー関数と onDocSelectionChanged2 ハンドラー関数が DocumentSelectionChanged イベントに関連付けられます。同じイベントに複数のイベント ハンドラーを関連付けることができます。両方のハンドラーは、DocumentSelectionChanged イベントが発生したときに、<div> と "message" を書き込んでいるだけです。

addHandlerAsync への呼び出しには、それぞれ addHandlerCallback コールバックと addHandlerCallback2 コールバックも含まれています。これらのコールバックも <div> と "message" を書き込んでいますが、コールバックが呼び出されるのは addHandlerAsync が完了したときの 1 回だけです。

同じように addHandlerAsync メソッドを使用すれば、JavaScript API for Office であらゆるイベントのイベント ハンドラーを関連付けることができます。

Office 用アプリのイベント モデルにおける重要なシナリオ

既に説明したように、JavaScript API for Office には、Office 用アプリのイベント モデルについて考える際に、整理して理解する必要のある 4 つの重要なシナリオがあります。API のすべてのイベントは、次の 4 つの重要なイベント シナリオのいずれかに分類されます。

  • Office.initialize のイベント
  • ドキュメント レベルの選択変更のイベント
  • バインド レベルの選択とデータ変更のイベント
  • 設定変更のイベント

Office.initialize のイベント: JavaScript API for Office の最も一般的なイベントは間違いなく Office.initialize イベントです。この初期化イベントは、作成する Office 用アプリごとに発生します。実際には、ランタイムが実行するコードの冒頭部分です。

Office Project 用の新しいアプリに対して Visual Studio 2012 から提供されるスタート コードを見ると、アプリの ProjectName.js ファイルでは、次のようにスタート コードの 1 行目に Office.initialize イベントのイベント ハンドラーが関連付けられているのがわかります。

// This function is run when the app is ready to
// start interacting with the host application;
// it ensures the DOM is ready before adding click handlers to buttons.
Office.initialize = function (reason) { /* handler code */ };

前回の「オブジェクト モデル階層」で説明したように、Office オブジェクトは JavaScript API for Office で最上位オブジェクトで、アプリの実行時インスタンスを表します。Office 用アプリ ランタイムが完全に読み込まれ、アプリとの対話操作の準備が完了すると、初期化イベントが発生します。したがって、初期化イベントのイベント ハンドラーは、事実上、残りのコードを実行する前に実行する必要のある、アプリとランタイム間の "ハンドシェイク" です。

Office.initialize イベントのハンドラーとして指定する関数は、1 つの引数 (InitializationReason 列挙型) を受け取ります。InitializationReason 列挙値は、Inserted と documentOpened の 2 つだけです。

  • Inserted は、ドキュメントにアプリが挿入された直後に、アプリの初期化が行われていることを示します。
  • documentOpened は、既にアプリが組み込まれているドキュメントが開かれた直後に、アプリの初期化が行われていることを示します。

ランタイムは、唯一の引数として InitializationReason 列挙値をハンドラー関数に渡します。この列挙値を調べ、その理由に応じてそれぞれの対応方法のコードに分岐することができます。

この処理の一例を以下に示します。

Office.initialize = function (reason) {
  // Display initialization reason.
  if (reason == "inserted")
  write("The app was just inserted.");
  if (reason == "documentOpened")
  write(
    "The app is already part of the document.");
}
// Function that writes to a div with
// id='message' on the page.
function write(message){
  document.getElementById(
  'message').innerText += message;
}

注: 上記のコード スニペットでは、"message" という id 属性値を持つ <div> 要素があることを前提としています。

興味深いことに、ハンドラーとして記述する関数内には何も含める必要がありません。ただし、この関数は存在する必要があります。存在しなければ、アプリ実行時にエラーが発生します。

ちなみに、Office.initialize イベントのイベント ハンドラーは、アプリで使用する可能性のある他のフレームワーク (jQuery など) の初期化に適しています。もう一度、Office 用アプリの新しいプロジェクトに対して Visual Studio で用意されているスタート コードを見ると、図 14 のようなコードを確認できます。

図 14 Office.initialize イベント ハンドラー内で他のフレームワークを初期化

Office.initialize = function (reason) {
  $(document).ready(function () {
    $('#getDataBtn').click(function () { getData('#selectedDataTxt'); });
    // If setSelectedDataAsync method is supported
    // by the host application, setDatabtn is hooked up
    // to call the method, else setDatabtn is removed.
    if (Office.context.document.setSelectedDataAsync) {
        $('#setDataBtn').click(function () { setData('#selectedDataTxt'); });
    }
    else {
      $('#setDataBtn').remove();
    }
  });
};

jQuery の .ready イベントは、Office.initialize イベント ハンドラー内で処理されています。これにより、jQuery コードから呼び出される前に JavaScript API for Office が確実に読み込まれます。

ドキュメント レベルの選択変更のイベント: ドキュメント レベルの選択変更のイベントは、ドキュメント内での選択が別の項目に移動するときに発生します。たとえば、ユーザーが、Word 文書内で現在選択している項目から離れ、文書内の別の範囲のテキストやオブジェクトなどをクリックすると、選択項目の変更に対してドキュメント レベルでイベントが発生します。

次のコードは、現在の選択項目の変更に対応する方法を示しています。

function addEventHandlerToDocument() {
    Office.context.document.addHandlerAsync(
      Office.EventType.DocumentSelectionChanged,
      MyHandler);
  }
  function MyHandler(eventArgs) {
    doSomethingWithDocument(eventArgs.document);

バインド レベルの選択とデータ変更のイベント: Office 用アプリのオブジェクト モデルのバインドは、ドキュメント内で一意名が付けられた領域へのリンク (バインド) を確立することで、ドキュメント (ワークシート) 内の特定の領域に一貫してアクセスする手段です。バインドを利用するには、まず、API に用意されているいずれかのメソッドを使用してバインドを 1 つ作成します。次に、一意識別子を使用して、作成した特定のバインドを参照します。

バインドはイベントのトリガーにもなるため、必要に応じてそのイベントに対応できます。具体的には、バインドしている領域内で選択の変更やデータの変更が行われるとイベントが発生します。次の 2 つのコード スニペットは、特定のバインド内での選択とデータの変更に対応する方法を示しています (どちらのコードも、"message" という id 属性値を持つ <div> 要素があることを前提としています)。

Binding.bindingSelectionChanged イベントに対する応答:

function addEventHandlerToBinding() {
  Office.select("bindings#MyBinding").addHandlerAsync(
    Office.EventType.BindingSelectionChanged,
    onBindingSelectionChanged);
}
function onBindingSelectionChanged(eventArgs) {
  write(eventArgs.binding.id + " has been selected.");
}
// Function that writes to a div with id='message' on the page.
function write(message){
  document.getElementById('message').innerText += message;
}

Binding.bindingDataChanged イベントに対する応答

function addEventHandlerToBinding() {
  Office.select("bindings#MyBinding").addHandlerAsync(
    Office.EventType.BindingDataChanged, onBindingDataChanged);
}
function onBindingDataChanged(eventArgs) {
  write("Data has changed in binding: " + eventArgs.binding.id);
}
// Function that writes to a div with id='message' on the page.
function write(message){
  document.getElementById('message').innerText += message;
}

設定変更のイベント: Office 用アプリのオブジェクト モデルでは、アプリ関連の設定を保存する手段を開発者に提供しています。Settings オブジェクトはプロパティ バッグとして動作し、アプリのカスタム設定をキーと値の組み合わせとして保存します。Settings オブジェクトには、関連付けられている Settings.settingsChanged というイベントがあり、保存されている設定が変更された場合に発生します。

Settings.settingsChanged イベントの詳細については、JavaScript API for Office に関する MSDN ドキュメント (https://msdn.microsoft.com/ja-jp/library/office/apps/fp142231.aspx) を参照してください。

次回: さらに高度なトピック

連載 2 回目の今回は、Office 用アプリから Office ファイルのコンテンツを取得および設定する方法の基礎を調べ、選択したデータを取得および設定する方法とファイル データをすべて取得する方法を示しました。次に、プロジェクト、タスク、ビュー、およびリソースの各データを Project 用アプリから取得する方法を見てきました。最後に、JavaScript API for Office のイベントと、そのイベントに対するコードの作成方法を調べました。

注: Project 用アプリのコンテンツの大部分を提供してくれた、Office 部門のプログラミング ライターである Jim Corbin に感謝します。

次回は、JavaScript API for Office に関するさらに高度なトピックとして、データ バインドとカスタム XML パーツを取り上げます。

Stephen Oliver は、Office 部門のプログラミング ライターを務める、マイクロソフト認定プロフェッショナル デベロッパー (SharePoint 2010) です。彼は、Excel Services や Word Automation Services に関する開発者向けドキュメントに加え、PowerPoint Automation Services の開発者向けドキュメントも執筆しています。Excel Mashup サイト (ExcelMashup.com、英語) の監督と設計に携わりました。

Eric Schmidt は、Office 部門のプログラミング ライターを務めています。彼は、広く使用されている Persist カスタム設定のコード サンプルなど、Office 用アプリの複数のコード サンプルを作成しました。また、他の製品や Office のプログラミング機能に含まれているテクノロジについても記事を執筆したりビデオを作成したりしています。

この記事のレビューに協力してくれた技術スタッフの Mark Brewster、Shilpa Kothari、および Juan Balmori Labra に心より感謝いたします。
Mark Brewster はアリゾナ大学で数学とコンピューター サイエンスの理学士を取得して 2008 年に卒業し、その後の 4 年間、マイクロソフトでソフトウェアを開発しています。彼は趣味と実益を兼ねて自転車に乗り、ビールを飲みながらレコード アルバムを聴くのが大好きです。

Shilpa Kothari (Bhavsar) は、マイクロソフトのテスト部門のソフトウェア エンジニアです。彼女は、Bing Mobile、Visual Studio、Office など、複数のマイクロソフト製品に携わり、ソフトウェアの QA とユーザー エクスペリエンスに熱心に取り組んでいます。彼女の連絡先は、shilpak@microsoft.com (英語のみ) です。

Juan Balmori Labra はプログラム マネージャーで最近の 3 年間は Microsoft Office JavaScript API に取り組んでいます。以前は、Office 2010 リリースと、Business Connectivity Services と Duet の出荷に携わっていました。レドモンドに移籍するという夢がかなう前は、マイクロソフト メキシコの公共部門コンサルティング業の主席アーキテクトを務めていました。