ページの表示・非表示状態を取得するPage Visibility API

HTML5 Advent Calendarの1日目です。
本来のAdvent Calendarとは、12月1日からクリスマスの25日まで、カードに作られた窓を1日に1つずつ開けていくというものです。一方、技術系のAdvent Calendarは、12月1日から25日までの間、毎日違う人が特定のテーマに沿ってブログ記事を書くというものです。ここでは、「HTML5」がテーマになります。他にも面白い記事が公開される予定ですので興味のある方は是非チェックしてみてください。

http://atnd.org/events/21987


私は、とりあえず1日目なので軽く、堅くPage Visibility APIというマイナーな仕様について解説したいと思います!


Page Visibility APIとは
Page Visibility APIは、ページの表示状態を取得するAPIです。document.hidden のプロパティから表示状態(false)、非表示状態(true)を確認することができます。以下の場合に非表示状態になります。

  • ウィンドウが最小化された場合
  • 同じウィンドウの他のタブを見ている場合
  • OSのロック画面中の場合(Chrome15で確認したところ状態は変わらず)


どんなときに使うかと言うと。
例えば、メールを定期的にチェックする処理があるとして、表示状態のときは頻繁にチェックし、非表示状態のときにはたまにチェックするのみにするような場合に利用できます。もしくは、非表示状態のときは一切動かす必要がないものもあるかもしれません。
表示・非表示状態の変更の検知は、visibilitychangeイベントを使います。以下は、Chromeでのサンプルコードです。

// for chrome
document.addEventListener('webkitvisibilitychange', function(){
  if ( document.webkitHidden ) {
    // 非表示状態
  } else {
    // 表示状態
  }
}, false);

