From 33058f2b292b3a581333bdfb21b8f671898c5060 Mon Sep 17 00:00:00 2001 From: Peter Bengtsson Date: Tue, 8 Dec 2020 14:40:17 -0500 Subject: initial commit --- .../basic_concepts_behind_indexeddb/index.html | 217 ++++ .../index.html | 134 ++ files/ja/web/api/indexeddb_api/index.html | 159 +++ .../api/indexeddb_api/using_indexeddb/index.html | 1334 ++++++++++++++++++++ .../using_indexeddb_in_chrome/index.html | 33 + 5 files changed, 1877 insertions(+) create mode 100644 files/ja/web/api/indexeddb_api/basic_concepts_behind_indexeddb/index.html create mode 100644 files/ja/web/api/indexeddb_api/browser_storage_limits_and_eviction_criteria/index.html create mode 100644 files/ja/web/api/indexeddb_api/index.html create mode 100644 files/ja/web/api/indexeddb_api/using_indexeddb/index.html create mode 100644 files/ja/web/api/indexeddb_api/using_indexeddb_in_chrome/index.html (limited to 'files/ja/web/api/indexeddb_api') diff --git a/files/ja/web/api/indexeddb_api/basic_concepts_behind_indexeddb/index.html b/files/ja/web/api/indexeddb_api/basic_concepts_behind_indexeddb/index.html new file mode 100644 index 0000000000..d0b39af1e0 --- /dev/null +++ b/files/ja/web/api/indexeddb_api/basic_concepts_behind_indexeddb/index.html @@ -0,0 +1,217 @@ +--- +title: 基本的な概念 +slug: Web/API/IndexedDB_API/Basic_Concepts_Behind_IndexedDB +tags: + - Advanced + - IndexedDB + - concepts +translation_of: Web/API/IndexedDB_API/Basic_Concepts_Behind_IndexedDB +--- +

{{DefaultAPISidebar("IndexedDB")}}

+ +
+

IndexedDB は、ユーザーのブラウザー内にデータを永続的に保存する手段です。ネットワークの状態にかかわらず高度な問い合わせ機能を持つ ウェブアプリケーションを作成できますので、オンラインとオフラインの両方で動作するアプリケーションになります。IndexedDB は大量のデータを保存するアプリケーション (例えばレンタル店の DVD カタログ) や、動作するために持続的なインターネット接続が不要なアプリケーション (例えばメールクライアント、To-Do リスト、メモ帳) で役に立ちます。

+
+ +

このドキュメントについて

+ +

この概論では、IndexedDB の本質的な概念や用語について論じます。これにより全体像を示すとともに、重要な概念を説明します。

+ +

以下の役に立つ項目があります:

+ + + +

IndexedDB の概要

+ +

IndexedDB では、"キー" でインデックス付けされたオブジェクトを保存および取り出すことができます。データベースに対して施したすべての変更は、トランザクションで発生します。たいていの Web ストレージ技術と同様に、IndexedDB も同一生成元ポリシーに従います。よって、保存済みデータは同一ドメイン内からアクセスできますが、異なるドメインにまたがってデータへアクセスすることはできません。

+ +

IndexedDB は、Web Workers を含むほとんどの状況で使用できる非同期 API です。以前は Web Workers で使用するための同期 API も含まれていましたが、Web コミュニティが無関心であったために仕様から削除されました。

+ +

IndexedDB と競合する仕様である WebSQL データベースがありましたが、W3C は 2010 年 11 月 18 日に非推奨にしました。IndexedDB も WebSQL もデータ保存の技術ですが、提供する機能が異なります。WebSQL データベースはリレーショナルデータベースシステムであるのに対して、IndexedDB はインデックス付きのテーブルシステムです。

+ +

重要な概念

+ +

ほかのタイプのデータベースを扱った経験から想定していることがあるなら、IndexedDB を扱うときにはそれを捨てましょう。そして、以下の重要な概念を覚えておいてください:

+ + + +

定義

+ +

この章では、IndexedDB API で使用する用語について定義および説明します。

+ +

データベース

+ +
+
データベース (database)
+
主に 1 個以上のオブジェクトストアで構成される、情報のリポジトリです。それぞれのデータベースが以下のものを持ちます: +
    +
  • 名称。これは特定の生成元に所属するデータベースを識別しており、データベースの存続期間を通じて不変です。名称は任意の文字列値です (空文字列を含む)。
  • +
  • +

    現在のバージョン。始めにデータベースを生成したとき、バージョンは特に指定しない限り整数の 1 になります。それぞれのデータベースは、任意の時点でバージョンを 1 つだけ持ちます。

    +
  • +
+
+
永続性 (durable)
+
+

以前の Firefox では、IndexedDB に永続性がありました。つまり、読み書きトランザクションにおいて、{{domxref("IDBTransaction.oncomplete")}} が発生するのは、すべてのデータがディスク上に実際に書き込まれている(フラッシュされている)ことが保証されたときに限ります。

+ +

Firefox 40 以降、IndexedDB トランザクションはパフォーマンスを向上させるために、永続性の保証を緩和しました ({{Bug("1112702")}} を参照)。これは IndexedDB をサポートする他のブラウザーと同じ動作です。この場合、{{Event("complete")}} イベントは OS がデータの書き込みを指示した時点で発生しますが、実際にはデータがディスク上に反映されていない可能性があります。これによりイベントをより早く発生させられますが、データをディスク上に反映する前に OS のクラッシュや電源断が発生するとトランザクション全体を失う危険性が若干あります。このような破壊的な事象はまれですので、ほとんどの利用者は心配する必要がないでしょう。

+ +
+

注記: Firefox では、何らかの理由 (例えば、後で再計算できない重要なデータを保存する) で永続性を保証したい場合は、実験的 (非標準) な readwriteflush モード ({{domxref("IDBDatabase.transaction")}} を参照) を使用してトランザクションを生成すると、complete イベントを発生させる前にディスクへの反映を強制させることができます。これは現在実験的な扱いであり、about:configdom.indexedDB.experimentaltrue に設定した場合に限り使用できます。

+
+
+
オブジェクトストア (object store)
+
+

データベースにデータを保存する仕組みです。オブジェクトストアはレコードを持続的に保持しており、これはキーと値のペアです。オブジェクトストア内のレコードは、キーによって昇順に整列して保存されています。

+ +

すべてのオブジェクトストアは、データベース内で一意な名称を持たなければなりません。オブジェクトストアは、任意でキージェネレーターキーパスを持つことができます。オブジェクトストアがキーパスを持つ場合は、インラインキーを使用します。それ以外の場合は、アウトオブラインキーを使用します。

+ +

オブジェクトストアのリファレンスドキュメントとして、{{domxref("IDBObjectStore")}} をご覧ください。

+
+
バージョン (version)
+
始めにデータベースを生成したとき、バージョンは整数の 1 になります。それぞれのデータベースは、一度に 1 つのバージョンを持ちます。一度に複数のバージョンを持つことはできません。バージョンを変更する唯一の方法は、現在のバージョンより大きなバージョンでデータベースを開くことです。これは versionchange トランザクションを開始して、upgradeneeded イベントが発生します。データベースのスキーマを更新できる唯一の場所が、このイベントのハンドラ内です。
+
注記: この定義は直近の仕様書で説明されており、最新のブラウザーのみ実装しています。古いブラウザーは非推奨かつ削除済みの IDBDatabase.setVersion() メソッドを実装しています。
+
データベース接続 (database connection)
+
データベースを開くことで生成される操作です。データベースは同時に複数の接続を持つことができます。
+
トランザクション (transaction)
+
+

特定のデータベースで行う、原子性を持つデータアクセスやデータ変更の操作のセットです。これは、データベース内のデータと対話する手段です。実際は、データベース内のデータの読み取りや変更はトランザクション内で実施しなければなりません。

+ +

書き込みトランザクションのスコープが重ならない限り、ひとつのデータベース接続で同時に複数のアクティブなトランザクションが存在できます。トランザクションのスコープは生成時に定義され、トランザクションがどのオブジェクトストアと対話できるかや、トランザクションの持続期間にわたって保持し続けるかを示します。よって例えば、データベース接続で flyingMonkey オブジェクトストアのみ対象とするスコープを持つ書き込みトランザクションがすでに存在するとき、unicornCentaur オブジェクトストアや unicornPegasus オブジェクトストアをスコープで持つ別のトランザクションを開始できます。読み取りトランザクションは、スコープが重なっていても複数実行できます。

+ +

トランザクションは持続期間が短いものを除き、長時間のトランザクションがストレージ資源をロックする状況から解放するために、ブラウザーが終了させることができます。トランザクションは中止させることができ、トランザクションによるデータベースの変更箇所はロールバックされます。また、開始するトランザクションや中止するトランザクションを待つ必要はありません。

+ +

トランザクションには readwritereadonlyversionchange の 3 つのモードがあります。オブジェクトストアやインデックスの生成および削除は、versionchange トランザクションを使用する場合に限り実行できます。トランザクションのタイプについて詳しくは、IndexedDB のリファレンスをご覧ください。

+ +

すべての操作はトランザクション内で発生しますので、トランザクションは IndexedDB の重要な概念です。トランザクションについて、特にバージョニングとの関係については、{{domxref("IDBTransaction")}} および関連ドキュメントをご覧ください。ここにリファレンスドキュメントもあります。

+
+
リクエスト (request)
+
データベースの読み書きを実施する操作です。すべてのリクエストは、ひとつの読み取りまたは書き込みの操作を表します。
+
インデックス (index)
+
+

インデックスは参照先オブジェクトストア (referenced object store)から呼び出されて、別のオブジェクトストアのレコードを検索するための特別なオブジェクトストアです。インデックスは持続的なキーと値のストレージであり、インデックスのレコードの値は、参照先オブジェクトストアのレコードのキーです。インデックス内のレコードは、参照先オブジェクトストアでレコードが挿入、更新、削除されるたびに、自動的に収集されます。インデックス内の各レコードは参照先オブジェクトストア内のレコードをひとつだけ示すことができますが、複数のインデックスが同一のオブジェクトストアを参照することもできます。オブジェクトストアが変更されると、そのオブジェクトストアを参照するすべてのインデックスが自動的に更新されます。

+ +

代わりに、キーを使用してオブジェクトストア内のレコードを検索することもできます。

+ +

インデックスの使用法について詳しくは、IndexedDB を使用するをご覧ください。インデックスのリファレンスドキュメントとして、IDBKeyRange をご覧ください。

+
+
+ +

キーと

+ +
+
キー (key)
+
+

オブジェクトストアに保存された値は、このデータ値によって編成および取り出しされます。オブジェクトストアはキージェネレーターキーパス、明示的に指定した値の、3 種類の生成源のいずれかからキーを得られます。キーは、自身の前にあるものより大きな数値を持つデータ型であることが必要です。オブジェクトストア内の各レコードはオブジェクトストア内で一意のキーを持たなければならず、オブジェクトストア内で複数のレコードが同じキーを持つことはできません。

+ +

キーは 文字列date、浮動小数点数値、配列のいずれかの型を使用できます。配列では、キーは空の値から無限大までの範囲を使用できます。また、配列の中に配列を含めることができます。文字列または整数値のキーしか使用できないという条件はありません{{fx_minversion_inline("11")}}。

+ +

代わりに、インデックスを使用してオブジェクトストア内のレコードを検索することもできます。

+
+
キージェネレーター (key generator)
+
指定した順序で新たなキーを生成する仕組みです。オブジェクトストアがキージェネレーターを持たない場合は、保存するレコードのキーをアプリケーションが提供しなければなりません。ジェネレーターはストア間で共有しません。これはむしろブラウザーの実装の細部であり、Web 開発において実際にはキージェネレーターの生成やアクセスは行いません。
+
インラインキー (in-line key)
+
保存される値の一部として保存されるキーです。これはキーパスを使用して見つけます。インラインキーは、ジェネレーターを使用して生成できます。キーが生成されると、キーパスを使用してキーを値の中に保存したり、キーとして使用したりすることができます。
+
アウトオブラインキー (out-of-line key)
+
保存する値とは別に保存されるキーです。
+
キーパス (key path)
+
オブジェクトストアやインデックスのどこからブラウザーがキーを取り出すべきかを定義します。有効なキーパスは空文字列、JavaScript の識別子、ピリオドで区切られた複数の JavaScript の識別子、あるいはそれらを収めた配列のいずれかを含むことができます。空白を含むことはできません。
+
値 (value)
+
+

