Archive for the ‘iOS’ Category

[オフラインで使いたい]スマホで Indexed Database API まだですか [iOSに実装はいつ?]


2013
05.09

皆様、おはようございます。今回は 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/

Indexed Database API

プログラムの中身はこんな感じ

※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.様)

などなど、参考にさせて頂きました。

[CSS3|jQuery|Webkit]translate3d等のtransformで設定した現在値を取得したい


2011
09.26

皆さんご無沙汰しております、Web屋の中の人です。

手術やらリハビリやら、少々複雑なシステム開発案件等でお勉強の方を長期に渡り休止しておりましたが久々にやってみようと言う事でインターフェイスの調整やバグ取り、むしろソースの整理なんかをしているとタイトルの状況になりました。

iPhoneiPad 上でアニメーションしたい時は、jQuery.animate は原則使い物になりませんので CSS3transform を利用してグリグリします。

 

ちなみに今回はタイトルに記載している通り「webkit」ブラウザのみ対象としていますのでご注意ください。
※Safari、Chromeなどなど。

 

ありがちな例を挙げると、js でスライダーなんかを作っている時に「この要素の x 座標は今ナンボ程動いてますか」と言う状況です。
jQuery を使っているなら簡単ですよね、

1
alert($('#hoge').css("left"));

これでパパッと解っちゃう訳で、非常に便利です。

所が CSS3transform で設定する時は、

1
2
3
#hoge {
    -webkit-transform: translate3d(237px, 5px, 0px);
}

こんな感じなので、x 座標ってどうやって書けば取得出来るの?となった訳です。

1
alert($('#hoge').css("-webkit-transform"));

取りあえずこう書いてみると、下記のように出力されました。

matrix(1, 0, 0, 1, 237, 5)

うーん、4番目が x で5番目が y なんだろうな…じゃあ z はどこに出るんだろう。

 

ブラウザ限定の強み、と言う事で素直に WebKitCSSMatrix で分解する事にした。仕様書通り使うと

1
2
3
var obj = document.getElementById('hoge');
var m = new WebKitCSSMatrix(obj.style.webkitTransform);
alert(m.e);

こんな感じになりますが、折角 jQuery を使っているので…

1
2
var m = new WebKitCSSMatrix($('#hoge').css('-webkit-transform'));
alert(m.e);

こんな感じでも取得可能です、結構乱暴に使えますね。
取得出来る値はコチラの仕様書に網羅されています、かなり色々設定出来るだけに複雑ですね…単純な物は a〜f って覚えておくのが良さそうです。

 

 

それではまた。

 

 

[iPhone|iPad]iOSにおけるhtml5::manifestの削除について Vol.2


2011
06.02

さて皆さん、早速ですが manifest は結局どう言う仕様なのよ?と言う事を私なりに解釈した解説を実機スクリーンショットを交えて紹介して行こうと思います。

 

最初は公式シミュレータでやっていたのですが、Mac の HDD を参照してしまうので容量 1TB とかあってキャッシュして数値を動かすのが非常に困難だったので実機にしました。実機は 16.9GB みたいな感じで 100MB 単位で動かせるので都合が良いです。

 

まずはこの manifest とやらはどれぐらいのキャッシュを許可出来るんだろうかと言う所から、仕様書によるとデフォルトは 5M だけど拡張可能との事。

検証方法は 3.2MB のm4v ファイル(何でも良いんだけどね)を複製しておいて、それのパスを追加しては更新の繰り返しと言う作業です。

適当な要素に #console 的な id を書いて、イベント拾って出力しながら経過を見守ります。

 

まず最初に警告が出たのはキャッシュが 10MB を超えた時でした、

10MBを超えた時に即座にアラートが出現

なんだか良く解らないのですが、「増やす」を選択しても「キャンセル」を選択しても unknown error が出力されてキャッシュが止まってしまいました。

コンソールにログを非同期に出力

取りあえず例外が投げられたらキャッシュをやり直す処理を追記して、さらにパスを増やして行くと…

25MBでも確認した

今度は 25MB を超えた時点でアラートが発生した、同じくエラーが発生したようで再びキャッシュをし直す処理が走りました。

取りあえずもうチョット容量欲しいかなと思うので、更にパスを追記。

50MBも確認した

おお、50MB 行けたね!凄い凄い、普通のサイトならデフォルト 5M で余裕だけど WebApp だと結構リソース多いからね。取りあえずこれぐらい有れば十分かな?

ここから先は試していませんが、50MB は確実に行ける事が分かりました。

 

しかしこれ…10-25-50 の度にいちいち聞かれるんだろうか、アプリに合わせて任意に指定出来ればスマートなのになぁ。

 

