次の方法で共有


Microsoft Office

JavaScript API for Office の調査: データ バインドとカスタム XML パーツ

Stephen Oliver
Eric Schmidt

コード サンプルをダウンロードする

JavaScript API for Office の詳しいチュートリアルを示す連載の第 3 回です。引き続きこの API を詳しく見ていきましょう。今回は、データ バインドと、カスタム XML パーツの操作をサポートする機能を取り上げます。第 1 回の「JavaScript API for Office の調査」(msdn.microsoft.com/magazine/jj891051、英語) では、オブジェクト モデルの概要を紹介しました。第 2 回の「JavaScript API for Office の調査: データ アクセスとイベント」(msdn.microsoft.com/magazine/jj991976) では、ファイル コンテンツをどのように取得するかという重要な概念を考察したほか、イベント モデルについて詳しく説明しました。この次の第 4 回では、3 種類目の Office 用アプリであるメール アプリに的をしぼって説明します。

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

Office 用アプリのデータ バインド

データ バインドを利用すると、ドキュメントの特定のデータ領域とアプリを密接に統合できます。領域内のデータをアプリの名前付きオブジェクトにバインドすることで、ユーザーが別のコンテンツを選択していても、アプリから名前付き領域内のデータにアクセスできるようにします。

バインドは一度作成されると、領域がページ上で移動されても (Word の場合)、別のワークシートにコピーされても (Excel の場合)、維持されます。たとえば、テーブルへのバインドは、ユーザーによってテーブル名が変更されても、維持されます。

領域のデータが変更されると、バインドによってアプリがフックできるイベントが発生します。このイベントから、アプリは変更されたデータにアクセスして、変更内容に応じた処理を行います。

バインドとアプリの "ビュー": たしかに、Office 用アプリでデータ バインドを使うと、Office ファイル内のデータ セットにアプリから直接アクセスでき、ユーザーの直接操作に頼らなくても、アプリでデータを分析できます。ただし、データ バインドの機能は、目的のデータへのアクセスを実現するだけではありません。データ バインドを使うと、アプリのカスタマイズ可能な必須コンポーネントとして、Office ファイル自体を取り込むことができます。

Office 用アプリの多くでは、インターフェイスを作業ウィンドウまたはコンテンツ アプリ UI の範囲内でのみ提供しています。これでも何も問題はありませんが、ごく単純に考えると、Office ファイル内のデータとその表示自体がアプリの "ビュー" であると言えます。ユーザーは、Office ファイル内のデータを操作します。ドキュメントのコンテンツ内に新しいデータを入力したり、既存のデータを変更したり、不要なデータを削除したりします。各 Office アプリケーションによって、使い慣れたユーザーが理解できるデータのビューが提供されます。

JavaScript API for Office のデータ バインド機能を利用することで、Office アプリケーションが提供するデータのビューをアプリから利用できるようになります。開発者は、Office に既に用意されている機能を使って、アプリの "インターフェイス" を開発できます。この方法なら、Office アプリケーションに組み込まれている機能を使い、アプリのビューのスタイルを設定できます。その後、データ バインドによって、アプリのビューと JavaScript ファイル内のビジネス ロジック "モデル" の連携を処理します。

もちろん、その逆もできます。Office ファイルをデータ ソースにして、データ モデルのコンテンツを格納し、アプリを使ってこのデータのビューを提供できます。バインドは柔軟性が高いため、必要に応じて、モデル ビュー コントローラー (MVC) パターンをアプリや Office ファイルに適用できます。

バインドの使用シナリオ: アプリでは以下の一般的な 3 とおりの方法を自由に組み合わせてバインドを使用でき、開発者の創造性が妨げられることはありません。

  • 領域内のデータがユーザーによって変更された場合にアプリが反応する。
  • アプリが領域内のデータを取得し、分析して、データをモデリングするか送信するかのオプションをユーザーに提示する。
  • アプリが外部データ ソースからデータをバインド先の領域にプッシュする。

Excel ブックに挿入されたシンプルな株価情報アプリを例に考えてみましょう。このブックには銘柄記号が保持されている列と、現在の株価が保持されている列があります。この場合、データ バインドを使ってアプリと銘柄記号の列をバインドして、アプリから列内の銘柄記号を取得できます。さらに、アプリで Web サービスを介して株価の変更情報を受け取り、Web サービスから送信された結果を解析するようにします。最後に、ワークシート内の価格の列とアプリをバインドし、値がリアルタイムに更新されるようにします。

ここからは、この処理を実際に行ってみましょう。株価情報ブックを作成して、Binding オブジェクトについて詳しく見ていきます。

Binding オブジェクトの使用: データ バインドの基盤となる機能は、Bindings コレクションと Binding オブジェクトにあります。

  • Bindings コレクションは、Office ファイルと Office 用アプリ間で行われたすべてのバインドを表します。アプリは、他のアプリが作成したバインドにはアクセスできません。
  • Binding オブジェクトは、Office ファイル内の特定の領域とアプリ間の名前付きバインド 1 つを表します。このオブジェクトは、データの取得、読み取り、設定を処理するメンバーと、バインド先領域内の変更に対応するメンバーをいくつか公開します。

