ブラウザDBを使ったWebサイトのオフラインモバイル対応!

 本記事の最新版はこちらです。
本サイトは今後更新されませんのでご注意ください。

 プラットフォーム iPad / iPhone / PC(Chrome / Safari )

ブラウザDBを使ってWebシステムをオフラインでも利用できるサイトにリニューアルします。
iPadやiPhone、Androidに最適なアプリケーションを簡単に構築できます。
ブラウザ上で動作するアプリケーションなのでモバイルOSの種類に関わらず構築できます。

 特徴

  • オフラインで使えるモバイルサイトを構築します
  • モバイルOS(iPad/iPhone、Android)に依存しません
  • AppStoreなどに公開する手間がなく、ブラウザだけで利用できます
  • モバイル端末の画面サイズや操作法に最適な画面レイアウトに組み換えます

 開発経緯や開発者による苦労話

オフラインで利用できるシステムを作るための技術としてHTML5を採用しました。
HTML5が策定する機能の内、特に次の2つの機能がこの仕組みの肝に当たります。
  • HTMLファイルや画像ファイルなどのリソースファイルをブラウザに保存しておく機能
  • ブラウザにデータベースを持たせてデータを管理する機能
ところがHTML5の仕様群はまだ完全ではなく、ブラウザのバージョンが更新されたときに動かなくなる可能性があります。特に、ブラウザにデータベースを持たせる機能は動かなくなる可能性が高く、この仕組みの実現性が危ぶまれました。

そこで我々は今後実現が堅いだろうと言われている機能(IndexedDB)と現時点で一番実現が進んでいる機能(WebSQLDatabase)の両方を切り替えて対応できる共通部品を作成しました。


 サービスへの活用例

  • AndroidとiOSでプログラムを別々に開発したくない場合に!
  • 外に出ることが多い営業さん向けのシステムに!
  • AppStoreやGooglePlayに公開したくないモバイルアプリケーションに!
  • 頻繁に更新するコンテンツアプリケーションに!

 デモ or 利用イメージの画像




 技術詳細
  • HTML5 Application Cache
  • HTML5 WebSQLDatabase/IndexedDB
  • jQuery / jQuery Mobile

HTML5のAPIでブラウザにデータベースを作成する方法は、2013年2月時点で2通りあります。
一つがWebSQLDatabaseであり、もう一つはIndexed Databaseです。
WebSQLDatabaseは、いくつかのブラウザで最も実現が進んでいるにもかかわらず、仕様として凍結が決まってしまっています。他方でIndexed Databaseは標準となるだろうと言われていますが、まだきちんと実装されたブラウザが少ないので現時点で使い物になりません。

Indexed Databaseは、KeyValueStore形式のデータベースであり、.put()や.get()といったメソッドでJavaScriptObject表記法(JSON)のデータを保持します。

後々に、Indexed Databaseを利用するとして現段階ではWebSQLDatabaseでKeyValueStore形式のデータベースラッパーを作成して、利用法を制限しておけば、後にIndexedDatabaseの実装が普及した時にも乗り換える事が出来るようになります。

下記に、そのデータベースラッパーのコードを記載します。
※ ChromeまたはSafariのみ対応です。

■ローカルデータベースを利用するための前処理部分
var db = {};
db.instance = null;
db.SIZE = 1024*1024*20;
db.VERSION = "1.0";
db.NAME = "KRONOS DB";
db.DESCRIPTION = "kronos local database";

/**
 * ローカル永続化領域をオープンします。
 * API内部で呼び出すので、利用者が呼び出す必要はありません。
 * 初期パラメータは以下で設定可能です。
 * db.SIZE db.VERSION db.NAME db.DESCRIPTION
 */
db.open = function() {
    db.instance = openDatabase(db.NAME, db.VERSION, db.DESCRIPTION , db.SIZE);
    // create Scheme
    db.instance.transaction(function(tr) {
        tr.executeSql("CREATE TABLE IF NOT EXISTS jsonstore ( KEY TEXT UNIQUE, VALUE TEXT ,TIMESTAMP )",[],
            function (data){ 
                console.log("data" + data); 
                console.log("CREATE TABLE SUCCESS");
            },
            function(){
                console.log("CREATE LOCAL DB TABLE ERROR");
            }
        );
    },function(error){console.log(error);});
    return db.instance;
};

