Carregar código Ext sob demanda

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
AtendenteLista = Ext.extend(Ext.grid.GridPanel,{

      //definição da interface

});

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:

/**
 * Carrega módulos dinamicamente
 * @param {String/Array} modules (required)
 * @param {Function} callback
 * @param {Object} scope
 */
Ext.require = function()
{
	var modulesLoaded = {};

	/**
	 * @private Cria tags script e monitora callback
	 * @param {String} src (required)
	 * @param {Function} callback
	 * @param {Object} scope
	 */
	var createScriptTag = function( module, callback, scope)
	{
		//tira extensão
		module = module.replace(/\.js/gi,"");

		//não carrega 2 vezes
		if( modulesLoaded[module] )
		{
	 		callback.call(scope,module);
			return;
		}
		modulesLoaded[module] = true;

		//cria tag e atributos
	    var script = document.createElement("script")
	    script.setAttribute('type','text/javascript');
		script.setAttribute('src',Ext.require.moduleUrl + module + '.js');

		//configura callback
		if(callback)
		{
		    if (script.readyState) //IE
			{
		        script.onreadystatechange = function()
				{
		            if (/loaded|complete|4/i.test(script.readyState+""))
					{
		                callback.call(scope,module);
						script.onreadystatechange =callback = scope = null;
		            }
		        };
		    }
			else //Others
			{
		        script.onload = function()
				{
		            callback.call(scope,module);
					script.onload = callback = scope = null;
		        };
		    }
		}

		//append
   	 	document.getElementsByTagName("head")[0].appendChild(script);
	}

	/**
	 * @private Class that manages async load of multiple modules
	 * @param {Array} modules
	 * @param {Function} callback
	 * @param {Object} scope
	 */
	var asyncProcess = function(modules, callback, scope)
	{
		this.totalToLoad = modules.length;
		this.totalLoaded = 0;
		this.finalCallback = callback||Ext.emptyFn;
		this.callbackScope = scope;

		for(var i = 0 ; i < modules.length ; i++ )
		{
			createScriptTag(modules[i], this.moduleCallback, this);
		}
	}

	Ext.apply(asyncProcess.prototype,{

		 totalToLoad	: 0
		,totalLoaded	: 0
		,finalCallback	: Ext.emptyFn
		,callbackScope	: undefined

		,moduleCallback	: function(module)
		{
			this.totalLoaded++;

			if(window[module] && window[module].prototype && window[module].prototype.$depends)
			{
				var dependents = [].concat(window[module].prototype.$depends);

				//remove dos dependentes os já carregados
				for(var i = dependents.length - 1 ; i != -1; i-- )
				{
					if( modulesLoaded[ dependents[i] ] == true)
						dependents.pop();
				}

				//se existe ainda dependentes para carregar
				if(dependents.length)
				{
					this.totalToLoad++;
					Ext.require(window[module].prototype.$depends,this.moduleCallback,this);
				}
			}

			if(this.totalLoaded == this.totalToLoad)
			{
				this.finalCallback.call(this.callbackScope);

				//destroy
				for(k in this)
					this[k] = null;
			}
		}
	});

	/*
	 * public function
	 */
	return function (modules, callback, scope)
	{
		if(!Ext.isArray(modules))
			modules = [modules];

		new asyncProcess(modules, callback, scope)
	}
}();

/**
 * @property {String} Indicates where to get the scripts from
 * @static
 */
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:

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

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

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

    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

Posts relacionados:

  1. Passo-a-passo: criando extensão para tela de login
  2. Como abrir páginas de um menu no centro de sua aplicação
  3. Criando Ext.Window para edição de dados de um grid – CRUD Local
  4. CRUD Avançado com Ext JS 3.0
  • Comentários [17]
    • Share