これらのオブジェクトについては、株価情報アプリを作成しながら、さらに詳しく説明します。

先に進む前に、データについて簡単に説明しましょう。図 1はこのアプリのビューを示しています。ご覧のとおり、デモのため、架空の銘柄記号を使っています。


図 1 Excel ブック内の、数式と条件付き書式が適用されている "Stocks" というテーブル

また、このブックには既にいくつかの "インテリジェンス" も追加されています。バインド先のデータ領域は、テーブルとして書式設定され、"Stocks" という名前が付けられています。右側の列の値には、テーブル内の他の値と比較するためのユーザー定義式が追加されています。また、このテーブルに条件付き書式を適用し、右側の列にアイコン セットが表示されるようにしています。

ご参考までにお話すると、アプリをデバッグするたびにテーブルを作り直さなくても済むように、このブックを Visual Studio 2012 のソリューションに追加しました。ブックをソリューションに追加するには、ソリューションに含まれるアプリ プロジェクトを右クリックし (既定のテンプレートを使っている場合は、ソリューション エクスプローラー内で 1 番上に表示されるプロジェクト)、[既存項目の追加] をクリックして目的のブックを選択します。次に、アプリ プロジェクトのプロパティで、[開始動作] を目的のブック ファイルに設定します。デバッグする場合は、ブックにアプリを手動で挿入する必要があります ([挿入] タブの [Office 用アプリ])。

初期化されたら、アプリのビジネス ロジックでは、バインドを設定したうえで、バインドのイベント Office.EventType.BindingDataChanged にイベント ハンドラーを追加する必要があります。図 2にそのコードを示します。コードは自己実行型の匿名関数内にカプセル化して、StockTicker 変数に含めています。スプレッドシートにあるテーブルの名前、バインド名、およびバインドはすべて、StockTicker "クラス" 内のクラス フィールドとして格納されています。StockTicker "クラス" が公開するのは、initializeBinding メンバーのみです。

図 2 Excel ブックへのバインドを作成し、そのバインドにデータ変更イベントへのハンドラーを追加

var StockTicker = (function () {
   var tableName = "Sheet1!Stocks",
       bindingName = "Stocks",
       binding;
   // Create the binding to the table on the spreadsheet.
   function initializeBinding() {
     Office.context.document.bindings.addFromNamedItemAsync(
       tableName,
       Office.BindingType.Table,
       { id: bindingName },
       function (results) {
         binding = results.value;
         addBindingsHandler(function () { refreshData(); });
     });
   }
   // Event handler to refresh the table when the
   // data in the table changes.
   var onBindingDataChanged = function (result) {
     refreshData();
   }
   // Add the handler to the BindingDataChanged event of the binding.
   function addBindingsHandler(callback) {
     Office.select("bindings#" + bindingName).addHandlerAsync(
       Office.EventType.BindingDataChanged,
       onBindingDataChanged,
       function () {
         if (callback) { callback(); }
     });
   }
   // Other member methods of this "class" ...
   return {
     initializeBinding: initializeBinding
   };
 })();

アプリとワークシート間のバインドを確立するには、addFromNamedItemAsync、addFromPromptAsync、addFromSelectionAsync など、JavaScript API の Document クラスにあるいくつかのメソッドのうちの 1 つを使用できます (addFromPromptAsync を使用できるのは Excel と Excel Web App のみです)。

バインド先の領域の名前がわかっているので (バインド先は Sheet1 の "Stocks" という名前のテーブルです)、addFromNamedItemAsync メソッドを使ってバインドを確立しています。Excel の範囲表記 (Sheet1!Stocks) を使って、テーブルの名前を渡します。このメソッド呼び出しの結果にバインド自体への参照が含まれています。このバインドへの参照をバインド変数 (クラス フィールド) に格納できます。

今回のコードでは、メソッドの bindingType パラメーターとして Office.BindingType.Table の値を渡しています。これにより "テーブル" 型のデータ バインドを作成することを示していますが、テキストやマトリックス型のバインドを指定することもできます。領域へのバインドをテーブル型にすることには、いくつかメリットがあります。たとえば、ユーザーが新しい列または行をテーブルに追加する場合、バインド領域の範囲も拡大されます。その逆もしかりです。バインドの基盤になる TableBinding オブジェクトは、列の追加、行の追加、さらにはテーブル内の全データの削除に使うプロパティを公開します。

(JavaScript API for Office のテキストおよびマトリックス データ型の詳細については、この連載の第 2 回目の「Office 用アプリから Office ファイルのコンテンツへのアクセス」を参照してください。)

