だいぶ前に書いた Backbone.js の入門記事で Backbone.Router を使ったサンプルを紹介した。
Router が表示する View を切り替える良い方法が思いつかなかったので、サンプルではメイン View をグローバル変数に格納して Router から触れるようにし、無理やり切り替えていた。今見ても、全然スマートな方法じゃないね。
あれから試行錯誤しながら、何個か Backbone.js を使ってアプリを作成してみて、ようやく自分の中で Backbone.Router の使い方が固まった。以前書いたサンプルを修正したのがこちら。
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <title>Backbone Router Sample</title> </head> <body> <div id="main"> <h1><a href="">Backbone Router Sample</a></h1> <div id="entries"> </div> </div> <!--エントリをリスト表示するとき使うテンプレート--> <script id="entry-template" type="text/template"> <div class="entry"> <h3><a href="javascript:void(0)"><%- title %></a></h3> </div> </script> <!--エントリの詳細を表示するとき使うテンプレート--> <script id="detail-template" type="text/template"> <h2><%- title %></h2> <div id="content"> <%- content %> </div> </script> <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"></script> <script type="text/javascript" src="http://documentcloud.github.com/underscore/underscore-min.js"></script> <script type="text/javascript" src="http://documentcloud.github.com/backbone/backbone-min.js"></script> <script type="text/javascript"> // エントリ。 var Entry = Backbone.Model.extend({ defaults: { title: "", content: "" } }); // ブログ。 var Blog = Backbone.Collection.extend({ model: Entry }); // エントリを表示するビュー。 var EntryView = Backbone.View.extend({ template: _.template($("#entry-template").html()), events: { "click a": "showDetail", }, render: function() { $(this.el).html(this.template(this.model.toJSON())); return this; }, // エントリタイトルをクリックしたら Router 経由で // 詳細ページに移動する showDetail: function() { Backbone.history.navigate("entry/" + this.model.id, true); return false; } }); // エントリの詳細を表示するビュー。 var DetailView = Backbone.View.extend({ template: _.template($("#detail-template").html()), render: function() { $(this.el).html(this.template(this.model.toJSON())); return this; } }); // メインのビュー。 // エントリの一覧を表示する。 var BlogView = Backbone.View.extend({ el: $("#main"), events: { "click h1>a": "showIndex" }, render: function() { var entriesEl = $(this.el).find("#entries"); $(entriesEl).empty(); this.model.each(function(entry) { var view = new EntryView({ model: entry }); view.render(); $(entriesEl).append(view.el); }, this); return this; }, // ブログタイトル(?) がクリックされたらトップページに移動する。 showIndex: function() { Backbone.history.navigate("", true); return false; }, // 渡された ID に該当するエントリの詳細を表示。 showDetail: function(id) { var entry = this.model.get(parseInt(id)); var view = new DetailView({ model: entry }); view.render(); var entriesEl = $(this.el).find("#entries"); $(entriesEl).empty(); $(entriesEl).append(view.el); } }); // ルーター。 var AppRouter = Backbone.Router.extend({ // routes でルーターのメソッドと URL パターンをマッピングする。 // ルーターのメソッドがリクエストハンドラになる。 routes: { "entry/:id": "show", "entry/": "list", "": "list" }, // ルーターの初期化。 // ビューとモデルはメンバとして保持する。 initialize: function() { this.blog = new Blog(); // テストデータを作成 this.blog.add(new Entry({ id: 1, title: "foo", content: "hoge" })); this.blog.add(new Entry({ id: 2, title: "bar", content: "fuga" })); this.appView = new BlogView({ model: this.blog }); this.appView.render(); }, list: function() { this.appView.render(); }, show: function(id) { this.appView.showDetail(id); } }); window.router = new AppRouter(); // 履歴監視スタート。 // Backbone.history.start({ pushState: true }); で pushState は有効にできるけど、 // Web サーバーでホストしないとうまく動かないので無効にしておく。 Backbone.history.start(); </script> </body> </html>
Router がメンバに View と Model(と Collection)を保持し、ルーティングでは、メンバに保持している View や Model を使って表示を切り替えている。この方法は backbone-rails のジェネレータが生成する Router が使っている方法で、CoffeeScript だったけど参考になった。
Router 経由でビューを切り替えたい場合、以前はグローバル変数に格納した Router を使っていたけど、Backbone.history.navigate を使うようにしている。Router の navigate は Backbone.history.navigate に委譲しているだけなので、わざわざ Router をグローバル変数に格納する必要は無かった。