この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
Indexed DBで検索結果をキャッシュする #2 の続きです。
実装(続き)2>
リストの描画
オブジェクトストアからデータを読み込み、画面に表示します。
//1ページに表示する件数
var RECORDS_IN_PAGE = 20;
//トランザクション開始 readonlyモード
var tx = db.transaction(STORE_NAME, 'readonly');
//トランザクションを介してオブジェクトストアを参照
var store = tx.objectStore(STORE_NAME);
//ソートに使用するインデックス名を画面から取得
var indexName = $('#sort').val();
//ページの位置を画面から取得
var fromNum = $('#page').val() * RECORDS_IN_PAGE;
//ソート項目に応じたカーソルを開く
var rq;
if(indexName) {
rq = store.index(indexName).openCursor();
} else {
rq = store.openCursor();
}
//現在のカーソル位置
var current = 0;
//tbody内に挿入するHTML
var rows = '';
$('personTable body').empty();
rq.onsuccess = function(e) {
cursor = rq.result;
if(cursor) {
if(current < fromNum) {
current = fromNum;
//ページの位置までカーソルを進める
cursor.advance(fromNum);
} else {
//カーソル位置のデータを取得し、HTMLを作成する
var data = cursor.value;
rows += '<tr data-key="' + data.id + '"><td>';
rows += data.id + '</td><td>';
rows += data.name + '</td><td>'
rows += data.nameKana + '</td><td>';
rows += data.zipcode + '</td></tr>\n';
current++;
if(current < RECORDS_IN_PAGE + fromNum) {
//カーソルを次に進める
cursor.continue();
} else {
//リストを描画
$('#personTable body').append(rows);
}
}
} else {
//リストを描画
$('#personTable body').append(rows);
}
};
[/javascript]
<p>
連続してデータを操作する場合、カーソルを使用します。カーソルは、オブジェクトストア自身またはそのインデックスから開きます。前者の場合、データはキーのフィールドでソートされます。後者の場合は対象のインデックスの対象フィールドでソートされます。
</p>
<p>
カーソルの移動は、カーソルオブジェクトの continue または advance メソッドを使用します。カーソルに設定された範囲と順序に応じて、一件または指定件数分カーソルが移動し、再度 onsuccess コールバックが呼び出されます。データの末尾に達した場合、result は null となります。
</p>
//次のレコードへ移動
cursor.continue();
//指定件数先のレコードへ移動
cursor.advance(件数);
注意すべき点として、これらの処理は非同期で実行されます。このため、カーソルを移動するたびに DOM の操作を行うような処理を記述してしまうと、カーソル移動に伴う onsuccess の度に繰り返し何度も画面の描画更新が発生し、描画パフォーマンスに悪影響が出る恐れがあります。上記の例では、1ページ分の繰り返し処理が終わるか、データの末尾に達して初めて DOM を操作しています。
また、トランザクションを readwrite モードで開始した場合、カーソルオブジェクトの update メソッドでデータの更新、delete メソッドでデータの削除ができます。
//カーソル位置のデータを更新
cursor.update({新しい値});
//カーソル位置のデータを削除
cursor.delete();
検索結果のフィルタ
検索結果を、氏名カナの前方一致でフィルタします。
$('find').on('click', function() {
var searchStr = $('nameKana').val();
//トランザクション開始 readonlyモード
var tx = db.transaction(STORE_NAME, 'readonly');
//トランザクションを介してオブジェクトストアを参照
var store = tx.objectStore(STORE_NAME);
//キーレンジの上限値文字列の生成(下限値+Unicodeの最後の文字)
var lastStr = searchStr + '\uFFFF';
//キーレンジ作成
var range = IDBKeyRange.bound(searchStr, lastStr);
//キーレンジを引数にカーソルを開く
var rq = store.index('byNameKana').openCursor(range);
$('personTable body').empty();
var rows = '';
rq.onsuccess = function(e) {
if(rq.result) {
//カーソル位置のデータを取得し、HTMLを作成する
var data = rq.result.value;
rows += '<tr data-key="' + data.id + '"><td>';
rows += data.id + '</td><td>';
rows += data.name + '</td><td>'
rows += data.nameKana + '</td><td>';
rows += data.zipcode + '</td></tr>\n';
//カーソルを次に進める
rq.result.continue();
} else {
//リストを描画
$personTableBody.append(rows);
}
}
});
氏名カナに作成したインデックスを使用して、前方一致検索を行います。前方一致検索のキーレンジを作成するには、入力された文字を下限値とし、その値の末尾に\uFFFFを付加した文字列を上限値とします。カーソルを進めてリストを描画する要領は先ほどと同じですが、カーソルが末尾に達するまでヒットした件数がわからないため、全件表示にしました。
また、今回使用した bound 以外にも、様々なキーレンジの指定方法があります。
//特定の値のみ
range = IDBKeyRange.only(値);
//下限
range = IDBKeyRange.lowerBound(下限値, 以上:false / 超える:true);
//上限
range = IDBKeyRange.upperBound(上限値, 以下:false / 未満:true);
//範囲
range = IDBKeyRange.bound(下限値, 上限値, 以上:false / 超える:true, 以下:false / 未満:true);
//昇順
durection = 'next';
//昇順(重複無し)
durection = 'nextunique';
//降順
durection = 'prev';
//降順(重複なし)
durection = 'prevunique';
rq = store.openCursor(range, direction);
詳細表示
クリックされた行のデータを取得し、全ての情報を詳細欄に表示します。キーが特定できるデータの取得は、カーソルを使用した場合と比べて簡単です。こちらを先に解説すべきなのですが、アプリケーションの構成上後回しになってしまいました。
$('#personTable tbody').on('click', 'tr', function() {
//トランザクション開始 readonlyモード
var tx = db.transaction(STORE_NAME, 'readonly');
//トランザクションを介してオブジェクトストアを参照
var store = tx.objectStore(STORE_NAME);
//イベント対象のTRノードからキーを取得
var key = $(this).attr('data-key');
//キーを元にデータを取得
var rq = store.get(parseInt(key));
rq.onsuccess = function() {
//データを詳細欄に表示
$('#lId').text(data.id);
$('#lName').text(data.name);
$('#lNameKana').text(data.nameKana);
$('#lSex').text(data.sex);
$('#lBloodType').text(data.bloodType);
$('#lBirthday').text(data.birthday);
$('#lZipcode').text(data.zipcode);
$('#lAddress').text(data.address);
$('#lAddressKana').text(data.addresskana);
}
});
Javascript は、== 演算子を使用すると(それが良いかどうかは別として)柔軟に型変換して比較を行ってくれますが、Indexed DB のキー値の比較に関しては、厳密な比較が行われます。今回、id の値は数値であるため、HTML の属性から取り出した String 値をそのまま get メソッドに渡しても、該当無しとなってしまうので注意が必要です。
接続の切断とデータベース削除
実際のアプリケーションでは、アクセスの度にデータベースを作り直す必要は無い場合が多いと思いますが、今回はサンプルアプリケーションなので、PC に余分なゴミが残らないよう、ウィンドウを閉じる際にデータベースを削除しておきます。Indexed DB のデータベースは、明示的に削除しない限りブラウザに永続的に残ります。
$(window).on('unload', function() {
if(db) {
db.close();
indexedDB.deleteDatabase(DB_NAME);
}
});
完成
これで完成です。実際に動かしてみます。
ページ遷移とソート順の変更が凄まじく速いですね。
まとめ
お気づきかとは思いますが、今回作成したアプリケーションの機能は、Indexed DB を使用しないで、配列操作のみで実現する事も可能です。しかし、Indexed DB を活用すると、一旦データをオブジェクトストアに格納した後は、全てのデータの配列をメモリ内に変数として保持する必要も無くなりますし、ソート順を変更するたびに実際に配列をソートする必要もありません。実際に動作させてみても分かる通り、非常に高速に動く事がわかります。工夫次第で、様々な用途に応用出来るのではないかと感じました。