大量データを表示する時に一部だけフェッチしたい
絞り込み前のデータを表示せざるを得ないリストなどでは、どうせ表示は一部しかできないので、表示に必要な部分だけをフェッチしておきたいものです。例えば1000件のアイテムのうち最初のフェッチは10件くらいでもいい時が多いですよね。
oj.Collection
Oracle JET を使っている場合 oj.Collection を利用することで簡潔に表現することができます。Cookbookにもoj.Collectionを使ったListViewの例などはありますが、リモートフェッチの詳細は書かれていません。
リモートフェッチがどのように制御されるかについては、oj.Collectionに書かれているものの、この記事を書いている時点ではサンプルがありません。
サンプル
ということで載せておきます。以下はHTML。ojListViewを使用しています。各アイテムはitemTemplateで表現されています。
<ul id="itemList" aria-label="item list using oj.collection" style="height:100%"
data-bind="ojComponent: {
component: 'ojListView',
data: itemDataSource,
item: {template: 'item_template'},
selectionMode: 'single',
rootAttributes: {style: 'width:100%;height:200px;overflow-x:hidden'},
scrollPolicy: 'loadMoreOnScroll',
scrollPolicyOptions: {fetchSize: 3}
}">
</ul>
<script type="text/html" id="item_template">
<li data-bind="attr: {id: itemId}">
<div class="oj-flex" style="flex-wrap: nowrap">
<div class="oj-flex-item">
<div>
<strong data-bind="text: itemName"/>
<span data-bind="text: itemDesc"/>
</div>
</div>
</div>
</li>
</script>
以下が対応するJavaScriptです。oj.Model.extend
の部分では、受信したデータに重複があった場合に重複排除ができるようにIDとなるフィールドを指定してあげています。
define(['ojs/ojcore', 'knockout', 'jquery', 'ojs/ojknockout',
'promise', 'ojs/ojlistview', 'ojs/ojcollectiontabledatasource', 'ojs/ojmodel'],
function (oj, ko, $) {
function ListViewModel() {
var self = this;
var itemModel = oj.Model.extend({
idAttribute: 'itemId'
});
var itemCollection = new oj.Collection(null, {
url: 'http://localhost:8080/api/item/list',
fetchSize: 3,
model: itemModel
});
self.itemDataSource = new oj.CollectionTableDataSource(itemCollection);
}
return new FilterViewModel();
}
);
itemDataSourceに基づいてojListViewが描画されます。このとき、fetchSizeが3なので、最初はhttp://localhost:8080/api/item/list?totalResults=true&limit=3&offset=0
というリクエストがサーバに送信されます。offset がゼロで limit が3ということは最初の3個ということですね。totalResults=true というのは、総件数を返してね!という意味です。
このリクエストに素直に次のような配列のみのJSONを返すとどうなるかというと・・・
[
{"itemId" : 1, "itemName" : "1番目", "itemDesc" : "1番目だよ"},
{"itemId" : 2, "itemName" : "2番目", "itemDesc" : "2番目だよ"},
{"itemId" : 3, "itemName" : "3番目", "itemDesc" : "3番目だよ"}
]
表示は正しくされるものの、残念ながらスクロールダウンしても4件目をフェッチしてくれることはありません。なぜなら、総件数を返していないからです。
ということで総件数をJSONで返したいのですが、その構造はどうしたらいいんだろう?となります。
実は、Arrayを返すと、oj.Collectionは暗黙的に次のように解釈しています。
{ "models" : [
{"itemId" : 1, "itemName" : "1番目", "itemDesc" : "1番目だよ"},
{"itemId" : 2, "itemName" : "2番目", "itemDesc" : "2番目だよ"},
{"itemId" : 3, "itemName" : "3番目", "itemDesc" : "3番目だよ"}
]}
modelsが追加されていますね。これは、oj.Collectionの構造を受け取るということです。ということで、次のように総件数である totalResults を入れて返すと ojListView などのコンポーネントは正しく解釈してくれます。
{ "totalResults" : 1000,
"models" : [
{"itemId" : 1, "itemName" : "1番目", "itemDesc" : "1番目だよ"},
{"itemId" : 2, "itemName" : "2番目", "itemDesc" : "2番目だよ"},
{"itemId" : 3, "itemName" : "3番目", "itemDesc" : "3番目だよ"}
]}
表示させた listView をスクロールさせると・・・
http://localhost:8080/api/item/list?totalResults=true&limit=3&offset=0
http://localhost:8080/api/item/list?totalResults=true&limit=3&offset=3
http://localhost:8080/api/item/list?totalResults=true&limit=3&offset=6
http://localhost:8080/api/item/list?totalResults=true&limit=3&offset=9
...
上のようにどんどんサーバー側にフェッチリクエストが来てくれます。めでたしめでたし。