Webアプリもオフライン実行? Indexed Databaseを使いこなそう:連載:人気順に説明する初めてのHTML5開発(2/2 ページ)
オフラインでもWebアプリ? それならブラウザ側でデータを管理できるKey-Value型簡易DB「Indexed Database」を使おう。
●データベースの初期化
最初は、IndexedDB(=データベース)にアクセスした際に実施する初期化の設定からだ。
……省略……
ページタイトル(キー)<input type="text" id="setkey" /><br />
ブックマークコメント(バリュー)<input type="text" id="setvalue" /><br />
ブックマークURL(バリュー)<input type="text" id="setvalue2" />
<input type="button" id="set" value="設定" /><hr />
取得したい値のキーを入力してください。<input type="text" id="getkey" />
<input type="button" id="show" value="値の取得" />
<input type="button" id="showIndex" value="アルファベットで始まるページタイトル一覧の取得" />
<ul id="list">
</ul>
<script type="text/javascript">
var version = "1.1";
var db;
// (1)IndexedDBの実装チェックとプレフィックスやイベント・ハンドラの登録
if (window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB) {
window.alert("このブラウザではindexedDB は利用できます");
var indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB;
var IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.mozIDBTransaction;
var IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.mozIDBKeyRange;
var IDBCursor = window.IDBCursor || window.webkitIDBCursor;
document.getElementById("set").addEventListener("click", setvalue, false);
document.getElementById("show").addEventListener("click", showvalue, false);
document.getElementById("showIndex").addEventListener("click", showindex, false);
// (2)IndexedDBにアクセス
var IDBreq = indexedDB.open("atmarkit");
} else {
window.alert("このブラウザではindexedDB は利用できません");
}
// IndexedDBアクセス成功時に呼び出されるコールバック関数
IDBreq.onsuccess = function (event) {
db = this.result;
// (3)IndexedDBのバージョン・チェック
if (version != db.version) {
// IndexedDBにバージョンをセット
var setVrequest = db.setVersion(version);
// バージョン・セット成功時に呼び出されるコールバック関数
setVrequest.onsuccess = function (event) {
// (4)オブジェクト・ストアの作成
var store = db.createObjectStore("Name", { keyPath: "BookmarkKey", autoIncrement: false });
}
}
}
……省略……
</script>
……省略……
(1)IndexedDBの実装チェックとプレフィックスやイベント・ハンドラの登録
IndexedDBの実装をチェックし、IndexedDBが実装されている場合はプレフィックスの差異を吸収するために、indexedDB/IDBTransaction/IDBKeyRange/IDBCursor変数に各IndexedDBの定義を設定している。また、set/show/showIndexボタンのクリック時のイベント・ハンドラ(=setvalue/showvalue/showindexメソッド。各メソッドの実装内容は後述)も、ここで設定している。
今回使用するIndexedDB関連のオブジェクトは、以下のとおりだ。
オブジェクト名 | 概要 |
---|---|
IDBCursor | オブジェクト・ストアで複数のレコードを検索する際に利用するオブジェクト |
IDBIndex | インデックスへアクセスする際に利用するオブジェクト |
IDBKeyRange | オブジェクト・ストアに対してまとめてデータを取得する際のキーの範囲を指定するオブジェクト |
IDBObjectStore | オブジェクト・ストアに対する処理を実施する際に利用するオブジェクト |
IDBRequest | データベースやオブジェクト・ストアへのアクセス結果を非同期で取得する場合のオブジェクト |
IDBTransaction | IndexedDBで扱うトランザクションの種類を定義し、トランザクションを生成するオブジェクト |
IndexedDBで利用する主なオブジェクト |
細かな使用方法は後述する。まだ、ブラウザ間の差異が大きい状況のため、実装チェックの中で(1)のような記載をするとよいだろう。
(2)IndexedDBにアクセス
IndexedDBにアクセスするオブジェクトも用意できたので実際にアクセスする。
IndexedDBのデータベースにアクセスするには、openメソッドを使用する。openメソッドの戻り値は、非同期APIで結果にアクセスできるIDBRequestオブジェクトだ。次のコードは、openメソッドの使用例だ。
var indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB;
var IDBreq = indexedDB.open("<DBの名前>");
サンプルでは、データベースの名前を「atmarkit」としている。
IDBRequestインターフェイスでは、以下のイベント・ハンドラを公開している。
イベントハンドラ | イベントトリガー |
---|---|
onsuccess | データベースのオープンが成功した際に発生 |
onerror | データベースのオープンが失敗した際に発生 |
IDBRequestインターフェイスのイベント・ハンドラ |
データベース・オープン以降の処理は、これらのイベント・ハンドラに記述するわけだ。なおonsuccessコールバック関数では、「this.result」プロパティ値(=データベース。IDBDatabaseオブジェクト)から、オープン成功時のIndexedDBのデータベース名(nameプロパティ)とデータベース・バージョン(versionプロパティ)を取得できる。
(3)IndexedDBのバージョン・チェック
オープンしたデータベースが、意図したバージョンのものかをチェックしている。
IndexedDBはWeb Storageよりも大きなデータのまとまりを取り扱うため、現在のスキーマ情報をバージョンで管理している。
よって、オブジェクト・ストアやインデックスの作成/削除も、バージョンの変更時にのみ実施できる。ここでは、現在のバージョンと想定したバージョン(サンプルでは「1.1」)が異なる場合にのみ、オブジェクト・ストアを作成するようにしているわけだ。openメソッドで初めてアクセスしたデータベース(サンプルでは「atmarkit」)の場合、そのデータベースにはまだ現在のバージョンが設定されていないため、初回は必ずオブジェクト・ストアが作成される。
バージョン設定の成功/失敗に対応して、それぞれonsuccess/onerrorイベント・ハンドラが用意されているので、オブジェクト・ストアやインデックスの作成/破棄の処理は、ここで行うことになる(サンプルでも、バージョン設定ができた場合のみ、オブジェクト・ストアとインデックスの作成を実施している)。
(4)オブジェクト・ストアの作成、ならびにインデックスの作成
データベースにアクセスできたとしても、肝心のオブジェクト・ストアがなければオブジェクトは保存できない。そのため、IndexedDBにアクセス後、最初にオブジェクト・ストアの作成が必要になる。これには、「this.result」プロパティ値(=データベース)のcreateObjectStoreメソッドを使用する。
var indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB;
var IDBreq = indexedDB.open("<DBの名前>");
IDBreq.onsuccess = function(event) {
var db = this.result;
db.createObjectStore("<オブジェクト・ストアの名前>",{ keyPath: <キーの名前>, autoIncrement: <インクリメントの利用の有無>});
}
autoIncrementプロパティには、キーのプロパティを自動でインクリメントするかどうかを、true/falseで指定する。
- autoIncrementプロパティが「true」で、in-line keyと併用する場合: keyPathプロパティの指定がなくとも、キーを自動生成する。
- autoIncrementプロパティが「true」で、out-of-line keyと併用する場合: オブジェクトの外部にキーを自動生成するので、データのほかにキーを渡す必要はない。詳しいコード例は後述する。
サンプルでは、オブジェクト・ストアの名前を「Name」に、keyPathプロパティ値を「BookmarkKey」に、autoIncrementプロパティ値を「false」に設定している。
以上でオブジェクト・ストアの作成は完了だ。
●データの追加と、キーを利用したデータの取得
続いて、実際に生成したデータベースに値を追加/取得するコードを見ていこう。
……省略……
ページタイトル(キー)<input type="text" id="setkey" /><br />
ブックマークコメント(バリュー)<input type="text" id="setvalue" /><br />
ブックマークURL(バリュー)<input type="text" id="setvalue2" />
<input type="button" id="set" value="設定" /><hr />
取得したい値のキーを入力してください。<input type="text" id="getkey" />
<input type="button" id="show" value="値の取得" />
<input type="button" id="showIndex" value="アルファベットで始まるページタイトル一覧の取得" />
<ul id="list">
</ul>
<script type="text/javascript">
……省略(前述の「IndexedDBサンプルにおけるデータベース初期化の記述例」のコード内容)……
// 値の保存時に呼び出されるコールバック関数
function setvalue(event) {
var key = document.getElementById("setkey").value;
var value = document.getElementById("setvalue").value;
var value2 = document.getElementById("setvalue2").value;
// (5)トランザクションを利用してオブジェクトの保存
var trans = db.transaction("Name", IDBTransaction.READ_WRITE);
var store = trans.objectStore("Name");
var data = { BookmarkKey: key, Comment: value, URL: value2 };
var request = store.put(data);
}
// 値の取得時に呼び出されるコールバック関数
function showvalue(event) {
var key = document.getElementById("getkey").value;
// (6)トランザクションを利用してオブジェクトの取得
var trans = db.transaction("Name", IDBTransaction.READ_WRITE);
var store = trans.objectStore("Name");
var req = store.get(key);
var ul = document.getElementById("list");
req.onsuccess = function (event) {
if (this.result === undefined) {
alert("そのキーで保存されているブックマークはありません");
} else {
ul.innerHTML += "<li><a href='" + this.result.URL + "' >[" + this.result.BookmarkKey + "]</a>" + this.result.Comment + "</li>";
}
}
}
</script>
……省略……
(5)トランザクションを利用してオブジェクトの保存
オブジェクト・ストアを参照/更新する場合は、常にトランザクション機能を利用する必要がある。具体的な利用方法は以下のとおりだ。
// トランザクションの開始
var trans = db.transaction("<オブジェクト・ストアの名前>", IDBTransaction.<トランザクション・モード>);
// オブジェクト・ストアのオープン
var store = trans.objectStore("<オブジェクト・ストアの名前>");
transactionメソッドは、トランザクションを開始するメソッドだ。第1パラメータにcreateObjectStoreメソッドで指定したオブジェクト・ストアの名前を、第2パラメータにトランザクション・モードを指定する。トランザクション・モードはIDBTransactionインターフェイスのメンバとして指定できる(下記の表を参照)。
モード | 概要 |
---|---|
READ_ONLY | 読み取り専用 |
READ_WRITE | 読み取りと書き込み |
VERSION_CHANGE | バージョン更新 |
IDBTransactionインターフェイスのトランザクション・モード |
上記の中で、VERSION_CHANGEモードだけは特殊な扱いとなる。VERSION_CHANGEモードはIndexedDBにsetVersionメソッドを使用したコールバック関数内で自動適用される。そのため、明示的にIDBTransactionインターフェイスから指定することはできない。
オブジェクト・ストアの取得は、トランザクションの操作対象となるobjectStoreメソッドで実施する。パラメータはオブジェクト・ストアの名前を設定するだけだ。戻り値はオブジェクト・ストアを操作するIDBObjectStoreインターフェイスのオブジェクトになる。
データを保存/参照/削除するメソッドは以下のとおりだ。
メソッド名 | 概要 |
---|---|
add(オブジェクト) | オブジェクトを保存 |
put(オブジェクト) | addメソッドと同様の機能を持つ |
remove(キー) | オブジェクトを削除 |
get(キー) | オブジェクトを取得 |
openCursor(レンジ, 検索方向) | 複数のデータを取得するためのカーソルを取得 |
IDBObjectStoreインターフェイスのメソッド |
具体的な利用方法(putメソッドの使用例)は、以下のとおりである。
// オブジェクト・ストアのオープン
var store = trans.objectStore("<オブジェクト・ストアの名前>");
var data = { <オブジェクト> };
var req = store.put(data);
サンプルでは、各テキストボックスに入力されたキー/バリューをそれぞれハッシュに詰め替えて、putメソッドで保存している。
なお、out-of-line keyを利用する場合は、以下のように記載する必要がある(太字部分がキーの指定)。
// autoIncrement有りで、inline-key利用時
// keyPath指定無し(キーは自動再生される)
var req = store.put({ name: "naoki", age: 29 });
// autoIncrement無しで、inline-key利用時
// keyPath指定: UserIDがキー
var req = store.put({ UserID: 1, name: "naoki", age: 29 });
// autoIncrement無しで、out-of-line key利用時
var req = store.put({ name: "naoki", age: 29 }, 1);
このように、out-of-line keyで保存する場合は、keyPathプロパティの指定は不要であるわけだ。
(6)トランザクションを利用してオブジェクトの取得
トランザクションの生成までは(5)と同様だ。(5)との違いは、データを取得するためにgetメソッドを使用している点だ。getメソッドは、キーをパラメータに設定することで、IDBRequestオブジェクトを戻り値として取得できる。レコードの各オブジェクトの取得は、onsuccessコールバック関数内で「this.result.<プロパティ名>」(例:「this.result.URL」)のように記述できる。
サンプルでは、図4のようにキーを基にデータを取得し、データに含まれるURLのリンクとコメントを表示している。
●インデックスの設定とカーソルによる検索
前項では、キーから値を取得したが、オブジェクト・ストア内にインデックスを設定し、カーソルを使用すると、オブジェクト・ストア内のデータをインデックス検索できる。
もし一度に複数のデータを取得したい場合、キーの名前が曖昧な場合などは、インデックスとカーソルを使うとよいだろう。カーソルとは、インデックスの範囲(レンジ)を指定して前方検索か後方検索する仕組みのこと(図5)。
サンプルでインデックスを利用したデータ取得の結果は、図6になる。
○インデックスの設定
まずは、最初にインデックスを設定する方法について解説する。先ほども述べたように、インデックスは、IndexedDBのバージョン設定変更に際してのみ作成/削除できる。
store.createIndex("<インデックス名>","<オブジェクト・ストアの名前>","<キー名>",
{ unique: <インデックスを一意のものにするかどうか(true/false)> });
インデックスは、オブジェクト・ストア作成の直後に作成するのが一般的だ。サンプルでは、以下のように記載している。
……省略……
// IndexedDBのバージョン・チェック
if (version != db.version) {
// IndexedDBにバージョンをセット
var setVrequest = db.setVersion(version);
// バージョン・セット成功時に呼び出されるコールバック関数
setVrequest.onsuccess = function (event) {
// オブジェクト・ストアの作成、並びにインデックスの作成
var store = db.createObjectStore("Name", { keyPath: "BookmarkKey", autoIncrement: false });
var index = store.createIndex("BookmarkKey", "BookmarkKey");
}
……省略……
サンプルでは、「BookmarkKey」オブジェクトに対してインデックスを作成している。createIndexメソッドの戻り値は、IDBIndexオブジェクト(=オブジェクト・ストアのインデックスに非同期でアクセスする機能を提供するオブジェクト)である。
○インデックスの利用
続いて、設定したインデックスを利用してみよう。カーソルを使用するパターンの1つとして、今回は、検索する範囲(=レンジ)を指定したうえで、データを取得してみよう。実際のコードは以下のとおりだ。
……省略……
// インデックスを使用した値の取得
function showindex(event) {
// (7)レンジの作成
var range = IDBKeyRange.bound("A","Z");
var trans = db.transaction("Name", IDBTransaction.READ_WRITE);
var store = trans.objectStore("Name");
// (8)インデックスからデータを検索し取得
var req = store.index("BookmarkKey").openCursor(range, IDBCursor.NEXT);
var ul = document.getElementById("list");
req.onsuccess = function (event) {
if (this.result === undefined) {
// データが無い場合の処理
} else {
ul.innerHTML += "<li><a href='" + this.result.value.URL + "'>[" + this.result.value.BookmarkKey + "]</a>" + this.result.value.Comment + "</li>";
console.log(this.result);
// 次の検索結果でonsuccessコールバック関数を呼び出す
this.result.continue();
}
}
}
……省略……
(7)レンジの作成
インデックスを利用したデータ取得には、IDBKeyRangeインターフェイスを使用する。IDBKeyRangeインターフェイスは、以下のメソッドを公開している
メソッド名 | 概要 |
---|---|
bound(最小値のキー, 最大値のキー) | 指定された範囲内のキー・レンジを生成 |
only(値) | 値を含む、新しいキー・レンジを生成 |
lowerBound(bound) | 下限だけでキー・レンジを生成 |
upperBound(bund) | 上限だけでキー・レンジを生成 |
IDBKeyRangeインターフェイスのメソッド |
具体的な利用方法は以下のとおりだ。
// キー・レンジを生成
var range = IDBKeyRange.bound(<最小値のキー>, <最大値のキー>);
サンプルでは、アルファベットの大文字「A」〜「Z」で始まるデータの範囲をboundメソッドで指定している。
(8)インデックスからデータを検索して取得
インデックスからデータをまとめて取得する場合は、IDBIndexインターフェイスのopenCursorメソッドを使用する。IDBIndexオブジェクトは、IDBObjectStoreインターフェイスのindexメソッドにインデックス名を指定することで取得できる。IDBIndexインターフェイスで定義されている主なメソッドは以下のとおりだ。
メソッド | 概要 |
---|---|
get(キーの値) | 指定されたキーの値に相当するオブジェクト・ストアを返す。複数ある場合には最初のオブジェクトを返す |
openCursor(キーの範囲, 並び順) | 指定されたキーの範囲でのインデックスの値を参照できるカーソルを返す |
IDBIndexインターフェイスの代表的なメソッド |
具体的な利用方法は以下のとおり。
// インデックスを利用したデータ取得
var req = store.index(<インデックス名>).openCursor(<キーの範囲>, <並び順>);
openCursorメソッドの第1パラメータには(7)で用意したIDBKeyRangeオブジェクトを、第2パラメータには、IDBCursorインターフェイスのメンバ(=定数値)を、それぞれ指定する。指定できる値は以下のとおりだ。
設定値 | 概要 |
---|---|
NEXT | 昇順 |
NEXT_NO_DUPLICATE | 昇順で重複なし |
PREV | 降順 |
PREV_NO_DUPLICATE | 降順で重複なし |
並び順で指定できる、IDBCursorインターフェイスの定数値 |
openCursorメソッドの戻り値はIDBRequestオブジェクトとなり、インデックスによる検索結果を1件ずつ返してくれる。次のレコードを取得したい場合は、「this.result」プロパティ値(=カーソル。IDBCursorWithValueオブジェクト)のcontinueメソッドを呼び出すことで、次のレコードでまたonsuccessコールバック関数が呼び出される仕組みだ。レコードの各オブジェクトの取得は、(onsuccessコールバック関数内で)「this.result.value.<プロパティ名>」(例:「this.result.value.URL」)のように記述できる。
■まとめ
以上が、IndexedDBの基本機能となる。Web Storageに比べると、「オブジェクトの集中管理」「トランザクションの利用」「インデックスを利用して、指定範囲内のデータをまとめて取得」といった機能がIndexedDBでは利用できることを確認いただけたのではないだろうか。Web Storageとの使い分けも勘案しつつ、適材適所で活用していただきたい。ただし、記事本文にも書いたようにIndexedDBはラスト・コール前の段階(=ワーキング・ドラフト)であるため、仕様変更がまだ発生する可能性が十分にある点には注意が必要だ。
本連載は、今回で最終回を迎える。本連載はモバイル開発において活用されているHTML5の人気機能を、今回を含めて計8回にわたって紹介してきた。
順位 | HTML5の機能 | 機能概要 |
---|---|---|
1 | Geolocation(1回) | 位置情報の取得 |
2 | Web Storage(2回) | クッキーよりも高機能なデータ保存の仕組み |
3 | ドラッグ&ドロップ(3回) | マウスなどを使用したドラッグ&ドロップ |
4 | セレクタ API(4回) | CSSセレクタを使用して要素にアクセス |
5 | WebSocket(5回) | サーバとブラウザ間での双方向通信 |
6 | ファイル API(6回) | ローカルのファイルを操作 |
7 | Web Workers(7回) | バックグラウンドでJavaScriptコードの並列実行 |
8 | Indexed Database(8回) | ブラウザ側上で利用できるキー/バリュー型のデータベース |
現時点で活用されているHTML5の人気機能 |
前半の1〜4回までの内容は、現時点で提供されているブラウザではほぼ実装されており、すぐにでも活用できたのが特徴だ。位置情報の取得やデータ保存、ユーザビリティ向上の操作性や新たなDOM操作の構文――これらを活用するだけでも、今までより便利なアプリケーションを構築できるだろう。
後半の5〜8回では、HTML5の中でも先進的な機能を中心に紹介してきた。リアルタイムWebを実現するWebSocket、ファイルの読み書きを実施するFile API、同期を取りJavaScriptファイルを並列実行するワーカー、そして今回紹介したインデックスやトランザクション機能を持つIndexedDB。これらは今年からより多くの場で活用され始めるだろう。ただし、ラスト・コール前の段階であるIndexedDBはまだもう少しだけ冷静に動向を見つめておくといいだろう。
多くのブラウザでHTML5の実装が進むであろう今年は、間違いなくHTML5の黎明期(れいめいき)になるだろう。本連載が読者諸氏にとってHTML5の入門の役に立てれば幸いだ。
Copyright© Digital Advantage Corp. All Rights Reserved.