それぞれのレコードは値を持っており、論理値数値文字列dateオブジェクト配列正規表現undefined、null を含む、JavaScript で表現可能なものをどれでも含むことができます。

+ +

オブジェクトまたは配列を保存する場合は、それらのプロパティや値もまた、有効な値をどれでも持つことができます。

+ +

また、Blob やファイルも保存可能です。仕様書 {{fx_minversion_inline("11")}}をご覧ください。

+
+
+ +

レンジとスコープ

+ +
+
スコープ (scope)
+
トランザクションの適用先であるオブジェクトストアやインデックスのセットです。読み取りのみのトランザクションのスコープは、同時に重ね合ったり実行することができます。一方、書き込みトランザクションのスコープは重ね合うことができません。同時に同一のスコープで複数のトランザクションを開始することはできますが、それらはキューに収められ、順番に実行されます。
+
カーソル (cursor)
+
キーレンジに属する複数のレコードにわたって反復処理を行うための仕組みです。カーソルは、反復処理を行うインデックスやオブジェクトストアがどれかを示す source を持ちます。またレンジ内の位置や、レコードキーの順序について増加方向に移動しているか減少方向に移動しているかの情報も持ちます。カーソルのリファレンスドキュメントとして、IDBCursorIDBCursorSync をご覧ください。
+
キーレンジ (key range)
+
+

キーとして使用する、何らかのデータ型の連続的な区間です。キーまたはキーレンジを使用して、オブジェクトストアやインデックスからレコードを取り出すことができます。下限または上限を使用して、レンジを制限またはフィルターリングできます。例えばキーが x から y の間であるすべての値に対して、反復処理を行うことができます。

+ +

キーレンジのリファレンスドキュメントとして、{{domxref("IDBKeyRange")}} をご覧ください。

+
+
+ +

制限

+ +

IndexedDB は、クライアントサイドのストレージが必要なほとんどのケースに対応します。しかし、以下のような一部のケースに対して設計されてはいません:

+ + + +

加えて、以下のような状況でブラウザーがデータベースを削除する場合があることを意識してください:

+ + + +

正確な状況やブラウザーの機能は時間とともに変化しますが、ブラウザーベンダーの一般的な考え方は、可能な限りデータを維持するよう最大限に努力することです。

+ +

次のステップ

+ +

主要な概念を習得したら、より具体的なことを学べるようになります。API の使用方法に関するチュートリアルである、IndexedDB を使用するをご覧ください。

+ +

関連情報

+ + diff --git a/files/ja/web/api/indexeddb_api/browser_storage_limits_and_eviction_criteria/index.html b/files/ja/web/api/indexeddb_api/browser_storage_limits_and_eviction_criteria/index.html new file mode 100644 index 0000000000..47c47bad45 --- /dev/null +++ b/files/ja/web/api/indexeddb_api/browser_storage_limits_and_eviction_criteria/index.html @@ -0,0 +1,134 @@ +--- +title: ブラウザーのストレージ制限と削除基準 +slug: Web/API/IndexedDB_API/Browser_storage_limits_and_eviction_criteria +tags: + - Database + - IndexedDB + - LRU + - Storage + - client-side + - eviction + - limit +translation_of: Web/API/IndexedDB_API/Browser_storage_limits_and_eviction_criteria +--- +
{{DefaultAPISidebar("IndexedDB")}}
+ +

クライアント側 (すなわちローカルディスク) に何らかのデータを保存するウェブ技術は何種類かがあります。ブラウザーがどれだけの容量をウェブデータストレージに割り当てるかや、容量の上限に達したときにどのデータを削除するかのプロセスは単純ではなく、またブラウザーにより異なります。この記事では、必要なローカルストレージの容量を確保するために、いつどのローカルコンテンツを破棄するのかをどうやって特定するのかを説明します。

+ +
+

メモ: 以下の情報はほとんどの最新ブラウザーでおおむね正確ですが、既知の詳細情報も記載しています。 Opera および Chrome は、すべての場合において同じ動作になるでしょう。 Opera Mini (Presto ベースで、サーバー側でレンダリングする) は、クライアントにデータを保存しません。

+
+ +

ブラウザーのデータストレージを使用する技術は何か?

+ +

Firefox では以下の技術が、必要なデータを保存するためにブラウザーのデータストレージを使用します。ここではそれらの技術を "クォータクライアント" と呼びます。

+ + + +
+

メモ: Firefox では、 Web Storage もすぐに同じストレージ管理ツールとして使えるようになり、それはこの文書で記述します。

+
+ +
+

メモ: プライベートブラウジングモードは、大半のデータストレージに対応していません。ローカルストレージのデータと Cookie は保存されますが、短命です。 — 最後のプライベートブラウジングウィンドウを閉じた時にデータは消去されます。

+
+ +

生成元の "最終アクセス日時" は、これらのいずれかによってアクティブ化/非アクティブ化される origin eviction によって、すべてのクォータクライアントでデータ削除が行われたときに更新されます。

+ +

Chrome/Opera では、 Quota Management API が AppCache, IndexedDB, WebSQL, File System API のクォータ管理を制御しています。

+ +

さまざまな種類のデータストレージ

+ +

同じブラウザー内で同じ保存方法を使用していても、解釈されるデータストレージの種類はさまざまです。この章では、さまざまなブラウザーで見つけられる多様なストレージについて説明します。

+ +

ストレージは 2 種類に分けられます。

+ + + +

Firefox では、永続的なストレージが使用されると、ユーザーにはデータが永続的になることを警告するポップアップが表示され、それが良いかどうかを尋ねます。一時的データストレージは明示的にユーザーにプロンプトを表示しません。

+ +

既定では、一時的なストレージがほとんどの使用環境 (例えば、標準的な Web アプリ) で使用され、永続的なストレージはインストールされたアプリ (例えば、Firefox OS やデスクトップ版 Firefox にインストールした Firefox アプリ、および Chrome アプリ) で使用されます。

+ +

データの保存先は?

+ +

それぞれのストレージタイプが別々のリポジトリに相当しており、ユーザーの Firefox プロファイル内のディレクトリーとは以下のように対応づけられます (ほかのブラウザーでは、若干異なるでしょう):

+ + + +
+

メモ: Storage API の導入後は、"permanent" フォルダーは廃止されると考えられます。"permanent" フォルダーは IndexedDB の永続的なタイプのデータベースのみ保存します。ボックスモードが "best-effort" や "persistent" であっても、データは <profile>/storage/default 以下に保存されます。

+
+ +
+

メモ: Firefox では URL バーに about:support と入力して移動して、プロファイルフォルダー の隣にある フォルダーを開く ボタン (Mac OS X では Finder で開く) を押下すると、プロファイルのフォルダーを見つけることができます。

+
+ +
+

メモ: プロファイルフォルダーでデータを保存する場所を見ていると、第 4 のフォルダー persistent が見つかるかもしれません。本来は更新や移行を単純化するため、少し前に persistent フォルダーを permanent フォルダーに改名しました。

+
+ +
+

メモ: ユーザーが <profile>/storage の配下に、独自のディレクトリーやファイルを作成すべきではありません。このようなことを行うと、ストレージの初期化が失敗します。例えば、{{domxref("IDBFactory.open()", "open()")}} でエラーイベントが発生します。

+
+ +

ストレージの制限

+ +

ブラウザーのストレージの最大容量は動的であり、ハードディスクドライブのサイズに応じて変わります。グローバルリミットはディスクの空き量量の 50% に決められます。Firefox では、クォータマネージャと飛ばれる内部のブラウザーツールが生成元ごとにどれだけディスク容量を使用しているかを絶えず注視しており、必要に応じてデータを削除します。

+ +

従ってハードディスクドライブが 500GB であれば、ブラウザーの合計ストレージサイズは 250GB になります。上限に達すると origin eviction と呼ばれる処理を実行して、ストレージの総量が再び上限を下回るまで、生成元全体に相当するデータを削除します。生成元内の一部分を削除するような縮小法はありません。生成元内のひとつのデータベースだけ削除すると、矛盾の問題が発生するおそれがあります。

+ +

また、グループリミットというもうひとつの制限もあります。これは、グローバルリミットの 20% として定義されます。それぞれの生成元は、グループ (生成元のグループ) の一部です。グループは、eTLD+1 ドメインごとに 1 つ作られます。例えば次の通り:

+ + + +

このグループでは mozilla.orgwww.mozilla.orgjoe.blogs.mozilla.org が、合わせてグローバルリミットの 20% を上限としてストレージを使用できます。firefox.com は、別に 20% の上限を持ちます。

+ +

これら 2 種類の制限は、制限に達したときの動作が異なります:

+ + + +
+

メモ: グループリミットは、上記で触れた最小のグループリミットにかかわらず、グローバルリミットより大きくすることはできません。グローバルリミットが 8MB といった本当に低メモリな状況では、グループリミットも 8MB となります。

+
+ +
+

メモ: グループリミットに達したとき、あるいは origin eviction で十分な空き容量を確保できないときは、ブラウザーで QuotaExceededError が発生します。

+
+ +
+

メモ: Chrome では、ソフトおよびハードのストレージのクォータの限界が M66 から変更されました。詳しい情報はこちらにあります。

+
+ +

LRU ポリシー

+ +

使用可能なディスク領域がすべて埋まったときは、クォータマネージャーが LRU ポリシーに基づいてデータの削除処理を始めます。もっとも過去に使用された生成元のデータが始めに削除され、上限に達しなくなるなるまで削除を繰り返します。

+ +

一時的なストレージを使用して、生成元ごとに "最終アクセス日時" を記録しています。一時的なストレージがグローバルリミットに達する (後に上限をさらに超える) と、現在使用していない (すなわち、データストアを開き続けているタブやアプリがない) 生成元をすべて発見しようとします。これらは、"最終アクセス日時" によって整列されます。 origin eviction を発生させたリクエストを満たすのに十分な領域を確保するまで、もっとも過去に使用された生成元を削除し続けます。

+ +

関連情報

+ + diff --git a/files/ja/web/api/indexeddb_api/index.html b/files/ja/web/api/indexeddb_api/index.html new file mode 100644 index 0000000000..1c8f968e3f --- /dev/null +++ b/files/ja/web/api/indexeddb_api/index.html @@ -0,0 +1,159 @@ +--- +title: IndexedDB API +slug: Web/API/IndexedDB_API +tags: + - API + - Advanced + - Database + - IndexedDB + - Landing + - Reference + - Storage +translation_of: Web/API/IndexedDB_API +--- +
{{DefaultAPISidebar("IndexedDB")}}
+ +

IndexedDB は、ファイルや blob を含む構造化された多くのデータを保存する、クライアントサイドのローレベル API です。この API は高パフォーマンスなデータの検索を行うために、インデックスを使用します。Web Storage は比較的少量のデータを保存するのに有用ではありますが、構造化された非常に多くのデータを扱うには不十分です。IndexedDB が解決策を提供します。本ページは、MDN における IndexedDB のランディングページです。ここでは API リファレンスへのリンク、使用ガイド、ブラウザーのサポート状況、主要なコンセプトの説明を掲載します。

+ +

{{AvailableInWorkers}}

+ +
+

メモ: IndexedDB API は強力ですが、シンプルな用途にとってはとても複雑に見えるかもしれません。シンプルな API が好ましいのでしたら、IndexedDB をより開発者フレンドリーに扱える localForagedexie.jsZangoDBPouchDBJsStore などのライブラリを検討してください。

+
+ +

主要概念と使用法

+ +

IndexedDB は SQL ベースの RDBMS に似たトランザクショナルデータベースシステムですが、SQL ベース の RDBMS が固定された列を持つテーブルを使用するのに対して、IndexedDB は JavaScript ベースのオブジェクト指向データベースです。IndexedDB では、キーでインデックス付けされたオブジェクトを保存および取り出すことができます。Structured Clone アルゴリズムがサポートする、任意のオブジェクトを保存できます。データベースのスキーマを定義する、データベースへの接続を確立する、そして一連のトランザクションでデータの取り出しや更新を行うことが必要です。

+ + + +
+

