2013年07月06日

【Packaged Apps】IndexedDBの超簡単なCRUDサンプル【Chrome】

ChromeやFirefoxでも実装されている Indexed Database API は非常に強力で有望なAPIだが、現時点ではネット上の情報が極めて少ない。
今回はその少ない情報をかき集め、超簡単なCRUDサンプルアプリを作り上げた。
IndexedDBの動きをざっくりと知る上では、 html5rocksさんの「今となっては動かないサンプル」よりは、学習しやすいサンプルアプリになったのではないかと自負している。

ただし今後はGoogleさんやMozillaさんの努力で仕様がガラリと変わる可能性もありそうなので常にアンテナを貼っておく必要がある。

では早速いってみよう。
まずはプロジェクト構成:
manifest.json
index.html
app.js
background.js
calculator-64.png
calculator-128.png

重要ファイルは太字で書いた。

manifest.json
{
  "name": "Indexed Database API sample App",
  "description": "Sample app using the Indexed Database API.",
  "version": "0.1",
  "app": {
    "background": {
      "scripts": ["background.js"]
    }
  },
  "icons": { "16": "calculator-16.png", "128": "calculator-128.png" },
  "permissions": []
}

いつもどおりのシンプルなマニフェスト。
permissionsも特に指定していない。

次は background.js
chrome.app.runtime.onLaunched.addListener(function() {
  chrome.app.window.create('index.html', {
    'bounds': {
      'width': 400,
      'height': 500
    }
  });
});

特に説明も不要だろう。(ここで説明が欲しい人はHelloworldで勉強して欲しい)

さて次は画面。
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>IndexedDB Example</title>
</head>
<body>
<h1>IndexedDB Example</h1>
<button id="init_btn">DB初期化</button><br/>
<input type="text" placeholder="textを入力" id="msg"></input>
<button id="add_btn">データ追加</button><br/>
<button id="get_btn">データ取得</button><br/>
<input type="text" placeholder="削除したいhogeId" id="hogeId"></input>
<button id="delete_btn">データ削除</button><br/>
<script src="app.js"></script>
</body>
</html>

見ての通り、データベースの初期化、データの追加、データの取得、データの削除が行える簡単な画面だ。

では、いよいよお待ちかねのJS本体。
app.js
// IndexDBインスタンス取得
var indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB;
// DBインスタンス
var db = null;
// DB名
var DB_NAME = "hogeDB";
/*
 * DBバージョン
 *
 * オブジェクトストアを初期化したい時は
 * upgradeneededイベントを発火させるために
 * DB_VERをインクリメントする
 */
var DB_VER = 1;

/**
 * DB初期化関数
 */
function initDB() {
  var req = indexedDB.open(DB_NAME, DB_VER);
  
  req.onsuccess = function (evt) {
    db = req.result;
    console.log('initDB success.');                                            
  };
 
  req.onerror = function (evt) {
    console.log("IndexedDB error: " + evt.target.errorCode);
  };
 
  /**
   * 注意!
   * オブジェクトストアの作成・削除は、
   * onupgradeneeded(バージョン更新のコールバック)内でしか実行不可
   */
  req.onupgradeneeded = function (evt) {
    // オブジェクトストア初期化
    req.result.deleteObjectStore("hogeStore");
    console.log('objectStore deleted.');
    // オブジェクトストア作成
    var store = evt.target.result.createObjectStore(
      "hogeStore", 
      { keyPath: "hogeId", autoIncrement: true }
    );
    store.createIndex("text", "text", { unique: false });
    store.createIndex("timestamp", "timestamp", { unique: false });
    console.log('objectStore defined.');
  };
}

/**
 * データ追加関数
 */
function addData(msg) {
  var data = { text: msg , timestamp: new Date().getTime() };
  var tx = db.transaction("hogeStore", "readwrite");
  var store = tx.objectStore("hogeStore");
  var req = store.add(data);
  req.onsuccess = function(evt){
    console.log('addData success.');
  };
  req.onerror = function(evt){
    console.error('addData failed. errorCode:' + evt.result.errorCode);
  };
}

/**
 * データ読込&ログ出力関数
 */
function getData() {
  var tx = db.transaction("hogeStore", "readwrite");
  var store = tx.objectStore("hogeStore");
 
  var req = store.openCursor();
  req.onsuccess = function(evt) {  
    var cursor = evt.target.result;  
    if (cursor) {  
      console.log("id:" + cursor.key 
        + " text:" + cursor.value.text 
        + " timestamp:" + cursor.value.timestamp 
        + "\n");                            
      cursor.continue();  
    } else {  
      console.log("End of rows.");  
    }  
  };
}

/**
 * データ削除関数
 */
function deleteData(hogeId) {
  var tx = db.transaction("hogeStore", "readwrite");
  var store = tx.objectStore("hogeStore");
  var req = store.get(hogeId);
  req.onsuccess = function(evt){
    store.delete(req.result.hogeId);
    console.log('id=' + req.result.hogeId + ' deleted.');
  };  
}

/**
 * オブジェクトストア削除関数
 */
function deleteStore() {
  if (db.objectStoreNames.contains("hogeStore")) {
    var req = db.deleteObjectStore("hogeStore");
    req.onsuccess = function(evt){
      console.log('deleteObjectStore success.');
    };
    req.onerror = function(evt){
      console.log('deleteObjectStore failed. errorCode:' + evt.target.errorCode);
    }
  }
}


/**
 * DB初期化ボタンクリック
 */
document.getElementById('init_btn').onclick = function() {
  initDB();
}

/**
 * データ追加ボタンクリック
 */
document.getElementById('add_btn').onclick = function() {
  var msg = document.getElementById('msg').value;
  addData(msg);
}

/**
 * データ取得ボタンクリック
 */
document.getElementById('get_btn').onclick = function() {
  getData();
}

/**
 * データ削除ボタンクリック
 */
document.getElementById('delete_btn').onclick = function() {
  var hogeId = document.getElementById('hogeId').value;
  deleteData(parseInt(hogeId));
}

大まかな流れとしては、

1.データベース取得
 indexedDB.open()

2.データストア(テーブル)取得
 tx = db.transaction()
 store = tx.objectStore()

3.データを追加、取得、削除
 objectStore.add()
 objectStore.get() / objectStore.openCursor()
 objectStore.delete()

となる。

このサンプルで最も注意が必要なのは、db.createObjectStore() や db.deleteObjectStore() が onupgradeneeded の中でしか実行できないということ。
例えばこのサンプルでオブジェクトストアを削除したい時は、 バージョン(DB_VER)を上げてupgradeneededイベントを発火させるしかない。
自分はここでかなりハマった。


参考
HTML5 の IndexedDB を使用した簡単な TODO リスト - HTML5 Rocks
Getting Started with IndexedDB - CodeProject
Using IndexedDB - IndexedDB | MDN
IndexedDB概要 - hifive


関連エントリ
【Packaged Apps】SyncFileSystemを使ってローカルファイルとリモートファイルを別け隔てなく扱う【Chrome】
posted by 寄り道退屈男 at 16:12 | Comment(0) | TrackBack(0) | Packaged Apps
この記事へのコメント
コメントを書く
お名前: [必須入力]

メールアドレス: [必須入力]

ホームページアドレス:

コメント: [必須入力]

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。
この記事へのトラックバックURL
http://blog.sakura.ne.jp/tb/70580250
※言及リンクのないトラックバックは受信されません。

この記事へのトラックバック