さて、ココからが本題。HDD どうなった?な部分ですが、48MB キャッシュした時点で下記のように変化がありました。

befor

after

使用可能の項目の値が 12.9GB から 12.8GB に減少しているのが確認出来ます、まぁローカルにキャッシュしたのだから当然ですね。

で、ここで放置してしまうとユーザーはこの消せないキャッシュの積み重ねでいつか苦しむ事になります。

なんとかこの値を 12.9GB に戻す事が今回のミッションと言う事です。

 

まず manifest から先ほどから追記しまくった m4v のパスを全て消しました、これでそのページにはキャッシュしなくても良いファイルに変わる筈です。更新を完了させてから容量を見ても…変わって無い。

Safari をタスクから切って OS を再起動しても駄目でした、一体どこに保存されているのやら。

通常キャッシュとは別物だと思っていたのですが、設定から Safari のキャッシュをクリアして、Safari を再起動してみたら…

キャッシュを消去

容量復活!

見事容量が復活しました!

これで何とかユーザーを救えそうです、キャッシュクリアの操作をさせるのは難しい事ですが manifest からパスを外してあげるアンインストール的な操作方法を用意してあげるのが良いのでは無いでしょうか。

キャッシュ自体はほっとけば何かの拍子消えるモノですし、多分。(もしかして消えない?)若しくはアプリサイトにその旨を記載しておくとかでも体裁は保てるのではないかと思います。

 

まとめ

  • manifest 自体は削除出来ない(今の所)
  • manifest からパスを外したファイルは通常キャッシュ領域に移動される、又は同じキャッシュ領域だが manifest に書かれていると削除されない仕様のどちらかである
  • manifest からパスを切ったファイルは Safari のキャッシュクリアで消せる
  • manifest 対応サイト乱立によるユーザー資源の浪費を食い止める操作を提供してあげないと大変な事になる
  • Apple が設定画面から消せるような機能を追加してくれるのに期待する

と言った感じですかね、細々したファイルなら然程気にしなくても良さそうですがやはり消せないゴミファイルが溜まって行くのは気持ちが良いモノではありません。

大きなキャッシュを残したいのであれば、削除出来る機能を提供してあげるのがマナーとなりそうですね。

今回は中々興味深い結果となりました、今後の仕様変更に注目して行きたいですね。

 

それではまた。

 

1 | 2

 

[iPhone|iPad]iOSにおけるhtml5::manifestの削除について Vol.1


2011
06.02

暑くなったり肌寒くなったりで体調を崩しそうな大阪ですが、皆様の周りは如何な具合でしょう?

 

さて、今回は結構重要な話しです。

 

今後隆盛を迎えるであろう WebApp はもちろんの事、これから先は PC ブラウザで閲覧する html5 コンテンツも快適性を追求して当たり前のように使われる事になると思われる manifest の仕様についてのレポートを作りました。

実は私は色々開発はしているものの、オフラインで動かすための manifest 指定と言うモノに少々気持ち悪さを覚えています。

理由は単純に「良く解らない」からです、これが凄く気持ち悪い。

 

manifest とは、非常に強力で有用な技術です。簡単に言えば超強力なキャッシュ機能と言うとしっくり来ますね。manifest ファイルにそのページで使われるリソースのパスを書いておくと、ローカルにそれらを保存して Web にアクセスする事無くそれらを利用する事が出来るようになります。

その性能はと言うと、大げさでも無くネイティブアプリに迫る高速読み込みが行え、おまけにオフラインでも何の問題も無いと言う高性能っぷり。

 

初めて知った時は「これはもう使うっきゃ無いよね!」と思いました、だって凄いでしょう?

所が使うに当たって当然出てくる疑問があります、それは「これってどうやって消すの?」と言うシンプルな疑問。

導入方法は数多くのサイトで語られていますが、manifest を消す方法、特に iOS では未だに確立されていません。唯一消す方法は復元を実行すると言う超力技だけと言う現状。

 

本当に消えない、と言うか消せないんです。後述しますが、私も実験中に順番を誤り個人的に所持している iPhone から二度と更新出来ない URL を作ってしまいました。

※これを回避する為の準備も紹介します、絶対にこれは守らないと泣きを見ます。

PC の方は結構なんでもありなので消せる見たいですが、我々が目を向けなければならないのは一般ユーザーの方達です。

 

例えば、こんなケースが想定出来ます。

  • 色々なサイトで manifest を使った高速ブラウジングが次々と実装される
  • ユーザーは色んなサイトを閲覧し続ける
  • なんだか iPhone/iPad の HDD 容量が凄い事になってる
  • 消せない助けて

 