コードでは次に、バインドの BindingDataChanged イベントへのハンドラーを追加します。バインド先の領域でデータが変更されたら、つまり、ユーザーが領域内のデータを変更したら、ローカルで定義している refreshData 関数を呼び出してテーブルの更新処理を開始する必要があります。また、テーブルはデータ ソースからのデータを使って更新されていないので、イベント ハンドラーが追加された後に refreshData を呼び出すことも必要です。

このコードの addBindingsHandler 関数は Office.select メソッドを使ってバインドを取得していますが、代わりに Bindings.getByIdAsync メソッドを使うこともできます。この 2 つのメソッドの最大の違いは、結果で返されるデータにアクセスするレベルです。Office.select メソッドは Binding オブジェクト プロミスを呼び出し元のコードに返します。メソッドが成功すると、返された Binding オブジェクトには、使用できるメンバーが限られた数しか保持されていません。Office.select を使ってバインドを選択することで、Binding オブジェクトのメンバーを直ちに呼び出すことができます。この方法なら、バインドにハンドラーを追加するために、バインドを取得する関数へのコールバックを追加する必要がありません。

(バインドへの参照を取得するローカルの "binding" 変数を使用すればよいだけではないかと考えられるかもしれません。実はそのとおりです。デモが目的なのでこのようなコードを作成しました。)

図 3 は、refreshData 関数と getBindingData 関数を示しています。refreshData 関数は、getBindingData を呼び出すことでワークシートからテーブル データを取得する非同期呼び出しのチェーンを開始します。getBindingData 関数には、Binding.getDataAsync メソッドへの呼び出しがあり、TableData オブジェクトとしてデータを返します。

図 3 バインド先テーブルからのデータの取得と Web サービスの呼び出し

var StockTicker = (function () {
   // Other members of this "class"...
   // Refresh the data displayed in the bound table of the workbook.
   // This function begins a chain of asynchronous calls that
   // updates the bound table.
   function refreshData() {
     getBindingData();
   }
   // Get the stock symbol data from the bound table and
   // then call the stock quote information service.
   function getBindingData() {
     binding.getDataAsync(
       {
         startRow: 0,
         startColumn: 0,
         columnCount: 1
       },
       function (results) {
         var bindingData = results.value,
             stockSymbols = [];
         for (var i = 0; i < bindingData.rows.length; i++) {
           stockSymbols.push(bindingData.rows[i][0]);
         }
         getStockQuotes(stockSymbols);
     });
   }
   return {
     // Exposed members of the "class."
   };
 })();

図 3 の getDataAsync への呼び出しでは、オプションのパラメーターに匿名オブジェクトの {coercionType: Office.CoercionType.Table} を渡し、明示的に取得するデータ型を指定する (またはデータ型を変更する) こともできます。ここでは取得するデータ型を指定していないので、getDataAsync 呼び出しは元のデータ型 (TableData オブジェクト) を使ってバインド データを返します。

第 2 回目で説明したように、TableData オブジェクトを使うと、操作するデータをより構造的に処理できます。具体的には、headers プロパティと rows プロパティがあるので、それらを使ってデータをテーブルから選択できます。この例では、テーブルの 1 列目から銘柄記号を取得するだけです。rows プロパティは配列の配列としてテーブルにデータを格納します。このとき、最初の配列の各項目がテーブルの 1 行に対応します。

TableData オブジェクトへのバインドを操作する場合、startRow パラメーターと startColumn パラメーターを使って、バインドから取得する行と列のサブセットを指定します。どちらのパラメーターもテーブルから抽出するデータの開始点は 0 で、テーブルの左上のセルが起点です (startRow パラメーターと startColumn パラメーターは一緒に使用しないと、例外が発生することに注意してください)。ここで必要なのはテーブルの 1 列目のデータだけなので、columnCount も 1 に設定して渡します。

目的の列のデータを取得できたら、各値を 1 次元の配列にプッシュします。図 3では、この銘柄記号の配列を引数として受け取る getStockQuotes 関数を呼び出しています。図 4 では、getStockQuotes 関数を使って、株価情報 Web サービスからデータを取得しています (ここではデモのため、Web サービスのコードを割愛しています)。Web サービスからの結果を解析したら、ローカルで定義している removeHandler メソッドを呼び出します。

図 4 Web サービスの呼び出しと、BindingDataChanged イベント ハンドラーの削除

var StockTicker = (function () {
   // Other members of this "class"...
   // Call a Web service to get new stock quotes.
   function getStockQuotes(stockSymbols) {
     var stockValues = [];
     // Make a call to the Web service and parse the results.
     // The results are stored in the stockValues variable, which
     // contains an array of arrays that include the stock symbol
     // with the current value.
     removeHandler(function () {
       updateTable(stockValues);
     });
   }
   // Disables the BindingDataChanged event handler
   // while the table is being updated.
   function removeHandler(callback) {
     binding.removeHandlerAsync(
       Office.EventType.BindingDataChanged,
       { handler: onBindingDataChanged },
       function (results) {
         if (results.status == Office.AsyncResultStatus.Succeeded) {
            if (callback) { callback(); }
         }
     });
   }
   return {
     // Exposed members of the "class."
   };
 })();

