SenchaのAPIドキュメンテーションにある Architecting Your App in Ext JS 4, Part 3 の翻訳をしました。これでMVC三部作の翻訳完了です。
とか思ったら、最後に “We will continue…” とある。まだ続くのかもしれません。
Architecting Your App in Ext JS 4, Part 3
Part 1 Part 2 の中で、PandoraスタイルアプリケーションをExt JS 4の新しい機能を用いて構築する方法を調べました。 複数のビュー、ストア、モデルのある複雑なUIにModel-View-Controllerアーキテクチャを適用することから始めました。 コントローラーからビューをコントロールする、 コントローラーがリスニングできるアプリケーションワイドなイベントを発火するのような、 アプリケーションを構築する基本的なテクニックを見てきました。 このパートでは、アプリケーションのMVCアーキテクチャの内部のコントローラーロジックの実装を続けます。
参照を取得する
アプリケーションの実装を再開する前に、Ext JS 4のMVCパッケージで利用可能な追加機能のいくつかを見てみましょう。 前のアーティクルにおいて、 Ext.application のコンフィグにsotresとmodels配列を追加することで、 アプリケーションにストアやモデルを自動的にロードする方法を見てきました。 この方法でそれぞれのストアの生成されたインスタンスには、 その名前と同じstoreIdが与えられることについても説明しました。
app/Application.js
Ext.application({
...
models: ['Station', 'Song'],
stores: ['Stations', 'RecentSongs', 'SearchResults']
...
});
storesやmodels配列に追加すると、これらのクラスをインスタンス化するとともに、 自動的にゲッターも生成されます。 controllersやviewsにも当てはまります。 sotres, models, controllers, views の各コンフィグはコントローラークラスにもあって、 アプリケーションのインスタンスと同じ方法で正しく動作します。 StationsストアをStationコントローラーの中から参照するためには、 コントローラーのsotres配列にストアを追加するだけでよいということです。
app/controller/Station.js
...
stores: ['Stations'],
...
こうすると、自動的に生成されたゲッターメソッド
getStationsStore
を使って、コントローラーの中であればどこからでもStationsストアへの参照を取得できます。
名前の規則は次のように単純でわかりやすくなっています。
views: ['StationsList'] // creates getter named 'getStationsListView' -> returns reference to StationsList class
models: ['Station'] // creates getter named 'getStationModel' -> returns reference to Station model class
controllers: ['Song'] // creates getter named 'getSongController' -> returns the Song controller instance
stores: ['Stations'] // creates getter named 'getStationsStore' -> returns the Stations store instance
重要で注意すべき、viewsとmodelsのゲッターはクラスへのリファレンスを返し、 storesとcontrollersのゲッターは実際のインスタンスを返すことです。
ビューインスタンスの参照
前のセクションで、stores, models, controllers, views コンフィグで
簡単にそれらの参照を取得できるように、
どのように自動的にゲッターが生成されるのか解説しました。
getStationsListView
ゲッターメソッドはビュークラスへの参照を返します。
このアプリケーションの処理では、StationsList内の最初のアイテムを選択したいと考えています。
この場合は、ビュークラスへの参照は必要なくて、
ビューポート内にある実際のStationListインスタンスへの参照が必要になります。
Ext JS 3では、存在するコンポーネントインスタンスへの参照を取得する非常に一般的なアプローチは Ext.getCmp メソッドでした。 このメソッドは引き続き動作しますが、Ext JS 4においては推奨される方法ではありません。 Ext.getCmp を使ってアプリケーションの中で参照するためには、全てのコンポーネントにユニークなIDを与えなければなりません。 新しいMVCパッケージでは、 Ext JS 4の新しい機能である ComponentQuery を活用する事によって コントローラー内にビューインスタンスへのリファレンスを置くことができます。
app/controller/Station.js
...
refs: [{
// A component query
selector: 'viewport > #west-region > stationslist',
ref: 'stationsList'
}]
...
refs
コンフィグで、ビューのインスタンスへの参照をセットアップできます。
これによりコントローラーのアクションの中からページ上のコンポーネントを参照し操作できます。
参照したいコンポーネントを指定するにはselectorプロパティの中でComponentQueryを使うことができます。
オブジェクトの中に他に必要な情報は
ref
プロパティです。
refs配列内にある各アイテムに対して自動的に生成されるゲッターの名前の一部として使われます。
例えば、
ref: 'stationsList'
(注: 大文字のLに注目)
と定義すると、
getStationsList
というゲッターがコントローラー上に生成されます。
一方、コントローラー内に参照を設定しない場合には、
Ext.getCmp
をコントローラーアクション内で使い続けることになります。
しかし、この方法はオススメしません。
なぜならプロジェクト内でユニークなコンポーネントIDの管理をしなければならなくなってしまい、
プロジェクトが大きくなると問題を起こすことがあるからです。
忘れてはいけない大事なことは、これらのゲッターはページ上にビューが実際に存在するかどうかとは関係なく生成されると言うことです。 ゲッターをコールしたときにselectorがページ上のコンポーネントの一致すると、 結果はキャッシュされるのでその後のゲッターの呼び出しはより速くなります。 しかし、selectorがページ上のビューと一致しなかった場合は、ゲッターはnullを返します。 これが意味することは、ビュー上に依存するロジックがありビューがページ上にまだ存在しない可能性がある場合には、 ゲッターが結果を返した場合だけ実行されるようにするチェックロジックを追加する必要があると言うことです。 また複数のコンポーネントがselectorに一致した場合には、最初の一つだけが返されます。 従って、取得したい一つのビューだけを特定するselectorを作るのがよい方法です。 最後に、参照しているコンポーネントが廃棄(destroy)された場合、 ページ上に一致する他のコンポーネントがない限り、ゲッターは再びnullを返すようになります。
アプリケーション起動時にコントローラーロジックをカスケードする
アプリケーションの開始時、ユーザーの既存のステーションをロードしたいと思います。
アプリケーションの
onReady
メソッドの中にこのロジックを置くことができますが、
MVCアーキテクチャには
全てのコントローラー、モデル、ストアがインスタンス化されて初期ビューがレンダリングされた直後に
各コントローラーで発火する
onLaunch
メソッドがあります。
これによりグローバルアプリケーションロジックとコントローラー特有のロジックを綺麗に分離することができます。
Step 1
app/controller/Station.js
...
onLaunch: function() {
// Use the automatically generated getter to get the store
var stationsStore = this.getStationsStore();
stationsStore.load({
callback: this.onStationsLoad,
scope: this
});
}
...
StationコントローラーのonLaunchメソッドはStrationストアのloadメソッドを呼び出す最適な場所に思えます。 ご覧のように、ストアがロードされた直後にコールバックが実行されるようにも設定しています。
Step 2
app/controller/Station.js
...
onStationsLoad: function() {
var stationsList = this.getStationsList();
stationsList.getSelectionModel().select(0);
}
...
このコールバックの中で自動的に生成されたゲッターを使ってStationsListインスタンスを取得し、
最初のアイテムを選択しています。
これによりStationsListの
selectionchange
イベントが発火します。
Step 3
app/controller/Station.js
...
init: function() {
this.control({
'stationslist': {
selectionchange: this.onStationSelect
},
...
});
},
onStationSelect: function(selModel, selection) {
this.application.fireEvent('stationstart', selection[0]);
},
...
アプリケーションイベントはアプリケーションの中にイベントをリッスンするようなコントローラーが多く張るような場合にとても便利です。
これらのコントローラーで同じビューイベントをリッスンする代わりに、
一つのコントローラーがビューイベントをリッスンして他のコントローラーがリッスンできるように
アプリケーションワイドなイベントを発火します。
またコントローラーが他のコントローラーとお互いについて知ったり依存したりせずに連携できるようになります。
onStationSelect
アクションの中で、
stationstart
というアプリケーションイベントを発火しています。
Step 4
app/controller/Song.js
...
refs: [{
ref: 'songInfo',
selector: 'songinfo'
}, {
ref: 'recentlyPlayedScroller',
selector: 'recentlyplayedscroller'
}],
stores: ['RecentSongs'],
init: function() {
...
// We listen for the application-wide stationstart event
this.application.on({
stationstart: this.onStationStart,
scope: this
});
},
onStationStart: function(station) {
var store = this.getRecentSongsStore();
store.load({
callback: this.onRecentSongsLoad,
params: {
station: station.get('id')
},
scope: this
});
}
...
Songコントローラーのinitメソッドで、
stationstart
アプリケーションイベントのリスナーを設定しています。
これが起こったときには、
RecentSongsストアにこのステーションの曲をロードする必要があります。
それを
onStationStart
メソッドで処理します。
RecentSongsストアへの参照を取得してそのloadメソッドをコールし、
ロードが終了した直後に発火するコントローラーアクションを定義しています。
Step 5
app/controller/Song.js
...
onRecentSongsLoad: function(songs, request) {
var store = this.getRecentSongsStore(),
selModel = this.getRecentlyPlayedScroller().getSelectionModel();
selModel.select(store.last());
}
...
RecentSongsストアのなかにステーションの曲がロードされたときに、
RecentlyPlayedScrollerの最後の曲を選択します。
そうするために
RecentlyPlayedScroller dataview
上のセレクションモデルを取得し、
そのselectメソッドにRecentSongsストア内の最後のレコードを渡してコールしています。
Step 6
app/controller/Song.js
...
init: function() {
this.control({
'recentlyplayedscroller': {
selectionchange: this.onSongSelect
}
});
...
},
onSongSelect: function(selModel, selection) {
this.getSongInfo().update(selection[0]);
}
...
スクローラーの中の最後の曲を選択したとき、
selectionchange
コントロールメソッド
(このイベントのリスナーはすでにセットアップしていて onSongSelectメソッドの中にあります)
の中で、
SongInfoビューの中のデータがアップデートすることによって
アプリケーションフローが完了します。
新しいステーションを始める
これで、アプリケーションフローを追加実装するのが簡単になりました。 新しいステーションを作成して選択するロジックを追加するのは次のようになります。
app/controller/Station.js
...
refs: [{
ref: 'stationsList',
selector: 'stationslist'
}],
init: function() {
// Listen for the select event on the NewStation combobox
this.control({
...
'newstation': {
select: this.onNewStationSelect
}
});
},
onNewStationSelect: function(field, selection) {
var selected = selection[0],
store = this.getStationsStore(),
list = this.getStationsList();
if (selected && !store.getById(selected.get('id'))) {
// If the newly selected station does not exist in our station store we add it
store.add(selected);
}
// We select the station in the Station list
list.getSelectionModel().select(selected);
}
...
概要
これまで先進的なコントローラ技術を使用してビューからロジックを分離することによって、 アプリケーションのアーキテクチャがわかりやすく保守しやすくなることを説明しました。 この段階でアプリケーションはすでにかなり機能しています。 新しいステーションを検索し追加でき、ステーションを選択するとそれを開始できます。 ステーションの曲はロードされ、曲とアーティストの情報が表示されます。
このシリーズの次のパートではスタイリングやカスタムコンポーネントの作成に焦点を当てて、 アプリケーションの改良を続けます。
سÙ„اÙÂ…ÙÂ…Ù† Û±Û¶ ÙÂرÙˆردÛŒن Ù…صاØÂبÙ‡ رؘ§ÃÃÂÂÙÂ….Ú†¯™Â†Ø¯ رÙˆز Ù†ÛŒاز است تا بتونÙÂ… بعد از Ù…صاØÂبÙ‡ تشکÛŒل پروندÙ‡ ÙÂدراÙ„ بدÙÂ…ØŸÙÂÛÂŒ رÙˆ باÛŒد بÙ‡ دÙ„ار کاÙ†ادا با خودÙÂ… ببرÙÂ…ØŸ