jQuery Mobile + Backbone.js でルーティングに Backbone.Router を使う方法

自分が jQuery Mobile と Backbone.js を組み合わせて使うときって、jQuery Mobile のデザインやウィジェットが使いたいだけで、ルーティングは Backbone.js でやりたいのがほとんど。

例えば List/Detail タイプのアプリを作っていて、URL に含まれている id によって表示する内容を変えたい場合、Backbone.js の Router を使うとキレイに実装できる。でも、普通に jQuery Mobile と Backbone.js を使っていると、リンクをクリックしたとき jQuery Mobile のページ移動処理が優先されてしまう。

じゃあ、Backbone.js のルーティングを使ってページ移動するにはどうすればいいかっていうと、jQuery Mobile の設定をいじってルーティングを無効にすればいい。身も蓋もないね。

<!DOCTYPE html>
<html>
    <head>
        <title>jQueryMobile+Backbone</title>
        <link rel="stylesheet" href="jquery.mobile-1.0.1.css" />
        <script type="text/javascript" src="jquery.js"></script>
        <script type="text/javascript" src="underscore.js"></script>
        <script type="text/javascript" src="backbone.js"></script>
        <script type="text/javascript">
            // jQuery Mobile の設定を行うためのイベントハンドラを登録する。
            // jQuery Mobile を読み込む前に登録しないと呼び出されない。
            $(document).bind("mobileinit", function() {
                $.mobile.ajaxEnabled = false;
                $.mobile.linkBindingEnabled = false;
                $.mobile.hashListeningEnabled = false;
                $.mobile.pushStateEnabled = false;
            });
        </script>
        <script type="text/javascript" src="jquery.mobile-1.0.1.js"></script>
    </head>
    <body>
        <div id="list" data-role="page">
            <div data-role="header">
                <h1>List</h1>
            </div>
            <div data-role="content">
                <ul data-role="listview">
                </ul>
            </div>
        </div>

        <div id="detail" data-role="page">
            <div data-role="header">
                <h1>Detail</h1>
            </div>
            <div data-role="content">
            </div>
        </div>

        <script type="text/javascript">
            var Entry = Backbone.Model.extend({
                defaults: {
                    title: "",
                    body: ""
                }
            });

            var EntryCollection = Backbone.Collection.extend({
                model: Entry
            });

            // エントリ一覧を表示するビュー
            var ListView = Backbone.View.extend({
                el: "#list",
                render: function() {
                    var $ul = this.$el.find("ul");
                    $ul.empty();

                    this.collection.each(function(entry) {
                        var view = new EntryView({ model: entry });
                        view.render();
                        $ul.append(view.el);
                    }, this);

                    // リストビューを再描画
                    $ul.listview("refresh");
                    return this;
                }
            });

            // エントリ一覧の各エントリを表示するビュー
            var EntryView = Backbone.View.extend({
                tagName: "li",
                template: _.template("<a href='#entries/<%= cid %>'><%= title %></a>"),
                render: function() {
                    var ctx = this.model.toJSON();
                    ctx.cid = this.model.cid;
                    var html = this.template(ctx);
                    this.$el.html(html);
                    return this;
                }
            });

            // エントリの詳細を表示するビュー
            var DetailView = Backbone.View.extend({
                el: "#detail",
                render: function() {
                    this.$el.find("h1").html(this.model.get("title"));
                    this.$el.find("div[data-role=content]").html(this.model.get("body"));
                    return this;
                }
            });

            // アプリケーションのルーター。
            // エントリポイントも兼ねている。
            var AppRouter = Backbone.Router.extend({
                // ルーティングを定義。
                routes: {
                    "": "showList",
                    "entries/:cid": "showDetail"
                },

                // ルーターを初期化。
                // テストデータを作成し、最初のページ(=一覧ページ)を表示。
                initialize: function() {
                    this.entries = new EntryCollection();
                    this.entries.add(new Entry({ title: "Foo", body: "Hoge" }));
                    this.entries.add(new Entry({ title: "Bar", body: "Fuga" }));

                    this.detailView = new DetailView();
                    this.listView = new ListView({ collection: this.entries });
                    this.listView.render();
                },

                // $.mobile.changePage を使ってページを切り返る。
                // ロケーションハッシュは変更しない。
                changePage: function(view, isBack) {
                    if (_.isUndefined(isBack)) {
                        isBack = false;
                    }
                    view.render();
                    $.mobile.changePage(view.$el, { changeHash: false, reverse: isBack });
                },

                // 一覧ページを表示。
                showList: function() {
                    this.changePage(this.listView, true);
                },

                // 詳細ページを表示。
                showDetail: function(cid) {
                    var entry = this.entries.getByCid(cid);
                    this.detailView.model = entry;
                    this.changePage(this.detailView);
                }
            });

            // ルーティング開始。
            $(function() {
                window.router = new AppRouter();
                Backbone.history.start();
            });
        </script>
    </body>
</html>