removeHandler 関数は binding.removeHandlerAsync メソッドを呼び出します。これにより、BindingDataChanged イベントのイベント ハンドラーが削除されます。このとき、ハンドラーを BindingDataChanged イベントに関連付けたままにしておくと、テーブルを更新したときに BindingDataChanged イベントが発生します。そこでイベント ハンドラーが再び呼び出されてテーブルを更新することになるため、無限ループが発生します。新しいデータでテーブルが更新されたら、イベント ハンドラーを再びイベントに追加します。

(もちろん、coercionType に "matrix" を使って、テーブルの列ごとにバインドを作成することもできます。そのうえで、ユーザーが編集できる列にのみイベントをフックします。)

removeHandlerAsync メソッドは、削除するハンドラーの名前を示すパラメーターとしてハンドラーを受け取ります。バインド イベントからハンドラーを削除する場合、ハンドラー パラメーターを使用するのはベスト プラクティスの 1 つです。

図 5 では、ローカルに定義した updateTable 関数を呼び出して、新しい株価でテーブルを更新しています。

図 5 バインド先テーブルからのデータの取得と Web サービスの呼び出し

var StockTicker = (function () {
   // Other members of this "class"...
   // Update the TableData object referenced by the binding
   // and then update the data in the table on the worksheet.
   function updateTable(stockValues) {
     var stockData = new Office.TableData(),
         newValues = [];
     for (var i = 0; i < stockValues.length; i++) {
       var stockSymbol = stockValues[i],
           newValue = [stockSymbol[1]];
       newValues.push(newValue);
     }
     stockData.rows = newValues;
     binding.setDataAsync(
       stockData,
       {
         coercionType: Office.CoercionType.Table,
         startColumn: 3,
         startRow: 0
       },
       function (results) {
         if (results.status == Office.AsyncResultStatus.Succeeded) {
           addBindingsHandler();
         }
     });  
   }
   return {
     // Exposed members of the "class."
   };
 })();

updateTable 関数は、Web サービスから渡されたデータを受け取り、バインド先のテーブルにこのデータを書き込みます。今回の例では、stockValues パラメーターに配列の配列がもう 1 つあります。この配列の配列における最初の配列の各項目は、銘柄記号とそれに対応する株価を保持します。バインド先のテーブルにデータを書き込むには、新しい TableData オブジェクトを作成して株価のデータをこのオブジェクトに挿入します。

TableData.rows プロパティに設定するデータの形状と、バインドに挿入するデータの形状が一致するように注意が必要です。何も考えずに新しい TableData オブジェクトをバインド先のテーブルに設定すると、たとえば、数式など、テーブル内の一部のデータを失う可能性があります。図 5 では、1 列のデータ (複数の配列から構成される配列で、構成要素である配列ごとに 1 項目が保持されている) として TableData オブジェクトにデータを追加しています。このデータをバインド先のテーブルに挿入する場合、この更新されたデータの列を該当する列に挿入する必要があります。

ここで再び startRow プロパティと startColumn プロパティを使います。updateTable 関数にはワークシート内のテーブルに TableData をプッシュする binding.setDataAsync への呼び出しが含まれています。この binding.setDataAsync に、startColumn と startRow のパラメーターが指定されています。ここでは、startColumn パラメーターが 3 に設定されています。これは、TableData オブジェクトのデータがテーブルの 4 列目から挿入されることを示しています。setDataAsync メソッドのコールバックでは、再び addBindingsHandler 関数を呼び出して、イベント ハンドラーをイベントに再適用しています。

binding.setDataAsync メソッドの処理が正常に完了すると、新しいテーブル データがバインド先の領域にプッシュされ、直ちに表示されます。ユーザーから見れば、このエクスペリエンスはシームレスです。ユーザーはテーブルのセルにデータを入力し、Enterキーを押すと、テーブルの "Value" 列が自動的に更新されます。

カスタム XML パーツ

JavaScript API for Office がサポートする機能で特に注目すべきなのは、Word のカスタム XML パーツを作成および操作できる機能です。JavaScript API for Office のカスタム XML パーツ関連の機能の持つ大きな可能性を引き出すには、少し背景を理解しておくとよいでしょう。具体的には、どのように Office Open XML (OOXML または OpenXML) ファイル形式、カスタム XML パーツ、コンテンツ コントロール、XML マッピングを組み合わせて、実に強力なソリューション、つまり、動的 Word 文書の作成を伴うソリューションを作成できるかを理解する必要があります。

