SharePoint アドイン製品一覧
SharePoint 2010 開発のステップ・バイ・ステップ
Windows Azure 入門
Windows Azure How-To 集
WCF / WF 入門
環境 :Visual Studio 2010jQuery 1.5.1datajs 1.0.2
こんにちは。
これまでの WCF Web Api では "作る側" を紹介しましたが、今回は、使う側 (Consumer) を紹介したいと思います。エバンジェリスト 井上 (大輔) も以前 記載していた datajs について紹介します。
datajs は、OData サービス (Web Api) を扱える JavaScript ライブラリーです。しかし、OData は RESTful なサービスなので、ブラウザー (JavaScript) から使用するなら jquery の jQuery.ajax ($.ajax)、jQuery.getJSON でも充分です。では、datajs を使う意義は何でしょうか ? (決して、jQuery とケンカしてしまうようなライブラリーではありません。jQuery との親和性もバッチリです !)そこで、まず基本的な使い方を紹介し、後半で、こうした datajs を使うメリットについて紹介したいと思います。
datajs の基礎
まずは、基本的な使い方についてです。
OData のサービスに接続 (データを取得) する際は、井上も記載している通り、OData.read か OData.request を使います。
補足 : なお、OData.read でも、ヘッダーなど (Accept ヘッダー、Cache コントロール、など)、リクエストの簡単なカスタマイズは可能です。
実際、OData.read は、内部で、OData.request を呼び出しています。いずれも、第一引数には、下記の構成の Request オブジェクト (下記の JSON オブジェクト) を渡しますが、OData.read では URI の文字列をそのまま渡すこともできます。
{ requestUri: "http://myserver/test.svc/Datas" }
{ requestUri: "http://myserver/test.svc/Datas", method: "GET"}
{ requestUri: "http://myserver/test.svc/Datas", method: "POST" data: { ...省略 }}
例えば、下記の通り記述すると、/DatajsSample/TestService.svc/Customers の OData サービス (Web Api) からデータを取得して、table に結果の一覧を表示します。(データは、内部で json フォーマットが使用されます。)
. . .<table border="1"> <thead> <tr> <th> Id </th> <th> Name </th> </tr> </thead> <script id="template1" type="text/x-jquery-tmpl"> <tr> <td> ${CustomerId} </td> <td> ${Name} </td> </tr> </script> <tbody id="tableData1"> </tbody></table><script type="text/javascript"> function loadData() { $('#tableData1').empty(); OData.read('/DatajsSample/TestService.svc/Customers', function (data) { $("#template1").tmpl(data.results).appendTo("#tableData1"); }, function (err) { alert(err.message); }); } $(document).ready(loadData);</script>. . .
補足 : 上記を動かすには、あらかじめ、CodePlex から datajs-1.0.2.min.js をダウンロードして、Script 参照 (src) を追加しておいてください。(ついこの前まで、バージョン 1.0.1 でしたが、頻繁にバージョン アップしているみたいです。。。)また、上記では、jquery の Templates プラグインも使用しています。
補足 : エラー「プロパティ parse の値を取得できません: オブジェクトは Null または未定義です。」が表示される場合は、下記の通り、スクリプトの参照を追加してください。こちら に記載した通り、Internet Explorer 7 以前や、Internet Explorer 8 以上でも Compatibility Mode で実施されている場合、JSON オブジェクト (JSON.parse、JSON.stringify などで使用) が参照できず、このエラーが発生します。<script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/json2/20110223/json2.js"></script>
もちろん、データの更新も可能です。(ここでは省略しますが、井上 大輔のブログ を参照してください。)
また、下記の通り、JSONP に対応したクロス ドメインの呼び出しも、1 行の追加で可能です。
補足 : 下記を動作させる前に、クロス ドメイン呼び出しが可能 ($callback が使用可能) になるように、以前、「ブラウザーからのクロス ドメイン接続 (JSONP) と SSL」 に記載した方法で、サービス側 (WCF Data Services など) を構成しておいてください。
. . .<script type="text/javascript"> OData.defaultHttpClient.enableJsonpCallback = true; function loadData() { $('#tableData1').empty(); // 別のドメインのサービスを呼び出し OData.read('http://other_domain/DatajsSample/TestService.svc/Customers', function (data) { $("#template1").tmpl(data.results).appendTo("#tableData1"); }, function (err) { alert(err.message); }); } $(document).ready(loadData);</script>. . .
また、OData.read, OData.request では、Basic 認証 (Basic authentication) を使用して OData サービス (Web Api) に ID / パスワードを渡して処理することもできます。(ただし、jsonp の場合は、もちろん、不可能です。)
また、OData 特有の $select、$filter などのパラメーターは、基本的に URI でそのまま渡します。(例えば、http://myserver/test.svc/Datas?$top=3 といった具合です。) 例えば、OData では、$metadata を使って、エンティティのメタ情報 (フィールド名、型、など) を取得できるため、下記の通り記述すると、エンティティ名 (Cutomers など)、プロパティ名 (CustomerId など)、プロパティの型 (Edm.String, Edm.Int など) の情報を表示できます。
function getMetadata() { OData.read('/DatajsSample/TestService.svc/$metadata', function (data) { $.each(data.dataServices.schema[0].entityType, function (i1, val1) { alert('Entity = ' + val1.name); $.each(data.dataServices.schema[0].entityType[i1].property, function (i2, val2) { alert(val2.name + ' is ' + val2.type); }); }); }, function (err) { alert('failure'); alert(err.message); }, OData.metadataHandler);}
また、リレーションも、同様に、$expand、$links などを使って処理できます。
補足 : 例えば、Orders と Customers の間にリレーションが張られている場合、http://myserver/DatajsSample/TestService.svc/Orders?$expand=Customers のような形式で Orders 以下の Customers が展開できます。また、Orders の 3 番目のエンティティと関連している Customers の Uri を取得する場合は、http://myserver/DatajsSample/TestService.svc/Orders(3)/$links/Customers で取得できます。
Batch 処理
と、ここまでなら、上述の通り、jquery を使えば充分でしょう。(わざわざ、こんなマイナーなライブラリーを使う必要はありません。)ここからは、OData が持つさまざまな仕様を datajs を使ってフル活用してみます。
まず、OData プロトコルでは、MIME のマルチパート (multipart) を使用して、複数の要求 (Request) を単一の HTTP リクエストで処理できる Batch と呼ばれる要求が可能です。(同様に、Response も Multipart で返ってきます。)
補足 : Batch リクエストは、ラウンド トリップを減らす目的で使用できますが、必ずしも、トランザクションを扱うものではあません。Batch でトランザクションを扱えるようにするかどうかは、サービスの実装に依存します。(例えば、Batch の 1 番目の更新要求に成功し、2 番目の更新要求に失敗しても、1 番目の要求がロールバックされるどうかはサービスの実装に依存します。)なお、WCF Data Services では、開発者が、サービス側の DataServiceProcessingPipeline.ProcessingChangeset イベント、DataServiceProcessingPipeline.ProcessedChangeset イベントを処理することで、トランザクション処理を実装することができるようです。(すみません、私自身、そういうコードを実装した経験はないですが。。。)
補足 : 同様に、OData の仕様では、Batch 処理時の実行順序についても、サービスの実装に依存します。(必ずしも、並べた順番で処理が実行されるとは限りません。順序実行、並行実行などは、サービスの実装に依存します。)
この OData の Batch 要求も、下記の通り、datajs を使って簡単に処理できます。(下記では、Customer と Orders の双方のエンティティを 1 回の Request で更新しています。)
function changeData() { OData.request({ requestUri: '/DatajsSample/TestService.svc/$batch', method: 'POST', data: { __batchRequests: [ { __changeRequests: [ { requestUri: 'Customers(1)', method: 'MERGE', data: { Name: '日本マイクロソフト'} }, { requestUri: 'Orders(2)', method: 'MERGE', data: { Name: '日本マイクロソフトからの発注', Price: 2000000} } ] } ] } }, function (data, response) { alert('success'); $.each(data.__batchResponses[0].__changeResponses, function(i, val) { alert('Status Code : ' + val.statusCode); }); }, function (err) { alert('failure'); alert(err.message); }, OData.batchHandler);}
補足 : 上記のコールバック関数の引数 response には、この Multipart の要求自体のレスポンス (全体の結果) が入っており、引数 data には、Multipart のそれぞれの結果が入っていま��。(なお、response.data には、引数 data と同じ結果が入っています。)Multipart の処理自体 (全体) は成功しても、それぞれの処理でエラーとなっている可能性があるため、response.statusCode の確認だけでなく、それぞれの data.__batchResponses[0].__changeResponses[i].statusCode (i = 0, 1, ...) の結果も確認しておきましょう。
ページング (Paging) とプリフェッチ (Prefetch)
レコード形式のデータを扱ってページングの機能を実現する場合、例えば、jQuery の dataTables プラグインなどを使ってページングを実現できますが、この場合でも、データ量が大量なケースでは、サーバー サイドの実装と連携することも考慮しなければなりません。OData の場合は、こうしたサーバー サイドのページングを実現する際に、$top、$skip などを使って、「データの X 番目から Y 番目を取得」といったことが可能ですが、datajs ライブラリー (及び、jQuery の Deferred) と組み合わせることで、こうしたサーバー サイドの仕組みと連携した動作を、エレガントに実装できます。
まず、datajs.createDataCache 関数を実行すると、データのフェッチや保管の処理をおこなう管理オブジェクト (cache オブジェクト) が生成されます。この際、初期化の引数として、json 形式で、データを取得する先の URI、ブラウザーに保管しておく最大キャッシュ サイズ (byte 数)、あらかじめプリフェッチをおこなうサイズ (データ件数) などを指定できます。(使用可能なプロパティの詳細については、こちら のドキュメントを参照してください。)なお、データをブラウザー側に保持しておく方法 (メカニズム) については、以下の 3 種類から指定可能です。何も指定しないと、既定で、DOM Storage が使用できるブラウザーでは DOM Storage が使用され、使用できないブラウザーではインメモリー (In-memory) が使用されます。(Indexed DB は、まだ実装していないブラウザーがほとんどだと思うので、実験的に用意されています。大量データを扱う場合に向いています。)
つぎに、作成 (初期化) された cache オブジェクトの readRange 関数を実行して、指定された範囲のデータを (OData サービスから) 取得できます。この際、データの取得を同期的におこなって結果を処理するのではなく、jQuery の Deferred を使用して、非同期に (バック グラウンドに) 処理をおこなって、データが揃った段階で指定された処理を実行できます。(予約できます。)
実際のコードを見てみましょう。例えば、以下のサンプル コードでは、ページのロードと共に、非同期で 300 件のデータが読み込まれ、ページ (HTML) 上にはそのうちの最初の 10 件が表示されます。内部では、10 件ずつ、合計 30 回の呼び出しが非同期におこなわれ、最初の 10 件が揃った段階でデータが表示されます。(下記の通り、readRange 関数の戻り値として Deffred オブジェクト (promises) が返ってくるので、then で処理を定義しています。)
. . .<table border="1"> <thead> <tr> <th> Id </th> <th> Name </th> <th> Price </th> </tr> </thead> <script id="template1" type="text/x-jquery-tmpl"> <tr> <td> ${OrderId} </td> <td> ${Name} </td> <td> ${Price} </td> </tr> </script> <tbody id="tableData1"> </tbody></table><script type="text/javascript"> var opt = { name: "testCache", source: '/DatajsSample/TestService.svc/Orders', pageSize: 10, prefetchSize: 300, cacheSize: -1 }; var cache = datajs.createDataCache(opt); var index = 0; function nextData() { cache.readRange(index, 10).then(function (results) { $('#tableData1').empty(); $("#template1").tmpl(results).appendTo("#tableData1"); }); index += 10; } $(document).ready(nextData);</script><input type="button" value="Next" onclick="javascript:nextData();" />. . .
「Next」ボタンを押すと、あらかじめ Prefetch されている次の 10 件 (11 件目から 20 件目のデータ) が表示され、バックエンドでは、非同期に、301 件目から 310 件目までのデータがロードされ、常に、300 件のデータが Prefetch された状態でブラウザー上に保持 (キャッシュ) されます。Fiddler などでキャプチャーしていただくとわかりますが、この際、サービスには、下記のような $skip、$top を使用した呼び出しがおこなわれています。
http://myserver/DatajsSample/TestService.svc/Orders?$skip=20&$top=10(21 件目から 30 件目のデータを取得する場合)
補足 : なお、上述の通り、データは (ブラウザー再表示後も) DOM Storage にキャッシュされています。(以降は、このキャッシュされたデータが使用されます。) このため、キャッシュを削除して新規にデータを取り直すには、以下の通りクリアをおこなってください。cache.clear();
また、jQuery の dataTables プラグインなどのように、フィルター機能と組み合わせることもできるようになっています。例えば、下記のサンプル コードでは、OrderId が偶数のデータのみを抽出し、10 件ずつ表示をおこないます。([Next] ボタンを押すと、次の偶数データが 10 件 表示されます。)
. . .<script type="text/javascript"> var opt = { name: "testCache", source: '/DatajsSample/TestService.svc/Orders', pageSize: 10, prefetchSize: 300, cacheSize: -1 }; var cache = datajs.createDataCache(opt); var index = 0; function nextData() { cache.filterForward(index, 10, function (item) { return (item['OrderId'] % 2 == 0); }).then(function (results) { $('#tableData1').empty(); $("#template1").tmpl($.map(results, function (element) { return element.item })).appendTo("#tableData1"); index = results[9].index + 1; }); } $(document).ready(nextData);</script><input type="button" value="Next" onclick="javascript:nextData();" />. . .
filterForward(index, count, func) は、データの index 番目から 順方向に データを func で判定し、count 個になるまでフィルターされたデータを検索して取得します。一方、逆方向に フィルターする場合は、filterBack 関数を使用します。
補足 : 前述の readRange では、results (上記) に抽出結果のアイテムの配列が入ります。しかし、上記の filterForward では、results[i].item (i = 0, 1, ...) で各アイテムを取得します。このため、上記の通り、jquery.map を使用しています。また、filterForward メソッドの第 1 引数にアイテムの開始インデックスを指定する必要がありますが、results[i].index (i = 0, 1, ...) によって、現在取得されている各アイテムのインデックスが取得できるため、上記コードの通り、これを使用して、次の開始インデックスを指定できます。
なお、これら filterForward、filterBack によるフィルター処理は、クライアント サイド (ブラウザー側) でおこなわれるので注意してください。(サーバー サイドのフィルターは、従来通り、$filter などを使用する必要があります。)
また、上記の cache オブジェクトが内部で使用するストア (store) ですが、このストアのみを単独で使用する API も提供されています。この store API については、以下に記述されていますので、参考にしてみてください。(ここでは、説明を省略します。)
[CodePlex] datajs store API :http://datajs.codeplex.com/wikipage?title=datajs%20store%20API