上記のコードにより、db.open()メソッドをコールしたタイミングで、WebSQLDatabase上にKEYとVALUEとTIMESTAMPというそれぞれのカラムを持ったテーブルが作成されます。(既に存在すれば作成しません)

■get()メソッド
/**
 * ローカルデータベースからキーに該当するデータを取得します。
 * @param key ストアキー
 * @param onsuccess( json , key , timestamp ) データ取得成功時のコールバック第1引数にデータがかえってくる(第1のみ必須)
 * @param onerror データ取得失敗時のコールバック(省略可能)
 */
db.get = function(key, onsuccess, onerror) {
    db.onsuccess = onsuccess;
    db.open().transaction(function(tr) {
        tr.executeSql("SELECT KEY,VALUE,TIMESTAMP FROM JSONSTORE WHERE KEY = ? ;", [key],
        function(tr, rs) {
            console.log(rs);
            if (rs && rs.rows.length > 0) { 
                var result = rs.rows.item(0);
                if (onsuccess) onsuccess(result.VALUE, result.KEY, result.TIMESTAMP);
            } else {
                if (onsuccess) onsuccess(null);
            }
        },
        function(tr,error) {
            if (onerror) onerror(error);
        });
    });
};

get()メソッドは、取得したいkeyとonsuccessというコールバック関数を引数にとるメソッドです。
WebSQLDatabaseにしろ、IndexedDatabaseにしろ、このような非同期型のAPIの方が、同期型API(var returndata = db.get(key);のような)よりも実装が進んでいる傾向にあります。

同期型のAPIの方がコードぱっと見たときには理解がしやすいのですが、使ってみると非同期APIの方がパフォーマンスも良く、モダンなJavaScriptコーディングには向いてると感じられます。

■put()メソッド
/**
 * データの登録と更新
 * @param key 登録先のキー
 * @param object 登録するデータ
 * @param onsuccess(トランザクションオブジェクト, 結果オブジェクト) 
 * @param onerror(イベントオブジェクト) データ更新時のコールバック(省略可能)
 * @param ondataexists データ存在時のコールバック(省略可能)
 */
db.put = function(key, valueText, onsuccess, onerror, ondataexists) {
    if($.isPlainObject(valueText)) {
        throw new TypeError("type error , db.put() method is not take Plain Object in valueText, call with JSON.stringify(valueText). " );
    }
    db.open().transaction(function(tr) {
        tr.executeSql("SELECT KEY, VALUE FROM JSONSTORE WHERE KEY = ? ;", [key], function(tr,rs) {
            console.log("data put '" + key+ "'");
            console.log(valueText);
            if (rs && rs.rows.length > 0) {
                var exeUpdate = true;
                if (ondataexists) {
                    // データがすでに存在した場合にコールバックに渡して、データを書き換えやUPDATE処理を実行させるかを判別する
                    var dbValueObj = JSON.parse(rs.rows.item(0).VALUE); // 参照渡しにするためにオブジェクト化
                    exeUpdate = ondataexists(dbValueObj);
                    valueText = JSON.stringify(dbValueObj);
                }
                if (exeUpdate) {
                    // exsists
                    tr.executeSql("UPDATE JSONSTORE set VALUE = ? , TIMESTAMP = ? WHERE KEY = ? ;", [valueText, new Date().getTime(), key]
                    ,onsuccess
                    ,onerror);
                }
            } else {
                // not exsists
                // put操作で更新のみ許容する場合はvalueTextに値を指定せず、ondataexistsを指定する。
                if (valueText) {
                    tr.executeSql("INSERT INTO JSONSTORE ( KEY , VALUE , TIMESTAMP ) VALUES ( ? , ? , ? );", [key, valueText, new Date().getTime()]
                    ,onsuccess
                    ,onerror);
                }
            }
        },
        function(tr, error){
            if (onerror) onerror(error);
        });
    });
};

put()メソッドもコールバック関数を受け付ける非同期型のAPIとしています。
データ存在時のコールバックなど、いくつも引数がありますが多くは省略可能です。



 まとめ

今までのWebシステムをiPhoneやiPadでも快適につかえるようにオフライン対応できます。
またAppStoreやGoogle Playに公開する手間がなく、頻繁に更新するようなアプリケーションや社内だけで利用するようなアプリケーションには最適です。