Carregar código Ext sob demanda

No post CRUD Avançado apresentei um pouco sobre a arquitetura que julgo ser ideal para criar aplicações Ext. Para relembrar um pouco os conceitos: nessa arquitetura eu proponho que cada interface seja representada por um classe que deve extender de um componente Ext, e ser mantida dentro de um arquivo com o mesmo nome.

//Arquivo AtendenteLista.js<br />
AtendenteLista = Ext.extend(Ext.grid.GridPanel,{</p>
<p>      //definição da interface</p>
<p>});

Acontece que no mesmo dia já recebi perguntas sobre como essa arquitetura poderia ser mantida em aplicações grandes, com centenas de arquivos javascript. Incluir todos eles na inicialização é inviável, mas se não incluirmos não conseguiremos enxergar as classes?! Então lhes trago o complemento para a arquitetura proposta, a “Cola” que liga uma interface com a outra, o carregamento de código sob demanda, ou Ext.require.

Estudei um pouco sobre carregamento dinâmico e criei esse método que permite carregar código sob demanda, respeitando inclusive dependências entre arquivos. O material de meu estudo postei no framebox blog no post Como carregar Javascript dinâmicamente. Segue:

/**<br />
 * Carrega módulos dinamicamente<br />
 * @param {String/Array} modules (required)<br />
 * @param {Function} callback<br />
 * @param {Object} scope<br />
 */<br />
Ext.require = function()<br />
{<br />
	var modulesLoaded = {};</p>
<p>	/**<br />
	 * @private Cria tags script e monitora callback<br />
	 * @param {String} src (required)<br />
	 * @param {Function} callback<br />
	 * @param {Object} scope<br />
	 */<br />
	var createScriptTag = function( module, callback, scope)<br />
	{<br />
		//tira extensão<br />
		module = module.replace(/.js/gi,&quot;&quot;);</p>
<p>		//não carrega 2 vezes<br />
		if( modulesLoaded[module] )<br />
		{<br />
	 		callback.call(scope,module);<br />
			return;<br />
		}<br />
		modulesLoaded[module] = true;</p>
<p>		//cria tag e atributos<br />
	    var script = document.createElement(&quot;script&quot;)<br />
	    script.setAttribute('type','text/javascript');<br />
		script.setAttribute('src',Ext.require.moduleUrl + module + '.js');</p>
<p>		//configura callback<br />
		if(callback)<br />
		{<br />
		    if (script.readyState) //IE<br />
			{<br />
		        script.onreadystatechange = function()<br />
				{<br />
		            if (/loaded|complete|4/i.test(script.readyState+&quot;&quot;))<br />
					{<br />
		                callback.call(scope,module);<br />
						script.onreadystatechange =callback = scope = null;<br />
		            }<br />
		        };<br />
		    }<br />
			else //Others<br />
			{<br />
		        script.onload = function()<br />
				{<br />
		            callback.call(scope,module);<br />
					script.onload = callback = scope = null;<br />
		        };<br />
		    }<br />
		}</p>
<p>		//append<br />
   	 	document.getElementsByTagName(&quot;head&quot;)[0].appendChild(script);<br />
	}</p>
<p>	/**<br />
	 * @private Class that manages async load of multiple modules<br />
	 * @param {Array} modules<br />
	 * @param {Function} callback<br />
	 * @param {Object} scope<br />
	 */<br />
	var asyncProcess = function(modules, callback, scope)<br />
	{<br />
		this.totalToLoad = modules.length;<br />
		this.totalLoaded = 0;<br />
		this.finalCallback = callback||Ext.emptyFn;<br />
		this.callbackScope = scope;</p>
<p>		for(var i = 0 ; i &lt; modules.length ; i++ )<br />
		{<br />
			createScriptTag(modules[i], this.moduleCallback, this);<br />
		}<br />
	}</p>
<p>	Ext.apply(asyncProcess.prototype,{</p>
<p>		 totalToLoad	: 0<br />
		,totalLoaded	: 0<br />
		,finalCallback	: Ext.emptyFn<br />
		,callbackScope	: undefined</p>
<p>		,moduleCallback	: function(module)<br />
		{<br />
			this.totalLoaded++;</p>
<p>			if(window[module] &amp;&amp; window[module].prototype &amp;&amp; window[module].prototype.$depends)<br />
			{<br />
				var dependents = [].concat(window[module].prototype.$depends);</p>
<p>				//remove dos dependentes os já carregados<br />
				for(var i = dependents.length - 1 ; i != -1; i-- )<br />
				{<br />
					if( modulesLoaded[ dependents[i] ] == true)<br />
						dependents.pop();<br />
				}</p>
<p>				//se existe ainda dependentes para carregar<br />
				if(dependents.length)<br />
				{<br />
					this.totalToLoad++;<br />
					Ext.require(window[module].prototype.$depends,this.moduleCallback,this);<br />
				}<br />
			}</p>
<p>			if(this.totalLoaded == this.totalToLoad)<br />
			{<br />
				this.finalCallback.call(this.callbackScope);</p>
<p>				//destroy<br />
				for(k in this)<br />
					this[k] = null;<br />
			}<br />
		}<br />
	});</p>
<p>	/*<br />
	 * public function<br />
	 */<br />
	return function (modules, callback, scope)<br />
	{<br />
		if(!Ext.isArray(modules))<br />
			modules = [modules];</p>
<p>		new asyncProcess(modules, callback, scope)<br />
	}<br />
}();</p>
<p>/**<br />
 * @property {String} Indicates where to get the scripts from<br />
 * @static<br />
 */<br />
