2013/1/20追記 この後のember.jsのバージョンアップにより、この記事の内容は古くなっています。本家サイトのRoutingの説明を見ることをおすすめします。ただ、現在もRouting周りの修正が頻繁に行われているため、近い将来に、さらに挙動が変更される可能性が高いことに注意してください。
Ember.jsは、Ember.Routerというアプリケーションの状態遷移を管理する機能を持っています。
Ember.Routerは、一般的なWebフレームワークにおけるrouting管理と同様に、URLと状態を紐付け、状態遷移を管理します。
今日は、Ember.Routerを使った画面遷移のサンプルを見ていきたいと思います。
まず、少し長いですがJavaScript側のコード全体を書いておきます。あとで解説しながら部分部分を見ていきます。
//// Application
var App = Ember.Application.create({
});
//// View/Controller pairs
App.ApplicationView = Ember.View.extend({
templateName: 'application',
classNames: ['application-view']
});
App.ApplicationController = Ember.Controller.extend({
slogan: 'A framework for creating ambitious web applications',
isSlogan: true
});
App.CarsView = Ember.View.extend({
templateName: 'cars'
});
App.CarsController = Ember.ArrayController.extend();
App.ShoesView = Ember.View.extend({
templateName: 'shoes'
});
App.ShoesController = Ember.ArrayController.extend();
App.SalutationView = Ember.View.extend({
templateName: 'salutation'
});
App.SalutationController = Ember.ObjectController.extend();
App.TraversalView = Ember.View.extend({
templateName: 'traversal'
});
App.TraversalController = Ember.ObjectController.extend();
App.HomeView = Ember.View.extend({
template: Ember.Handlebars.compile('<p><a {{action goHome href=true}}><em>Go Home</em></a></p>')
});
App.HomeController = Ember.ObjectController.extend();
//// 起動時処理
App.ready = function(){
console.log("Created App namespace");
};
//// Router定義
App.Router = Ember.Router.extend({
enableLogging: true,
goToCars: Ember.Route.transitionTo('cars'),
goToShoes: Ember.Route.transitionTo('shoes'),
goHome: Ember.Route.transitionTo('index'),
root: Ember.Route.extend({
index: Ember.Route.extend({
route: '/',
connectOutlets: function(router, context){
router.get('applicationController').connectOutlet('greeting', 'salutation',
{ greeting: "My Ember App" });
router.get('applicationController').connectOutlet('body', 'traversal'); }
}),
shoes: Ember.Route.extend({
route: '/shoes',
enter: function ( router ){
console.log("The shoes sub-state was entered.");
},
connectOutlets: function(router, context){
router.get('applicationController').connectOutlet('greeting', 'salutation',
{ greeting: "Shoes Route" });
router.get('applicationController').connectOutlet('body', 'shoes');
router.get('applicationController').connectOutlet('footer', 'traversal');
router.get('traversalController').connectOutlet('home');
}
}),
cars: Ember.Route.extend({
route: '/cars',
enter: function ( router ){
console.log("The cars sub-state was entered.");
},
connectOutlets: function(router, context){
router.get('applicationController').connectOutlet('greeting', 'salutation',
{ greeting: "Cars Route" });
router.get('applicationController').connectOutlet('body', 'cars');
router.get('applicationController').connectOutlet('footer', 'traversal');
router.get('traversalController').connectOutlet('home');
}
})
})
});
App.initialize();
このコードに対応するテンプレートは以下になります。
Routerで定義された状態ごとに、表示用のテンプレートを用意しています。
<script type="text/x-handlebars" data-template-name="application">
{{outlet greeting}}
<p {{bindAttr class="isSlogan"}} >Ember is: {{slogan}}</p>
{{outlet body}}
{{outlet footer}}
</script>
<script type="text/x-handlebars" data-template-name="cars">
<hr/>
<h1>Cars</h1>
<p>Here in my car / I feel safest of all</p>
</script>
<script type="text/x-handlebars" data-template-name="shoes">
<hr/>
<h1>Shoes</h1>
<p><a href="http://youtu.be/v_Yx0X-eHn8">Nü Shooz?</p>
</script>
<script type="text/x-handlebars" data-template-name="salutation">
<h1>{{greeting}}</h1>
</script>
<script type="text/x-handlebars" data-template-name="traversal">
<hr/>
<p><a {{action goToCars href=true}}>Go To Cars</a></p>
<p><a {{action goToShoes href=true}}>Go To Shoes</a></p>
{{outlet}}
</script>
ではコードを見て行きましょう。まず簡単なところから。
var App = Ember.Application.create({
いつもと同様、ネームスペースのためにアプリケーションオブジェクトを作成しています。routing制御のために、アプリケーションオブジェクトが必要になります。
//// View/Controller pairs
App.ApplicationView = Ember.View.extend({
...
この行以降は、いつもと同様にView, Controllerを定義しています。画面遷移ごとに、ViewとControllerを定義しているのでかなりの量になっています。
名前には規約があり、後で出てくるRouter定義の中で使用するconnectOutletの第二引数に渡される名前とXxxView, XxxControllerの Xxx の部分が一致している必要があります。
次はちょっと飛びますが、最後の行です。
App.initialize();
アプリケーションの初期化処理を行います。initialize()メソッドが行う処理は以下のとおりです。
- App.Routerのインスタンスを作成し、App.routerにセットします。
- 定義されているView, Contollerのクラスすべてのインスタンスを作成し、App.routerのプロパティにセットします。
- Viewに対してcontrollerプロパティに、対応するControllerクラスのインスタンスをセットする。
次はいよいよ本題であるRouter定義を見ていきます。
App.Router = Ember.Router.extend({
root: Ember.Route.extend({
index: Ember.Route.extend({
}),
shoes: Ember.Route.extend({
}),
cars: Ember.Route.extend({
}),
})
})
大きな構造として、App.Routerは上のような構造になります。
rootメンバは、Routerに必ず定義しないといけません。これがないとエラーになります。
index, shoes, carsは、それぞれURLに割り当てられ、アプリケーションの状態を表現します。Ember.jsアプリケーションは、これらのRouterを遷移して、アプリケーションの状態を表現します。
goToCars: Ember.Route.transitionTo('cars'),
goToShoes: Ember.Route.transitionTo('shoes'),
goHome: Ember.Route.transitionTo('index'),
これらの定義は、Viewテンプレートからactionの引数に渡されて使用されます。それぞれcars, shoes, indexへの状態遷移を行います。
使用例を示します。
<p><a {{action goToCars href=true}}>Go To Cars</a>
次に、各Router定義の中身を見ていきます。
shoes: Ember.Route.extend({
route: '/shoes',
enter: function ( router ){
console.log("The shoes sub-state was entered.");
},
connectOutlets: function(router, context){
router.get('applicationController').connectOutlet('greeting', 'salutation',
{ greeting: "Shoes Route" });
router.get('applicationController').connectOutlet('body', 'shoes');
router.get('applicationController').connectOutlet('footer', 'traversal');
router.get('traversalController').connectOutlet('home');
}
}),
routeメンバは、マッピングされるURLを指定しています。
enterメンバに指定されたメソッドは、このRouterの状態に遷移したタイミングで呼び出されます。
connectOutletsというメンバが、状態ごとの画面遷移の肝になります。Viewテンプレートに{{outlet}}ヘルパを記述すると、状態ごとに表示するView/Controllerを切り替えることができます。
router.get('applicationController').connectOutlet('body', 'shoes');
connectOutletsの中で上のように定義されているとします。第一引数がoutlet名。
<script type="text/x-handlebars" data-template-name="application">
{{outlet body}}
</script>
そしてテンプレートにこのように記述されていると、shoes状態に遷移した時、{{outlet body}}の箇所に、connectOutlet第二引数、'shoes'に対応するView/Controllerである、ShoesController/ShoesViewの内容が挿入されます。
{{outlet}}の引数がないなら、connectOutletがView/Controllerのプリフィクスを1つだけ引数をとります。以下の例だと、HomeView/HomeControllerを挿入します。
router.get('traversalController').connectOutlet('home');
以上がEmber.Routerの基本的な使い方になります。
以下のページで、実際に動いているサンプルを確認できます。
なかなか直感的に状態遷移を記述できると感じました。