OOXML 形式: Office 2007 から Office ドキュメントの新しいファイル形式として OOXML が導入されています。OOXML は、Office 2010 と Office 2013 では既定のファイル形式になっています (Office ドキュメントが OOXML 形式であるかどうかは、ファイルの拡張子で見分けられます。OOXML は拡張子が 4 文字で、多くの場合、Word 文書なら ".docx"、Excel スプレッドシートなら ".xlsx"、PowerPoint ドキュメントなら ".pptx" など、"x" で終わります)。

本質的に OOXML 形式の Office ドキュメントは .zip ファイルです。各 .zip ファイルには "パーツ" と呼ばれる XML ファイルが含まれ、これらが Office ドキュメントを構成しています。Office ドキュメントの名前を変更して、たとえば Word 文書の .docx を .zip に変更し、中のファイルを調べてみると、Office ドキュメントは、.zip パッケージ内でフォルダーを使って整理されている複数の XML ファイルのコレクションにすぎないことがわかります (図 6 参照)。


図 6 Office オープン XML (OOXML) 形式のドキュメントの構造

カスタム XML パーツの基礎: Office アプリケーションが OOXML 形式の Office ドキュメントを新規作成するときに常に作成される標準の XML パーツ (主なドキュメント プロパティを記述する組み込みの XML パーツなど) がありますが、興味深いことに Word 文書、Excel ブック、または PowerPoint プレゼンテーションに独自の "カスタム XML" パーツを追加することもできます。カスタム XML パーツは、Office ドキュメントを構成する .zip パッケージ内の XML ファイルのコレクションに追加されます。カスタム XML パーツは、ドキュメントのファイル構造内に格納されますが、エンド ユーザーには表示されません。そこで、ファイル構造内の隠しデータとして Office ドキュメントの特定のインスタンスと共に移動する、ビジネス データを挿入できます。このカスタム XML はアプリから操作できます。これがまさに、JavaScript API for Office がサポートする機能です。

コンテンツ コントロール: カスタム XML のドキュメントへの追加を実現する OOXML 形式とそのファイル構造のほかに、Word 2007 ではコンテンツ コントロールが追加されました。これは、カスタム XML パーツを補完する便利な機能です。

コンテンツ コントロールを使うと、プレーン テキスト、リッチ テキスト、図、日付、さらには繰り返しデータなど、特定の種類のデータを保持する固定領域を Word 文書内に定義できます。カスタム XML パーツを補完するコンテンツ コントロールの最大の特徴は、XML マッピングを使ったデータ バインドです。

XML マッピング: コンテンツ コントロールは、ドキュメントに含まれる XML パーツ内の XML 要素にバインド、つまり "マッピング" できます。たとえば、企業では、バックエンド システムからビジネス データをカスタム XML パーツとして、そのカスタム XML パーツにマッピングされたコンテンツ コントロールがある Word 文書に挿入できます。コンテンツ コントロールはカスタム XML パーツの特定のノードにバインドされていて、エンド ユーザーがこの Word 文書を開くと、XML マッピングされたコンテンツ コントロールに自動的にカスタム XML パーツのデータが入力されます。または、その逆に、コンテンツ コントロールがマッピングされた同じ Word 文書を使って、エンド ユーザーがコンテンツ コントロールにデータを入力するシナリオも可能です。Word 文書が保存されると、マッピングされたコンテンツ コントロールのデータが XML ファイルに保存されます。その後、アプリを使って、保存した Word 文書のカスタム XML パーツからデータを収集し、バックエンド システムにデータをプッシュできます。JavaScript API for Office には、このようなアプリを開発するための手厚いサポートが用意されています。

JavaScript API for Office を使ったカスタム XML パーツの操作: Office JavaScript オブジェクト モデルのアプリで使われるカスタム XML パーツ API の重要な要素を詳しく説明するには、例を使うのが最もわかりやすいでしょう。ここでは、Office/SharePoint アプリ デベロッパー ポータルの「サンプル」で提供されている "invoice manager" サンプル (bit.ly/YRdlwt、英語) を使って説明します。invoice manager サンプルは、バックエンド システムからデータを取得して請求書を生成するという、ドキュメント動的生成シナリオの例です。この場合、データは顧客の名前と送付先住所、および顧客の購入明細を示す関連リストです。

このサンプルには、請求書の新規作成に使われるテンプレート ドキュメントが含まれています。このテンプレート ドキュメントには、顧客の名前、住所、および顧客の購入明細テーブルを配置するレイアウトが設定されています。このドキュメントの顧客名、住所、購入明細の各セクションがコンテンツ コントロールです。各コンテンツ コントロールは、顧客の請求書データを保持するために作成されたスキーマ内のノードにマップされています (図 7 参照)。


図 7 カスタム XML パーツにマップされているドキュメント サーフェス上のコンテンツ コントロール

invoice manager サンプル アプリの UI は非常にシンプルです (図 8参照)。


図 8 Invoice Manager サンプル アプリの UI