メモ: たいていのウェブストレージ技術と同様に、IndexedDB も同一オリジンポリシーに従います。よって、保存済みデータは同一ドメイン内からアクセスできますが、異なるドメインにまたがってデータへアクセスすることはできません。

+
+ +

同期と非同期

+ +

IndexedDB を扱う操作は非同期に実行しますので、他のアプリケーションを妨げません。 IndexedDB は元々同期 API と非同期 API の両方を提供していました。同期 API は Web Worker 内でのみの使用を意図していましたが、必要性に疑問があったため仕様から削除されました。ただし、同期 API はウェブ開発者から十分な要望がある場合は、将来再び導入される可能性があります。

+ +

ストレージの上限と破棄基準

+ +

クライアントサイド (すなわちローカルディスク) に何らかのデータを保存するウェブ技術がいくつかあり、IndexedDB はそのような技術としてもっともよく語られます。ブラウザーがどれだけの容量をウェブデータストレージに割り当てるかや、容量の上限に達したときにどのデータを削除するかのプロセスは単純ではなく、またブラウザーにより異なります。Browser storage limits and eviction criteria で、少なくとも Firefox ではどのようにしているかを説明しようとしています。

+ +

インターフェイス

+ +

データベースへのアクセスを行いたい場合は、window オブジェクトの indexedDB 属性上の open() を呼び出してください。このメソッドは {{domxref("IDBRequest")}} オブジェクトを返します。{{domxref("IDBRequest")}} オブジェクト上で発火したイベントによってアプリケーションが呼び出されることにより、非同期操作が行われます。

+ +

データベースへの接続

+ +
+
{{domxref("IDBEnvironment")}}
+
IndexedDB 機能へのアクセスを提供します。{{domxref("window")}} および {{domxref("worker")}} オブジェクトによって実装されています。このインターフェイスは 2.0 仕様の一部ではありません。
+
{{domxref("IDBFactory")}}
+
データベースへのアクセスを提供します。indexedDB グローバルオブジェクトによって実装されており、従って API へのエントリーポイントになります。
+
{{domxref("IDBOpenDBRequest")}}
+
データベースを開くリクエストを表します。
+
{{domxref("IDBDatabase")}}
+
データベース接続を表します。データベースとのトランザクション処理を行うためのみに使用されます。
+
+ +

データの取り出しと変更

+ +
+
{{domxref("IDBTransaction")}}
+
トランザクションを表します。(アクセスしたいオブジェクトストアの) スコープを指定し、(読み取り専用または読み書き可能といった) アクセスの種類を定義して、データベースへのトランザクションを作成します。
+
{{domxref("IDBRequest")}}
+
データベースへのリクエストの処理、および結果へのアクセスを提供する汎用インターフェイスです。
+
{{domxref("IDBObjectStore")}}
+
IndexedDB 内のデータセットにアクセスできるオブジェクトストアを表し、主キーを使用して探索します。
+
{{domxref("IDBIndex")}}
+
こちらも IndexedDB データベース内のデータのサブセットにアクセスできますが、レコードの探索に主キーではなくインデックスを使用します。{{domxref("IDBObjectStore")}} より高速に動作する場合があります。
+
{{domxref("IDBCursor")}}
+
オブジェクトストアとインデックスをイテレートします。
+
{{domxref("IDBCursorWithValue")}}
+
オブジェクトストアとインデックスをイテレートして、カーソルの現在の値を返します。
+
{{domxref("IDBKeyRange")}}
+
データベースから一定の範囲のデータを取り出すために使用可能な、キーの範囲を定義します。
+
{{domxref("IDBLocaleAwareKeyRange")}} {{Non-standard_inline}}
+
データベースから一定の範囲のデータを取り出すために使用可能な、インデックス用に指定したロケール (createIndex() の optionalParameters をご覧ください) の規則によって並べ替えたキーの範囲を定義します。
+
+ +

カスタムイベントインターフェイス

+ +

本仕様では、以下のカスタムインターフェイスでイベントが発生します:

+ +
+
{{domxref("IDBVersionChangeEvent")}}
+
IDBVersionChangeEvent インターフェイスは、{{domxref("IDBOpenDBRequest.onupgradeneeded")}} イベントハンドラ関数によってデータベースのバージョンが変更されたことを表します。
+
+ +

廃止インターフェイス

+ +

仕様の早期段階で定義されていたインターフェイスの一部が、現在、削除されています。以前書いたコードを最新の仕様に合わせて更新する際に必要になるであろうことから、削除されたインターフェイスに関するドキュメントは残してあります:

+ +
+
{{domxref("IDBVersionChangeRequest")}} {{obsolete_inline}}
+
データベースのバージョンの変更リクエストを表現します。データベースのバージョンを変更する方法が ({{domxref("IDBDatabase.setVersion")}} ではなく {{domxref("IDBFactory.open")}} を呼び出すように) 変わりました。また削除された {{domxref("IDBVersionChangeRequest")}} に代わり {{domxref("IDBOpenDBRequest")}} インターフェイスが用意されています。
+
{{domxref("IDBDatabaseException")}}  {{obsolete_inline}}
+
データベース操作が実行されている間に発生した例外状況を表します。
+
{{domxref("IDBTransactionSync")}} {{obsolete_inline}}
+
同期型の {{domxref("IDBTransaction")}} です。
+
{{domxref("IDBObjectStoreSync")}} {{obsolete_inline}}
+
同期型の {{domxref("IDBObjectStore")}} です。
+
{{domxref("IDBIndexSync")}} {{obsolete_inline}}
+
同期型の {{domxref("IDBIndex")}} です。
+
{{domxref("IDBFactorySync")}} {{obsolete_inline}}
+
同期型の {{domxref("IDBFactory")}} です。
+
{{domxref("IDBEnvironmentSync")}} {{obsolete_inline}}
+
同期型の {{domxref("IDBEnvironment")}} です。
+
{{domxref("IDBDatabaseSync")}} {{obsolete_inline}}
+
同期型の {{domxref("IDBDatabase")}} です。
+
{{domxref("IDBCursorSync")}} {{obsolete_inline}}
+
同期型の {{domxref("IDBCursor")}} です。
+
+ +

+ + + +

仕様書

+ + + + + + + + + + + + + + + + + + + + + +
仕様書状態備考
{{SpecName("IndexedDB 2")}}{{Spec2("IndexedDB 2")}} 
{{SpecName('IndexedDB')}}{{Spec2('IndexedDB')}}初回定義
+ +

関連情報

+ + diff --git a/files/ja/web/api/indexeddb_api/using_indexeddb/index.html b/files/ja/web/api/indexeddb_api/using_indexeddb/index.html new file mode 100644 index 0000000000..d183fa77cc --- /dev/null +++ b/files/ja/web/api/indexeddb_api/using_indexeddb/index.html @@ -0,0 +1,1334 @@ +--- +title: IndexedDB を使用する +slug: Web/API/IndexedDB_API/Using_IndexedDB +tags: + - API + - Advanced + - Database + - IndexedDB + - Storage + - Tutorial +translation_of: Web/API/IndexedDB_API/Using_IndexedDB +--- +

{{DefaultAPISidebar("IndexedDB")}}

+ +
+

IndexedDB は、ユーザーのブラウザー内にデータを永続的に保存する手段です。ネットワークの状態にかかわらず高度な問い合わせ機能を持つ ウェブアプリケーションを作成できますので、オンラインとオフラインの両方で動作するアプリケーションになります。

+
+ +

このドキュメントについて

+ +

このチュートリアルでは、IndexedDB の非同期 API の使い方を見ていきます。IndexedDB について詳しくない場合は、始めに IndexedDB の基本的な概念をお読みください。

+ +

IndexedDB API のリファレンスドキュメントとして、IndexedDB の記事とそのサブ記事をご覧ください。IndexedDB で使用されるオブジェクトの型や、同期 API および非同期 API のドキュメントがあります。

+ +

基本パターン

+ +

IndexedDB で推奨される基本パターンは、以下のようになります:

+ +
    +
  1. データベースを開きます。
  2. +
  3. データベース内に、オブジェクトストアを作成します。
  4. +
  5. データの追加や取り出しといった、データベース操作のトランザクションを開始して、リクエストを行います。
  6. +
  7. 適切な DOM イベントを受け取ることにより、操作が完了するのを待ちます。
  8. +
  9. 結果 (リクエストオブジェクトで見つけることができます) に応じた処理を行います。
  10. +
+ +

これらの主要な概念を踏まえることで、より具体的な手続きを理解できます。

+ +

ストアを作成および構築する

+ +

実験的なバージョンの IndexedDB を使用する

+ +

まだ接頭辞を使用しているブラウザーでコードのテストを行いたい場合は、以下のコードを使用するとよいでしょう:

+ +
// 以下の行に、テストを行いたい実装の接頭辞を含めてください。
+window.indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
+// 関数内でない場合は、"var indexedDB = ..." を使用しないでください。
+// さらに、window.IDB* オブジェクトへの参照が必要でしょう:
+window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction || {READ_WRITE: "readwrite"}; // この行は、古いブラウザー向けにオブジェクトの定数が必要である場合に限り、必要になります。
+window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;
+// (Mozilla はこれらのオブジェクトに接頭辞をつけていませんので、window.mozIDB* は不要です)
+ +

接頭辞を使用している実装は不具合がある、未完成、あるいは古い版の仕様に従っている可能性がありますので注意してください。よって、これらを製品版のコードで使用することは推奨しません。サポートしているものとして失敗するより、未サポートとする方が好ましいでしょう:

+ +
if (!window.indexedDB) {
+    window.alert("このブラウザーは安定版の IndexedDB をサポートしていません。IndexedDB の機能は利用できません。");
+}
+
+ +

データベースを開く

+ +

プロセス全体は以下のようにして始めます:

+ +
// データベースを開く
+var request = window.indexedDB.open("MyTestDatabase", 3);
+
+ +

わかりますか? データベースを開くことも他の操作と同様であり、"リクエスト" が必要です。

+ +

データベースを開くリクエストは、すぐにはデータベースを開いたりトランザクションを開始したりはしません。open() 関数を呼び出すと、結果 (成功) またはイベントとして扱うエラー値を伴う IDBOpenDBRequest オブジェクトを返します。IndexedDB のほとんどの他の非同期関数も同様であり、結果またはエラーを伴う IDBRequest オブジェクトを返します。open 関数の結果は IDBDatabase のインスタンスです。

+ +

open メソッドの第 2 引数は、データベースのバージョンです。データベースのバージョンは、データベースのスキーマ、すなわちデータベース内のオブジェクトストアとその構造を決定します。データベースが存在しない場合に open 操作でデータベースが作成されると、onupgradeneeded イベントが発生して、そのイベントハンドラでデータベースのスキーマを作成できます。データベースが存在する場合に従来より高いバージョン番号を指定すると、すぐに onupgradeneeded イベントが発生して、そのイベントハンドラで更新されたスキーマを提供できます。詳しくは、後ほどデータベースのバージョンを更新するで説明します。また、{{domxref("IDBFactory.open")}} のリファレンスページもご覧ください。

+ +
+

重要: バージョン番号は unsigned long long 型の数値であり、とても大きい整数にすることができます。また浮動小数点数値は使用できず、使用した場合は upgradeneeded イベントが発生せず、もっとも近い小さな数値に変換されてトランザクションが始まるでしょう。よって、例えばバージョン番号として 2.4 を使用しないでください:
+ var request = indexedDB.open("MyTestDatabase", 2.4); // 行ってはいけません。バージョンは 2 に丸められます

+
+ +

ハンドラを生成する

+ +

ほぼすべてのリクエスト生成に合わせて始めに行いたいことは、成功およびエラーのハンドラを生成することでしょう:

+ +
request.onerror = function(event) {
+  // request.errorCode に対して行うこと!
+};
+request.onsuccess = function(event) {
+  // request.result に対して行うこと!
+};
+ +

2 つの関数 onsuccess()onerror() のどちらが呼び出されるのでしょう? すべてが成功すると成功イベント (すなわち type プロパティが "success" である DOM イベント) が、requesttarget として発生します。イベントが発生すると requestonsuccess() 関数が、success イベントを引数として呼び出されます。一方、何らかの問題がある場合はエラーイベント (すなわち type プロパティが "error" である DOM イベント) が request で発生します。これは、エラーイベントを引数として onerror() 関数を呼び出します。

