url ルーティング
アプリケーションをルーターでコントロールするには
普通に web サイトでは、ユーザーはリンクをクリックしたりフォームに入力することで様々なページを行き来します。しかし、シングルページアプリケーションでは、ユーザーのインタラクションがあっても新しいページをロードしません。その代わり、コンポーネントがそのインタラクションに反応して、シングルページ内で取り扱われます。では、どうやってユーザーがブラウザの進む/戻る ボタンを使えるようにするのでしょうか? 答えは、ext js 5 の新しいルーターを使って uri ハッシュの変化を処置することです。
ルーティングですること
ルーティングを使うとアプリケーションの状態をブラウザの履歴スタックから辿ることができます。また、ルーティングはアプリケーション中までディープリンクをはることができ、アプリケーション内の特定の部分に直接リンクをはることができます。
ルーティングでしないこと
ルーティングはデータやセッションを保存するためには使いません。データはクッキーやローカルストレージなどの永続的なデータソースに保存されるべきのものです。ルーティングはアプリケーションの状態を追跡するためだけの存在です。
ハッシュとは何か
ブラウザでインターネットをナビゲートする際には uri を使いますが、uri には複数の部分があります。 uri の例を見てみましょう。
http://www.example.com/apps/users#user/1234 |
よく見かけるものですね。しかし、#user=1234
は分からないかもしれません。uri のこの部分は「ハッシュ」、または、フラグメント識別子と呼ばれるものです。ハッシュについて詳しくは
http://en.wikipedia.org/wiki/fragment_identifier
を確認してください 。このハッシュはアプリケーションが履歴スタックを現在のページをリロードすることなくコントロールする方法を提供しています。ハッシュが変化すれば、ブラウザはその uri を丸ごと履歴スタックに加え、それによりブラウザの進む/戻る ボタンを押すことで、ハッシュ (変化があったかもしれない) を含んだ uriを行ったり来たりすることができます。例えばハッシュを次のように変更したら何が起きるでしょう。
http://www.example.com/apps/users#user/5678 |
ブラウザは hashchange
イベントを発火し、アプリケーション内ではそれを利用することができます。
ユーザーは戻るボタンを押すことで、#user=1234
ハッシュに戻ることができます。この通知により、アプリケーション内での変化に反応することができます。ここでの重要なポイントは、ハッシュはサーバーには送られていないということです。ハッシュはユーザーの uri の解釈によって処置されるものなのです。ext router は、アプリケーションの状態追跡とディープリンクができるブラウザのハッシュ機能に依存しています。
アプリケーション内でのルーティングの実装
router クラスは、ext js 5 から新たに加わった要素であり、mvc アプリケーション内でのハッシュ変化の解釈を非常に簡単なものにするために作られました。以前からハッシュ変化に反応するための ext.util.history クラスはありました。しかし、ext.util.history はよりマニュアルなプロセスを必要としていたため、より適した router クラスが必要でした。router は、viewcontroller 内で route を定義することで、簡単に ext js 5 mvc アプリケーションに統合できます。ルートとはハッシュと一致する文字列で、ext アプリケーション内でのディープリンクを可能にするものです。 次は、router を実装したコントローラーの基本的な実装です。
ext.define('myapp.view.main.maincontroller', { extend : 'ext.app.viewcontroller', routes : { 'users' : 'onusers' }, onusers : function() { //... } }); |
route
は #users
ハッシュに反応し、コントローラインスタンスにスコープされた onusers
メソッドを実行します。
ハッシュをアップデートする
ハッシュをアップデートために、コントローラに redirectto
メソッドが存在します。
this.redirectto('user/1234'); |
これはハッシュを "#user/1234"
にアップデートし、ハッシュを認識するように設定されたルートが実行されます。現在のハッシュがアップデートしようとしているハッシュと同じ場合があるかもしれません。この場合、redirectto
は false を返し、ハッシュはアップデートされず、設定されたルートも実行されません。
redirectto
は2つ目のパラメーターを渡すことができ、そこに true を渡すと、ルーターに強制的に反応させることができます。
this.redirectto('user/1234', true); |
現在のハッシュが redirectto
に渡されたハッシュと一致した場合でも、ルーターは一致するルートを実行します。
デフォルト トークン
アプリケーションが開始すると、ハッシュが指定されていない場合のデフォルトハッシュを追加することができます。例えば、#home
ハッシュが指定された時にダッシュボードを表示する場合、ハッシュが指定されていなければ #home
ハッシュを uri に加えたくなります。
/app/view/application.js
ファイル内の defaulttoken
コンフィグを使うと、デフォルトハッシュを有効化できます。
ext.define('myapp.application', { extend : 'ext.app.application', //... defaulttoken : 'home' }); |
アプリケーションが開始すると、uri の現在のハッシュをチェックしてくれます。もしそこでハッシュが定義されていれば、ルートハンドラーを実行します。ハッシュが存在しなければ、#home ハッシュを追加し、ルートハンドルが実行され適切にハンドリングされます。
パラメーター付きのハッシュ
ハッシュ内でパラメーターを認識することもできます。ユーザーid などはハッシュに含ませたいパラメーターの一つかもしれません。先ほど #user/1234
をハッシュで使用する例を紹介しました。この場合、1234
を id パラメーターとして認識させたいのです。そこでコントローラを #user/1234
ハッシュに反応するように設定しみましょう。
ext.define('myapp.view.main.maincontroller', { extend : 'ext.app.viewcontroller', routes : { 'user/:id' : 'onuser' }, onuser : function(id) { //... } }); |
使用したルートは 'user/:id'
とコロン 「:
」があります。これはパラメーターが存在するということを意味し、そのパラメーターは onuser
メソッドの引数として渡されます。メソッドはルートで指定されたのと同じ数の引数を同じ順番で受け取るので、複数のパラメーターを含めることも可能です。
ハッシュパラメーターのフォーマット
ユーザー id に対し特別なフォーマットを強要したい場合があります。ここまで説明している例では id は数字で表記されています。conditions
コンフィグを使うとオブジェクトと一致させることができます。
ext.define('myapp.view.main.maincontroller', { extend : 'ext.app.viewcontroller', routes : { 'user/:id' : { action : 'onuser', conditions : { ':id' : '([0-9]+)' } } }, onuser : function(id) { //... } }); |
この例を見てみましょう。まず、onuser
メソッドは action
コンフィグに移動されています。これはルートに文字列を指定したのと同様に動作します。次に conditions
コンフィグにオブジェクトを指定します。オブジェクトのキーは、コントロールしたいコロンのついたパラメーター名です。そして値には正規表現文字列 (regexp オブジェクトではありません) をセットします。ここでは :id
には ([0-9]+)
をセットしています。それにより、0から 9の数字ならどんな長さでも一致します。ここで文字列を利用するのは、regexp オブジェクトは、ハッシュ全体を一致させるために作られているからであり、ルートに複数のパラメータが存在した場合、正規表現文字列を結合して、一つの regexp オブジェクトにしなければならないからです。パラメーターに conditions が指定されない場合は、デフォルトでは次の様になります。
([%a-za-z0-9\\-\\_\\s,]+) |
ルートのハンドリング
ルートのハンドリングを中止しなければいけない状況もあるかもしれません。ある場合では、管理者権限をチェックして現在のユーザーがアプリケーションのある部分を見ることができるかどうかを判断しなければいけないかもしれません。ルートでは before
アクションを使うように設定することができ、それにより現在のルートを止めたり、全てのルートを止めたり、ルートの実行をそのまま進めたりすることができます。
次の例はルートの実行を続けさせます。
ext.define('myapp.view.main.maincontroller', { extend : 'ext.app.viewcontroller', routes : { 'user/:id' : { before : 'onbeforeuser', action : 'onuser' } }, onbeforeuser : function(id, action) { ext.ajax.request({ url : '/security/user/' + id, success : function() { action.resume(); } }); }, onuser : function(id) { //... } }); |
onbeforeuser
メソッドには、:id
パラメーターが引数として渡されますが、最後の引数が action
になっています。action
の resume
メソッドが実行された場合、ルートの実行は継続し、onuser
メソッドがその後にコールされます。ルートの実行を続ける前に、ajax リクエストが終わるのを待つことも可能です。
現在のルート実行を止めるときには、stop
メソッドを使います。
ext.define('myapp.view.main.maincontroller', { extend : 'ext.app.viewcontroller', routes : { 'user/:id' : { before : 'onbeforeuser', action : 'onuser' } }, onbeforeuser : function(id, action) { ext.ajax.request({ url : '/security/user/' + id, success : function() { action.resume(); }, failure : function() { action.stop(); } }); }, onuser : function(id) { //... } }); |
ext js アプリケーションは複雑になることもあり、同じルートをリッスンする複数のコントローラがあるかもしれません。しかし、before
アクションは一つだけのコントローラしか設定されることはなく、一つの ajax リクエストしか発火されません。もし action
引数の stop
メソッドに true
を渡すと、現在処理しているものだけでなく、全てのキューにあるルートの実行を止めることができます。
ext.define('myapp.view.main.maincontroller', { extend : 'ext.app.viewcontroller', routes : { 'user/:id' : { before : 'onbeforeuser', action : 'onuser' } }, onbeforeuser : function(id, action) { ext.ajax.request({ url : '/security/user/' + id, success : function() { action.resume(); }, failure : function() { action.stop(true); } }); }, onuser : function(id) { //... } }); |
ここで、ajax リクエストが失敗すると、このルートの全てのコントローラの全てのハンドラーをキャンセルするように stop
に true
とパスします。
注: resume
もしくは stop
メソッドを実行しない場合、ルーティングはインタラプトされ、最後まで適切に実行されません。いずれかのメソッドをどこかで実行することが重要です。
一致しないルートのハンドリング
ハッシュが変更されても、そのハッシュと一致するルートが見つからない場合、ルーターは何もしません。ルーターはハッシュを変えることもせず、一致しなかったハッシュを放っておきます。ルーターは unmatchedroute
イベントをアプリケーションのインスタンス上で発火しますので、ext.application
呼び出しでリッスンすることもできます。
Ext.application({ name : 'MyApp', listen : { controller : { '#' : { unmatchedroute : 'onUnmatchedRoute' } } }, onUnmatchedRoute : function(hash) { //... } }); |
複数のルートをシングルハッシュで使うには
ext js アプリケーションは複雑になることもあるため、一つのハッシュで複数のルートを使わなければいけない時もあるかもしれません。ルーターはこれを処理できるように設定されているため、コントローラ内で設定されているルートに再設定は必要ありません。その代わり、パイプ (|
) を使ってハッシュを区切ることができます。例えば以下のようなハッシュです。
`#user/1234|messages` |
この場合、1234
という id のユーザーの詳細を表したいのですが、messages
も表示したいとします。各ルートはハッシュが記述され順番で実行されます。そして、互いにサンドボックス化されます。簡単に言えば、user/1234
ルートを止めれば、messages
ルートは実行を続けます。一つ覚えておかなければいけないことは、各ルートの実行順番は、ハッシュ内の順番と同じということです。user/1234
ルートは常に messages
ルートより先に実行されます。
ext.app.route.router.multipletoken
プロパティを変えれば区切り記号を変えることもできます。