Ember.Routerによる、アプリケーション状態遷移/管理

> 2013/1/20追記 この後のember.jsのバージョンアップにより、この記事の内容は古くなっています。[本家サイトのRoutingの説明](http://emberjs.com/guides/routing/defining-your-routes/)を見ることをおすすめします。ただ、現在もRouting周りの修正が頻繁に行われているため、近い将来に、さらに挙動が変更される可能性が高いことに注意してください。

Ember.jsは、Ember.Routerというアプリケーションの状態遷移を管理する機能を持っています。
Ember.Routerは、一般的なWebフレームワークにおけるrouting管理と同様に、URLと状態を紐付け、状態遷移を管理します。
今日は、Ember.Routerを使った画面遷移のサンプルを見ていきたいと思います。

まず、少し長いですがJavaScript側のコード全体を書いておきます。あとで解説しながら部分部分を見ていきます。

```js:app.js
//// 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で定義された状態ごとに、表示用のテンプレートを用意しています。

```html
  <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&uuml; 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>
```



ではコードを見て行きましょう。まず簡単なところから。

```js
var App = Ember.Application.create({
```

いつもと同様、ネームスペースのためにアプリケーションオブジェクトを作成しています。routing制御のために、アプリケーションオブジェクトが必要になります。


```js
//// View/Controller pairs
App.ApplicationView = Ember.View.extend({

...
```

この行以降は、いつもと同様にView, Controllerを定義しています。画面遷移ごとに、ViewとControllerを定義しているのでかなりの量になっています。
名前には規約があり、後で出てくるRouter定義の中で使用するconnectOutletの第二引数に渡される名前とXxxView, XxxControllerの Xxx の部分が一致している必要があります。

次はちょっと飛びますが、最後の行です。

```js
App.initialize();
```

アプリケーションの初期化処理を行います。initialize()メソッドが行う処理は以下のとおりです。

* App.Routerのインスタンスを作成し、App.routerにセットします。
* 定義されているView, Contollerのクラスすべてのインスタンスを作成し、App.routerのプロパティにセットします。
* Viewに対してcontrollerプロパティに、対応するControllerクラスのインスタンスをセットする。


次はいよいよ本題であるRouter定義を見ていきます。


```js
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を遷移して、アプリケーションの状態を表現します。

```js
  goToCars:  Ember.Route.transitionTo('cars'),
  goToShoes:  Ember.Route.transitionTo('shoes'),
  goHome:  Ember.Route.transitionTo('index'),
```

これらの定義は、Viewテンプレートからactionの引数に渡されて使用されます。それぞれcars, shoes, indexへの状態遷移を行います。
使用例を示します。

```html
  <p><a {{action goToCars href=true}}>Go To Cars</a>
```

次に、各Router定義の中身を見ていきます。


```js
    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を切り替えることができます。

```js
        router.get('applicationController').connectOutlet('body', 'shoes');
```

connectOutletsの中で上のように定義されているとします。第一引数がoutlet名。

```html
  <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を挿入します。

```js
        router.get('traversalController').connectOutlet('home');
```

以上がEmber.Routerの基本的な使い方になります。
以下のページで、実際に動いているサンプルを確認できます。

http://kmdsbng.github.com/emberjs_router_sample/

なかなか直感的に状態遷移を記述できると感じました。