+ +

IndexedDB API は必要なエラー処理を最小限にするよう設計されていますので、多くのエラーイベントを見ることはないでしょう (少なくとも、API に慣れていなければ!)。しかしデータベースを開く場合は、エラーイベントが発生する一般的な状況があります。もっとも多いであろう問題は、データベースを作成する許可をユーザーが ウェブアプリに与えなかったことです。IndexedDB の主要な設計目標のひとつが、オフラインで使用するために大量のデータを保存できるようにすることです。(各ブラウザーでどれだけの量のストレージを持てるかについては、ストレージの制限をご覧ください)

+ +

広告ネットワークやコンピュータを汚染させる悪意のある Web サイトをブラウザーが許可したくないことは明らかですので、ブラウザーは ウェブアプリが初めてストレージ用に IndexedDB を開こうとしたときに、ユーザーへプロンプトを表示します。ユーザーはアクセスを許可または拒否できます。またブラウザーのプライバシーモードでの IndexedDB ストレージは、匿名のセッションを閉じるまでの間だけメモリ上に存在します (Firefox のプライベートブラウジングモードや Chrome のシークレットモードのことですが、2015 年 11 月現在の Firefox ではこれが未実装ですので、Firefox のプライベートブラウジングでは IndexedDB をまったく使用できません)。

+ +

ユーザーがデータベース作成の要求を許可して、成功コールバックを実行する成功イベントを受け取ったと想定します。次は何を行うのでしょうか? 以下のリクエストは indexedDB.open() の呼び出しを伴って生成されており、request.resultIDBDatabase のインスタンスですので、以降のためにこれを保存したいことは確実です。よって、コードは以下のようになるでしょう:

+ +
var db;
+var request = indexedDB.open("MyTestDatabase");
+request.onerror = function(event) {
+  alert("なぜ私の ウェブアプリで IndexedDB を使わせてくれないのですか?!");
+};
+request.onsuccess = function(event) {
+  db = event.target.result;
+};
+
+ +

エラーを処理する

+ +

前述のとおり、エラーイベントはバブリングします。エラーイベントはエラーを発生させたリクエストをターゲットにして、さらにトランザクションや最終的にデータベースオブジェクトへバブリングします。すべてのリクエストにエラーハンドラを追加することを避けたい場合は、代わりに以下のように、ひとつのエラーハンドラをデータベースオブジェクトに追加することができます:

+ +
db.onerror = function(event) {
+  // このデータベースのリクエストに対するすべてのエラー用の
+  // 汎用エラーハンドラ!
+  alert("Database error: " + event.target.errorCode);
+};
+
+ +

データベースを開く際によく発生するエラーのひとつが VER_ERR です。これはディスクに保存されているデータベースのバージョンが、開こうとしているバージョンより大きいことを表します。これは、必ずエラーハンドラで処理しなければならないエラーの実例です。

+ +

データベースを作成またはデータベースのバージョンを更新する

+ +

新しいデータベースを作成したり既存のデータベースのバージョンを更新したりする ({{anch("Opening a database","データベースを開く")}}際に、従来より大きなバージョン番号を指定する) と onupgradeneeded イベントが発生して、request.result (すなわち、以下の例の db) に設定した onversionchange イベントハンドラに IDBVersionChangeEvent オブジェクトが渡されます。upgradeneeded イベントのハンドラでは、このバージョンのデータベースで必要なオブジェクトストアを作成します:

+ +
// このイベントは最新のブラウザーにのみ実装されています
+request.onupgradeneeded = function(event) {
+  // IDBDatabase インターフェイスに保存します
+  var db = event.target.result;
+
+  // このデータベース用の objectStore を作成します
+  var objectStore = db.createObjectStore("name", { keyPath: "myKey" });
+};
+ +

この場合、データベースは旧バージョンのデータベース由来のオブジェクトストアをすでに持っていますので、それらのオブジェクトストアを再作成する必要はありません。新しいオブジェクトストアを作成するか、不要になった旧バージョンのオブジェクトストアを削除することだけが必要です。既存のオブジェクトストアを変更しなければならない (例えば keyPath を変更する) 場合は、古いオブジェクトストアを削除してから、新たな設定で再作成しなければならりません。(これはオブジェクトストア内の情報を削除しますので注意してください! この情報を保存しなければならない場合は、データベースをアップグレードする前にデータを読み出して、別の場所に保存してください)

+ +

既存の名称を使用してオブジェクトストアを作成しようとする (あるいは、存在しない名称のオブジェクトストアを削除しようとする) と、エラーが発生します。

+ +

onupgradeneeded イベントから正常に抜けた場合は、データベースを開くリクエストの onsuccess ハンドラが実行されます。

+ +

Chrome 23 以降および Opera 17 以降の Blink/Webkit は、現行バージョンの仕様をサポートします。IE10 以降も同様です。他の実装や古い実装では現行バージョンの仕様を実装しておらず、indexedDB.open(name, version).onupgradeneeded シグネチャが未サポートです。古い Webkit/Blink でデータベースのバージョンを更新する方法について、詳しくは IDBDatabase のリファレンス記事をご覧ください。

+ +

データベースを構築する

+ +

次に、データベースを構築します。IndexedDB はテーブルではなくオブジェクトストアを使用しており、ひとつのデータベースに複数のオブジェクトストアを含めることができます。値をオブジェクトストアへ保存するたびに、値がキーと関連付けられます。オブジェクトストアでキーパスを使用するかキージェネレータを使用するかに応じて、キーを供給する方法がいくつか存在します。

+ +

以下の表で、キーを供給するさまざまな方法を示します:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
キーパス (keyPath)キージェネレータ (autoIncrement)説明
なしなしこのオブジェクトストアは、数値や文字列といったプリミティブ値を含む、どのような種類の値でも保持できます。新たな値の追加を望むたびに、個別のキー引数を供給しなければなりません。
ありなしこのオブジェクトストアは、JavaScript オブジェクトのみ保持できます。オブジェクトはキーパスと同じ名称のプロパティを持たなければなりません。
なしありこのオブジェクトストアは、どのような種類の値でも保持できます。キーは自動的に生成されます。また、特定のキーを使用したい場合は個別のキー引数を供給できます。
ありありこのオブジェクトストアは、JavaScript オブジェクトのみ保持できます。通常はキーが生成されて、オブジェクトでキーパスと同じ名称を持つプロパティに、生成されたキーの値を保存します。ただしそのようなプロパティがすでに存在している場合は、生成された新たなキーではなく、そのプロパティの値をキーとして使用します。
+ +

オブジェクトストアがプリミティブではなくオブジェクトを保持していれば、オブジェクトストアでインデックスを作成することもできます。インデックスは、オブジェクトのキーではなく保存されたオブジェクトのプロパティの値を使用して、オブジェクトストア内に保存された値を検索することを可能にします。

+ +

さらにインデックスには、保存されたデータに単純な制限を強制する機能があります。インデックスを作成する際に unique フラグを設定すると、インデックスのキーパスで同じ値を持つオブジェクトが複数保存されないことを、インデックスが保証します。よって例えば人々の集団の情報を保持するオブジェクトストアがある場合に、同じメールアドレスを持つ人が 2 人存在しないことを保証したい場合は、これを強制するために unique フラグを設定したインデックスを使用するとよいでしょう。

+ +

これには混乱するかもしれませんので、シンプルな例で概念を説明するべきでしょう。始めに、例で使用する顧客データをいくつか定義します:

+ +
// 顧客データがどのようなものかを示します
+const customerData = [
+  { ssn: "444-44-4444", name: "Bill", age: 35, email: "bill@company.com" },
+  { ssn: "555-55-5555", name: "Donna", age: 32, email: "donna@home.org" }
+];
+
+ +

もちろん、誰かの社会保障番号を顧客テーブルの主キーとして使用するべきではないですし (社会保障番号を持っていない人もいます)、年齢の代わりに誕生日を保管することもできますが、これらの不適切な選択は利便性のために無視して先へ進みましょう。

+ +

次に、データを保存する IndexedDB を作成するところを見てみましょう:

+ +
const dbName = "the_name";
+
+var request = indexedDB.open(dbName, 2);
+
+request.onerror = function(event) {
+  // エラー処理
+};
+request.onupgradeneeded = function(event) {
+  var db = event.target.result;
+
+  // 顧客の情報を保存する objectStore を作成します。
+  // "ssn" は一意であることが保証されていますので、キーパスとして使用します。
+  // あるいは少なくとも、キックオフミーティングで言われたことです。
+  var objectStore = db.createObjectStore("customers", { keyPath: "ssn" });
+
+  // 顧客を名前で検索するためのインデックスを作成します。
+  // 重複する可能性がありますので、一意のインデックスとしては使用できません。
+  objectStore.createIndex("name", "name", { unique: false });
+
+  // 顧客をメールアドレスで検索するためのインデックスを作成します。2 人の顧客が同じメールアドレスを
+  // 使用しないようにしたいので、一意のインデックスを使用します。
+  objectStore.createIndex("email", "email", { unique: true });
+
+  // データを追加する前に objectStore の作成を完了させるため、
+  // transaction oncomplete を使用します。
+  objectStore.transaction.oncomplete = function(event) {
+    // 新たに作成した objectStore に値を保存します。
+    var customerObjectStore = db.transaction("customers", "readwrite").objectStore("customers");
+    for (var i in customerData) {
+      customerObjectStore.add(customerData[i]);
+    }
+  };
+};
+
+ +

先に示したように、onupgradeneeded はデータベースの構造を変えることができる唯一の場所です。ここではオブジェクトストアの作成および削除や、インデックスの構築および削除が可能です。

+ +

オブジェクトストアは createObjectStore() を 1 回呼び出して作成します。このメソッドの引数は、ストアの名前とパラメータオブジェクトです。パラメータオブジェクトは省略可能ですが、重要なオプションプロパティを定義したり、作成したいオブジェクトストアの型を改良することができますので、とても重要です。この例では "customers" という名前のオブジェクトストアを要求して、keyPath を定義しています。keyPath は、ストア内で個々のオブジェクトを一意にするプロパティです。この例では、社会保障番号が一意であることが保証されていますので "ssn" にしています。"ssn" は、objectStore に保存するすべてのオブジェクトに与えなければなりません。

+ +

また、保存されたオブジェクトの name プロパティを参照する、"name" という名前のインデックスも要求しています。createObjectStore() と同様に createIndex() も、作成したいインデックスの型を改良するための省略可能な引数 options オブジェクトを指定できます。name プロパティを持たないオブジェクトを追加することはできますが、そのオブジェクトは "name" インデックス内に現れません。

+ +

以上でオブジェクトストアに保存された顧客オブジェクトを、ssn を使用して直接、またはインデックスを使用して名前をもとにして、取り出すことができます。この仕組みについて詳しくは、インデックスを使用する をご覧ください。

+ +

キージェネレータを使用する

+ +

オブジェクトストアを作成するときに autoIncrement フラグを設定すると、そのオブジェクトストアでキージェネレータを使用できます。デフォルトで、このフラグは設定されません。

+ +

キージェネレータを使用すると、オブジェクトストアに値を追加するのに応じて自動的にキーが生成されます。オブジェクトストアでキージェネレータを初めて作成した時点では、キージェネレータの値が常に 1 になります。基本的に、新たに自動生成されるキーは、前のキーから 1 増加した値になります。データベースのトランザクションが中止されるなど、データベースの操作が取り消された場合を除き、キージェネレータの現在の値が減少することはありません。従って、オブジェクトストアからレコードを削除したりすべてのレコードをクリアしたりしても、オブジェクトストアのキージェネレータには影響がありません。

+ +

以下のように、キージェネレータを持つ別のオブジェクトストアを作成できます:

+ +
// indexedDB を開きます。
+var request = indexedDB.open(dbName, 3);
+
+request.onupgradeneeded = function (event) {
+
+    var db = event.target.result;
+
+    // autoIncrement フラグに true を設定した、"names" という名前のオブジェクトストアを作成します。
+    var objStore = db.createObjectStore("names", { autoIncrement : true });
+
+    // "names" オブジェクトストアはキージェネレータを持っていますので、値 name のキーは自動的に生成されます。
+    // 追加したレコードは以下のようになります:
+    // キー : 1 => 値 : "Bill"
+    // キー : 2 => 値 : "Donna"
+    for (var i in customerData) {
+        objStore.add(customerData[i].name);
+    }
+};
+ +

