[iOS 8] Safari 8 でIndexedDBが使えるようになった!

Safari 8がIndexedDBに対応

Safari 8からIndexedDBのAPIが対応されたのでためしてみました。
サンプルアプリとして、簡単なTODOリストを作成してみます。

現在の対応状況

ios8-indexdb

上記、図表からSafari 8に対応したみたいですね!

ここににきて、ある程度のブラウザでIndexedDBのAPIが使える状況になりました!

IndexedDBとは?

key-value ストア型のデータベースをローカルに構築できる仕組みです。
利用可能な容量は制限があり、大規模な容量は格納できません。Chrome では 5MB 等とのこと。 Safari 8の容量についての情報がまだないので、随時キャッチアップしていければと思います。

LocalStorageとの比較

ユーザーのローカル環境(ブラウザ)にデータを保存するための代表格としては、 LocalStorageが有名かとおもいます。IndexedDBとどんな違いがあるのでしょうか?

機能 IndexedDB LocalStorage
オリジン単位でのデータの保存
トランザクション機能 ×
インデックス機能 ×
利用できるストア(RDBのテーブルぽいもの) 複数 単一

一つのローカルDBに複数のストア(テーブル)を保持できるのは魅力ですね!

実装方法

今回はindexedDBの簡単な動作検証をしてみたいと思います。

詳細な実装に関しては、弊社メンバー執筆した以下の記事をご覧いただければと思います。

ブラウザが対応しているかのチェック

IndexedDBが対応しているのを確認する手っ取り早い方法として、windowオブジェクトにIndexedDBプロパティがあるかで確認することができます。

IndexedDBプロパティの記述方法として、各ブラウザが対応しているベンダ・プレフィックスを付けて記述して確認してみます。

var indexedDB = window.indexedDB || window.mozIndexedDB || window.msIndexedDB || window.webkitIndexedDB;
if (indexedDB) {
  window.alert("IndexedDBが使えまます");
} else {
  window.alert("IndexedDBが使えません");
}

上記ソースを記述したhtmlを実機で確認した際が以下になります。
safari 8で確認した際は、IndexedDBプロパティがあるのを確認できました。

img-indexeddb-001_v2

img-indexeddb-002_v2

実際の動作

データベースのオープン

データベースを接続した際に、以下のコールバックが呼ばれます。

    // todosデータベースをオープンする
    var version = 1;
    var openRequest = webkitIndexedDB.open("todoList", version);

    // データベースの初回時オープン時とバージョンの変更時に呼ばれる
    openRequest.onupgradeneeded = function(e) {
	// 初回時 or データベースのバージョン変更時の処理
    }

    // IndexedDBオープン成功時に呼ばれる
    openRequest.onsuccess = function(e) {
	// データベースオープンが成功後の処理
    };

    // IndexedDBオープンの失敗時に呼ばれる
    openRequest.onerror = function(e) {
      // エラーがおこった場合、一旦コンソールログで確認
      console.log("Database error: " + e.target.errorCode);
    };

オブジェクトストアの作成

オブジェクトストアとは、RDBでのテーブルのポジションに近いものです。
IndexedDB のオープン後、データベースの初回オープン時とバージョンの変更時に呼ばれるます
onupgradeneeded()内で、createObjectStore()を実行しオブジェクトストアを作成する感じです。


    var version = 1;
    var openRequest = webkitIndexedDB.open("todoList", version);    

    // データベースの初回時オープン時とバージョンの変更時に呼ばれる
    openRequest.onupgradeneeded = function(e) {
        var db = e.target.result;
        // オブジェクト・ストアの作成
        db.createObjectStore( "todo" , { "keyPath" : "timeStamp"} );
        };
    }

createObjectStore()メソッドに渡す値

  • 第1引数 : 作成するオブジェクトストアの名前 ("todo")
  • 第2引数 : キーの設定({ "keyPath" : "timeStamp"})

トランザクションの開始

デーベースオープンの成功後に、呼ばれるコールバック関数、onupgradeneededメソッドonsuccessメソッドで返ってくる、IDBDatabaseオブジェクトを変数 dbに一旦格納しておきます。

    
    openRequest.onupgradeneeded = function(e) {
        // IDBDatabaseオブジェクトを格納
        var db = e.target.result;
        // オブジェクト・ストアの作成
        db.createObjectStore( "todo" , { "keyPath" : "timeStamp"} );
        };
    }

    var trans     = db.transaction("todo", "readwrite");
    var store     = trans.objectStore("todo");

トランザクションの生成

IDBDatabase.transaction()でトランザクションを生成します。

  • 第1引数 : オブジェクトストアを指定 ("todo")
  • 第2引数 : トランザクションモードの指定("readwrite")

トランザクションモードは、readonly(読み取り専用)、readwrite(読み書き可)、versionchange(バージョン更新)の3種類があるとのこと

トランザクションを開始するオブジェクトストアの指定

objectStore()でオブジェクトストアを指定することができます。

レコードの操作

それではオブジェクトストアに格納されているレコードの操作をしてみたいと思います。

参照

指定したデータストアのトランザクション開始後、 openCursor()メソッドを実行します。成功して返ってくるonsuccess()コールバック内で取得します。

    var trans     = db.transaction("todo", "readwrite");
    var store     = trans.objectStore("todo");
    var cursorRequest = store.openCursor();

    cursorRequest.onsuccess = function(e) {
      // レコードを取得
      var result = e.target.result;
    };

登録・更新

指定したデータストアのトランザクション開始後、put()メソッドの引数にデータを渡します。

    var trans     = db.transaction("todo", "readwrite");
    var store     = trans.objectStore("todo");
    var todoText  = 'hogehoge';

    var data = {
      "text": todoText,
      "timeStamp": new Date().getTime()
    };

    var request = store.put(data);