エンド ユーザーがアプリの UI のドロップダウン ボックスから請求書番号を選択すると、その請求書番号に関連付けられている顧客データがアプリのメイン領域に表示されます (図 9 参照)。


図 9 カスタム XML パーツからのデータが設定された Invoice Manager の UI

ユーザーが [Populate] (データ設定) をクリックすると、表示されているデータがカスタム XML パーツとしてドキュメントにプッシュされます。コンテンツ コントロールはカスタム XML パーツ内のノードにマップされているため、カスタム XML パーツがドキュメントにプッシュされると、コンテンツ コントロールのマップ先の各 XML ノードのデータが直ちに表示されます。(この例のように) カスタム XML パーツをその場で置換できますが、パーツがコンテンツ コントロールのマップ先のスキーマに従っている限り、コンテンツ コントロールはマップされているデータを表示します。図 10 は、ドキュメントのカスタム XML パーツにコンテンツ コントロールがマップされている invoice manager の納品書フォームです。


図 10 カスタム XML パーツ内のノードにマップされているコンテンツ コントロール、バインド先のデータが表示されている

CustomXmlParts オブジェクト

CustomXmlParts.addAsync カスタム XML パーツを操作するには、まず、JavaScript API for Office を使ってドキュメントにカスタム XML パーツを追加する方法を知る必要があります。customXmlParts.addAsync メソッドを使うことが、カスタム XML パーツを追加する唯一の方法です。名前からわかるように、customXmlParts.addAsync メソッドは、カスタム XML パーツを非同期に追加し、次のようなシグネチャを持ちます。

Office.context.document.customXmlParts.addAsync(xml [, options], callback);

この関数の 1 つ目の必須パラメーターは、XML の文字列です。これはカスタム XML パーツの XML です。前述のとおり、invoice manager では、ドキュメント サーフェスのコンテンツ コントロールにマップされているカスタム XML パーツを使用していますが、まず、カスタム XML として挿入する顧客データを取得する必要があります。アプリ全体のロジックが保持されている InvoiceManager.js ファイルでは、ユーザー定義関数の setupMyOrders を使って、バックエンド システムから顧客データを取得する処理のシミュレーションを行っています。この関数によって、3 件の顧客の注文を表す 3 つのオブジェクトから成る配列が作成されます。もちろん、顧客の購入履歴を格納および取得する方法としてさまざまな手段が考えられます (SQL データベースなど)。ただし、このアプリではシンプルにまとめるため、3 件の "定義済みの" 顧客の注文をアプリ内で作成しています。

注文オブジェクトを作成したら、オブジェクトが表すデータを XML に戻し、customXmlParts.addAsync の呼び出しで使用できるようにする必要があります。これは initializeOrders 関数内で処理しています。initializeOrders 関数では、アプリの UI を準備し、イベント ハンドラーを UI 上のコントロールに関連付ける処理も行っています。注意が必要な点は、[Populate] (データ設定) のクリック イベントのイベント ハンドラーを関連付ける jQuery コード内にあります (図 11 参照)。

図 11 [Populate] (データ設定) のクリック イベントのイベント ハンドラーの書き込み

$("#populate").click(function () {
   var selectedOrderID = parseInt($("#orders option:selected").val());
   _document.customXmlParts.getByNamespaceAsync("", function (result) {
     if (result.value.length > 0) {
       for (var i = 0; i < result.value.length; i++) {
         result.value[i].deleteAsync(function () {
         });
       }
     }
   });
   var xml = $.json2xml(findOrder(myOrders, selectedOrderID));
   _document.customXmlParts.addAsync(xml, function (result) { });
 });

基本的に、[Populate] (データ設定) のクリック イベントのイベント ハンドラーとして機能する匿名関数は、注文オブジェクト (setupMyOrders 関数により作成) を XML の文字列に変換し、customXmlParts.addAsync メソッドを呼び出して、注文情報を含む XML の文字列を 1 つ目の必須パラメーターとして渡します。

customXml­Parts.addAsync のもう 1 つのパラメーターは callback です。もちろん、これは、コード内で定義したメソッドへの参照でも、匿名関数でもかまいません。invoice manager サンプルでは、匿名インライン関数を使っています。

_document.customXmlParts.addAsync(xml,
   function (result) { });

JavaScript API for Office の callback の通例として、AsyncResult オブジェクトが callback の唯一の引数として渡されます。customXmlParts.addAsync とすべての customXmlParts 関数で、AsyncResult オブジェクトを使って次の処理を実行できます。

  • AsyncResult.value プロパティを使って、新規作成されたカスタム XML パーツへの参照を取得する
  • AsyncResult.status プロパティを使って、要求の結果を取得する
  • AsyncResult.error プロパティを使って、エラーが発生した場合にエラーについての情報を取得する
  • AsyncResult.asyncContext プロパティを使って、独自の状態データ (customXmlParts.addAsync の呼び出しに含めている場合) を取得する