キージェネレータについて詳しくは、"W3C Key Generators" をご覧ください。

+ +

データを追加する、読み出す、削除する

+ +

新しいデータベースで何かを行えるようにする前に、トランザクションを開始しなければなりません。トランザクションはデータベースオブジェクトから生じており、トランザクションの対象にしたいオブジェクトストアを指定しなければなりません。トランザクションの内部では、データを保持しているオブジェクトストアへのアクセスや、リクエストの実行が可能です。次に、データベースに変更処理を行うのか、あるいはデータベースから読み出すだけかを決めなければなりません。トランザクションは readonlyreadwriteversionchange の 3 つのモードを使用できます。

+ +

データベースの "スキーマ" や構造を変更する (オブジェクトストアやインデックスを作成または削除する) には、トランザクションを versionchange モードにしなければなりません。このトランザクションは、version を指定して {{domxref("IDBFactory.open")}} メソッドを呼び出すことによって開きます。(最新の仕様を実装していない WebKit ブラウザーは {{domxref("IDBFactory.open")}} メソッドの引数が、データベースの name の 1 つしかありません。よって、versionchange トランザクションを開始するには {{domxref("IDBVersionChangeRequest.setVersion")}} を呼び出さなければなりません)

+ +

既存のオブジェクトストアからレコードを読み出すには、トランザクションで readonly モードまたは readwrite モードを使用できます。既存のオブジェクトストアに変更処理を行うには、トランザクションを readwrite モードにしなければなりません。このようなトランザクションは {{domxref("IDBDatabase.transaction")}} で開きます。このメソッドの引数は 2 つあり、storeNames (アクセスしたいオブジェクトストアの配列で定義されるスコープ) とトランザクションの mode (readonly または readwrite) です。またこのメソッドは、{{domxref("IDBTransaction.objectStore")}} メソッドを持つトランザクションオブジェクトを返します。objectStore メソッドは、オブジェクトストアにアクセスするために使用できます。デフォルトでは、モードを指定しなければ readonly モードでトランザクションを開きます。

+ +
+

注記: Firefox 40 で、IndexedDB トランザクションはパフォーマンスを向上させるために、永続性の保証を緩和しました ({{Bug("1112702")}} を参照)。以前は readwrite モードのトランザクションで、すべてのデータをディスク上に反映したことが保証された場合に限り {{domxref("IDBTransaction.oncomplete")}} 発生しました。Firefox 40 以降では OS がデータの書き込みを指示した時点で complete が発生しており、実際にはデータがディスク上に反映されていない可能性があります。これにより complete イベントをより早く発生させられますが、データをディスク上に反映する前に OS のクラッシュや電源断が発生するとトランザクション全体を失う危険性が若干あります。このような破壊的な事象はまれですので、ほとんどの利用者は心配する必要がないでしょう。何らかの理由 (例えば、後で再計算できない重要なデータを保存する) で永続性を保証しなければならない場合は、実験的 (非標準) な readwriteflush モード ({{domxref("IDBDatabase.transaction")}} を参照) を使用してトランザクションを生成すると、complete イベントを発生させる前にディスクへの反映を強制させることができます。

+
+ +

トランザクションで適切なスコープおよびモードを使用すると、データアクセスを高速化できます。ヒントを 2 つ紹介します:

+ + + +

データベースにデータを追加する

+ +

データベースを作成したら、書き込みを行いたいと考えるでしょう。これは以下のようにします:

+ +
var transaction = db.transaction(["customers"], "readwrite");
+// 注記: 古い実験的な実装では、"readwrite" の代わりに非推奨の定数 IDBTransaction.READ_WRITE を使用します。
+// そのような実装をサポートしたい場合は、以下のように記述します:
+// var transaction = db.transaction(["customers"], IDBTransaction.READ_WRITE);
+ +

transaction() 関数は引数が 2 つあり (ひとつは省略可能)、トランザクションオブジェクトを返します。第 1 引数は、トランザクションの対象にするオブジェクトストアのリストです。トランザクションですべてのオブジェクトを対象にしたい場合は空の配列を渡すことができますが、仕様書では空の配列に対して InvalidAccessError を生成すべきとされていますので、行わないようにしてください。第 2 引数に何も指定しなければ、読み取り専用のトランザクションになります。書き込みを行いたい場合は、"readwrite" フラグを渡さなければなりません。

+ +

以上で、存続期間を理解しなければならないトランザクションができました。トランザクションは、イベントループととても密接に結びついています。トランザクションを作成して、それを使用せずにイベントループに戻ると、トランザクションが非アクティブ状態になります。トランザクションをアクティブにし続ける唯一の方法が、トランザクションでリクエストを行うことです。リクエストが完了すると DOM イベントが発生して、リクエストが成功したと仮定すれば、コールバックの実行中にトランザクションを拡張するもうひとつの機会を得られます。トランザクションを拡張せずにイベントループへ戻ると、トランザクションは非アクティブ状態になります。保留中のリクエストがある限り、トランザクションはアクティブであり続けます。トランザクションの存続期間はごくシンプルですが、慣れるまでには少々時間がかかるでしょう。さらにいくつかの例も、理解する助けになるでしょう。TRANSACTION_INACTIVE_ERR エラーコードを見始めた場合は、何らかの誤りがあるでしょう。

+ +

トランザクションは errorabortcomplete の 3 種類の DOM イベントを受け取る可能性があります。error イベントがバブリングする方法について話したとおり、トランザクションは、生成したあらゆるリクエストが由来のエラーイベントを受け取ります。より細かいポイントとして、エラー時のデフォルトの動作は、エラーが発生したトランザクションを中止させることです。エラーイベントで始めに stopPropagation() を呼び出して、他の処理を行うようにエラー制御を行わなければ、トランザクション全体がロールバックします。この設計によってエラー制御を考えるよう強いられますが、きめ細かいエラー制御がとても複雑になる場合は、データベースに対して包括的なエラー制御を追加することもできます。エラーイベントを制御しない場合やトランザクションで abort() を呼び出した場合は、トランザクションがロールバックされて、abort イベントが発生します。それ以外の場合は、すべての保留中のリクエストが完了した後に complete イベントが発生します。多くのデータベース操作を行っている場合は、個々のリクエストではなくトランザクションを追跡すると、確実に健全性を促進します。

+ +

トランザクションを確保したら、そこからオブジェクトストアを取得しなければならないでしょう。トランザクションは、作成時に指定したオブジェクトストアだけを提供します。そして、必要なデータをすべて追加できます。

+ +
// すべてのデータがデータベースに追加されたときに行う処理
+transaction.oncomplete = function(event) {
+  alert("All done!");
+};
+
+transaction.onerror = function(event) {
+  // エラー制御を忘れずに!
+};
+
+var objectStore = transaction.objectStore("customers");
+for (var i in customerData) {
+  var request = objectStore.add(customerData[i]);
+  request.onsuccess = function(event) {
+    // event.target.result == customerData[i].ssn;
+  };
+}
+ +

add() を呼び出して生成されたリクエストの result は、追加された値のキーです。よってこのケースでは、オブジェクトストアでキーパスとして ssn プロパティを使用していますので、追加されたオブジェクトの ssn プロパティと等しくなります。add() 関数では、データベース内に同一のキーを持つオブジェクトが存在しないことを要求しますので注意してください。既存の項目を変更しようとする場合や、既存の項目があるかを配慮しない場合は、{{anch("Updating an entry in the database", "データベース内の項目を更新する")}} の章で説明している put() 関数を使用できます。

+ +

データベースからデータを削除する

+ +

データの削除もよく似ています:

+ +
var request = db.transaction(["customers"], "readwrite")
+                .objectStore("customers")
+                .delete("444-44-4444");
+request.onsuccess = function(event) {
+  // 削除完了!
+};
+ +

データベースからデータを取得する

+ +

データベースは情報を持っていますので、いくつかの方法でデータを読み出すことができます。まずは、単純に get() を使用します。以下のように、値を読み出すためにキーを提供しなければなりません:

+ +
var transaction = db.transaction(["customers"]);
+var objectStore = transaction.objectStore("customers");
+var request = objectStore.get("444-44-4444");
+request.onerror = function(event) {
+  // エラー処理!
+};
+request.onsuccess = function(event) {
+  // request.result に対して行う処理!
+  alert("Name for SSN 444-44-4444 is " + request.result.name);
+};
+ +

"単純に" 読み出すにも多くのコードがあります。データベースレベルでエラー処理を行うとすれば、コードを少々短縮できることを以下に示します:

+ +
db.transaction("customers").objectStore("customers").get("444-44-4444").onsuccess = function(event) {
+  alert("Name for SSN 444-44-4444 is " + event.target.result.name);
+};
+ +

どのように動作するかわかりますか? オブジェクトストアが 1 つしかありませんので、トランザクションで必要とするオブジェクトストアのリストを渡さずに、名称を文字列で渡しています。また、データベースから読み出すだけですので、"readwrite" トランザクションは不要です。モードを指定せずに transaction() を呼び出すと、"readonly" トランザクションになります。さらに細かいことですが、実はリクエストオブジェクトを変数に保存していません。DOM イベントはターゲットとしてリクエストを持ちますので、result プロパティを得るためにイベントを使用できます。

+ +

トランザクションでスコープやモードを制限することにより、データアクセスを高速化できることに留意してください。ヒントを 2 つ紹介します:

+ + + +

データベース内の項目を更新する

+ +

読み出したデータを更新して IndexedDB に書き戻す方法は、とてもシンプルです。先ほどのサンプルを多少更新しましょう:

+ +
var objectStore = db.transaction(["customers"], "readwrite").objectStore("customers");
+var request = objectStore.get("444-44-4444");
+request.onerror = function(event) {
+  // エラー処理!
+};
+request.onsuccess = function(event) {
+  // 更新したい、古い値を取得します。
+  var data = request.result;
+
+  // オブジェクト内の値を、希望する値に更新します。
+  data.age = 42;
+
+  // 更新したオブジェクトを、データベースに書き戻します。
+  var requestUpdate = objectStore.put(data);
+   requestUpdate.onerror = function(event) {
+     // エラーが発生した場合の処理
+   };
+   requestUpdate.onsuccess = function(event) {
+     // 成功 - データを更新しました!
+   };
+};
+ +

ここでは objectStore を作成して、ssn の値 (444-44-4444) で特定される顧客レコードの取り出しを要求しています。リクエストの結果を変数 (data) に代入して、そのオブジェクトの age プロパティを更新します。そして、顧客レコードを objectStore に書き戻して前の値を上書きする、第 2 のリクエスト (requestUpdate) を作成します。

+ +
+

注記: このケースではデータベースから読み出すだけでなく書き込みも行いたいので、readwrite トランザクションを指定しました。

+
+ +

カーソルを使用する

+ +

get() を使用する際は、読み出したいキーがどれかを知っていることが必要です。オブジェクトストア内のすべての値を渡り歩きたい場合は、カーソルを使用できます。以下のようなものです:

+ +
var objectStore = db.transaction("customers").objectStore("customers");
+
+objectStore.openCursor().onsuccess = function(event) {
+  var cursor = event.target.result;
+  if (cursor) {
+    alert("Name for SSN " + cursor.key + " is " + cursor.value.name);
+    cursor.continue();
+  }
+  else {
+    alert("No more entries!");
+  }
+};
+ +

openCursor() 関数は、引数がいくつかあります。第一に、すぐに取得するキーレンジオブジェクトを使用して、読み出すアイテムの範囲を制限できます。第二に、反復処理を行いたい方向を指定できます。上記の例では、すべてのオブジェクトを昇順方向に反復します。カーソルの成功イベントのコールバックは、やや特殊です。カーソルオブジェクト自体は、リクエストの result です (上記の例ではショートハンドを使用しており、event.target.result になります)。そして実際のキーと値は、カーソルオブジェクトの key プロパティと value プロパティで見つかります。進み続けたい場合は、カーソルで continue() を呼び出さなければなりません。データの終端に達した (または、openCursor() リクエストにマッチする項目が存在しない) 場合は成功のコールバックを受け取りますが、result プロパティが undefined になります。

