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!
Posts relacionados:





Parabéns pelo ótimo artigo e continue sempre com esta vontade de criar melhorias dia-a-dia !!! Abraços
Valeu Julierme! Participação da galera é sempre bom para a continuidade dos posts ;)
Legal Bruno, gostei muito do post, será muito util para meus projetos
abs
Espetacular !!!
Bruno, teria como adpatar o Loader.js, para que ele fizesse uma busca recursiva dentro da pasta informada para ele?
Exemplo:
scripts
|___Login
…….|___Login.js
Assim ele vai varrendos as pastas dentro de scripts e carregando os módulos.
Como ficaria essa alteração no script?
Como assim recursivo? Informar a pasta e carregar todos os javascript dessa pasta? Se for isso não é possível, porque o javascript não acessa arquivos físicos no servidor.
Tenha em mente isso: você precisa indicar qual o arquivo, para que o javascript crie a tag dinâmicamente e o browser carregue esse script via protocolo HTTP. O javascript em si não vasculha diretórios e nem enxerga arquivos do servidor.
Abraço!
Kra realmente muito interessante esse seu esquema de carregamento por demanda… pena que o próprio ext não é assim =/ iria ser uma mão na roda se ele funciona-se dessa maneira ou semelhante.
Parabens
Cara segue uma adaptação que realizei devido as minhas necessidades.
E parabéns pelo script muito util.
http://extjs.com.br/forum/index.php?topic=2495.msg14398#msg14398
Bom fiz algumas adaptações no loader.js ficou interessante, gosto de separar meus arquivos por pastas exemplo:
usuario
– listagem
– cadastro
perfil
– listagem
– cadastro
Bom até ai tudo bem carregou perfeito, porem estou usando o crud avançado e quando clico no cadastrar usuario por exemplo ele não funciona ou simplesmente não chama, o que pode ser?!
Difícil dizer sem ver o seu código Douglas… Se tiver muita dificuldade podes criar um post no fórum que fica mais fácil de te ajudar! ;) Abs!
Tranquilo fazer isso… teria como eu te enviar um video mostrando o que ocorre?!
Como faço para aplicar esse load sobre demanda no CRUD Avançado?!
Muito bom mesmo, parabéns…
Olá Bruno. Tenho acompanhado e aprendido bastante com seus materiais. Obrigado.
Além do Extjs eu também estou estudando o touch e ví que tem também o GWT. Você conhece sobre o GWT? Saberia me dizer se ele tem um desempenho melhor que o ExtJs? principalmente em situações como carregamento? Eu estou em mente fazer uma pesquisa sobre isso, mas se vc souber já me pouparia um pouco de tempo nesse momento tão corrido em que estou vivendo…
Abraços e Parabéns pela sua FANTÁSTICA contribuição.
Não saberia lhe dizer a respeito do GWT, não tenho muito contato com esse cara. Sinceramente prefiro eu mesmo escrever meu código Javascript do que ter um compilador transformando Java em JS. Mas pode ser preciosismo meu…