Ext.require.moduleUrl = 'scripts/';

Não é de meu interesse explicar linha a linha do código, somente como utilizá-lo. Vamos comentar os parâmetros da função Ext.require:

  • {String/Array} nomeArquivo: Pode ser uma string, carregando um único arquivo, ou um array de strings. *Obrigatório!
  • {Function} callback: Referência para função que será invocada quando todo(s) o(s) arquivo(s) estiver(em) carregado(s).
  • {Object} escopo: Seguindo padrões de Ext, temos a opção de executar a função de retorno em um escopo definido pelo usuário.
  • Alguns exemplos rápidos:

    <br />
    //carregar 1 arquivo<br />
    Ext.require('MeuArquivo', function(){ alert(&quot;MeuArquivo carregado!&quot;); );</p>
    <p>//carregar multiplos<br />
    Ext.require(['CadastroAtendente','CadastroCliente'],function()<br />
    {<br />
        //this está no escopo de um painel<br />
        this.add({<br />
                xtype: 'cadastro-atendente'<br />
        },{<br />
                xtype: 'cadastro-cliente'<br />
        });</p>
    <p>    this.doLayout();<br />
    },this)

    Importante notar que existem alguns detalhes a serem respeitados para que o carregamento de dependencias ocorra corretamente. Veja este exemplo:

    //Arquivo ProdutoCadastro.js<br />
    ProdutoCadastro = Ext.extend({</p>
    <p>    $depends: ['CategoriaLista','FabricanteLista']</p>
    <p>    //..demais atributos, métodos, etc...</p>
    <p>});

    Para que dependência de arquivos funcione o nome da classe deve ter o mesmo nome do arquivo. Para carregar o arquivo dinamicamente você informa o nome dele. Após carregado o Ext.require verifica se existe alguma classe dentro desse arquivo com o nome especificado. Se existir, a estrutura da classe é examinada em busca da cláusula $depends, e então as dependências podem ser carregadas.

    E é isso aí pessoal. Em complemento postei mais código na seção exemplos aqui do extDesenv. O que você achou desse método? Você têm alguma proposta de melhoria ao código? Forte abraço e até a próxima!

    server-side.zip

    Código Completo

    Demo Online

    Demo Online