如何でしょう、何とも無惨な事になると思いませんか?ここで彼等に「復元しろ」とか「初期出荷状態に戻せ」なんて言える筈ありません。

この問題の指摘は Apple 社にもメールしておこうと思います、設定の Safari から manifest を個別削除出来る機能をつけないとえらい事になるんじゃないのか?と。

 

さて、取りあえず今の所 manifest 自体は「消す事が出来ない」仕様のようなので、せめて「容量を解放する方法はあるのか?」と言う方向で検証してみましょう。

 

まず manifest を触る際に、絶対に準備しなければならない事があります。

それは Javascript による manifest のチェック機能です、これを必ず最初に作る所から始めましょう。

これが無いと、そのページを開いた瞬間「消す事が出来ないキャッシュが生成されて、以後そのキャッシュしか見ない」と言う状況が発生します。

そうです、永久にそのデバイスからはその URL の情報を更新出来なくなります。先ほど少し触れましたが、私は個人所有の実機でこれをやらかしました :(

 

Javascript ソース

1
2
3
4
5
6
7
8
9
10
var appCache = window.applicationCache;
var checkUpdate = function() {
    if(navigator.onLine) appCache.update();
    else alert('Please update for online.');
}

appCache.addEventListener('updateready', function() {
    appCache.swapCache();
    location.reload();
}, false);

html ソース

1
<input onclick="checkUpdate()" type="button" value="update" />

 

簡単に要所だけ書くとこんな感じのソースになります、ここでは挙動を調べる事に集中するので詳しい設置方法や、細かいイベントの返り値なんかは、凄く丁寧に解説して下さっているブログがあるのでそちらを参照してください。

ちなみにこの appCache.update() はロード完了後に自動的に一回だけ呼ばれますが、記事を読んでると絶対じゃないそうなので念のため適当なボタンからアップデートをチェックする関数を実装しています。

 

そして manifest ファイルを準備して html 要素に manifest=”ファイル名” を追記すれば晴れて「何度でも更新出来る manifest 付きページ」の完成です。やったね!

 

次回は実機のスクリーンショットを交えて、HDD の容量変化を見ながら実際に manifest キャッシュを行ったモノを紹介します。

 

それではまた。

 

1 | 2

 

[iPhone/iPad]WebApp制作における読み込み時間/オフライン問題


2011
05.18

皆さんこんにちわ、最近随分日が長くなったなぁとしみじみ思います。

そして随分書きかけのエントリーが溜まって来たなぁと思います。

消化しないとただの豪華なオンラインメモ帳になってしまいます、これはいけません。

 

そう言う事も踏まえてなお、単発ネタです。

iPhone / iPad をターゲットにした WebApp でネックになるのは読み込み時間とオフライン時の挙動です、普通にブラウザアプリを作る感覚で行くと下記のような問題にブチ当たります。

  • ページ遷移の度に読み込んでたらストレス溜まる
  • かと言って全部最初に読み込んでおいてメモリに展開すると色々と苦しい
  • そもそもオフラインだとページ遷移とか非同期通信自体不可能じゃない

ネイティブアプリは全てローカルにリソースを置く事で回避出来るのですが、Web ブラウザを使っている以上このような問題が起こります。

 

困りました、調べました、解決しました。

 

なんの事は無い、iOSAndroidWEB Database API を実装しているじゃないか。良く考えると双方 Webkit 系だもんね、今更気づいたのかよ!と言わざるを得ません。てへ。

この API はブラウザの JS から双方のネイティブアプリを触っている方はお馴染みの SQLite3 へとアクセスする事が出来ます、つまり始まる前に全部ローカルにデータを保存しておく事が出来ます。ちょっと試した事が無いのですが blob 型が有るのでバイナリも突っ込めるはず。

そしてもう1つの問題、オフライン時にページを開こうとしたりリロードしたりしようとすると「ページを開けません。インターネットに接続していません。」みたいなエラーが出るのですが、これも .manifest を使えば回避可能です。

オフラインだとこうなります

これは強力なキャッシュ機能で、指定したファイルを全てローカルに保存して次回からはとにかくここから読み出すようです。中身を更新したい時は .manifest の中身の文字を1文字でも変えれば再構成されるようなので、ページの要素を読み込む前に .manifest のチェックが先に実行されているみたいです。

要するにバージョン管理をきちんと行い、.manifest にはそのバージョン名を入れておけば常に最新の状態をユーザーに提供出来ると言う訳ですね。

 

さて、これで…かなりネイティブアプリに迫る事が出来るようになったハズです。

思っていた以上に私は WebApp について解っていなかったのがショックですが、これで同じ土俵には上がれたと思うので益々精進したいと思います。

 

それではまた。