削除

指定したデータストアのトランザクション開始後、delete()メソッドの引数に、 レコードのキーを渡して削除します。
(レコードのキーはユニークな値にする必要がります。)

    var trans     = db.transaction("todo", "readwrite");
    var store     = trans.objectStore("todo");
    // カラムの主キーID(タイムスタンプ等のユニークな値にする)
    var id        = '1410526196340';
    var request = store.delete(id);

今回のサンプルのソース一式をこちら記載します。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>Safari 8 IndexedDB</title>
  <script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
</head>
<body>

<input type="text" id="todoItem" name="todo" />
<input type="button" id="btnAddTodo" value="タスク追加"/>
<input type="button" id="btnResetTodo" value="リセット"/>
<input type="button" id="btnDeleteDB" value="DB削除"/>

<ul id="todoItems">
</ul>

<script>
  var indexedDB = window.indexedDB || window.mozIndexedDB || window.msIndexedDB || window.webkitIndexedDB;
  if (indexedDB) {
    // 
    var app = {}
    app.indexedDB = {};
    app.indexedDB.db = null;
  } else {
    window.alert("IndexedDBが使えません");
  }

  app.indexedDB.open = function() {

    // todosデータベースをオープンする
    var version = 1;
    var openRequest = webkitIndexedDB.open("todoList", version);

    // データベースの初回時オープン時とバージョンの変更時に呼ばれる
    openRequest.onupgradeneeded = function(e) {
        app.indexedDB.db = e.target.result;
        // オブジェクト・ストアの作成
        app.indexedDB.db.createObjectStore( "todo" , { "keyPath" : "timeStamp"} );

        e.target.transaction.oncomplete = function() {
          app.indexedDB.getAllTodoItems();
        };
    }

    // IndexedDBオープン成功時に呼ばれる
    openRequest.onsuccess = function(e) {
      // 宣言していた app.indexedDB.db に IDBDatabaseオブジェクトを格納
      app.indexedDB.db = e.target.result;
      app.indexedDB.getAllTodoItems();
    };

    // IndexedDBオープンの失敗時に呼ばれる
    openRequest.onerror = function(e) {
      // エラーがおこった場合、一旦コンソールログで確認
      console.log("Database error: " + e.target.errorCode);
    };

  };

  app.indexedDB.deleteDB = function() {
    // todosデータベースを削除する
    webkitIndexedDB.deleteDatabase("todoList");
  };

  app.indexedDB.getAllTodoItems = function() {

    var initTodoItems = $("#todoItems").html("");
    var initTodoItem = $("#todoItem").val("");
    
    var db    = app.indexedDB.db;
    var trans = db.transaction("todo", "readwrite");
    var store = trans.objectStore("todo");

    var cursorRequest = store.openCursor();

    cursorRequest.onsuccess = function(e) {
      var result = e.target.result;
      if(!!result == false)
        return;

      renderTodo(result.value);
      result.continue();
    };

  };

  app.indexedDB.addTodo = function() {

    var db        = app.indexedDB.db;
    var trans     = db.transaction("todo", "readwrite");
    var store     = trans.objectStore("todo");
    var todoText  = $("#todoItem").val();
    
    if( todoText == "") return;

    var data = {
      "text": todoText,
      "timeStamp": new Date().getTime()
    };

    var request = store.put(data);
  
    request.onsuccess = function(e) {
      app.indexedDB.getAllTodoItems();
    };
  
    request.onerror = function(e) {
      console.log("Error Adding: ", e);
    };

  };

  app.indexedDB.deleteTodo = function(e) {

    var db        = app.indexedDB.db;
    var trans     = db.transaction("todo", "readwrite");
    var store     = trans.objectStore("todo");
    var id        = Number(e.target.id);
    
    var request = store.delete(id);

    request.onsuccess = function(e) {
      init();
    };

  };

  app.indexedDB.resetTodo = function() {

    var db        = app.indexedDB.db;
    var trans     = db.transaction(["todo"], "readwrite");
    var store     = trans.objectStore("todo");
    var request   = store.clear();

    request.onsuccess = function (event) {
        init();
    }

  };

  function renderTodo(row) {
    var $todoItems = $("#todoItems");
    $todoItems.append("<li>" + row.text +"<a id='" + row.timeStamp +  "' class='btnDeleteTodo' href='#'>[ 削除 ]</a></li>");
  }

  function init() {
    app.indexedDB.open();
  }

  // 読み込み実行
  $(function(){

    var $btnAddTodo    = $("#btnAddTodo");
    var $btnResetTodo  = $("#btnResetTodo");
    var $btnDeleteDB   = $("#btnDeleteDB");

    $btnAddTodo.on("click",app.indexedDB.addTodo);
    $btnResetTodo.on("click",app.indexedDB.resetTodo);
    $btnDeleteDB.on("click",app.indexedDB.deleteDB);
    
    $(document).on('click', '.btnDeleteTodo', app.indexedDB.deleteTodo);

    init();

  });

</script>
</body>
</html>

まとめ

今回のSafari 8へのバージョンアップで、やっとIndexedDB APIの使用が可能になりました。
これで、各最新の主要ブラウザでの使用ができるようになりましが、各ブラウザ事に若干の操作方法がちがったりするので使用する際は、その点を注意しなければいけません。。。。
ですが、今回のバージョンアップでローカルに保存できるデータ操作にバリエーションが増えたことで、モバイルWebブラウザで動作しているゲームやアプリケーション等、機能の幅が増えていくのではないでしょうか?