+ +

カーソルをよく使用するパターンのひとつが、以下のようにオブジェクトストア内の全データを読み出して、配列に追加することです:

+ +
var customers = [];
+
+objectStore.openCursor().onsuccess = function(event) {
+  var cursor = event.target.result;
+  if (cursor) {
+    customers.push(cursor.value);
+    cursor.continue();
+  }
+  else {
+    alert("Got all customers: " + customers);
+  }
+};
+ +
+

注記: Mozilla は、このような処理を行うために getAll() も実装しています (および getAllKeys() もあり、これは現在、about:config の設定項目 dom.indexedDB.experimental で隠しています)。これらは IndexedDB 標準の一部ではなく、将来削除する可能性があります。これらは便利であると考えられますので、実装しました。以下のコードは、前出の例とまったく同じことを行います:

+ +
objectStore.getAll().onsuccess = function(event) {
+  alert("Got all customers: " + event.target.result);
+};
+ +

これはオブジェクトを横着な方法で作成するため、カーソルの value プロパティの検索に関してパフォーマンスコストが発生します。例えば getAll() を使用するとき、Gecko はすべてのオブジェクトを一度に作成しなければなりません。例えばそれぞれのキーを検索することにのみ関心がある場合は、getAll() よりもカーソルを使用する方がとても効率的です。オブジェクトストア内の全データの配列を得ようとしている場合は、getAll() を使用しましょう。

+
+ +

インデックスを使用する

+ +

SSN は個人を一意に識別しますので、キーとして SSN を使用して顧客データを保管することは論理的です。(プライバシーの観点でよいアイデアであるかは別の問題であり、この記事の対象外です) 一方、名前で顧客を検索しなければならない場合は、正しいものが見つかるまでデータベース内のすべての SSN に対して反復処理を行わなければなりません。この方法による検索はとても遅いため、代わりにインデックスを使用するとよいでしょう。

+ +
var index = objectStore.index("name");
+
+index.get("Donna").onsuccess = function(event) {
+  alert("Donna's SSN is " + event.target.result.ssn);
+};
+ +

"name" カーソルは一意ではないので、name"Donna" が設定されている項目は複数存在する可能性があります。この場合は常に、キーの値がもっとも小さいものを取得します。

+ +

指定した name に該当するすべての項目にアクセスしなければならない場合は、カーソルを使用します。インデックス上で、2 種類のカーソルを開くことができます。ノーマルカーソルは、インデックスのプロパティと、オブジェクトストア内のオブジェクトを紐づけます。キーカーソルは、インデックスのプロパティと、オブジェクトストア内にオブジェクトを保存するために使用するキーを紐づけます。これらの違いを以下に示します:

+ +
// 顧客レコードのオブジェクト全体を得るために、ノーマルカーソルを使用します。
+index.openCursor().onsuccess = function(event) {
+  var cursor = event.target.result;
+  if (cursor) {
+    // cursor.key は "Bill" のような名前、cursor.value はオブジェクト全体です。
+    alert("Name: " + cursor.key + ", SSN: " + cursor.value.ssn + ", email: " + cursor.value.email);
+    cursor.continue();
+  }
+};
+
+// 顧客レコードのオブジェクトのキーを得るために、キーカーソルを使用します。
+index.openKeyCursor().onsuccess = function(event) {
+  var cursor = event.target.result;
+  if (cursor) {
+    // cursor.key は "Bill" のような名前、cursor.value は SSN です。
+    // 保存されたオブジェクトの他の部分を直接取得する方法はありません。
+    alert("Name: " + cursor.key + ", SSN: " + cursor.value);
+    cursor.continue();
+  }
+};
+ +

カーソルの範囲や方向を指定する

+ +

カーソルで参照する値の範囲を制限したい場合は、IDBKeyRange オブジェクトを使用して、openCursor() または openKeyCursor() の第 1 引数として渡します。ひとつのキーのみ許可するキーレンジ、下限または上限の片方を持つキーレンジ、あるいは下限と上限の両方を持つキーレンジを作成できます。境界は "closed" (すなわち、キーレンジは指定した値を含む) または "open" (すなわち、キーレンジは指定した値を含まない) のどちらにもできます。使い方を以下に示します:

+ +
// "Donna" にのみマッチします。
+var singleKeyRange = IDBKeyRange.only("Donna");
+
+// "Bill" より先のすべてにマッチします。"Bill" を含みます。
+var lowerBoundKeyRange = IDBKeyRange.lowerBound("Bill");
+
+// "Bill" より先のすべてにマッチします。ただし "Bill" は含みません。
+var lowerBoundOpenKeyRange = IDBKeyRange.lowerBound("Bill", true);
+
+// "Donna" までのすべてにマッチします。ただし "Donna" は含みません。
+var upperBoundOpenKeyRange = IDBKeyRange.upperBound("Donna", true);
+
+// "Bill" から "Donna" までにマッチします。ただし "Donna" は含みません。
+var boundKeyRange = IDBKeyRange.bound("Bill", "Donna", false, true);
+
+// いずれかのキーレンジを使用するには、openCursor()/openKeyCursor() の第 1 引数として渡します。
+index.openCursor(boundKeyRange).onsuccess = function(event) {
+  var cursor = event.target.result;
+  if (cursor) {
+    // マッチした場合の処理。
+    cursor.continue();
+  }
+};
+ +

昇順 (すべてのカーソルのデフォルトの方向) ではなく、降順に反復処理を行いたい場合があるかもしれません。方向の切り替えは、openCursor() の第 2 引数に prev を渡すことで実現します:

+ +
objectStore.openCursor(boundKeyRange, "prev").onsuccess = function(event) {
+  var cursor = event.target.result;
+  if (cursor) {
+    // 項目に対して行う処理
+    cursor.continue();
+  }
+};
+ +

方向を変えたいだけで表示する結果は制限しない場合は、第 1 引数に null を渡します:

+ +
objectStore.openCursor(null, "prev").onsuccess = function(event) {
+  var cursor = event.target.result;
+  if (cursor) {
+    // 項目に対して行う処理
+    cursor.continue();
+  }
+};
+ +

"name" インデックスは一意ではありませんので、name が同じ項目が複数存在する可能性があります。キーは常に一意でなければならないため、オブジェクトストアでこのような状況は発生できないことに注意してください。インデックスに対して反復処理を行う際に重複を取り除きたい場合は、方向のパラメータに nextunique (逆向きであれば prevunique) を指定します。nextunique または prevunique を使用すると、常にキーが最小の項目が返ります。

+ +
index.openKeyCursor(null, "nextunique").onsuccess = function(event) {
+  var cursor = event.target.result;
+  if (cursor) {
+    // 項目に対して行う処理
+    cursor.continue();
+  }
+};
+ +

有効な方向の引数については、"IDBCursor Constants" をご覧ください。

+ +

ウェブアプリが別のタブで開かれているときにバージョンを変更する

+ +

データベースのバージョン変更が必要である場合に ウェブアプリでそのようなことを行うときは、ユーザーが古いバージョンの ウェブアプリをタブで開いている場合に別のタブで新しいバージョンのアプリを読み込んだときに発生することを考慮しなければなりません。データベースの実際のバージョンより大きなバージョンを指定して open() を呼び出すときは、データベースに変更を施す前に、他にデータベースを開いているものが明示的に要求を認めなければなりません (それらを閉じるか再読み込みするまで、onblocked イベントが発生します)。使い方を以下に示します:

+ +
var openReq = mozIndexedDB.open("MyTestDatabase", 2);
+
+openReq.onblocked = function(event) {
+  // 他のタブがデータベースを読み込んでいる場合は、処理を進める前に
+  // それらを閉じなければなりません。
+  alert("このサイトを開いている他のタブをすべて閉じてください!");
+};
+
+openReq.onupgradeneeded = function(event) {
+  // 他のデータベースはすべて閉じられました。すべての処理を行います。
+  db.createObjectStore(/* ... */);
+  useDatabase(db);
+};
+
+openReq.onsuccess = function(event) {
+  var db = event.target.result;
+  useDatabase(db);
+  return;
+};
+
+function useDatabase(db) {
+  // 別のページがバージョン変更を求めた場合に、通知されるようにするためのハンドラを追加するようにしてください。
+  // データベースを閉じなければなりません。データベースを閉じると、別のページがデータベースをアップグレードできます。
+  // これを行わなければ、ユーザーがタブを閉じるまでデータベースはアップグレードされません。
+  db.onversionchange = function(event) {
+    db.close();
+    alert("新しいバージョンのページが使用可能になりました。再読み込みしてください!");
+  };
+
+  // データベースを使用する処理
+}
+
+ +

すでに開かれているアプリが新たにデータベースを開こうとするコードを開始したが、古いバージョンのデータベースを使用している状況に対処するため、VersionError エラーもリッスンしましょう。

+ +

セキュリティ

+ +

IndexedDB は同一生成元の原則を使用します。すなわち、ストアとサイトの生成元 (通常、サイトのドメインまたはサブドメイン) を紐づけますので、他の生成元からアクセスすることはできません。

+ +

サードパーティの window コンテンツ (例えば {{htmlelement("iframe")}} のコンテンツ) は、ブラウザーがサードパーティ Cookie を禁止していない限り、自身が埋め込まれている生成元の IndexedDB ストアにアクセスできます ({{bug("1147821")}} をご覧ください)。

+ +

ブラウザーの終了に関する警告

+ +

ブラウザーを終了するとき (例えばユーザーが "終了" や "閉じる" ボタンをクリックしたとき)、データベースを含むディスクは予期せず削除されたり、データベースストアへのパーミッションが失われたり、次のことが起きたりします:

+ +
    +
  1. 影響するデータベース (あるいは、ブラウザーを終了する場合はすべての開いているデータベース) の各トランザクションは AbortError とともに中断されます。この効果は各トランザクションで {{domxref("IDBTransaction.abort()")}} が呼ばれたのと同等です。
  2. +
  3. すべてのトランザクションが完了していたら、データベース接続は閉じられます。
  4. +
  5. 最後に、データベース接続を表す {{domxref("IDBDatabase")}} オブジェクトは {{event("close")}} イベントを受け取ります。{{domxref("IDBDatabase.onclose")}} イベントハンドラを使ってこのイベントをリッスンできます。その結果、データベースが予期せず閉じられたことがわかります。
  6. +
+ +

上記の挙動は新しく、下記のブラウザーリリース以降で利用できます: Firefox 50, Google Chrome 31 (おおよそ)。

+ +

このブラウザーバージョンの前は、トランザクションは静かに中断され、{{event("close")}} イベントは発火せず、予期せぬデータベースの停止を検出する方法はありませんでした。

+ +

ユーザーはいつでもブラウザーを終了することができますので、特定のトランザクションが完了することをあてにしたり、完了しなかったことを知ったりすることはできません。この動作が暗示することがいくつかあります。

+ +

第一に、データベースであらゆるトランザクションが終了したときに、常に一貫性がある状態を保つように注意するべきです。例えば、ユーザーが編集可能な項目のリストを保存する IndexedDB を使用していると想定します。オブジェクトストアを消去してから新たなリストを書き込むことにより、編集後のリストを保存します。あるトランザクションでオブジェクトストアを消去して、別のトランザクションで新たなリストを書き込むとすれば、消去した後かつ書き込む前にブラウザーが閉じられる危険性があり、その場合は空のデータベースが残ります。これを避けるために、消去と書き込みをひとつのトランザクションに結合しましょう。

+ +

第二に、データベースのトランザクションと unload イベントを紐づけるべきではありません。ブラウザーを閉じることで unload イベントが発生した場合、unload イベントハンドラで作成したトランザクションは完了しません。ブラウザーのセッションにわたって情報を管理するための直感的な方法は、ブラウザー (または特定のページ) を開いたときに情報を読み込んで、ユーザーとブラウザーとの対話に応じて更新して、ブラウザー (またはページ) を閉じるときに保存する流れです。しかし、これは動作しないでしょう。データベースのトランザクションは unload イベントハンドラで作成されますが、これらは非同期処理ですので、実行できるようになる前に中止されるでしょう。

+ +

