自前でアドイン(MVC Application Architecture)の仕組みを作る(その2)
いろいろ試していたんですが、これがまた我ながら行き当たりばったり感丸出しでした。
整理してみると、問題点が何点かありました。
- Ext.LoaderやExt.requiresの正しい使い方を理解していなかった。
- 遅延レンダリングにまつわる描画の落とし穴を知らなかった。
- MVC Application Architecture の仕組みを全然理解していなかった。
で、結局理解不足が原因だ!つうことで、MVC Application Architecture の時の、
エントリポイントからの挙動を追ってみることにしました。
まずはapp.jsに記述されているExt.applicationメソッドです。
渡しているconfigオプションを眺めてみると、
Applicationとしてのロード順序?依存関係?はこんな感じ?(聞くな)
Ext.application configオプションのcontrollersでコントローラクラス群を指定 ↑ここで指定されている各コントローラクラスの定義を見ると、 それぞれのconfigオプションのviews、stores、modelsプロパティで、 コントローラが使用するクラス群が指定されている。 コントローラクラスがロードされると、 その後views、stores、modelsで指定したクラス群もロードされる感じ? launchメソッドでExt.container.Viewport、またはこれを継承したクラスのインスタンスを生成。 ではViewportのクラス定義はどうなってる? Viewportクラス定義ではview系のクラスをロードする記述は一切ない。 いきなりcofigオプションのitemsでxtype使ってview系のインスタンスを生成する。 なので、必要なクラスはcontorollerで先にロードされているふう?
次に実際にExt.applicationの中で何が実行されているのか定義を見てみました。
場所は、「ext-4.0.2a/src/core/src/Ext-more.js」です。
Ext.application = function(config) { Ext.require('Ext.app.Application'); Ext.onReady(function() { Ext.create('Ext.app.Application', config); }); };
これだけです。
Ext.app.Applicationのインスタンスを生成してる以外、特にヒネたことはしていません。
Ext.app.Applicationは、configを渡されているので、
コンストラクタでconfigオプションで指定したcontrollerをロードしてるかもしれません。
なので、次はExt.app.Applicationのコンストラクタを見ます。
ソースコードのありかは、「ext-4.0.2a/src/app/Application.js」になります。
コンストラクタはこんなことをやっています。
Ext.define('Ext.app.Application', { extend: 'Ext.app.Controller', // ※中略 /** * Creates new Application. * @param {Object} config (optional) Config object. */ constructor: function(config) { config = config || {}; // 引数のコンフィグを自分自身のスコープに設定。 Ext.apply(this, config); var requires = config.requires || []; // アプリ名nameと、アプリフォルダappFolderをひも付けてロードパスを追加。 Ext.Loader.setPath(this.name, this.appFolder); // this.pathsで何が取れるんだ?環境変数? // それを更にExt.Loader.setPathでパス登録してるっぽいけど、 // Ext.Object.eachメソッドは何をしてるんだろう。 // 多分Lispのmap的なことをやっているんだろうが、 // 第2引数の関数オブジェクトの第2引数に何が入ってくるのかわからん。 // 分からんが、このメソッドはまぁ飛ばしてもよかろ。 if (this.paths) { Ext.Object.each(this.paths, function(key, value) { Ext.Loader.setPath(key, value); }); } this.callParent(arguments); this.eventbus = Ext.create('Ext.app.EventBus'); // ここでcontrollersが登場した。何やってる? var controllers = Ext.Array.from(this.controllers), ln = controllers && controllers.length, i, controller; this.controllers = Ext.create('Ext.util.MixedCollection'); // getModuleClassNameって何やってんだ? // 省略しちゃったコードに記述されてた。 // 「指定されたアプリ名 + "." + 第2引数 + "." + 第1引数」でクラスパスを生成してる感じ。 // んで、それをrequiresに追加してる。 if (this.autoCreateViewport) { requires.push(this.getModuleClassName('Viewport', 'view')); } // ここだ!!configオプションで定義したコントローラ供をrequiresに追加登録してる! for (i = 0; i < ln; i++) { requires.push(this.getModuleClassName(controllers[i], 'controller')); } //で、Ext.reguireで、全部のクラスをロードしてる。ここでやってんのね〜。。 Ext.require(requires); // ページロード時の処理で、登録したコントロールのinitメソッドを実行させる。 Ext.onReady(function() { for (i = 0; i < ln; i++) { controller = this.getController(controllers[i]); controller.init(this); } this.onBeforeLaunch.call(this); }, this); }, // ※中略 });
要約すると、アプリケーションの名前空間とソースコードのルートディレクトリを単純に結びつけた後、
ディレクトリパスとクラスパスを同じ構成とみなして(これは標準的な動作っぽい?)、
Ext.requiresでロードしているだけなんですね。
前エントリの、自分が理想的とするディレクトリ構成の場合、アプリ名をSimpleAppとすると、やっていることは単純にコレです。
// index.html?app.js?が存在するディレクトリをカレントディレクトリとし、 // アプリケーションの名前空間をSimpleAppとした場合、 // それと./appディレクトリをひも付ける。 Ext.Loader.setPath('SimpleApp','app'); // ひも付けたあと、ディレクトリ構成とクラスパス構成は対応するから、 // フルクラスパスを指定して各jsファイルをロードしてる。 Ext.requires([ 'SimpleApp.controller.Controller1', 'SimpleApp.controller.Controller2', 'SimpleApp.view.Viewport', 'SimpleApp.view.View1', 'SimpleApp.view.View2', 'SimpleApp.model.Model1', 'SimpleApp.model.Model2', 'SimpleApp.store.Store1', 'SimpleApp.store.Store2' ]);
特に複雑なことをしているわけではありませんでした。
これを踏まえて、前エントリのaddins/subapp1やaddins/subapp2ディレクトリ配下を「サブアプリ」とみなした場合、
これを実現するためには、サブアプリの名前空間としては、
SampleApp.subapp1 SampleApp.subapp2
という感じで管理したいわけです。
そうすると、実行されるべきコードはこんな感じになります。
// サブアプリ1を動的にロード Ext.Loader.setPath('SimpleApp.subapp1','addins/subapp1'); Ext.requires([ 'SimpleApp.subapp1.controller.SubController1', 'SimpleApp.subapp1.view.SubView1', 'SimpleApp.subapp1.model.SubModel1', 'SimpleApp.subapp1.store.SubStore1' ]); // サブアプリ2を動的にロード Ext.Loader.setPath('SimpleApp.subapp2','addins/subapp2'); Ext.requires([ 'SimpleApp.subapp2.controller.SubController2', 'SimpleApp.subapp2.view.SubView2', 'SimpleApp.subapp2.model.SubModel2', 'SimpleApp.subapp2.store.SubStore2' ]);
この一連の動作を、ロードしたい場所に記述すれば、
正常に動的ロードができるはずです。
が、一つ落とし穴がありました。
Ext.applicationが実行される前に、この一文が必要なようです。
Ext.Loader.setConfig({enable: true});
これを設定しておかないと、動的ロードの機能が有効にならないとかなんとか。。