最後の処理については、customXmlParts.addAsync メソッドのもう 1 つのパラメーターとして、省略可能な options オブジェクトがあったことに注意してください。

Office.context.document.customXmlParts.addAsync(
   xml [, options], callback);

この options オブジェクトは、独自のユーザー定義オブジェクトをコールバックの呼び出しに渡す手段として用意されています。

invoice manager サンプルからわかるように、customXmlParts.addAsync の呼び出し内の匿名関数は何の処理も行いませんが、おそらく運用環境では、なんらかの理由でカスタム XML パーツが追加されなかった場合に、インスタンスを適切に処理するため、エラー チェックを実行する必要があるでしょう。

CustomXmlParts.getByNamespaceAsync: invoice manager で実際に使われているカスタム XML パーツを操作するためのもう 1 つの JavaScript API for Office の重要な要素は、[Populate] (データ設定) のクリック イベント ハンドラー コードにある customXmlParts.getByNamespaceAsync メソッドです。customXmlParts.getByNamespaceAsync のシグネチャは次のとおりです。

Office.context.document.customXmlParts.getByNamespaceAsync(
   ns [, options], callback);

1 つ目の必須パラメーター "ns" は、取得するカスタム XML パーツの名前空間を示す文字列です。したがって、customXmlParts.getByNamespaceAsync からは、ドキュメント内のカスタム XML パーツで、指定した名前空間を使っているパーツの配列が返されます。invoice manager サンプルで作成されるカスタム XML パーツは名前空間を使用していないので、customXmlParts.getByNamespaceAsync は、名前空間パラメーターの引数として空文字列を渡します (図 12 参照)。

図 12 CustomXmlParts.getByNamespaceAsync メソッドの使用

$("#populate").click(function () {
   var selectedOrderID = parseInt($("#orders option:selected").val());
   _document.customXmlParts.getByNamespaceAsync("", function (result) {
     if (result.value.length > 0) {
       for (var i = 0; i < result.value.length; i++) {
         result.value[i].deleteAsync(function () {
         });
       }
                     }
      });
      var xml = $.json2xml(findOrder(myOrders, selectedOrderID));
      _document.customXmlParts.addAsync(xml, function (result) { });
    });
    var selOrder = $("#orders option:selected");
    popOrder(selOrder.val());

JavaScript API for Office の非同期関数の通例として、customXmlParts.getByNamespaceAsync にも省略可能な options パラメーターと callback パラメーターがあります。

CustomXmlParts.getByIdAsync: ドキュメント内のカスタム XML パーツを取得する方法として最後に customXmlParts.getByIdAsync を紹介します。このシグネチャは次のとおりです。

Office.context.document.customXmlParts.getByIdAsync(id [, options], callback);

この関数は、カスタム XML パーツの GUID を使って、パーツを 1 つだけ取得します。カスタム XML パーツの GUID は、ドキュメント パッケージ内にある itemPropsn.xml ファイルで確認できます。また、customXmlPart の id プロパティを使って、GUID を取得することもできます。ここで重要なのは、GUID の文字列では、中かっこ ("{}") で GUID を囲んでおく必要があることです。

invoice manager サンプルでは customXmlParts.getByIdAsync 関数を使用していませんが、次のコードで使い方は十分にわかります。

function showXMLPartBuiltId() {
   Office.context.document.customXmlParts.getByIdAsync(
     "{3BC85265-09D6-4205-B665-8EB239A8B9A1}", function (result) {
     var xmlPart = result.value;
     write(xmlPart.id);
   });
 }
 // Function that writes to a div with id='message' on the page.
 function write(message){
   document.getElementById('message').innerText += message;
 }

id パラメーターのほかに、customXmlParts.getByIdAsync メソッドには、customXmlParts.addAsync や customXmlParts.getByNamespaceAsync と同様に、省略可能な options パラメーターと、必須パラメーターの callback があり、使い方も他の 2 つの関数と同じです。

CustomXmlPart オブジェクト: customXmlPart オブジェクトは単一の XML パーツを表します。メソッドを使って customXmlPart オブジェクトから customXmlParts への参照を取得すると、いくつかのプロパティを利用できます (図 13 参照)。

図 13 CustomXmlPart プロパティ

名前 説明
builtIn CustomXMLPart が組み込みであるかどうかを示す値を取得します。
id CustomXmlPart の GUID を取得します。
namespaceManager 現在の CustomXMLPart に対して使用される名前空間プレフィックス マッピング (CustomXMLPrefixMappings) のセットを取得します。

CustomXmlPart には関連付けられているイベントもあります (図 14 参照)。

図 14 CustomXmlPart プロパティ

名前 説明
nodeDeleted ノードが削除されるときに発生します。
nodeInserted ノードが挿入されるときに発生します。
nodeReplaced ノードが置換されるときに発生します。

ただし、今回は customXmlPart オブジェクトの主要メソッドの中から開発者が使うことの多いメソッドのみを取り上げます。このようなメソッドを図 15 に示します。

