Chaplinでのroutes登録のイベント取り回し関係をまとめました

[Chaplinjs](http://chaplinjs.org/)で渡されるroutesについて、イベントのpub/subの挙動を真面目に読んでいなかったところを読んだのでまとめました。

![Chaplin.route.event.png](https://qiita-image-store.s3.amazonaws.com/0/3379/2f6c7006-2f14-43ec-f735-f5c972131da9.png)

端的にいうと、イベント(`route:match`)をpublishするための登録フロー(Router -> Route -> Backbone.history)とsubscribeするための登録フロー(Dispatcher -> Route)があり、監視されているURLの変更(popstateやhashchangeなど)でイベントが発火 -> Controllerのロードとactionの実行、になっています。

## Application
Applicationは内部で下記のinitを実行します。
* initDispatcher
* initLayout
* initMediator
* initRouter

## publishのフロー

### initRouter
initRouterの中ではApplicationに渡された`routes`と`options`を受け取ります

```coffeescript
@router = new Router options
```

として、routerを初期化し、`Router#match`というメソッドを受け、渡された`routes`を実行します。

`routes`は下記のように`pattern`と`target`(に加え必要であれば`options`)を受け、URLの変更に対して適切なControllerのactionを対応付けます。

initRouterの中では

```coffeescript
routes? @router.match
```

が実行され、それぞれの`match pattern, target`が実行されます。

`match`では、

```coffeescript
route = new Route pattern, controller, action, options
```

として、`route`オブジェクトが作成され、その後

```coffeescript
Backbone.history.handlers.push {route, callback: route.handler}
```

としてhistory handlerに登録されます。
最後に`route`が返却されますが、`routes`の中では特になにも処理されていません。

`routes`の中で記述したルーティングが処理される機会は、このinitRouterだけになります。

### Backbone.history
では、このルーティングはどのように制御されているかというと、すべてはBackbone.historyの中だけで処理されます。

Backbone.historyは、`start`メソッドが実行されると、hashChangeだけなのか、pushStateを見るのか、などのフラグ判別の処理の末、

```coffeescript
      if (this._hasPushState) {
        addEventListener('popstate', this.checkUrl, false);
      } else if (this._wantsHashChange && this._hasHashChange && !this.iframe) {
        addEventListener('hashchange', this.checkUrl, false);
      } else if (this._wantsHashChange) {
        this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
      }
```

というように、

1. popstate
2. hashchange
3. setInterval
の3つの方法でURL変化の監視が開始されます。

URLの変化に対して`history.checkUrl`が実行されます。
この中で、`setInterval`に対応するために実際にURLが変化しているかどうかのチェックが行われ、通過すると、`history.loadUrl`が実行されます。

### Backbone.history.loadUrl
ここでようやく、`Backbone.history.handlers`に登録しておいたrouteが評価されます。

```coffeescript
      return _.any(this.handlers, function(handler) {
        if (handler.route.test(fragment)) {
          handler.callback(fragment);
          return true;
        }
      });
```

handlersの中から、現在の`fragment`(ざっくりURL)にマッチするものが存在するかが`Route#test`でチェックされ、マッチした`callback`が実行される、という挙動になります。

ここでの`callback`は、前述のとおり、`Chaplin.Router`で`Backbone.history.handlers.push {route, callback: route.handler}`として登録されています。


### Chaplin.Route#handlerでのイベントpublish
さて、`Route#handler`はどうなっているでしょうか？

例えば `/users/33/posts/123?fields=comments` のような `fragment` が渡されると、`query` も含めてパースされ最終的には

```coffeescript
routes = {path, @action, @controller, @name, query}
```

というオブジェクトをつけて

```coffeescript
    @publishEvent 'router:match', route, actionParams, options
```

として、イベントが発行されます。

## subscribeのフロー

### Chaplin.Dispatcher

Chaplin.Dispatcherは初期化時に`route:match`イベントを`@dispatch`で待ち受けます。

もろもろのチェックが通ると、`Dispatcher#loadController(name, handler)`が発火します。
`loadController`では、ついに動的なcontrollerのロードが行われます。
Applicationの初期化時にoptionsで渡した`controllerSuffix`や`controllerPath`が加味され、controllerのファイルパスが作成されます。

ここでのhandlerは

```coffeescript
(Controller) =>
      @controllerLoaded route, params, options, Controller
```

となっており、ロードされたコントローラに対して、ルーティングの結果得られたパラメタなどが渡されます。

loadControllerの中では、作成されたファイルパスに対して、`define.amd`または`require`でcontrollerモジュールが呼び出されhandler(というかcontrollerLoaded)が実行されます。

### Dispatcher#controllerLoadedとDispatcher#executeAction
その後は、controllerLoadedでControllerのインスタンスが作成され、executeActionに引き渡され、その中で`action`が実行される事になります。

そこで、ようやく各種のControllerで定義したメソッドのところに届くことになります

```coffeescript
class SomeController extends Chaplin.Controller
  someAction: (params, routes, options)->
```

## まとめ

長くなりましたが、順序としては

1. Applicationの初期化
2. subscribe処理
  1. `route:match`のsubscribe
  2. イベント受け取り時の`route, params, options`を元に、動的にcontrollerのロード/インスタンス化/Controller#Actionの実行
3. publish処理
  1. Application#initRouter
  2. routes(Router#match)により、Backbone.history.handlersへの追加
  3. Backbone.history.startによるURL変化の監視
  4. handlers内のmatchするhandlerのcallbackの実行
  5. Route#handlerでの`route:match`イベントのpublish

となっていることを読み解きました。