実は通常のブラウザー終了であっても、IndexedDB のトランザクションが完了するよう保証する手段はありません。{{bug(870645)}} をご覧ください。通常の終了通知の回避策として、トランザクションの状況を追跡して、アンロード時にトランザクションが完了していないことをユーザーに警告するための beforeunload イベントを追加するとよいでしょう。

+ +

少なくともアボート通知と{{domxref("IDBDatabase.onclose")}}を追加することで、いつ起こったのかがわかります。

+ +

ロケールを意識した並べ替え

+ +

Mozilla は Firefox 43 以降に、IndexedDB のデータでロケールを意識した並べ替えを行う機能を実装しました。デフォルトでは、IndexedDB は文字列の並べ替えで国際化にまったく対処せず、すべてが英語のテキストであるかのように並べ替えられます。例えば b、á、z、a は以下のように並べ替えられます:

+ + + +

これは明らかに、ユーザーが望むデータの並べ替えではありません。例えば Aaron と Áaron は、連絡先一覧で隣り合うべきです。従って適切な国際化並べ替えを実現するには、データセット全体をメモリに読み込んで、クライアントサイド JavaScript で並べ替えを実行しなければならず、非効率的です。

+ +

この新機能は、開発者が {{domxref("IDBObjectStore.createIndex()")}} を使用してインデックスを作成する際にロケールを指定できるようにします (引数を確認してください)。データセットに対して反復処理を行うためにカーソルを使用するときに、ロケールを意識した並べ替えを行いたい場合は、特化した {{domxref("IDBLocaleAwareKeyRange")}} を使用できます。

+ +

また {{domxref("IDBIndex")}} には、ロケールが指定されているか、およびどのロケールが指定されているかを特定するために追加された新たなプロパティがあります。locale (指定されたロケール、または未指定であれば null を返します) と isAutoLocale (プラットフォームの既定のロケールを使用する自動ロケールでインデックスが作成されていれば true、そうでなければ false を返します) です。

+ +
+

注記: 現在、この機能はフラグで隠されています。有効化して実験するには、about:config に移動して dom.indexedDB.experimental を有効化してください。

+
+ +

包括的な IndexedDB のサンプル

+ +

HTML コンテンツ

+ +
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
+
+    <h1>IndexedDB Demo: storing blobs, e-publication example</h1>
+    <div class="note">
+      <p>
+        Works and tested with:
+      </p>
+      <div id="compat">
+      </div>
+    </div>
+
+    <div id="msg">
+    </div>
+
+    <form id="register-form">
+      <table>
+        <tbody>
+          <tr>
+            <td>
+              <label for="pub-title" class="required">
+                Title:
+              </label>
+            </td>
+            <td>
+              <input type="text" id="pub-title" name="pub-title" />
+            </td>
+          </tr>
+          <tr>
+            <td>
+              <label for="pub-biblioid" class="required">
+                Bibliographic ID:<br/>
+                <span class="note">(ISBN, ISSN, etc.)</span>
+              </label>
+            </td>
+            <td>
+              <input type="text" id="pub-biblioid" name="pub-biblioid"/>
+            </td>
+          </tr>
+          <tr>
+            <td>
+              <label for="pub-year">
+                Year:
+              </label>
+            </td>
+            <td>
+              <input type="number" id="pub-year" name="pub-year" />
+            </td>
+          </tr>
+        </tbody>
+        <tbody>
+          <tr>
+            <td>
+              <label for="pub-file">
+                File image:
+              </label>
+            </td>
+            <td>
+              <input type="file" id="pub-file"/>
+            </td>
+          </tr>
+          <tr>
+            <td>
+              <label for="pub-file-url">
+                Online-file image URL:<br/>
+                <span class="note">(same origin URL)</span>
+              </label>
+            </td>
+            <td>
+              <input type="text" id="pub-file-url" name="pub-file-url"/>
+            </td>
+          </tr>
+        </tbody>
+      </table>
+
+      <div class="button-pane">
+        <input type="button" id="add-button" value="Add Publication" />
+        <input type="reset" id="register-form-reset"/>
+      </div>
+    </form>
+
+    <form id="delete-form">
+      <table>
+        <tbody>
+          <tr>
+            <td>
+              <label for="pub-biblioid-to-delete">
+                Bibliographic ID:<br/>
+                <span class="note">(ISBN, ISSN, etc.)</span>
+              </label>
+            </td>
+            <td>
+              <input type="text" id="pub-biblioid-to-delete"
+                     name="pub-biblioid-to-delete" />
+           </td>
+          </tr>
+          <tr>
+            <td>
+              <label for="key-to-delete">
+                Key:<br/>
+                <span class="note">(例えば、 1, 2, 3, etc.)</span>
+              </label>
+            </td>
+            <td>
+              <input type="text" id="key-to-delete"
+                     name="key-to-delete" />
+            </td>
+          </tr>
+        </tbody>
+      </table>
+      <div class="button-pane">
+        <input type="button" id="delete-button" value="Delete Publication" />
+        <input type="button" id="clear-store-button"
+               value="Clear the whole store" class="destructive" />
+      </div>
+    </form>
+
+    <form id="search-form">
+      <div class="button-pane">
+        <input type="button" id="search-list-button"
+               value="List database content" />
+      </div>
+    </form>
+
+    <div>
+      <div id="pub-msg">
+      </div>
+      <div id="pub-viewer">
+      </div>
+      <ul id="pub-list">
+      </ul>
+    </div>
+
+ +

CSS コンテンツ

+ +
body {
+  font-size: 0.8em;
+  font-family: Sans-Serif;
+}
+
+form {
+  background-color: #cccccc;
+  border-radius: 0.3em;
+  display: inline-block;
+  margin-bottom: 0.5em;
+  padding: 1em;
+}
+
+table {
+  border-collapse: collapse;
+}
+
+input {
+  padding: 0.3em;
+  border-color: #cccccc;
+  border-radius: 0.3em;
+}
+
+.required:after {
+  content: "*";
+  color: red;
+}
+
+.button-pane {
+  margin-top: 1em;
+}
+
+#pub-viewer {
+  float: right;
+  width: 48%;
+  height: 20em;
+  border: solid #d092ff 0.1em;
+}
+#pub-viewer iframe {
+  width: 100%;
+  height: 100%;
+}
+
+#pub-list {
+  width: 46%;
+  background-color: #eeeeee;
+  border-radius: 0.3em;
+}
+#pub-list li {
+  padding-top: 0.5em;
+  padding-bottom: 0.5em;
+  padding-right: 0.5em;
+}
+
+#msg {
+  margin-bottom: 1em;
+}
+
+.action-success {
+  padding: 0.5em;
+  color: #00d21e;
+  background-color: #eeeeee;
+  border-radius: 0.2em;
+}
+
+.action-failure {
+  padding: 0.5em;
+  color: #ff1408;
+  background-color: #eeeeee;
+  border-radius: 0.2em;
+}
+
+.note {
+  font-size: smaller;
+}
+
+.destructive {
+  background-color: orange;
+}
+.destructive:hover {
+  background-color: #ff8000;
+}
+.destructive:active {
+  background-color: red;
+}
+
+ +

JavaScript コンテンツ