非常に単純で簡単ですね!ちなみに最近のブラウザは、バックグラウンドタブの1秒以下の間隔のsetTimeoutやsetIntervalに何もしなくても自動的に遅延が掛かるので注意です。(詳細は、モダンブラウザのタイマー処理の制限をWebSocketを使って突破する(WebWorkersについて追記あり)
2011年12月1日現在では、以下の対応ブラウザでベンダープリフィックス付きでアクセスできます。

対応ブラウザ
Chrome 13+ Firefox 10 IE 10


デモ
既に秀逸なデモがあったので、それを紹介します。:p


Internet Explorer 10 Test Drive - Page Visibility API -
表示・非表示の状態をタイムラインで表示するデモです。

http://www.samdutton.com/pageVisibility/


Using the Page Visibility API
非表示の状態のときに動画の再生をストップし、再表示された際に再生をリスタートするデモです。タブのアイコンで再生マーク、一時停止マークがつくのが面白いです。

http://www.samdutton.com/pageVisibility/


APIリファレンス
Properties

プロパティ 説明
document.hidden ページ表示状態(false: 表示、true: 非表示)
document.visiblityState ページ表示状態の詳細(PAGE_HIDDEN("hidden"): 非表示、PAGE_VISIBLE("visible"): 表示、PAGE_PRERENDER("prerender"): ページのレンダリング完了前)

Events

イベント 説明
visibilitychange ページの表示状態が変化した際に発生する


W3Cの仕様書はこちら。

W3C Working Draft - Page Visibility -
http://www.w3.org/TR/2011/WD-page-visibility-20110602/


まとめ
特に面白いことを書いているわけではないですが、とりあえず「こんなAPIもあるよ」ということでマイナーなPage Visibility APIを紹介してみました。HTML5は、他にも細かい仕様がどんどん追加されていて追っかけるのが大変ですね。また機会があれば他のAPIも紹介していきたいと思います!

GTUG BootCamp 2011 Japanで「はじめてのChrome Extension」というお話をしました

ちょっと書くのが遅れてしまいましたが、GDD2011の直前に行われたGTUG BootCamp 2011 Japanにて「はじめてのChrome Extension」というお話をして来ました。
このBootCampは、どちらかというと初心者向けのイベントで、各技術の入門的なセッションやハンズオンが中心になっています。GDDが中級者以上の方を対象としたイベントなので、そこへステップアップするためのものという位置付けです。



私がお話させて頂いたのは「はじめてのChrome Extension」というChrome Extensionのセッションです。近年、Chromeブラウザのシェアは順調に伸びてきており、それに伴ってChrome Web AppsやChrome Extensionについても関心を持つ人が増えているのではないかと思います。
セッションでは、初心者向けということでChrome Extensionの簡単な説明からインストール、作成、公開までの手順をひと通り説明しました。目標は、「Chrome Extensionを知らない人が、家に帰って気軽に試して、何か作れること」だったので既にある程度触っている人には優しかったと思います。とくに難しいことは書いていないので、以下の資料を見て頂ければ、大体わかるのではないかと思います。


資料の中で、以下の3つのサンプルを作成しています。

  • ポップアップ(Browser Action)で"Hello, World!"を表示するもの(SampleExtension1.zip 直
  • バックグラウンド(Background Page)で動作して一定時間経過後に"Hello, World!"を表示するもの(SampleExtension2.zip 直
  • すべてのサイトに"Hello, World!"を勝手に表示するもの(Content Scripts)(SampleExtension3.zip 直

それぞれサンプルソースを用意したのでよかったら参考にしてください。見れば分かる通り、"Hello, World!"尽しなので中身はみなさんのアイディアで補ってください(笑)
ライブコーディングでメソッド名をちょっと間違えてうまく動かなかったりという失態もしてしまいましたが、おおむね簡単にできるということは分かっていただけたかと!(動かなくてもそのままスルーしましたが、ちょっと見切るのが早かったかも。。)


他にも、いろいろなセッションやハンズオンがあり、いずれも興味深い内容だったので、もうちょっと知りたい方は、@fumiさんのブログをご覧になってみてください。

GTUG Bootcamp 2011 by Fumi's Travelblog


当日は、無料でお弁当とお茶が配布されたり、懇親会でビールとピザ+いろいろな方とお話できたりと楽しい一日でした。もろもろ提供して頂いたGoogleさんありがとうございます!



家に帰ってBlogを書くまでがBootCampだったので、これで私もようやくBootCamp終了です。お疲れ様でした!また来年お会いしましょう。

jQuery Mobile beta3がリリースされました!


jQuery Mobile beta3がリリースされたので、例によって変更点を簡単に紹介するだけの手抜き記事です。
詳細は本家を参照してください。
http://jquerymobile.com/blog/2011/09/08/jquery-mobile-beta-3-released/


変更点1.
pjaxが実装されました。history.pushStateに対応したブラウザであれば、従来のhash based URLではなく、綺麗なURLになります。

例えば、前はこんなURLだったのが
http://jquerymobile.com/demos/1.0b3/#/docs/api/events.html


こういう感じにURLが綺麗になります
http://jquerymobile.com/demos/1.0b3/docs/api/events.html

ソースを見るためにいちいち#を削らなくても大丈夫です。
ただし、ブラウザがhistory.pushStateに対応している必要があります。


変更点2.
新しくbeforechangepage、pagechange、pagechangefailedイベントが実装され、同イベントをフックすることによって簡単にページの内容を動的に生成できるようになりました。

//サンプル
$(document).bind( "pagebeforechange", function( e, data ) {
// 遷移先のページを生成
});

以前まであったpagebeforeshowやpageshowとは違います。pagebeforeshowは遷移先のページが存在しないとエラーになります。
まあページを生成するのは自分でDOMを構築しないといけないのは一緒ですが…。


変更点3.
iOS5がサポート対象に追加されました。iOS5では、position:fixedやoverflow:autoに対応しているため、固定ツールバーやページ遷移が凄くなめらかになります。

特に明示的に対応という感じではないですが。iOS5のposition:fixed対応などはjQuery Mobileじゃなくても喜んでる人が多いと思います。


変更点4.
A-gradeサポートにBlackberry 7が追加されました。これでモダンなデバイスはほぼすべて対応されています。


jQuery Mobile beta3の変更点は取りあえずそのぐらいかな。さすがにバージョンがあがってきたので大きな変更点は多くないです。バグフィクスの内容は困ってた人は各自確認しましょう。(とりあえず、リンクボタンが押しっぱなしになるのは直っていたので対応コードを削除しないと)


その他

  • jQuery Mobile beta3 その他1:アニメーションに使われているkeyframeベースからCSS3 transitionsへの移行は、正式リリース後に行う。理由は現行のブラウザでは両手法のパフォーマンスに差異がない(Operaはパフォーマンスが落ちる)
  • jQuery Mobile beta3 その他2: 必要なモジュールだけ組み込んで利用するダウンロードツールは現在準備中。(UIはいらないからページ遷移だけ使うとか、特定のパーツがいらないとか)


次は、いよいよRC1(正式版候補)で9月末にリリース予定です!

jQuery Mobile beta2がリリースされました!

とりあえず、私のツイートの内容をまとめただけの内容になりますが変更点は以下の通りです。詳細についてはまとめてくれるブログがたくさんあると思うのでそちらを参照してください。

まとめてくれたブログ[8/8追記]
jQuery Mobile のbeta2がでたので例によって変更点まとめた
http://havelog.ayumusato.com/develop/javascript/e281-jqm_beta1_changelog.html

[jQuery Mobile] ベータ2 リリースノート
http://screw-axis.com/2011/08/05/jquery-mobile-beta2/

  • 来月にはjQuery Mobile beta3がリリースされる予定。念願のpushState対応が入ります。
  • 対応プラットフォームが増えています。
  • 各ウィジットの分離が可能になりました。現在は手動でコンパイルすることで可能ですが、将来的にはビルド&ダウンロードページを提供予定です。
  • 各ウィジットにcreateイベントが実装されました。refreshと使い分けてください。
  • domCache機能が追加され、ページ要素のキャッシュの制御をできるようになっています。対象のページでdata-dom-cache="false"を指定するとDOM Treeに残らなくなりますのでページの肥大化を防ぐことができます。
  • ページのlazy loadが可能になっています。ajax遷移が遅いと感じた人はお試しください。a要素にdata-prefetch属性を追加します。また、スクリプトでは、$.mobile.loadpage(url)で実行することで可能です
  • autoInitializePageが復活しています。これは、最初のページの装飾を自動で行うかどうかの設定です。以前は、jQuery Mobileを読み込むだけで勝手にjqm化されていたので自分でごにょごにょやりたい方に。
  • $.mobile.loadingMessageがruntimeな設定になりました。これによってshowPageLoadingMsgメソッドで自由なメッセージを通知できるようになりました。
  • swipeイベント発生の条件をカスタマイズできるようになりました。また、ページ遷移時にいろいろな問題を引き起こしていたvclickイベントを利用しなくなりました。これによってuseFastClickの設定は削除されています。
  • 1ソース単一ページの場合には、data-role="page"要素が省略可能になりました。また、data-role="header","footer","content"も同様に省略可能です。1ソースマルチページの場合には従来通り必要なので注意してください。
  • チェックボックスラジオボタンのデザインがシンプルになりました。その他、スタイルにwebkit以外の各ベンダープリフィックスを追加、コードのフォーマットを整形して綺麗にするなど。
  • 以前まであったXSSが修正されています。

jQuery MobileでGoogle Analyticsを使う方法

jQuery Mobileもβ版になって触る人が増えてきました。jQuery MobileでGoogle Analyticsを使ったお話もちらほらでてきたようなので、私のやり方もひとつ紹介してみようと思います。
jQuery Mobileでは、普通にGoogle Analyticsのコードをコピペしても最初のページしかトラッキングしてくれません。ひとつのファイルにすべてのページをまとめている場合は当然としても、複数のページに分割している場合でも正しくトラッキングできません。


jQuery MobileでGoogle Analyticsがうまく動かない理由

jQuery Mobileでページ遷移する場合、ajaxで遷移先のページのHTMLを読み取って取り込んでいます。そのため、遷移先のdata-role="page"で指定された要素以外の部分はまったく読み込まれません。文字だけだとよくわからないと思うので次のサンプルをみてください。

  • 遷移先のページ
<!DOCTYPE html>
<html lang="ja">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, minimum-scale=1, maximum-scale=1">
	<title>遷移先ページ</title>
	<link rel="stylesheet" href="http://code.jquery.com/mobile/1.0b1/jquery.mobile-1.0b1.min.css" />
	<script src="http://code.jquery.com/jquery-1.6.1.min.js"></script>
	<script src="http://code.jquery.com/mobile/1.0b1/jquery.mobile-1.0b1.min.js"></script>
</head>
<body>

<!-- ここから〜 -->
<section data-role="page">
	<header data-role="header"></header>
	<article data-role="content"></article>
	<footer data-role="footer"></footer>
</section>
<!-- 〜ここまで -->

</body>
</html>


jQuery Mobileは、上記のサンプルのコメントで明記した「ここから〜」「〜ここまで」の間しか読み込みません。そのため、それ以外の場所に書いたコードはすべて無視されます。Google Analyticsはもちろん、その他のスクリプトやスタイルも読み込まれないので注意が必要です。


jQuery MobileでGoogle Analyticsをうまく動かす方法

次のコードを貼り付けるだけです。

/*
 * Google Analytics 設定
 */
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-****-*']);

// ページトラッキング
$(document).bind('mobileinit', function(){
	$(':jqmData(role="page")').live('pageshow', function (event, ui) {
		_gaq.push(['_trackPageview', $.mobile.activePage.jqmData('url')]);
	});
}

// Google Analytics 読み込み
$(document).ready(function(){
	var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
	ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
	var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
});


上記コードは、必ずjQueryjQuery Mobileのスクリプトの間に埋め込んでください。もしページを複数のファイルに分割している場合は、jsファイルに保存してすべてのページで読み込んでください。複数のファイルに分割した場合、どのページがスタートページになるかわからないためです。(スタートページ以外ではこの記述は無視されるので重複することはありません。)

<script src="http://code.jquery.com/jquery-1.6.1.min.js"></script>
<!-- ここに埋め込む  -->
<script src="http://code.jquery.com/mobile/1.0b1/jquery.mobile-1.0b1.min.js"></script>


解説

jQuery Mobileに対応している部分は以下のコードだけです。

// ページトラッキング
$(document).bind('mobileinit', function(){
	$(':jqmData(role="page")').live('pageshow', function (event, ui) {
		_gaq.push(['_trackPageview', $.mobile.activePage.jqmData('url')]);
	});
}


jQuery Mobileのページ表示イベントで、そのページのURLを取得してトラッキングしているだけです。そのページのURLは、リンク先の指定に"#page-id"のようなID指定であれば"#"を除いたpage-idに、URLで指定した場合はそのままURLが入ります。


おまけ

IDで遷移している場合に、Google Analyticsのレポートに"top"だとか"home"だとかIDが指定されて微妙だと思っている方がいるかもしれません。実はjQuery Mobileでは、data-url属性を使って任意のURLをページ遷移に使う方法があります。

<a href="/test.html">test.htmlへ</a>

<section data-role="page" data-url="/test.html">
	<!-- 略 -->
</section>


この指定方法でも正しくページ遷移ができます。この場合、トラッキングされるのは"/test.html"になるので普通のページのように見えます。ちなみにdata-urlは、本来jQuery Mobileが自動生成する属性ですが、このように任意の値を指定することができます。(data-urlを指定すると#page-idによる遷移ができなくなるので気をつけてください。)
これを応用すると次のようなこともできます。

<a href="/test.html">test.htmlへ</a>
  • test.html
<section data-role="page" data-url="/hoge.html">
	<!-- 略 -->
</section>

分割したtest.htmlへアクセスしていますが、トラッキングされるのは"/hoge.html"です。また、読み込み後は、/hoge.htmlという指定でこのページ遷移することもできます。擬似的なリダイレクトのような扱いにもなるので、うまく使ってみてください。


あとなんかおまけ2的なことを書こうと思ったのに忘れてしまった。。

Indexed Database API について

Indexed Database API(以下、indexedDB)について、これまで追いかけてきた情報をとりまとめたので公開します。

indexedDBは当初は仕様が固まっておらず、サンプルコードも当然のように動かなかったので(今も動きませんが…)、検証するにはかなりハードな状況でした。最近になってどうにか動くようになってきたので、@komasshu さんと色々やり取りしながら一通りの動作を確認しました。

現時点で利用できるブラウザは Chrome 9 以降 または Firefox 4 beta 8 以降となります。まだまだ仕様は動いていますので、検証の際は、なるべく最新の開発版を使うことをおすすめします。本エントリーでは、Chrome 9 beta 、Firefox 4 beta 8 にて検証します。また、資料は、2011年1月20日時点の W3C Editor's Draft を参照しています。


1. Indexed Database API とは
indexedDBは、ブラウザが持つ NoSQL ベースのデータベースです。オリジン(Origin*1)ごとに固有の領域を持ち、複数のデータベースを保持することができます。


2. 基本的な使い方
(1) データベースの作成(接続)
現時点では indexedDB(IDBFactory)にベンダープリフィックスを付けてアクセスします。Chrome であれば、webkitIndexedDB。Firefoxであれば moz_indexedDB(beta8)、または mozIndexedDB(beta9)です。


サンプルコード1

//Chrome、Firefoxのベンダープリフィックス対応
var indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.moz_indexedDB;

//データベース作成(接続)
var db = null;
var req = indexedDB.open("library");

//成功時コールバック
req.onsuccess = function(evt) {
    db = evt.result;
};

//失敗時コールバック
req.onerror = function(err) {
    alert(err.code + ":" + err.message);
};

データベース操作の結果は、リクエスト(IDBRequest)のコールバックに登録することになります。


(2) オブジェクトストア・インデックスの作成
オブジェクトストアは、データを格納するための入れ物です。RDBMSのテーブルに相当します。オブジェクトストア、及びインデックスの作成と削除はデータベースのバージョン変更時にしか行うことができませんので、注意が必要です。


サンプルコード2

//DBのバージョン変更
var verReq = db.setVersion("1.0");
verReq.onsuccess = function(evt) {

    //オブジェクトストア作成(in-line key として isbn を指定)
    var store = db.createObjectStore("books", "isbn", false);

    //Chromeの場合、以下のように記載
    //var store = db.createObjectStore("books", {"keyPath": "isbn"}, false);

    //インデックス作成
    store.createIndex("name", false);
};

オブジェクトストアには、各データを一意に識別するキーが必要です。キーは、createObjectStore の第2引数で指定します。キーには、2種類あり、キーを指定した場合の in-line key 、省略した場合の out-of-line key があります。キーの種類、及び第3引数の autoIncrement の値でデータ追加時の動作が異なります。


・in-line key
データ内のプロパティをキーとします。データ追加時には、該当プロパティに値が存在する必要があります。
autoIncrement が true の場合、該当プロパティは存在しなくても問題ありません。


・out-of-line key
データの外にキーを持ちます。データ追加時には、別途キーを渡す必要があります。
autoIncrement が true の場合、別途キーを渡す必要はありません。


(3) データの追加・削除
オブジェクトストアに格納できるデータは、その名の通りオブジェクト(structured clone*2)です。データはトランザクションを経由して操作します。


サンプルコード3

//Chromeのベンダープリフィックス対応
var IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction;

//データ作成
var book = {
    name: "徹底解説 HTML5 APIガイドブック コミュニケーション系API編",
    description: "AjaxやHTTPで高速なリアルタイム通信アプリケーション開発を可能にする新APIの仕様と使い方を詳細に解説。",
    author: "小松 健作",
    isbn: "4798028215"
};

//トランザクションの開始とオブジェクトストアの取得
var store = db.transaction([], IDBTransaction.READ_WRITE).objectStore("books");

//データ追加
store.add(book);    //putでも同様の結果が得られる


//データ削除
store.delete("4798028215");

オブジェクトストア作成時に、isbn を in-line key に指定しているので、データ作成時に値をセットした isbn が key になります。ただし Chrome では、まだ in-line key の指定が無視されるので、コメントのように記述しないと動作しません。createObjectStoreに第2引数に{"keyPath": "isbn"}のように指定すると動作するようです。

有効なキーは、string、date、long、float です。null 及び floatの Inifinite は有効なキーですが、NaN は無効です。また、undefined は許可されていません。


(4) データの検索・変更
データの検索方法には、1件だけ取得する get と 複数取得するための openCursor があり、データを変更したい場合、openCursor で取得してから行います。


サンプルコード4 〜 データの検索

//トランザクションの開始とオブジェクトストアの取得
var store = db.transaction().objectStore("books");

//Firefox4の場合、transactionの引数の省略ができないのでnullを入れておく
//var store = db.transaction(null).objectStore("books");

//get
var req = store.get("4798028215");
req.onsuccess = function(evt) {
    var value = evt.result;
    console.log(value);
};

//openCursor、インデックス経由で取得
var req = store.index("name").openCursor("徹底解説 HTML5 APIガイドブック コミュニケーション系API編");
req.onsuccess = function(evt) {
    var cursor = evt.result;
    if ( cursor ) {
        console.log(cursor.value);

        //Firefox4の場合、index経由だとvalueにkeyが入っているので再度検索
        //var getReq = store.get(key).onsuccess = function(getEvt){
        //    console.log(getEvt.result);
        //};
        cursor.continue();
    }
};

get の結果に複数のデータがある場合は、最初の1件のみ取得できます。また、インデックスを利用する場合も get 、openCorsor 等、同様のメソッドがあるので同じ方法で取得することができます。


サンプルコード5 〜 データの変更

//Chromeのベンダープリフィックス対応
var IDBKeyRange= window.IDBKeyRange|| window.webkitIDBKeyRange;

//トランザクションの開始とオブジェクトストアの取得(READ_WRITE)
var store = db.transaction([], IDBTransaction.READ_WRITE).objectStore("books");

//キーを範囲指定するにはIDBKeyRangeを使う。
var req = store.openCursor(IDBKeyRange.lowerBound("4844329278", false));

req.onsuccess = function(evt) {
    var cursor = evt.result;
    if ( cursor ) {
        //description変更、changedフラグ追加
        var book = cursor.value;
        book.description = "updated";        
        book.changed = true;

        //データ変更反映
        cursor.update(book);
        cursor.continue();
    }
};

データの変更を行う場合は、トランザクションを READ_WRITE で開始します。カーソルを使ってデータを取得後、変更し、同じくカーソルの update メソッドを介して変更を反映します。プロパティの追加なども問題なくできます。


(5) その他 Tips
・データのソート
オブジェクトストア内のデータは、キーの昇順で格納されています。そのため、昇順で取得する必要があれば、そのまま取得します。降順で取得したければ、openCursor 時の第2引数で、IDBCursor.PREV を指定します。インデックスでも同様です。

//Chromeでの実行例
var req = db.transaction().objectStore("books").index("name").getCursor("", webkitIDBCursor.PREV);
req.onsuccess = function(evt) {
    var cursor = evt.result;
    if ( cursor ) {

        //降順にデータ取得
        console.log(cursor.value);
        cursor.continue();
    }
};

応用として、最少値、最大値を取得したい場合は、対象のプロパティにインデックスを作成して、最初の1件を取得すれば求められます。

キーの順序は、string、date、longとfloatとなっており、longとfloatはその数値で比較されます。


トランザクションのコミットとロールバック
トランザクションのコミットは、別のトランザクションが開始されるか、トランザクションが発生したメソッドが終了すれば、暗黙的にコミットされます。ロールバックはエラーやタイムアウトが発生した場合には自動的にロールバックされますが、明示的に指定したい場合は、abort を使います。

//トランザクション開始
var tx = db.transaction([], IDBTransaction.READ_WRITE);

/*
 複数のデータ操作
*/

//トランザクションの中断とロールバック
tx.abort();


データベースのバージョン変更時は、リクエストの result に入っているトランザクションを使ってロールバックします。

//トランザクション開始
var req = db.setVersion("1.1");
req.onsuccess = function(evt) {
    var tx = evt.result;

    /*
     スキーマの変更など
     */

    //トランザクションの中断とロールバック
    tx.abort();
};


3. 簡易API一覧
IDBFactory

メソッド・プロパティ 説明 コメント
open(name) nameで指定されたDBを作成する。
既に存在していればDBに接続する。
以前あった第2引数のdescriptionは
なくなりました
deleteDatabase(name) nameで指定されたDBを削除する。 - - 未実装

IDBRequest

メソッド・プロパティ 説明 コメント
onsuccess リクエスト成功時コールバック
onerror リクエスト失敗時コールバック
readyState リクエストの状態
LOADING: 実行中
DONE: 完了

IDBDatabase

メソッド・プロパティ 説明 コメント
name DB名
objectStoreNames DBに格納されている全オブジェクトストアの名前
version DBのバージョン
close() DB接続を閉じる。 -
createObjectStore(name, keyPath, autoIncrement) nameで指定されたオブジェクトストアを作成する。 ChromekeyPath を{"keyPath": "keyPath"}のようにオブジェクトで渡します。
deleteObjectStore(name) nameで指定されたオブジェクトストアを削除する。
setVersion(version) DBバージョンを更新する。 以下のメソッドは、バージョン更新のコールバックでしか実行できない。
createObjectStore
deleteObjectStore
createIndex
deleteIndex
transaction(storeNames, mode, timeout) storeNamesで指定されたオブジェクトストア(デフォルト:全オブジェクトストア)を対象としてトランザクションを開始する。
modeトランザクションの種類を指定する。
READ_ONLY: 読込専用
READ_WRITE: 書込可能
VERSION_CHANGE: バージョン更新
(デフォルト:READ_ONLY)
timeout
ミリ秒単位(デフォルト:ブラウザ固有)
引数はすべてオプションだが、Firefox は transaction() のような記述ができない。ChromeFirefoxともにtimeoutは無視される。

IDBObjectStore

メソッド・プロパティ 説明 コメント
name オブジェクトストア名
keyPath キーのプロパティ名
indexNames オブジェクトストアに設定されたインデックス名
put(value, key) オブジェクトストアにデータを追加する。keyは、オブジェクトストア作成時に、キーとキーの自動生成を指定しない場合に必須となる。
add(value, key) putと同様。
delete(key) keyリテラルのキー値)のデータを削除する。
get(key) keyリテラルのキー値または、IDBKeyRange)のデータを取得する。複数ある場合は、最初の1件を返す。 ChromeはIDBKeyRangeでの指定ができない。
clear() すべてのデータを削除する。keyは -
openCursor(range, direction) rangeリテラルのキー値または、IDBKeyRange)で指定された範囲のデータを取得する。directionで取得順序を指定する。(IDBCursor)
NEXT: 昇順
NEXT_NO_DUPLICATE: 昇順(重複なし)
PREV: 降順
PREV_NO_DUPLICATE: 降順(重複なし)
rangeについて、ChromeはIDBKeyRangeでの指定ができない。逆にFirefoxでは、リテラルのキー値での指定ができない。
createIndex(name, keyPath, unique) keyPathで指定したプロパティを対象とするインデックスを作成する。uniqueで一意性を指定する。
index(name) インデックスを取得する。
deleteIndex(indexName) インデックスを削除する。

IDBIndex

メソッド・プロパティ 説明 コメント
name インデックス名
storeName インデックスの対象とするオブジェクトストア名
keyPath インデックスの対象とするプロパティ名
unique 一意であるか
get(key) keyリテラルのキー値または、IDBKeyRange)のデータを取得する。複数ある場合は、最初の1件を返す。 ObjectStoreの同名のメソッドと同様。Firefoxの場合、インデックス経由の取得結果はキーとなる。
getKey(key) getと同様だが、取得対象がキーとなる。
openCursor(range, direction) rangeリテラルのキー値または、IDBKeyRange)で指定された範囲のデータを取得する。directionで取得順序を指定する。(IDBCursor)
NEXT: 昇順
NEXT_NO_DUPLICATE: 昇順(重複なし)
PREV: 降順
PREV_NO_DUPLICATE: 降順(重複なし)
ObjectStoreの同名のメソッドと同様。Firefoxの場合、インデックス経由の取得結果はキーとなる。
openKeyCursor(range, direction) openCursorと同様だが、取得対象がキーとなる。

IDBCursor

メソッド・プロパティ 説明 コメント
direction 走査方向
NEXT: 昇順
NEXT_NO_DUPLICATE: 昇順(重複なし)
PREV: 降順
PREV_NO_DUPLICATE: 降順(重複なし)
key カーソルを開くときに使用したキー
value カーソルの現ポジションのデータ Firefoxの場合、インデックス経由では、キーとなる。
continue(key) カーソルの現ポジションを次の走査方向へ進める。イベントが再発生し、カーソルの現ポジションが更新される。keyリテラルのキー値または、IDBKeyRange)を指定した場合、該当するキーまでカーソルを進める。 ChromeFirefoxともにkeyの指定は受け付けない。
delete() カーソルの現ポジションのデータを削除する。
update(value) カーソルの現ポジションのデータをvalueで更新する。 -

IDBTransaction

メソッド・プロパティ 説明 コメント
mode トランザクションの種類
READ_ONLY: 読込専用
READ_WRITE: 書込可能
VERSION_CHANGE: バージョン更新
以前あった、SNAPSHOT_READはなくなりました。
db 接続しているデータベース。
objectStore(name) トランザクションの対象となるオブジェクトストアの中からnameで指定されたオブジェクトストアを取得する。
abort() トランザクションを中断し、ロールバックする。
onabort トランザクション中断時のコールバック
oncomplete トランザクション完了時のコールバック
ontimeout タイムアウト時のコールバック - -


4. おわりに
indexDB は、まだまだ実装途中です。バージョンが変われば、そのままのコードでは動かなくなる可能性がかなり高いので、本格的なアプリを作れるようになるには、もう少し時間がかかるでしょう。それでも是非、試してみたいという方は、本エントリーを参考にして頂ければ幸いです。もちろん、(自称)html5-developers-jp indexedDB担当としては、今後もメンテナンスしていきたいと思いますので、ご指摘、ご質問等あればコメントでもMLでもツイッターでもお気軽にどうぞ!


まだまだ書くこともたくさんありますが(手元のサンプルコードもリファクタリングして公開しないといけないですし…)、まずはこのぐらいで。
@komasshu さんのこてさきAjax indexedDB を触ってみたも合わせてどうぞ

*1:origin … プロトコルドメイン、ポートからなる、Webサイトを指し示す固有の情報。URLとは似て非なるもの。

*2:structured clone … オブジェクトから、関数と循環参照などを排除したもの。単純なリテラル、Date、Object、Arrayを含む。

Web Storage について

ひさしぶりのエントリになります。JavaScript Advent Calendar 2010 の24日担当です。
この Advent Calendar では、12月1日からクリスマスの12月25日まで1日ずつ持ち回りでblogに書くことになっています。お題に"JavaScript"とあるように、blogは、JavaScriptに関するものになります。他の方々も面白いことを色々を書いておられますので、是非読んでみてください。


さて、何やら世間はクリスマスイブで盛り上がっていますが、仕事で缶詰の私には関係ありませんので(もちろん仕事がなくても関係ありませんが…)、今回は、HTML5 の API のひとつである、Web Storage について紹介したいと思います。
割と初心者向けの内容になります。


Web Storage とは
Web Storage は、データをブラウザ上に永続的に保持するための仕様で、キーと値をセットとした連想配列のようにデータを保持します。仕様では、localStorage と sessionStorage に分かれており、それぞれ以下の特徴があります。いずれも、オリジン(プロトコル、ドメイン、ポート)ごとに保存されます。

  • localStorage

ブラウザを閉じても保持される。同一オリジンであれば、ウィンドウ間でも共有できる。
 

  • sessionStorage

ブラウザを閉じると消去される。ウィンドウごとのセッションで有効。


仕様書: W3C Web Storage Editor's Draft
http://dev.w3.org/html5/webstorage/


この Web Storage ですが、モダンなブラウザでは既に対応済みなので、他のHTML5のAPIの中では、比較的、安心して使うことができます。特にIE8が対応していることが大きいでしょう。(IE6などのブラウザでもGearsで対応できるようです)

サポートブラウザ

IE 8 Firefox 3.6 Chrome 7 Safari 5 Opera 10


localStorageとsessionStorageのAPIは共通で、以下のメソッドを介して利用します。

API一覧
localStorage/sessionStorage

メソッド 説明
length ストレージの長さを返す。
key(n) n番目のkeyを返す。
getItem(key) keyに対応する値を返す。
setItem(key, value) keyに対応するvalueを保存する。
removeItem(key) keyに対応する値を削除する。
clear() ストレージを消去する。


簡単な使い方

// データを保存する。
localStorage.setItem("hoge", "test");

// データを取り出す。
localStorage.getItem("hoge");
> "test"

// 次のように記述することもできます
// データを保存する。
localStorage.hoge2 = "test2";
localStorage["hoge3"] = "test3";

// データを取り出す。
localStorage.hoge2;
> "test2"

localStorage["hoge3"];
> "test3"

//保存されている値をすべて表示する
for ( var i = 0, len = localStorage.length; i < len; i++ ) {
  alert(localStorage.getItem(localStorage.key(i));
}


注意事項として、Web Storage で保存できる値は、仕様上は Any となっており、JSオブジェクトでも登録できるはずですが、残念ながらまだ実装されているブラウザはなく、すべて文字列として保存する形になっています。

// 配列を保存しても文字列になってしまう
var ary = ["aaa", "bbb"];
localStorage.setItem("hoge", ary);

localStorage.getItem("hoge");
> "aaa,bbb"


そのため、JSオブジェクトを扱いたい場合は、JSON.stringifyとJSON.parseを利用するなどして文字列に変換してから利用することをおすすめします。

// 配列をJSON.stringifyで文字列に変換してから保存
var ary = ["aaa", "bbb"];
localStorage.setItem("hoge", JSON.stringify(ary));

// 取り出した文字列をJSON.parseでJSオブジェクトに変換する
JSON.parse(localStorage.getItem("hoge"));
> ["aaa", "bbb"]


保存できるデータ量
きちんと計測したわけではないので、詳細は省略させて頂きますが、ChromeやSafariは5MB。IE8は10MB程度のようです。Chromeのブラウザ拡張であれば unlimitedStorage オプションを指定することによって制限を解除できます。unlimitedStorageオプションが有効なのはWeb SQL DatabaseとIndexed Databaseのみでした。


クッキーとの違い
以前までの、ブラウザ上でのデータの保持方法として主にクッキーが使われていましたが、クッキーと比べて以下の違いがあります。

  • 通信量の削減

クッキーはリクエストを投げるたびにクッキーの内容をすべてサーバーに送信しているため、トラフィックが増大する。そのため、クッキーに保存するデータ量が多くなればなるほど、サイトが重くなったりするなどの弊害がある。Web Storageは、ローカル内だけで完結するため、余計なトラフィックが増えない。

  • セキュリティの向上

クッキーはローカルで保持しておくべきデータでもリクエストのたびに送信しているため、ネットワークの経路上でデータが漏洩する可能性があった。もちろんWeb Storage でも基本的なアクセシビリティはクッキーと同じで、XSSなどで盗まれる恐れがありますが。
※いずれにせよ、パスワードや個人情報などの機微なデータを保存しないようにするべきです。


その他の注意事項
Web Storage で使う上で注意しておくことは以下の通りです。

  • localStorage の削除

localStorage は、保存期限がないため削除しないでいるとゴミデータが残ってしまいます。使わなくなったデータはきちんと削除するか、そもそもブラウザの起動を跨いで保存する必要がない場合は、sessionStorage を使いましょう。

  • スコープの範囲

Web Storage のスコープはオリジン(プロトコル、ドメイン、ポート)なので、共有サーバーやひとつのドメインで複数のアプリケーションを管理している場合には、データが混在してしまいます。そういった環境で利用するのであれば、衝突しないよう命名規則などに留意することや、全件処理及びクリア処理などに気をつけましょう。


関連するイベント
Web Storage には、StorageEvent というイベントが用意されており、データ更新時などの動きをハンドリングすることができます。


StorageEvent

プロパティ 説明
key 更新されたキー
oldValue 更新前の値
newValue 更新後の値
url 更新されたページのURL
storageArea 更新されたStorage(localStorageまたは、sessionStorage)

// StorageEventを登録
window.addEventListener("storage", function(evt) {
  console.log("old:" + evt.oldValue + "/new:" + evt.newValue);
}, false);

// データ更新
//localStorage.setItem("hoge", "aaa");
localStorage.setItem("hoge", "bbb");
> "old:aaa/new:bbb"


ただ、StorageEventは主に開発版などで実装されており、正式版での実装状況はまちまちになると思われるので、使用する場合は注意してください。

IE 9 beta Firefox 4 beta8 Chrome 9 Webkit nighty Opera 11


活用事例
Web Storage は既にメジャーなサイトでも積極的に活用されています。
以下は、そのうちのサイトの一部です。他にも、こんなところで使ってるよ!など、知ってる方がいらっしゃいましたら教えて頂くと嬉しいです。

タイムライン右側のフォロー、フォロワー部分のHTMLをlocalStorageでキャッシュしています。

おすすめリストなどのページングのページ番号の保持にsessionStorageが使われています。

  • その他ブラウザ拡張等

拡張の設定などにlocalStorageが使われていることが多いです。



おわりに
そろそろ、時間もやばそうなのでこの辺にしておきたいと思います。(24日をオーバーしてしまう!)Web Storage は、現在でも大きな部分から小さいなところまで幅広く使っていける機能ですので、サイトを構築する際には、是非とも検討していただけたら面白いのではないでしょうか。


それでは、Happy Coding!