自前でアドイン(MVC Application Architecture)の仕組みを作る(その2)

いろいろ試していたんですが、これがまた我ながら行き当たりばったり感丸出しでした。
整理してみると、問題点が何点かありました。

  1. Ext.LoaderやExt.requiresの正しい使い方を理解していなかった。
  2. 遅延レンダリングにまつわる描画の落とし穴を知らなかった。
  3. 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});

これを設定しておかないと、動的ロードの機能が有効にならないとかなんとか。。