皆様、おはようございます。今回は Indexed Database API についてです。
そう言えば今年は蛇年ですね、蛇。
最近すっかり見なくなってしまいましたが、昔はシマヘビなんかを見かける度に追いかけ回したものです。
Indexed Database API ってどうなの?
さて、久々にガッチリと プログラム関係の技術情報 で行きたいと思います。初心忘れるべからずです。
当社は今年も引き続きスマホやタブレットの web アプリを推して行きたいと思っているのですが、必ずネックになる部分があります。
それはやはり「 ローカルでデータベースを操作する 」、これでしょう。
当サイトでも色々検証しました マニフェスト と組み合わせれば、オーディオ / ビデオ系の再生を除けばかなり ネイティブアプリ のようにゴキゲンな動作をするアプリが作成可能になります。
黎明期は JS から「 SQLite 」を扱う事が出来る Web SQL Database API 一択だったので、何も悩むこと無く使っていたのですが、 2010 年だか 2011 年だかにこの API が HTML5 の仕様策定範囲から外されてしまいました。(出典 : Livecast 様)
よって去年( 2012 年)は今後メンテナンスが無く、いつ消えても文句は言えない状態の Web SQL Database API なんて使ってらんないぜ!推奨されている Indexed Database API ってのに乗り換えよう!
と色んな解説サイトを巡りながらサンプルを作ってみたのは良かったのですが…
iOS で動かないのよ、コレが。
http://labs.vividworks.jp/indexedDB/
プログラムの中身はこんな感じ
※jQueryを読み込んでます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 | var IDB = window.indexedDB || window.webkitIndexedDB; var cursorIDB = window.IDBCursor || window.webkitIDBCursor; var transactionIDB = window.IDBTransaction || window.webkitIDBTransaction; var keyrangeIDB = window.IDBKeyRange|| window.webkitIDBKeyRange; var DB_NAME = 'TEST'; var DB_VERSION = '1.0'; var DB_TODO = 'todo'; var DB_SORT = false; var odb; alert(IDB); //================================================= // DBのバージョン別 Object Store 初期化オブジェクト //================================================= var DB_OBJECT_STORES = { '1.0': function(odb) { var objectStore = initObjectStore(odb, DB_TODO, { keyPath: "id", autoIncrement: true }); objectStore.createIndex('flg', 'flg'); } }; //================================================= // Object Storeのイニシャライズ //------------------------------------------------- // DBスキーマが変更されていたら既存の Object Storeを破棄して再生成する。 // 存在し無い場合は新規で生成する。 //------------------------------------------------- // @param: object // @param: string // @param: object //------------------------------------------------- // @return: object //================================================= var initObjectStore = function(odb, name, option) { var check = odb.objectStoreNames; for(var key in check) { if(check[key] == name) odb.deleteObjectStore(name); } var objectStore = odb.createObjectStore(name, option); return objectStore; }; //================================================= // DBのバージョンを比較 //------------------------------------------------- // @param: string // @param: string //------------------------------------------------- // @return: bool //================================================= var checkVersion = function(current, latest) { if(current != latest) return false; else return true; }; //================================================= // DBオープン //------------------------------------------------- // @param: object //================================================= var openDB = function(callback) { var req = IDB.open(DB_NAME); // オープンに成功 req.onsuccess = function(event) { alert("success"); odb = req.result; var current = odb.version || 0; var latest = DB_VERSION; if(!checkVersion(current, latest)) { odb.setVersion(latest).onsuccess = function(event) { for(var key in DB_OBJECT_STORES) DB_OBJECT_STORES[key](odb); typeof callback == "function" && callback(); }; } else { typeof callback == "function" && callback(); } getTodo(function(todo) { showTodo(todo); }, DB_SORT); }; // オープンに失敗 req.onerror = function(event) { alert("error"); }; }; //================================================= // ObjectStore の取得 //------------------------------------------------- // @param: string // @param: string //------------------------------------------------- // @return: object //================================================= var getObjectStore = function(name, mode) { var transaction; switch(mode) { case 'READ_WRITE': transaction = odb.transaction(name, transactionIDB.READ_WRITE); break; } return transaction.objectStore(name); }; //================================================= // データ書き込み //================================================= var putTodo = function() { if($('#title').val().length > 0) { var data = { flg: 0, title: $('#title').val() }; var todoObjectStore = getObjectStore(DB_TODO, 'READ_WRITE'); var put = todoObjectStore.put(data); put.onsuccess = function() { $('#title').val(''); getTodo(function(todo) { showTodo(todo); }, DB_SORT); alert('登録しました。'); }; put.onerror = function() { alert('登録に失敗しました。'); }; } }; //================================================= // データ読み込み //================================================= var getTodo = function(callback) { var todoObjectStore = getObjectStore(DB_TODO, 'READ_WRITE'); var res = new Array(); var get; switch(DB_SORT) { case 0: get = todoObjectStore.index('flg').openCursor(0); break; case 1: get = todoObjectStore.index('flg').openCursor(1, cursorIDB.PREV); break; default: get = todoObjectStore.openCursor(); break; } get.onsuccess = function() { var cursor = get.result; if(!cursor) { typeof callback == "function" && callback(res); return; } res.push(cursor.value); cursor.continue(); }; get.onerror = function() { alert('読み込みに失敗しました。'); }; }; //================================================= // データ更新 //================================================= var updateTodo = function(id, mode) { var todoObjectStore = getObjectStore(DB_TODO, 'READ_WRITE'); var update = todoObjectStore.openCursor(keyrangeIDB.lowerBound(id, false)); update.onsuccess = function() { var cursor = update.result; if(cursor) { var data = cursor.value; data.flg = mode; cursor.update(data); cursor.continue(); } getTodo(function(todo) { showTodo(todo); }, DB_SORT); }; update.onerror = function() { alert('更新に失敗しました。'); }; }; //================================================= // データ削除 //================================================= var deleteTodo = function(id) { var todoObjectStore = getObjectStore(DB_TODO, 'READ_WRITE'); var del = todoObjectStore.delete(id); del.onsuccess = function() { getTodo(function(todo) { showTodo(todo); }, DB_SORT); alert('削除しました。'); }; del.onerror = function() { alert('削除に失敗しました。'); }; }; //================================================= // データ表示 //================================================= var showTodo = function(todo) { var container = document.getElementById('todoList'); for(var i = container.childNodes.length - 1; i >= 0; i--) { container.removeChild(container.childNodes[i]); } for(var key in todo) { var li = document.createElement('li'); var update = document.createElement('input'); var del = document.createElement('input'); var html = ''; update.type = 'button'; update.value = '変更'; update.id = 'todoUpdate_' + todo[key].id; del.type = 'button'; del.value = '削除'; del.id = 'todoDel_' + todo[key].id; del.onclick = function() { if(confirm("削除しますか?")) deleteTodo(parseInt(this.id.replace('todoDel_', ''))); }; if(todo[key].flg == 0) { html += '[未処理]'; update.onclick = function() { updateTodo(parseInt(this.id.replace('todoUpdate_', '')), 1); }; } else { html += '[処理済]'; update.onclick = function() { updateTodo(parseInt(this.id.replace('todoUpdate_', '')), 0); }; } html += todo[key].title; li.appendChild(document.createTextNode(html)); li.appendChild(update); li.appendChild(del); container.appendChild(li) } }; //================================================= // ソート切り替え //================================================= var sortTodo = function(id) { switch(id) { case 'sort1': DB_SORT = false; break; case 'sort2': DB_SORT = 0; break; case 'sort3': DB_SORT = 1; break; } getTodo(function(todo) { showTodo(todo); }, DB_SORT); }; //================================================= // メイン処理 //================================================= $(function() { $('#register').click(function() { putTodo(); }); $('#sort1').click(function() { sortTodo($(this).attr('id')); }); $('#sort2').click(function() { sortTodo($(this).attr('id')); }); $('#sort3').click(function() { sortTodo($(this).attr('id')); }); openDB(function() { // }); }); |
いやマジで、上記サンプルを iOS 系で開いてもらえば分かりますが、11 行目の DB Object 取得部分で既に undefined が返ってくる。( =未実装 )
こっち使えと言いつつ実際は使えないってどうなん?
ねぇねぇ?どうして欲しいの??
と、割と本気で思います。
2013 年には…と淡い期待も有ったのですがやはりダメなようです。
iPhone4 、 iPad Retina のそれぞれ最新の iOS ファームで確認( 2013/01/08 )。ちなみに Chrome for iOS でも当然ダメだった、ベースの webkit が同じなんだから当たり前か。
勿論 PC や Mac の webkit 系なら動くよ!多分 IE 10 とかも大丈夫。
結局「 無いものは仕方がない 」と言う事で、引き続き SQLite でゴリゴリ作るしか無いのかなぁ。
私は別に代替えで推奨の物があるなら、別に SQL だろうが key / value 型であろうが何でも構わないと思っています。
だって、欲しいものは「 要件の動きが実現可能 」「 動作の安定感 」「 アップデートによる保守性 」じゃないですか?
今市場を見れば「 全力で HTML5 を使えるのは スマホ / タブレット だけ! 」と言うのは火を見るより明らかです、ブラウザが webkit 系しか無い訳だし。
じゃあ、さっさと何とかしてちょうだい!と思うのはワガママなのでしょうか。
どう考えても PC or Mac より、スマホ or タブレットの方が ローカル DB の恩恵が大きい と言う事実が追い打ちをかけています。
据え置き機がオフライン状態でしか使えないなんて事、回線トラブルとか引越し直後以外に有るの?って感じですし。
個人が買うマシンは明らかに 過剰なオーバースペック になって行ってるし、回線だって ISDN が珍しくなってきているこのご時世に「 トラフィックを減らす 」「 高速ブラウジング 」なんてのは、本当に大きな大きなサイトを運営していて、かなりサーバがしんどいよ、きっついよって思ってる極々一握りの世界なんじゃ無いかって思うんです。
実際は色々無茶が出来る据え置き機で開発を重ね、仕様が安定してから iOS 等のコンパクト OS に移植するって事なんでしょうけどね。
今年も悶々としながら続報を待っています、もう少し勉強を進めるべきか。
なんとなく下書きが残っていたので投稿しました。
それではまた。
Indexed Database API について(へっぽこプログラマーの日記様)
Indexed Database API(HTML クイックリファレンス様)
Indexed Database APIによるデータベース(Libro.様)などなど、参考にさせて頂きました。