アプリケーションアーキテクチャの概要
Ext JS は MVC と MVVM 両方のアプリケーションアーキテクチャに対応しています。これらのアーキテクチャは共に、特定のコンセプトに沿って、アプリケーションコードを論理的な塊を元に分類しています。どちらのアプローチも、アプリケーションをどう分けるかによって強みが違います。
このガイドの目的は、これらのアーキテクチャを組み立てるコンポーネントに関しての基礎知識を提供することです。
MVCとは
MVC アーキテクチャでは、クラスの大半は Model か View か Controller です。ユーザーは Model が持っているデータを表示している View とやりとりします。そういったやりとりは Controller によってモニタリングされていて、それに対して View や Model を必要に応じてアップデートすることで反応します。
View と Model は基本的には互いを認識しあっていません。これは、 Controller がアップデートを指揮する責任を持っているからです。一般的にですが、 Controller は MVC アプリケーション内の大半のアプリケーションロジックを含んでいます。View にはビジネスロジックはほとんどありません (全くない場合もあります) 。 Model はデータに対するインターフェースという役割であり、そういったデータを加工するためのビジネスロジックを含んでいます。
MVC の目的はアプリケーション内の各クラスの責任を定義づけることです。各クラスにはっきりと定義づけられた責任があるため、暗黙のうちに大きな環境から分離されます。これによりアプリは簡単にテストできますし、メンテナンスも楽になり、コードも再利用しやすくなります。
MVVMとは
MVC と MVVM の大きな違いは、MVVM が ViewModel と言って、View を抽出化した機能を持っていることです。ViewModel は Model のデータと View が表示しているデータの違いを「データバインディング」という機能を使ってコーディネートします。
その結果、 Model とフレームワークが最大限に働く事で、View に直接関係するアプリケーションロジックを減らすことができるのです。
Ext JS 4 をお使いの方
Ext JS 5 は MVMM アーキテクチャへの対応も導入し、MVC の (C) への対応も改良されました。これらの改良点を知って有効活用して欲しいと思っていますが、それと同時に私たちは、既存の Ext JS 4 MVC アプリケーションに手を加えなくても機能することにも努力を費やしました。
MVC と MVVM
どちらがあなたのアプリケーションに適しているかを考える場合、まずはそれぞれの頭文字が何を意味するかについて話しましょう。
(M) Model – これはアプリケーションのデータです。いくつかのクラス ( Model と呼ばれるもの) がデータのフィールドを定義します (例:user-name と passwor をもつ User Model ) 。Model はデータパッケージを全体で、アソシエーションによって他の Model とリンクすることができます。
Model はグリッドやその他のコンポーネントにデータを提供するために、普段は Store と一緒に使われます。また、Model はバリデーションや変換などのデータロジックを置く理想的な場所です。
(V) View – View は視覚的に表示されるコンポーネントです。例えばグリッド、ツリーやパネルは View として認識されています。
(C) Controller – Controller はアプリが動くために必要とする、View のロジックを維持する場所として使われます。これには View のレンダリング、ルーティング、などのアプリケーションロジックが含まれます。
(VM) ViewModel( View Model ) – ViewModel は View と直接関係するデータを管理するクラスです。コンポーネントにバインドする許可を与え、データが変わり次第更新します。
これらのアプリケーションアーキテクチャはフレームワークコードに対して構造と一貫性をもたらします。私たちが提案するコーディング規則に沿うだけで、たくさんの利益をもたらしてくれます:
各アプリケーションは同じように動くので、一度覚えるだけで大丈夫です。
アプリケーション間でコードを共有することが簡単です。
Sencha Cmd を使う事で アプリケーションの最適化されたバージョンを生成する事ができます。
サンプルアプリを作る
ピースの一つひとつを解説する前に、 Sencha Cmd を使用してアプリをビルドしてみましょう。以下のコマンドをコマンドラインから入力してみましょう:
sencha generate app -ext MyApp ./app cd app sencha app watch |
注: もしここで何が起きているか理解できない場合は、 Getting Started guide をチェックしてください。
アプリケーションの概要
MVC、MVVM、MVC+VM パターンを作るピースについて触れる前に、Cmd で作られたアプリケーションの構造を見てみましょう。
ファイル構造
Ext JS アプリケーションは、どのアプリでも同じ、統合されたひとつのディレクトリ構造で出来ています。私たちが推奨するレイアウトでは、全てのクラスは app
フォルダーに入れられています。このフォルダーは
Model、
Store、
View エレメントを配置してあるサブフォルダーを含みます。
View 、
ViewController、
ViewModel
などの View エレメントはまとめた方が組織的にもより良い働きをします (次の “main” View フォルダ参照) 。
名前空間
各クラスの最初の行にはクラスの名前があります。この名前は名前空間と呼ばれます。名前空間の書き方は次の様になります。
<アプリ名>.<フォルダ名>.<クラス名(ファイル名)> |
サンプルアプリ内では。“MyApp” がアプリ名で、 “view” がフォルダ名、“main” がサブフォルダ名で、“Main” がクラス名でありファイル名です。この情報を元にフレームワークは以下の場所で Main.js というファイルを探します:
app/view/main/Main.js |
もしファイルが見つからなければ、Ext JS は状況を変えるまでエラーを表示します。
アプリケーション
index.html を見る事でアプリケーションの査定を始めましょう。
<!DOCTYPE HTML> <html> <head> <meta charset="UTF-8"> <title>MyApp</title> <!-- The line below must be kept intact for Sencha Cmd to build your application --> <script id="microloader" type="text/javascript" src="bootstrap.js"></script> </head> <body></body> </html> |
Ext JS は Microloader を使って、app.json
ファイルに記されたアプリケーションリソースをロードします。これにより、index.html
に追加する必要はありません。app.json
があれば、全てのアプリケーションのメタデータが一箇所に置かれます。それにより、
Sencha Cmd
はアプリをより簡潔に効率よくコンパイルできます。
app.json
には沢山のコメントがあって、記述できる情報についての情報を調べるための最高のリソースになります。
app.js
先ほどアプリケーションを生成した時に、私たちは Application.js
内にクラスを作成し、app.js
の中でそのインスタンスを作りました。app.js
の中身は次の様になります。
/* * This file is generated and updated by Sencha Cmd. You can edit this file as * needed for your application, but these edits will have to be merged by * Sencha Cmd when upgrading. */ Ext.application({ name: 'MyApp', extend: 'MyApp.Application', autoCreateViewport: 'MyApp.view.main.Main' //------------------------------------------------------------------------- // Most customizations should be made to MyApp.Application. If you need to // customize this file, doing so below this section reduces the likelihood // of merge conflicts when upgrading to new versions of Sencha Cmd. //------------------------------------------------------------------------- }); |
autoCreateViewport
は Ext JS 5 の新しい機能です。autoCreateViewport にコンテナークラスを指定することで、どのクラスでも View ポートとして使えます。上記の例では MyApp.view.main.Main
(コンテナー クラス) を Viewport としています。
autoCreateViewport コンフィグはアプリケーションに指定された View を作り、 Viewport プラグイン. をアタッチさせるよう指示します。これで View をドキュメントボディーに接続させます。
Application.js
どの Ext JS アプリケーションも
Application クラス
のインスタンスから始まります。このクラスは、app.js
から立ち上げることができ、テスト用にインスタンス化もできます。
以下が Sencha Cmd を使ってアプリを生成した時に自動的に作られる
Application.js
の中身です。
Ext.define('MyApp.Application', { extend: 'Ext.app.Application', name: 'MyApp', stores: [ // TODO: add global/shared stores here ], launch: function () { // TODO - Launch the application } }); |
この Application クラス には、アプリの名前空間や共有 Store などの、アプリケーションのグローバル設定を含みます。
Views
View とは Ext.Component のサブクラスであるコンポーネントにすぎません。 View はアプリケーションの外見に関する情報を全て含んでいます。
スターターアプリの ”main” フォルダー内にある Main.js を開くと以下のようなコードを見る事ができます。
Ext.define('MyApp.view.main.Main', { extend: 'Ext.container.Container', xtype: 'app-main', controller: 'main', viewModel: { type: 'main' }, layout: { type: 'border' }, items: [{ xtype: 'panel', bind: { title: '{name}' }, region: 'west', html: '<ul>...</ul>', width: 250, split: true, tbar: [{ text: 'Button', handler: 'onClickButton' }] },{ region: 'center', xtype: 'tabpanel', items:[{ title: 'Tab 1', html: '<h2>Content ...</h2>' }] }] }); |
View にはアプリケーションロジックが全く含まれていない事がわかりますね。View のロジックに関する部分は全て ViewController, に配置します。それについては次のセクションでふれたいと思います。
この View では、west と center のリージョンを持つ border レイアウト のコンテナーを定義しています。 各リージョンには、 button を配置した toolbar のある panel と tab panel が配置されています。 これらのコンセプトについてわからなければ、 Getting Started Guide を参照ください。
この View の二つの興味深い部分は、 controller と viewModel コンフィグです。
controller コンフィグ
controller コンフィグでは、その View のための ViewController を指定できます。この様に View に ViewController が指定されると、イベントハンドラーやレファレンスの入れ物になります。これは ViewController に コンポーネントや View が発火したイベントと 1対1 の関係を結びます。 Controller については後のセクションで更にふれたいと思います。
ViewModel Config
viewModel コンフィグでは、View に ViewModel を指定できます。 ViewModel はこのコンポーネントとその子コンポーネントのデータプロバイダーです。ViewModel に保存するデータは、通常、表示したり編集したりしたいコンポーネントに bind コンフィグを追加することで使われます。
上記の View を見ると、west リージョンのパネルのタイトルは ViewModel とバインドされていることがわかります。つまりタイトルはデータの “name” 値が読み込まれるということであり、これは ViewModel で管理されています。もし ViewModel のデータが変われば、タイトルの値も自動で着にアップデートされます。ViewModel については後半でも解説していきます。
ViewController
次に、
ViewController
について見ていきましょう。スターターアプリが作った ViewController MainController.js
はこのようにになっています:
Ext.define('MyApp.view.main.MainController', { extend: 'Ext.app.ViewController', requires: [ 'Ext.MessageBox' ], alias: 'controller.main', onClickButton: function () { Ext.Msg.confirm('Confirm', 'Are you sure?', 'onConfirm', this); }, onConfirm: function (choice) { if (choice === 'yes') { // } } }); |
Main.js
という View に戻ってみると、tbar ボタンの handler コンフィグが指定されていることがわかります。そのハンドラーは ViewController 内の onClickButton という関数と対応付けられています。このように、この ViewController は特別な設定など必要なく、そのイベントに対応しているということになります。
これによりアプリケーションにロジックを追加するのが非常に簡単になります。ViewController が View と1対1の関係を結んでいるので、onClickButton 機能を定義さえすればいいのです。
View のボタンをクリックすると、メッセージボックスが生成されます。メッセージボックスはコールバックに onConfirm がセットされていて、同じ ViewController にスコープされるようになっています。
ViewController は次の様にデザインされています。
明示的に “listeners” や “reference”コンフィグを使用することで View に接続します。
View のライフサイクルを活用することで、関連づけされた ViewController を自動的に管理します。インスタンス化から破棄まで、
Ext.app.ViewController
は、参照されたコンポーネントと結ばれています。同じ View クラスの2つ目のインスタンスには、それ自身の ViewController インスタンスが作られます。これらの View が破棄された場合、関連する ViewController インスタンスも同時に破棄されます。ネストした View をより直感的にするために、カプセル化を提供します。
ViewModel
次は
ViewModel
です。
MainModel.js
というファイルを開くと、以下のコードが見られるはずです。
Ext.define('MyApp.view.main.MainModel', { extend: 'Ext.app.ViewModel', alias: 'viewmodel.main', data: { name: 'MyApp' } //TODO - add data, formulas and/or methods to support your view }); |
ViewModel はデータオブジェクトを管理するクラスです。そしてこのクラスは、データに関心のある View にバインドして、変更について通知をする許可を与えます。 ViewController のように、 ViewModel はリファレンスされた View のものです。ViewModel は View と関連づけされているため、コンポーネント階層の中の、祖先コンポーネントが持っている親 ViewModel とリンクすることができます。これにより、親 ViewModel のデータを子 View が簡単に「継承」することができます。
Main.js 内の ViewModel コンフィグを使い、View を ViewModel に結合させました。この結合により、View の viewModel を宣言することで、自動的にデータをセットするため、セッターとコンフィグのバインドができるようにします。
データは MainModel.js
の例ではインラインになっています。そして、このデータはどんなものでも、どんな場所からきたものでも大丈夫です。データはプロキシー (AJAX、REST、など) から提供されることもあるでしょう。
Model と Store
Model と Store はアプリケーションの情報のゲートウェイとなるものです。大半のデータはこの2つのクラスによって、送られ、取得され、整理され、モデリングされます。
Model
Ext.data.Model はアプリ内にある永続的なデータ (どのようなタイプでも) を表します。各 Model にはフィールドやアプリケーションにデータをモデリングさせる関数があります。 Model は大抵の場合、Store と接合されて使用されます。そして Store はグリッド、ツリー、チャートなどのデータバインドコンポーネントで使うことができます。
私たちのサンプルアプリケーションには、現状では Model がないので以下のコードを加えます。
Ext.define('MyApp.model.User', { extend: 'Ext.data.Model', fields: [ {name: 'name', type: 'string'}, {name: 'age', type: 'int'} ] }); |
名前空間のセクションで述べたように、app/model の下に位置する User.js
を 作りましょう。
フィールド
Ext.data.Model はレコードの値を保持し “fields”の定義をします。 Model クラスは “fields” コンフィグを使ってこれらのフィールドを宣言することができます。この場合、“name” は文字列 (string) として宣言され、age は 整数 (integer) です。 他の フィールドタイプ もあり、API ドキュメントに載っています。
フィールドを宣言する利点はたくさんありますが、する必要があるわけではありません。fields コンフィグを含まなければ、データは自動的に読み込まれ、データオブジェクトに入れられます。もしデータに対して次のことが必要であれば fields 定義しなければなりません。
- バリデーション
- 既定値
- convert 関数
次は Store を設定して、これら二つの動作を見てみましょう。
Stores
Store はクライアント側のレコード (Model クラスのインスタンス) のキャッシュです。Store は内部に含まれているレコードの、ソート、フィルタ、検索機能を提供します。
このサンプルアプリケーションには Store がありませんが、心配はありません。Store を定義して、 Model をアサインすればよいだけです。
Ext.define('MyApp.store.User', { extend: 'Ext.data.Store', model: 'MyApp.model.User', data : [ {firstName: 'Seth', age: '34'}, {firstName: 'Scott', age: '72'}, {firstName: 'Gary', age: '19'}, {firstName: 'Capybara', age: '208'} ] }); |
上記の内容を、app/store に配置した User.js
に追加します。
Store を Application.js
の store コンフィグに加えると、Store のグローバルインスタンスを作ることができます。Application.js 内の store コンフィグは以下のようになっています。
stores: [ 'User' ], |
この例では、Store が直接データを含んでいます。実際の多くの状況では、Model や Store のデータをプロキシーを使って集めなければいけないことがあります。プロキシーはデータプロバイダとアプリケーション間でのデータ転送をしてくれます。
Model、Store、データプロバイダについては Data Guide で詳しく解説しています。
次のステップ
私たちは Ticket App. という堅牢で、便利なアプリを作りました。このアプリケーションはログイン/ログアウト セッションを管理し、データバインドを取り入れ、MVC+VM アーキテクチャを活用する場合でのベスト・プラクティスを示します。この例はたくさんコメントされていて、全てのことができる限りクリアになっています。
もし時間があれば、 Ticket App に触れ、理想的なMVC+VM アプリケーションアーキテクチャについて知って欲しいと思います。