+ +
(function () {
+  var COMPAT_ENVS = [
+    ['Firefox', ">= 16.0"],
+    ['Google Chrome',
+     ">= 24.0 (you may need to get Google Chrome Canary), NO Blob storage support"]
+  ];
+  var compat = $('#compat');
+  compat.empty();
+  compat.append('<ul id="compat-list"></ul>');
+  COMPAT_ENVS.forEach(function(val, idx, array) {
+    $('#compat-list').append('<li>' + val[0] + ': ' + val[1] + '</li>');
+  });
+
+  const DB_NAME = 'mdn-demo-indexeddb-epublications';
+  const DB_VERSION = 1; // この値は long long を使用します (float ではありません)
+  const DB_STORE_NAME = 'publications';
+
+  var db;
+
+  // ビューの無駄な再読み込みを避けるため、どのビューが表示されているかを追跡するために使用します
+  var current_view_pub_key;
+
+  function openDb() {
+    console.log("openDb ...");
+    var req = indexedDB.open(DB_NAME, DB_VERSION);
+    req.onsuccess = function (evt) {
+      // ガベージコレクションの問題を避けるため、結果を得る際は
+      // "req" より "this" を使用する方がよい
+      // db = req.result;
+      db = this.result;
+      console.log("openDb DONE");
+    };
+    req.onerror = function (evt) {
+      console.error("openDb:", evt.target.errorCode);
+    };
+
+    req.onupgradeneeded = function (evt) {
+      console.log("openDb.onupgradeneeded");
+      var store = evt.currentTarget.result.createObjectStore(
+        DB_STORE_NAME, { keyPath: 'id', autoIncrement: true });
+
+      store.createIndex('biblioid', 'biblioid', { unique: true });
+      store.createIndex('title', 'title', { unique: false });
+      store.createIndex('year', 'year', { unique: false });
+    };
+  }
+
+  /**
+   * @param {string} store_name
+   * @param {string} mode "readonly" または "readwrite"
+   */
+  function getObjectStore(store_name, mode) {
+    var tx = db.transaction(store_name, mode);
+    return tx.objectStore(store_name);
+  }
+
+  function clearObjectStore(store_name) {
+    var store = getObjectStore(DB_STORE_NAME, 'readwrite');
+    var req = store.clear();
+    req.onsuccess = function(evt) {
+      displayActionSuccess("Store cleared");
+      displayPubList(store);
+    };
+    req.onerror = function (evt) {
+      console.error("clearObjectStore:", evt.target.errorCode);
+      displayActionFailure(this.error);
+    };
+  }
+
+  function getBlob(key, store, success_callback) {
+    var req = store.get(key);
+    req.onsuccess = function(evt) {
+      var value = evt.target.result;
+      if (value)
+        success_callback(value.blob);
+    };
+  }
+
+  /**
+   * @param {IDBObjectStore=} store
+   */
+  function displayPubList(store) {
+    console.log("displayPubList");
+
+    if (typeof store == 'undefined')
+      store = getObjectStore(DB_STORE_NAME, 'readonly');
+
+    var pub_msg = $('#pub-msg');
+    pub_msg.empty();
+    var pub_list = $('#pub-list');
+    pub_list.empty();
+    // 以前のコンテンツを表示しないようにするため、iframe をリセットします
+    newViewerFrame();
+
+    var req;
+    req = store.count();
+    // リクエストは、トランザクションに対して作成された順で実行され、
+    // 結果も同じ順序で返されます。
+    // よって、以下の計数は実際の pub のリストより前に表示されるかもしれません
+    // (このケースでは、アルゴリズム的に重要ではありません)。
+    req.onsuccess = function(evt) {
+      pub_msg.append('<p>There are <strong>' + evt.target.result +
+                     '</strong> record(s) in the object store.</p>');
+    };
+    req.onerror = function(evt) {
+      console.error("add error", this.error);
+      displayActionFailure(this.error);
+    };
+
+    var i = 0;
+    req = store.openCursor();
+    req.onsuccess = function(evt) {
+      var cursor = evt.target.result;
+
+      // カーソルが何かを指している場合に、データを要求します
+      if (cursor) {
+        console.log("displayPubList cursor:", cursor);
+        req = store.get(cursor.key);
+        req.onsuccess = function (evt) {
+          var value = evt.target.result;
+          var list_item = $('<li>' +
+                            '[' + cursor.key + '] ' +
+                            '(biblioid: ' + value.biblioid + ') ' +
+                            value.title +
+                            '</li>');
+          if (value.year != null)
+            list_item.append(' - ' + value.year);
+
+          if (value.hasOwnProperty('blob') &&
+              typeof value.blob != 'undefined') {
+            var link = $('<a href="' + cursor.key + '">File</a>');
+            link.on('click', function() { return false; });
+            link.on('mouseenter', function(evt) {
+                      setInViewer(evt.target.getAttribute('href')); });
+            list_item.append(' / ');
+            list_item.append(link);
+          } else {
+            list_item.append(" / No attached file");
+          }
+          pub_list.append(list_item);
+        };
+
+        // ストア内の次のオブジェクトに移動する
+        cursor.continue();
+
+        // このカウンタは、個別の ID を作成するためだけに使用する
+        i++;
+      } else {
+        console.log("No more entries");
+      }
+    };
+  }
+
+  function newViewerFrame() {
+    var viewer = $('#pub-viewer');
+    viewer.empty();
+    var iframe = $('<iframe />');
+    viewer.append(iframe);
+    return iframe;
+  }
+
+  function setInViewer(key) {
+    console.log("setInViewer:", arguments);
+    key = Number(key);
+    if (key == current_view_pub_key)
+      return;
+
+    current_view_pub_key = key;
+
+    var store = getObjectStore(DB_STORE_NAME, 'readonly');
+    getBlob(key, store, function(blob) {
+      console.log("setInViewer blob:", blob);
+      var iframe = newViewerFrame();
+
+      // 直接ダウンロードするという意味で、blob に
+      // 直接リンクを設定することはできません。
+      if (blob.type == 'text/html') {
+        var reader = new FileReader();
+        reader.onload = (function(evt) {
+          var html = evt.target.result;
+          iframe.load(function() {
+            $(this).contents().find('html').html(html);
+          });
+        });
+        reader.readAsText(blob);
+      } else if (blob.type.indexOf('image/') == 0) {
+        iframe.load(function() {
+          var img_id = 'image-' + key;
+          var img = $('<img id="' + img_id + '"/>');
+          $(this).contents().find('body').html(img);
+          var obj_url = window.URL.createObjectURL(blob);
+          $(this).contents().find('#' + img_id).attr('src', obj_url);
+          window.URL.revokeObjectURL(obj_url);
+        });
+      } else if (blob.type == 'application/pdf') {
+        $('*').css('cursor', 'wait');
+        var obj_url = window.URL.createObjectURL(blob);
+        iframe.load(function() {
+          $('*').css('cursor', 'auto');
+        });
+        iframe.attr('src', obj_url);
+        window.URL.revokeObjectURL(obj_url);
+      } else {
+        iframe.load(function() {
+          $(this).contents().find('body').html("No view available");
+        });
+      }
+
+    });
+  }
+
+  /**
+   * @param {string} biblioid
+   * @param {string} title
+   * @param {number} year
+   * @param {string} url ダウンロードしてローカルの IndexedDB データベースに保存する
+   *   画像の URL。この URL の背後にあるリソースは、"同一生成元ポリシー" に従います。
+   *   よって、この方法を動作させるために URL は、このコードを配置する
+   *   Web サイト/アプリと同一生成元であることが必要です。
+   */
+  function addPublicationFromUrl(biblioid, title, year, url) {
+    console.log("addPublicationFromUrl:", arguments);
+
+    var xhr = new XMLHttpRequest();
+    xhr.open('GET', url, true);
+    // 希望する responseType を "blob" に設定
+    // http://www.w3.org/TR/XMLHttpRequest2/#the-response-attribute
+    xhr.responseType = 'blob';
+    xhr.onload = function (evt) {
+      if (xhr.status == 200) {
+        console.log("Blob retrieved");
+        var blob = xhr.response;
+        console.log("Blob:", blob);
+        addPublication(biblioid, title, year, blob);
+      } else {
+        console.error("addPublicationFromUrl error:",
+        xhr.responseText, xhr.status);
+      }
+    };
+    xhr.send();
+
+    // jQuery 1.8.3 では新しい "blob" responseType を扱わないため、
+    // ここでは jQuery を使用できません。
+    // http://bugs.jquery.com/ticket/11461
+    // http://bugs.jquery.com/ticket/7248
+    // $.ajax({
+    //   url: url,
+    //   type: 'GET',
+    //   xhrFields: { responseType: 'blob' },
+    //   success: function(data, textStatus, jqXHR) {
+    //     console.log("Blob retrieved");
+    //     console.log("Blob:", data);
+    //     // addPublication(biblioid, title, year, data);
+    //   },
+    //   error: function(jqXHR, textStatus, errorThrown) {
+    //     console.error(errorThrown);
+    //     displayActionFailure("Error during blob retrieval");
+    //   }
+    // });
+  }
+
+  /**
+   * @param {string} biblioid
+   * @param {string} title
+   * @param {number} year
+   * @param {Blob=} blob
+   */
+  function addPublication(biblioid, title, year, blob) {
+    console.log("addPublication arguments:", arguments);
+    var obj = { biblioid: biblioid, title: title, year: year };
+    if (typeof blob != 'undefined')
+      obj.blob = blob;
+
+    var store = getObjectStore(DB_STORE_NAME, 'readwrite');
+    var req;
+    try {
+      req = store.add(obj);
+    } catch (e) {
+      if (e.name == 'DataCloneError')
+        displayActionFailure("This engine doesn't know how to clone a Blob, " +
+                             "use Firefox");
+      throw e;
+    }
+    req.onsuccess = function (evt) {
+      console.log("Insertion in DB successful");
+      displayActionSuccess();
+      displayPubList(store);
+    };
+    req.onerror = function() {
+      console.error("addPublication error", this.error);
+      displayActionFailure(this.error);
+    };
+  }
+
+  /**
+   * @param {string} biblioid
+   */
+  function deletePublicationFromBib(biblioid) {
+    console.log("deletePublication:", arguments);
+    var store = getObjectStore(DB_STORE_NAME, 'readwrite');
+    var req = store.index('biblioid');
+    req.get(biblioid).onsuccess = function(evt) {
+      if (typeof evt.target.result == 'undefined') {
+        displayActionFailure("No matching record found");
+        return;
+      }
+      deletePublication(evt.target.result.id, store);
+    };
+    req.onerror = function (evt) {
+      console.error("deletePublicationFromBib:", evt.target.errorCode);
+    };
+  }
+
+  /**
+   * @param {number} key
+   * @param {IDBObjectStore=} store
+   */
+  function deletePublication(key, store) {
+    console.log("deletePublication:", arguments);
+
+    if (typeof store == 'undefined')
+      store = getObjectStore(DB_STORE_NAME, 'readwrite');
+
+    // 仕様書 http://www.w3.org/TR/IndexedDB/#object-store-deletion-operation によれば
+    // Object Store Deletion Operation アルゴリズムの結果は undefined であり、
+    // あるレコードが実際に削除されたかを、リクエストの結果を確認して
+    // 知ることはできません。
+    var req = store.get(key);
+    req.onsuccess = function(evt) {
+      var record = evt.target.result;
+      console.log("record:", record);
+      if (typeof record == 'undefined') {
+        displayActionFailure("No matching record found");
+        return;
+      }
+      // 警告: 削除するには、作成時に使用したものとまったく同じキーを使用しなければ
+      // なりません。作成時のキーが数値であった場合は、削除時も数値でなければ
+      // なりません。
+      req = store.delete(key);
+      req.onsuccess = function(evt) {
+        console.log("evt:", evt);
+        console.log("evt.target:", evt.target);
+        console.log("evt.target.result:", evt.target.result);
+        console.log("delete successful");
+        displayActionSuccess("Deletion successful");
+        displayPubList(store);
+      };
+      req.onerror = function (evt) {
+        console.error("deletePublication:", evt.target.errorCode);
+      };
+    };
+    req.onerror = function (evt) {
+      console.error("deletePublication:", evt.target.errorCode);
+    };
+  }
+
+  function displayActionSuccess(msg) {
+    msg = typeof msg != 'undefined' ? "Success: " + msg : "Success";
+    $('#msg').html('<span class="action-success">' + msg + '</span>');
+  }
+  function displayActionFailure(msg) {
+    msg = typeof msg != 'undefined' ? "Failure: " + msg : "Failure";
+    $('#msg').html('<span class="action-failure">' + msg + '</span>');
+  }
+  function resetActionStatus() {
+    console.log("resetActionStatus ...");
+    $('#msg').empty();
+    console.log("resetActionStatus DONE");
+  }
+
+  function addEventListeners() {
+    console.log("addEventListeners");
+
+    $('#register-form-reset').click(function(evt) {
+      resetActionStatus();
+    });
+
+    $('#add-button').click(function(evt) {
+      console.log("add ...");
+      var title = $('#pub-title').val();
+      var biblioid = $('#pub-biblioid').val();
+      if (!title || !biblioid) {
+        displayActionFailure("Required field(s) missing");
+        return;
+      }
+      var year = $('#pub-year').val();
+      if (year != '') {
+        // EcmaScript 6 エンジンでは use Number.isInteger を使用するとよい
+        if (isNaN(year))  {
+          displayActionFailure("Invalid year");
+          return;
+        }
+        year = Number(year);
+      } else {
+        year = null;
+      }
+
+      var file_input = $('#pub-file');
+      var selected_file = file_input.get(0).files[0];
+      console.log("selected_file:", selected_file);
+      // UI に入力されたファイルの値を取得したら UI をリセットする方法を参考として置きますが、
+      // これを行うよりも HTML フォーム内の "reset" 型の
+      // input を使用します。
+      //file_input.val(null);
+      var file_url = $('#pub-file-url').val();
+      if (selected_file) {
+        addPublication(biblioid, title, year, selected_file);
+      } else if (file_url) {
+        addPublicationFromUrl(biblioid, title, year, file_url);
+      } else {
+        addPublication(biblioid, title, year);
+      }
+
+    });
+
+    $('#delete-button').click(function(evt) {
+      console.log("delete ...");
+      var biblioid = $('#pub-biblioid-to-delete').val();
+      var key = $('#key-to-delete').val();
+
+      if (biblioid != '') {
+        deletePublicationFromBib(biblioid);
+      } else if (key != '') {
+        // EcmaScript 6 エンジンでは use Number.isInteger を使用するとよい
+        if (key == '' || isNaN(key))  {
+          displayActionFailure("Invalid key");
+          return;
+        }
+        key = Number(key);
+        deletePublication(key);
+      }
+    });
+
+    $('#clear-store-button').click(function(evt) {
+      clearObjectStore();
+    });
+
+    var search_button = $('#search-list-button');
+    search_button.click(function(evt) {
+      displayPubList();
+    });
+
+  }
+
+  openDb();
+  addEventListeners();
+
+})(); // Immediately-Invoked Function Expression (IIFE)
+
+ +

{{LiveSampleLink('Full_IndexedDB_example', "オンラインのライブデモを試す")}}

+ +

関連情報

+ +

必要に応じて、より多くの情報を知るための記事集です。

+ +

リファレンス

+ + + +

チュートリアルとガイド

+ + + +

ライブラリ

+ + diff --git a/files/ja/web/api/indexeddb_api/using_indexeddb_in_chrome/index.html b/files/ja/web/api/indexeddb_api/using_indexeddb_in_chrome/index.html new file mode 100644 index 0000000000..c55da4940f --- /dev/null +++ b/files/ja/web/api/indexeddb_api/using_indexeddb_in_chrome/index.html @@ -0,0 +1,33 @@ +--- +title: chrome(ブラウザー内部) で IndexedDB を使う +slug: Web/API/IndexedDB_API/Using_IndexedDB_in_chrome +translation_of: Mozilla/Tech/XPCOM/Using_IndexedDB_in_chrome +--- +
{{DefaultAPISidebar("IndexedDB")}}
+ +

IndexedDB API は、通常、コンテンツ JavaScript からユーザーのブラウザーにデータを格納するために使用されます(概要については IndexedDB の使用を参照してください) 。ただし、この API には、システム特権の JavaScript から、Components.utils.importGlobalProperties() 関数を使用してアクセスすることもできます:

+ +
Components.utils.importGlobalProperties(["indexedDB"]);
+
+// ここからは、コンテンツから IndexedDB を使うのと同様
+var req = indexedDB.open("my-database");
+// ...
+ +

サンドボックスを作成していて、その中で indexedDB を使用できるようにするには、Sandbox コンストラクタで wantGlobalProperties オプションを使用します:

+ +
var options = {
+  "wantGlobalProperties": ["indexedDB"]
+}
+var principal = Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal);
+var sandbox = Components.utils.Sandbox(principal, options);
+
+// サンドボックスから indexedDB にアクセスできるようになる
+var sandboxScript = 'var req = indexedDB.open("my-database");';
+Components.utils.evalInSandbox(sandboxScript, sandbox);
+
+ +

Firefox 33以前では、nsIIndexedDatabaseManager サービスの initWindowless メソッドを使用して chrome コードから indexedDB にアクセスしました。このメソッドは Firefox 33 で削除されました。

+ +
+

訳注: ここでの chrome コードとは、Google chrome ではなく、chrome特権付きのコードを指しています

+
-- cgit v1.2.3-54-g00ecf