図 15 CustomXmlPart メソッド

名前 説明
addHandlerAsync customXmlPart オブジェクト イベントのイベント ハンドラーを非同期に追加します。
deleteAsync このカスタム XML パーツをコレクションから非同期に削除します。
getNodesAsync 指定された XPath に一致するこのカスタム XML パーツ内の CustomXmlNodes を非同期に取得します。
getXmlAsync このカスタム XML パーツ内の XML を非同期に取得します。

CustomXMLPart.addHandlerAsync: customXmlPart.addHandlerAsync メソッドは、カスタム XML パーツの変更に応答するイベント ハンドラーの書き込みを処理します。customXmlPart.addHanderAsync メソッドのシグネチャは次のとおりです。

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

1 つ目の必須パラメーターは Office.EventType 列挙値です。これは、処理対象の Office オブジェクト モデルのアプリのイベントの種類を示します。次の必須パラメーターは、イベントのハンドラーです。ここで重要なことは、ハンドラーが起動されると、JavaScript API for Office は、処理するイベントの種類固有のイベント引数パラメーターを渡します (NodeDeletedEventArgs、NodeInsertedEventArgs、または NodeReplacedEventArgs)。次に、JavaScript API for Office の非同期関数の通例として、省略可能な options パラメーターと callback パラメーターを指定することもできます。

データ入力フォームなど、ドキュメントが使用されるシナリオを考えてみましょう。ユーザーがデータをフォームに入力すると、フォームからデータが収集されます。フォームには、繰り返しセクション コンテンツ コントロールが組み込まれているので、ユーザーが繰り返し項目を入力するたびに、新しいノードが基盤のカスタム XML パーツに追加されます。ノードが追加、つまり挿入 (insert) されるたびに、NodeInserted イベントが発生します。(すべての customXmlPart イベントと同様に) NodeInserted イベントには、customXmlPart.addHandlerAsync を使って対応できます。

図 16 は NodeInserted イベントに対応する処理の例です。

図 16 CustomXmlPart.NodeInserted イベントのイベント ハンドラーの書き込み

function addNodeInsertedEvent() {
   Office.context.document.customXmlParts.getByIdAsync(
     "{3BC85265-09D6-4205-B665-8EB239A8B9A1}", function (result) {
     var xmlPart = result.value;
     xmlPart.addHandlerAsync(Office.EventType.NodeInserted,
       function (eventArgs) {
         write("A node has been inserted.");
     });
   });
 }
 // Function that writes to a div with id='message' on the page.
 function write(message){
   document.getElementById('message').innerText += message;
 }

CustomXMLPart.deleteAsync: もちろん、カスタム XML パーツを追加する方法だけではなく、削除する方法を知ることも大切です。この機能は、customXmlPart.deleteAsync メソッドにより提供されます。CustomXmlPart.deleteAsync は非同期関数で、シグネチャは次のとおりです。

customXmlPart.deleteAsync([options ,] callback);

invoice manager のサンプルに戻って、customXMLPart.deleteAsync の実際の使い方を見てみましょう。

$("#populate").click(function () {
   var selectedOrderID = parseInt($("#orders option:selected").val());
   _document.customXmlParts.getByNamespaceAsync("", function (result) {
     if (result.value.length > 0) {
       for (var i = 0; i < result.value.length; i++) {
         result.value[i].deleteAsync(function () {
         });
       }
     }
 });

[Populate] (データ設定) のクリック イベント ハンドラー内で、名前空間が "空" のカスタム XML パーツが存在していないかどうかがプログラム ロジックによって確認されます。そのようなカスタム XML パーツが存在している場合、customXmlPart.deleteAsync メソッドを使って該当する各パーツを削除します。

カスタム XML パーツの操作に関しては他にもさまざまな機能がありますが、今回説明した内容で、JavaScript API for Office によるカスタム XML パーツ向けの便利な機能の概要をつかんでいただけると思います。

次回: メール アプリ

連載の 3 回目に当たる今回は、Office 用アプリでデータを操作する高度な手法をいくつか説明しました。データ バインドを使って Excel のテーブルにさらにインテリジェンスを追加する方法を確認しました。また、Word 用アプリでカスタム XML パーツを利用して、文書の自動作成をサポートする方法も見てきました。

この連載の最終回となる次回は、JavaScript API for Office をメール アプリに適用しながら、JavaScript API for Office について詳しく見ていきます。メール アプリでは、JavaScript API for Office 機能の独特な使い方を確認でき、アプリ開発者や Exchange 管理者がメールを操作するための強力なツールを構築できます。

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 (Microsoft)、Shilpa Kothari (Microsoft)、および Juan Balmori Labra (Microsoft) に心より感謝いたします。
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 の出荷に携わっていました。レドモンドに移籍するという夢がかなう前は、マイクロソフト メキシコの公共部門コンサルティング業の主席アーキテクトを務めていました。