Código completo e demo online no final do post
Nesse tutorial iremos criar um CRUD completo de usuários. CRUD é um acrônimo de Create, Read, Update e Delete, ou seja, as 4 principais ações de um cadastro simples usando banco de dados: Criar, Ler, Atualizar e Deletar. Iremos abordadar conceitos como:
- Criando um Grid e populando-o com dados do servidor com paginação
- Abrindo janela pop-up ao clicar em um registro
- Popular um formulário com base no registro
- Como carregar corretamente um combo ao popular o formulário
- Submitando um formulário Ext
O código apresentado não é simplesmente didático, ele é um exemplo da codificação que eu uso em meu dia-a-dia na programação Ext JS. Vou incrementar então nesse tutorial algumas coisas de arquitetura de aplicação Ext e também abordar conceitos de Orientação a Objetos.
Para dar uma visão geral do que será feito estou anexando um print do resultado final. A interface será baseada no outro tutorial meu chamado “Como abrir páginas de um menu no centro de sua aplicação“. O CRUD em si será formado de um grid de usuários, e ao usuário clicar em um registro uma tela de edição irá aparecer. Também usaremos a mesma tela para realizar inclusão de novos registros.
Preparando Terreno
Como em grande parte dos tutoriais irei utilizar a pasta examples do Ext para armazenamento da minha aplicação. Nela irei criar uma sub-pasta chamada crudUsuarios. Estou utilizando a IDE Aptana Studio e já tenho um projeto preparado para essa aplicação. Também já tenho um site no Apache criado e tudo está funcional. Podemos começar!
Server-Side
No lado do servidor usaremos PHP e MySQL. Gostaria de criar uma outra versão desse tutorial com ASP.NET e SQLServer mas alguém tem que mostrar interesse :). Não irei fazer nenhum passo-a-passo da programação server-side, estou disponibilizando um .zip com os arquivos e também dois diagramas: um de banco de dados e outro dos fontes PHP.
Listagem de Usuários
Para construção desse CRUD partimos do princípio de que cada interface é uma classe, e cada classe deve extender de um componente do Ext. Iremos dividir cada classe em um arquivo, portanto para a interface da listagem de usuários vamos criar uma classe chamada UsuarioLista e vamos extendê-la de Ext.grid.GridPanel. Esse é o ponto fundamental de uma boa arquitetura de aplicação Ext. Fazendo cada interface ser representada por uma classe podemos nos beneficiar de todo o ciclo de vida de um componente Ext (criação, renderizaçãom, destruição…), além de evitar a “misturança” de código que sempre acaba ocorrendo. Como o Ext aborda o conceito de “aplicação-de-uma-só-página” é fundamental estruturar cada interface dessa maneira.
Iremos sobreescrever o método initComponent, que é o metodo invocado para executar configurações no componente, e nele iremos definir um store para o grid, suas colunas e alguns atributos extra. Iremos definir o evento de clique sobre o grid no método initEvents. Veja como está a estrutura básica até agora:
var UsuarioLista = Ext.extend(Ext.grid.GridPanel,{
//Config Options {
border : false
,stripeRows : true
,loadMask : true
//}
//inits {
,initComponent: function()
{
//store do grid
this.store = new Ext.data.JsonStore({
url : 'Ajax/UsuarioAjax.php'
,root : 'rows'
,idProperty : 'usuarioID'
,totalProperty : 'totalCount'
,autoLoad : true
,autoDestroy : true
,baseParams : {
action : 'listaUsuarios'
,limit : 30
}
,fields:[
{name:'usuarioID' , type:'int'}
,{name:'nome' , type:'string'}
,{name:'UF' , type:'string'}
,{name:'cidade' , type:'string'}
,{name:'nivelHierarquicoID' , type:'int'}
,{name:'nivelHierarquicoDescricao' , type:'string'}
]
});
//demais atributos do grid
Ext.apply(this,{
viewConfig:{
emptyText : 'Nenhum registro encontrado'
,deferEmptyText : false
}
,bbar: new Ext.PagingToolbar({ //paginação
store : this.store
,pageSize : 30
})
,tbar: ['->',{
text : 'Novo usuário'
,iconCls: 'silk-add'
,scope : this
,handler: this._onBtnNovoUsuarioClick
},{
text : 'Excluir Selecionados'
,iconCls: 'silk-delete'
,scope : this
,handler: this._onBtnExcluirSelecionadosClick
}]
,columns:[{
dataIndex : 'nome'
,header : 'Nome'
},{
dataIndex : 'UF'
,header : 'Estado'
},{
dataIndex : 'cidade'
,header : 'Cidade'
},{
dataIndex : 'nivelHierarquicoDescricao'
,header : 'Nivel Hierarquico'
}]
})
//super
UsuarioLista.superclass.initComponent.call(this);
}
,initEvents: function()
{
//super
UsuarioLista.superclass.initEvents.call(this);
this.on({
scope : this
,rowdblclick: this._onGridRowDblClick
});
}
//}
//Overrides{
,onDestroy: function(){}
//}
//Listeners {
,_onBtnNovoUsuarioClick: function(){}
,_onBtnExcluirSelecionadosClick: function(){}
,_onGridRowDblClick: function( grid, rowIndex, e ) {}
//}
//Demais métodos {
//}
});
Ext.reg('e-usuariolista',UsuarioLista);
Dessa forma o nosso grid já deve estar requisitando dados no servidor e renderizando o grid. Como não temos ainda registros vamos criar a ação do botão “Novo usuário”. Este botão quando clicado irá criar a janela de cadastro e mostrá-la. Como também iremos mostrar essa janela ao editar um registro, vamos encapsular a rotina de criação da janela em um método para evitar repetição de código:
,_onBtnNovoUsuarioClick: function()
{
//cria janela de cadastro
this._criaWindowUsuario();
//seta atributos
this._winUsuario.setUsuarioID(0);
//mostra
this._winUsuario.show();
}
,_criaWindowUsuario: function()
{
if(!this._winUsuario)
{
this._winUsuario = new UsuarioCadastro({
renderTo : this.body //restringe área da janela
,listeners :{
scope : this
,salvar : this._onCadastroUsuarioSalvarExcluir
,excluir: this._onCadastroUsuarioSalvarExcluir
}
});
}
return this._winUsuario;
}
Nesse pedaço de código demonstro um conceito muito importante, o reaproveitamento de janelas. Como essa janela pode ser invocada várias vezes é uma importante otimização criá-la somente uma única vez, guardar sua referência, e depois só ir reciclando ela. Portanto, no método _criaWindowUsuario, eu verifico se a referência da janela existe. Se não existir, eu crio, senão somente retorno a referência. Com a referência em mãos, seja ela nova ou reciclada, iremos setar o id do usuário para a janela e ela irá “se virar” para mostrar as informações corretas. Mas atenção, essa otimização requer que sua janela tenha o config. option closeAction setado para hidden ao inves de destroy (veremos isso na definição da janela).
Assim como criamos uma janela e guardamos sua referência, precisamos também destruí-la e liberar sua referência. Aproveitamos o método onDestroy do grid para fazer isso. Quando o grid for destruído, deve destruir também a janela.
,onDestroy: function()
{
UsuarioLista.superclass.onDestroy.apply(this,arguments);
//destrói a janela de usuário e limpa sua referência
Ext.destroy(this._winUsuario)
this._winUsuario = null;
}
Além do conceito de reciclagem existe um outro apresentado no código de criação da janela, os eventos personalizados. Veja que ao criar a janela eu associo um listener aos eventos salvar e excluir. Esses eventos não são nativos do Ext. Nós iremos programá-los, para que quando o usuário salve ou exclua um usuário, o grid seja recarregado.
,listeners :{
scope : this
,salvar : this._onCadastroUsuarioSalvarExcluir
,excluir: this._onCadastroUsuarioSalvarExcluir
}
...
,_onCadastroUsuarioSalvarExcluir: function()
{
//recarrega grid
this.store.reload();
}
Janela de Cadastro
A janela de cadastro é outra interface, por isso um outro arquivo e uma outra classe. Vamos criar a classe UsuarioCadastro e extendê-la de Ext.Window. Vamos em initComponent criar o formulário e ainda já definir o método onDestroy liberando as referências realizadas:
var UsuarioCadastro = Ext.extend(Ext.Window,{
//Config Options {
//UsuarioCadastro
usuarioID: 0
//super
,modal : true
,constrain : true
,maximizable: true
,width : 450
,height : 300
,title : 'Cadastro de Usuário'
,layout : 'fit'
/* Essa janela será reaproveitada, por isso closeAction deve ser HIDE*/
,closeAction: 'hide'
//}
//Acessores {
,setUsuarioID: function(usuarioID)
{
this.usuarioID = usuarioID;
}
//}
//Inits {
,constructor: function()
{
this.addEvents({
salvar : true
,excluir: true
});
//super
UsuarioCadastro.superclass.constructor.apply(this, arguments);
}
,initComponent: function()
{
//combo de nivel hierarquico
this.comboNivelHierarquico = new Ext.form.ComboBox({
fieldLabel : 'Nivel Hierárquico'
,hiddenName : 'nivelHierarquicoID'
,xtype : 'combo'
,triggerAction : 'all'
,valueField : 'nivelHierarquicoID'
,displayField : 'descricao'
,emptyText : 'Selecione um Nível'
,allowBlank : false
,store : new Ext.data.JsonStore({
url : 'Ajax/NivelHierarquicoAjax.php'
,baseParams : {
action: 'listaNiveisHierarquicos'
}
,fields:[
{name: 'nivelHierarquicoID', type:'int'}
,{name: 'descricao' , type:'string'}
]
})
})
//formulário
this.formPanel = new Ext.form.FormPanel({
bodyStyle : 'padding:10px;'
,border : false
,autoScroll : true
,defaultType: 'textfield'
,defaults : {
anchor: '-19'
}
,items:[{
fieldLabel : 'Nome'
,name : 'nome'
,allowBlank : false
,maxLength : 100
},{
fieldLabel : 'Estado'
,name : 'UF'
,anchor : ' '
,width : 50
,maxLength : 2
},{
fieldLabel : 'Cidade'
,name : 'cidade'
,maxLength : 100
}
,this.comboNivelHierarquico]
})
Ext.apply(this,{
items : this.formPanel
,bbar : ['->',{
text : 'Salvar'
,iconCls: 'icon-save'
,scope : this
,handler: this._onBtnSalvarClick
},
this.btnExcluir = new Ext.Button({
text : 'Excluir'
,iconCls: 'silk-delete'
,scope : this
,handler: this._onBtnDeleteClick
})
,{
text : 'Cancelar'
,iconCls: 'silk-cross'
,scope : this
,handler: this._onBtnCancelarClick
}]
})
//super
UsuarioCadastro.superclass.initComponent.call(this);
}
//}
//Override {
,show: function(){
//super
UsuarioCadastro.superclass.show.apply(this,arguments);
//TODO: rotina para carregar formulario
}
,onDestroy: function()
{
//super
UsuarioCadastro.superclass.onDestroy.apply(this,arguments);
this.formPanel = null;
}
//}
//Listeners {
,_onBtnSalvarClick: function(){}
,_onBtnDeleteClick: function(){}
,_onBtnCancelarClick: function(){}
//}
});
Depois disso sua janela já pode ser visualizada, porém ainda não está funcional. Para incluir um novo usuário devemos definir uma ação para o botão “Salvar”. Perceba os passos realizados: a validação é realizada, o submit é realizado, e após tudo isso disparamos o evento personalizado salvar. Um único método no Ajax trata da inserção e da atualização, única diferença é que na inserção usuarioID é igual a zero e na atualização não.
,_onBtnSalvarClick: function()
{
//pego o formulário
var form = this.formPanel.getForm();
//verifico se é valido
if(!form.isValid())
{
Ext.Msg.alert('Atenção','Preencha corretamente todos os campos!');
return false;
}
//crio uma máscara
this.el.mask('Salvando informações');
/*
* Submitando formulário
*/
form.submit({
url : 'Ajax/UsuarioAjax.php'
,params : {
action : 'criaAtualizaUsuario'
,usuarioID : this.usuarioID
}
,scope:this
,success: function() //ao terminar de submitar
{
//tirá máscara
this.el.unmask();
//esconde janela
this.hide();
//evento disparado
this.fireEvent('salvar',this);
}
});
}
Feito isso já estamos aptos a acessar a listagem, clicar em novo usuário, preencher o formulário e salvar. Ao fazer isso a janela será fechada, o grid atualizado e nosso novo registro listado.
Vamos então agora ver como é feita a atualização. Para isso precisamos, na classe UsuarioLista, definir o método que monitora o duplo click nas linhas do grid. Fazemos basicamente o que fizemos ao clicar em novo usuário, porém agora o id do usuário não será mais zero.
,_onGridRowDblClick: function( grid, rowIndex, e )
{
//busca registro da linha selecionada
var record = grid.getStore().getAt(rowIndex);
//extrai id
var usuarioID = record.get('usuarioID');
//cria janela de cadastro
this._criaWindowUsuario();
//seta atributos
this._winUsuario.setUsuarioID(usuarioID);
//mostra
this._winUsuario.show();
}
A classe UsuarioLista está fazendo sua parte, agora basta a janela fazer a sua. Ao mostrar a janela devemos verificar qual o valor da variável usuarioID. Se for zero, o formulário deve ser resetado (lembra que ele pode ser reciclado e conter valores antigos?). Caso não for zero, o formulário deverá ser populado.
,show: function()
{
//super
UsuarioCadastro.superclass.show.apply(this,arguments);
if(this.usuarioID !== 0) //se tem usuario
{
//pode excluir
this.btnExcluir.show();
//crio uma máscara
this.el.mask('Salvando informações');
/*
* Carregando o formulário. Ele deve respeitar algums formatos especificiados na documentação ext de
* Ext.form.Action.Load, como por exemplo conter uma propriedade success e data.
*/
this.formPanel.getForm().load({
url : 'Ajax/UsuarioAjax.php'
,params : {
action : 'buscaUsuario'
,usuarioID : this.usuarioID
}
/*
* Quando o formulário carregar vou tratar problemas de carregar o combo.
* e tirar a máscara
*/
,scope : this
,success: this._onFormLoad
});
}
else //se não existir usuario
{
//não pode excluir
this.btnExcluir.hide();
/*
* Resetando o formulário
*/
this.formPanel.getForm().reset();
}
}
Na rotina que popula o formulário existe um problema com o combo. O store do combo de níveis hierárquicos não foi carregado. O formulário ao ser populado vai tentar setar valor no combo, porém como o store está vazio o próprio ID vai ser atribuído. Resultado disso, temos na interface o id do nivel hierarquico e não a descrição. Esse é um problema muito enfrentado por iniciantes. Existem duas abordagens, carregar o combo de níveis hierarquicos antes do form, ou criar um registro local. Eu prefiro a segunda opção por ter performance melhor. Meu combo pode conter centenas de registros e meu form conter dezenas desses combos. Carregá-los todos de uma vez pode degradar a performance da tela. Por isso junto com os dados do formulário eu também retorno não somente o ID do combo mas também a descrição. Com o id e a descrição em mãos posso criar um registro local:
,_onFormLoad: function(form, request)
{
var data = request.result.data;
if( data.nivelHierarquicoID ) //se o registro possui nivelHierarquicoID
{
//crio novo registro
var novoRegistro = new this.comboNivelHierarquico.store.recordType({
nivelHierarquicoID : data.nivelHierarquicoID
,descricao : data.nivelHierarquicoDescricao
});
//adicionado no store
this.comboNivelHierarquico.store.insert(0,novoRegistro);
//e seto o valor
this.comboNivelHierarquico.setValue(data.nivelHierarquicoID);
}
//tiro uma máscara
this.el.unmask();
}
Até aqui já podemos listar usuários, criar novos usuários, e alterar usuários já registrados, resta somente a remoção de registros. Duas formas são propostas: remover o usuário na tela de seu cadastro, e remover múltiplos usuários na listagem. Ambos os métodos são bem fáceis e vou somente postá-los a vocês.
Em UsuarioLista o método é este:
,_onBtnExcluirSelecionadosClick: function()
{
//busco selecionados
var arrSelecionados = this.getSelectionModel().getSelections();
if( arrSelecionados.length === 0 )
{
Ext.Msg.alert('Atenção','Selecione ao menos um registro!')
return false;
}
Ext.Msg.confirm('Confirmação','Deseja mesmo excluir o(s) registro(s) selecionado(s)?',function(opt){
if(opt === 'no')
return;
var usuariosID = [];
for( var i = 0 ; i < arrSelecionados.length ; i++ )
{
usuariosID.push( arrSelecionados[i].get('usuarioID') );
}
this.el.mask('Excluindo usuários');
Ext.Ajax.request({
url : 'Ajax/UsuarioAjax.php'
,params : {
action : 'deletaUsuarios'
,'usuariosID[]' : usuariosID
}
,scope : this
,success: function()
{
this.el.unmask();
this.store.reload();
}
});
},this);
}
E em UsuarioCadastro este:
,_onBtnDeleteClick: function()
{
Ext.Msg.confirm('Confirmação','Deseja mesmo excluir esse registro?',function(opt)
{
if(opt === 'no')
return
this.el.mask('Excluir usuário.');
Ext.Ajax.request({
url : 'Ajax/UsuarioAjax.php'
,params : {
action : 'deletaUsuario'
,usuarioID : this.usuarioID
}
,scope : this
,success: function()
{
this.el.unmask();
this.hide();
/*
* Evento personalizado excluir sendo disparado
*/
this.fireEvent('excluir',this);
}
})
},this)
}
Conclusão
Bem galera, acho que acabei juntando muito conteúdo em um post só porém me esforcei ao máximo para deixar o código bem comentado e o post bem organizado. Espero ter sido o mais completo possível e ter sanado diversas dúvidas. Também espero ter demonstrado como organizar melhor o seu código, fazendo cada interface ser representada por uma classe. Vejo diversos códigos muito complicados em que o programador realiza um monte de carregamento dinâmico de javascript, uma “mistureba” de componentes, utiliza eval e iframes em todo lugar, etc…
O Ext é fácil! O difícil é entender que Javascript não é somente linguagem de validação de formulário e sim uma linguagem que implementa sua maneira de orientação a objetos e por isso necessita de uma abordagem mais profissional.
Estou a disposição aqui através dos comentários, no twitter, no email contato[at]extdesenv.com, no fórum brasileiro (bt_bruno)…enfim, entre em contato! :D E comentem! Seus comentários são incentivo para novos posts! Até a próxima!
Posts relacionados:








Bruno, ficou muito bom seu CRUD, especialmente para quem está começando. No entando, tenho duas sugestões:
1- Que tal utilizar o RowEditor para edição das linhas? Creio que é uma solução bem mais elegante.
2- O próximo exemplo poderia ser um CRUD “pai e filho”, como alguns chamam. Aqueles onde cada linha (cadastro) está vinculado a uma outra lista. Por exemplo: cadastro de uma NF de entrada e a lista dos items que estão na nota fiscal.
Esse é um CRUD mais elaborado e bastante utilizado. então creio que seja um ótimo exemplo. Precisando de ajuda, estamos aí :)
Abraços
Grande Miguel! Valeu pelas sugestões! O RowEditor de fato é bem mais bacana, porém como queria abordar também algumas questões de formulário, acabei decidindo pela pop-up. Mas nada impede de uma versão com RowEditor ;) E sobre o CRUD pai filho, ótima questão. Estou pensando em fazer um “CRUD local”, aonde todos os dados vão ficando em buffer para depois serem salvos, e o exemplo de Nota Fiscal é ideal para isso.
Abraço!
Parabéns Bruno pelo post! Eu q estou começando e acabei de achar este post e estou estudando o mesmo para inicializar na programação web Ext JS, PHP e MySQL, achei muito bacana. Parabéns ao Miguel tmb, pois o exemplo q ele deu é muito bom e justamente o q eu estava procurando, como fazer em PHP e MySQL e acabei encontrando este post bem legal. Seria de MUITA IMPORTÂNCIA esta nova versão! Espero q sai logo logo. Parabéns mesmo a vc’s e aguardo a novo versão! Grande abraço a todos!
Nossa, Melhor tutorial até agora, parabéns mesmo pela iniciativa, agora sim entendi os conceitos de extends, aplly, classes no ext e outros. Muito obrigado, acho que isso vai ajudar muitas pessoas, ficou otimo, bem estruturado e comemtado. parabéns pelo blog, espero que cada dia cresça mais, com certeza vou divulgar esse blog.
Seu entusiasmo e agradecimento me serve como forte incentivo para tutorias ainda melhores. Muito obrigado pela participação Wemerson! Abraço!
Me tira uma dúvida por favor…
Em um grande sistema, aonde são declarados muitas classes, muitas referencias aos arquivos js…. Como a idéia “aplicação-de-uma-só-página”, o navegador do cliente/usuário iria ter que carregar na abertura do sistema todas os arquivos js, correto? Em um sistema grande, um erp, isso não afetaria diretamente o desempenho do carregamento?
Não gosto nem um pouco da idéia de abrir cada ‘módulo’ em um iframe, pois afinal… só pelo fato de se um iframe já é motivo para ficar de nariz torcido… Porém com ele, nesse unico aspecto, ele iria carregando os arquivos js somente a medida que o usuário for abrindo… não é verdade? Ou estou aquivocado?
Excelente pergunta! Você está correto sim nas afirmações. Carregar todos os javascript é inviável e usar iframes degrada muito a performance. Aí que entra um conceito complementar a toda a arquitetura: Carregamento de Javascript Sob-Demanda. Eu não entrei ainda a fundo na arquitetura por isso não comentei. Fiz apenas uma breve introdução sobre esse assunto no blog Framebox: Como carregar Javascript dinâmicamente . Em breve vou postar aqui um exemplo específico para Ext.
Abraço!
Olá Bruno! Parabéns pelo tutorial!!!
Sobre o carregamento de javascript sob demanda, você me indica outro tutorial ou material mais completo?
Poxa, acabei de analisar sobre o carregamento dinamico no outro post… Muito interessante! Depois vou ler com dedicação total os artigos do NZakas e do SSouders…
Você aplica essa técnica em todos seus sistemas ext js?
Aguardando um exemplo especifico… quem sabe em cima desse CRUD avançado… :D
Abraços e sucesso a todos!
Opa, muito bom este artigo, segui à risca e ficou show de bola!
Mas assim, como quero extender ele, preciso criar um form com cadastro de usuário, e aí entra senha e validação da senha.
Tentei acoplar o método desse artigo (http://blog.adampresley.com/2008/advanced-data-validation-using-extjs/), porém não tive sucesso. Até consegui fazer os campos ficarem do tipo password, mas as a parte de validação em si não funciona. Poderia me dar um auxílio?
Valeu!
Olá Rafael,
legal saber que está servindo de base para você este tutorial. Sobre a validação de senha é rapidinho de fazer, os próprios exemplos do Ext mostram como. Se você está tendo problemas, pode recorrer ao fórum brasileiro. Posta lá sua dúvida que eu mesmo ou outro desenvolvedor com certeza lhe ajudará.
Abraço!
A empresa que trabalho adotou o EXT para confecção de suas interfaces. Confesso que já xinguei até a quarta geração de quem criou esse framework. kkkk
Parabenizo-te pela ajuda à comunidade de desenvolvedores Ext. De longe seu artigo é o mais esclarecedor. Espero que continue com seu empenho e sou mais um a fazer coro pelo exemplo pai-filho, utilizando uma NF.
Bom dia
eu sou novo em programação com o extjs
eu baixei o arquivo e não consegui popular os dados no grid
o combo funciona mas o grid nao
algume pode me ajudar por favor
obrigado.
Bruno, parabéns pelo tutorial! muito bom.
deixa eu te perguntar: porque vc usa scope: this nos botoes da toolbar?
valeu!
Bruno, primeiro quero parabenizar pelos tutoriais. Muito Obrigado. Mas a minha dúvida, e acho que é também de muitas pessoas, seria saber como é o processo para você aprender as informações até conseguir fazer um tutorial tão rico. Eu gostaria de saber resumidamente o que você faz… se você lê algum livro sobre Ext, ou estuda a API, ou lê algum tutorial em inglês para se basear etc. Obrigado!!!
Fico grato pelo comentário Antonio! Aprender Ext requer um esforço um pouco maior que outros frameworks, porque sua curva de aprendizado é grande. Atribuímos a ele essa característica pela sua complexidade, número de componentes e etc… Fundamentalmente meu conhecimento vêm da experiência diária com o framework, mas leitura do fórum não podem ser descartadas.
Veja que não existe muito material não oficial do Ext. A grande parte está no site deles. Uma boa pedida seria iniciar alterando exemplos já prontos do framework, e quando estiver seguro podes criar suas próprias aplicações. Adicione a isso um pouco de leitura do ExtDesenv e dos posts do fórum brasileiro e americano, isso irá refinar o seu código e torná-lo um melhor programador.
Enfim, é isso! Qualquer coisa não hesite em conversar! Abraço, e sucesso!
E ai Bruno, show estes seus tutoriais.
Como vc comentou a cima no tutorial, eu tenho muito interesse que vc fizesse um tutorial destes de Ext e asp.net C#.
Abraços
Bacana Henrique, assim que eu puder irei adaptar o server-side para .NET. Te aviso!
abs!
Acho que você já deve estar acostumado a ouvir elogios a respeito do seu tutorial e com toda razão. Meus parabéns, ele é de fato fonte de grande motivação para quem quer desenvolver usando este framework. Bruno uma duvida minha: É possível usar ASP no lugar do PHP?
MUITO OBRIGADO VALEU MESMO SEU SITE AJUDA MUITAS PESSOAS NO DIA DIA
Bruno,
Parabens pelo site, está me ajudando muito no aprendizado da Extjs, principalmente nos padrões pois sei que precisamos de atenção dobrada ao utilizar esse framework.
Abrs,
Samir
Grande tutorial!!!
Mas tive um probleminha que não estava populando a lista, coloquei os parâmetros sort e dir (baseParams) na atribuição da store criada no arquivo UsuarioLista.js, ficando assim:
// ...
,baseParams: {action: "listaUsuarios"
,limit: 30
,start: 0
,sort: "Nome"
,dir: "ASC"
}
// ...
Outro problema foi que o combobox da janela estava duplicando os dados devido ao evento onFormLoad do arquivo UsuarioCadastro.js, na linha que tem o seguinte código:
//adicionado no store
this.comboNivelHierarquico.store.insert(0,novoRegistro);
deixei assim:
//adicionado no store se nao houverem registros
if(this.comboNivelHierarquico.store.getCount()==0){
this.comboNivelHierarquico.store.insert(0,novoRegistro);
}
Isto ocorreu devido a form não estar criada na primeira vez e se fechamos ela fica oculta e duplica o valor do combo.
Abraço a todos.
Valeu pelas modificações Rubens. Assim que eu puder vou dar uma refatorada no exemplo. Abs!
Muito bom Bruno … Podemos criar n sistemas com esta
base que postou.
Porém sou novo aqui e já estou com meio caminho andado
tentando seguir seu exemplo. Não entendi a forma que vc
chama a listagem de cadastros : “Ao dá 2 cliques em usuarios
crie uma nova aba com a listagem dos cadastros”. Entendi que
a listagem de cadastros é um arquivo a parte .js … Não entendi como
vc o chama.
Abraço e Parabens pela iniciativa.
Diogo
Olá Diogo! Acompanhe o seguinte:
A listagem de usuarios está num arquivo separado chamado UsuarioLista.js, isso você sacou. Nesse arquivo tenho uma classe UsuarioLista que extende de GridPanel, e no final registro o xtype da classe como ‘e-usuariolista’. Ponto.
No arquivo index.html eu faço a inclusão desse script, e então a tela de listagem só está esperando para ser usada, certo?
Por fim no arquivo Principal.js, aonde eu coordeno toda as aberturas das telas, eu tenho no método onNodeClick a minha rotina para abrir essa listagem. “Ao dar dois cliques em usuários eu crio uma nova aba, e dentro dessa aba eu coloco minha listagem utilizado o XTYPE defido anteriormente.
Espero ter lhe ajudado. A rotina é um pouco complicadinha =/
abs!
Olá Bruno,
Conheci o ExtJs por acaso, estava pesquisando algumas coisas sobre o JQuery e acabei por me deparar com o ExtJs…. Gostei muito dele.
Estou usando como base sua arquitetura para montar uma administração de portais (CMS). Já consegui progredir bastante. Seguindo os exemplos dos seus posts (e me matando um pouco com o framework) consegui integrar o carregamento dinâmico de scripts, de outras coisas (editor WYSIWYG, editor de temas, etc…).
Porém, ainda estou com algumas dúvidas:
- Na sua janela de cadastro de Usuário, quando não é informado nenhum nome / Nível hierárquico, aparece a mensagem de erro, porém não exibe o ícone de alerta descrevendo o motivo do erro. Olhei os códigos de exemplo que vem com o extjs 3.2 e a única diverença que eu vi é que você coloca o FormPanel em um Window. Por que não exibe os ícones?
- Em alguns casos, eu queria exibir o FormPanel diretamente no TabPanel. Um exemplo disso seria uma parte onde é cadastrada as configurações do sistema ou uma parte de “altere sua senha”. Eu até consegui criar uma classe que herda o Ext.form.FormPanel e exibir na tela, mas estou com o mesmo problema de validação dos dados (consta o erro, mas não exibe nenhuma mensagem). Tem como resolver isto?
[]´s
Ricardo
1. Aparecer ou não o ícone é configurável. Existe um config. option chamado msgTarget que indica se o erro deve aparecer num tooltip (“qtip”), ou com um ícone ao lado (“icon”), ou até mesmo com uma mensagem abaixo do campo (“under”). Veja na documentação de Ext.form.Field essa propriedade.
Para definir um padrão você pode fazer isto: Ext.form.Field.prototype.msgTarget = ‘side’
2. Será que a explicação 1 corrige este?
abs!
Bruno,
Muito obrigado, funcionou usando msgTarget = ’side’ e eu tb tive que colocar Ext.QuickTips.init(); no início do scritp.
Estou com uma dificuldade agora: dentro do form eu queria colocar uma grid com 2 botões (no tbar) para incluir e alterar dados dessa grid. Por algum motivo, quando eu seleciono um item da grid dentro do form, o mesmo index é selecionado da grid principal (a que é rendenizada na aba). Além disso, mesmo cancelando o form os dados da grid não são apagador. Tem como resolver isso?
Sei que explicando assim é bem difícil. Caso seja necessário, eu enviarei o código para vc ver.
obrigado!
Olá Ricardo! Nesse caso sugiro abrir um post no fórum brasileiro, exemplificando com o seu código. Dessa maneira além de receber minha ajuda você recebe o apoio de toda a comunidade.
Forte abraço!
Bruno, seu exemplo ficou muito bom, e a forma como o código ficou estruturado simplesmente excelente. Tenho uma dúvida, quando clicamos duas vezes na linha pra abir a edição o combobox vai duplicando os valores. POr exemplo: Se Tenho um usuário com nivel de funcionário, e depois clico em outra linha com o mesmo nivel funcionário. Aparecem dois niveis funcionários no combo. Como resolver isso? Grato e parabens pelo site.
Olá Wagner! O problema ocorre porque no método _onFormLoad eu crio novos registros no combo de superior hierárquico sem verificar se o mesmo já existe. Estarei revisando e traduzindo esse post, então sua correção vai estar em breve online.
Abs!
Bruno.
Muito obrigado pela resposta rápida, seu trabalho é muito bom. Aguardo ancioso as correções.
Grato.
Bruno,
Segue abaixo alteração que fiz no seu fonte. No método _onFormLoad adicionei uma nova linha que resolveu o problema.
//atualiza o store
this.comboNivelHierarquico.store.reload();
Então o método ficou assim:
_onFormLoad: function(form, request)
{
/* Muitos sabem do inconveniente problema de carregar um formulário e nos combos vir o ID e não a
* descrição. Isso acontece porque o store do combo ainda não esta carregado, e por isso o form não encontra
* nenhum registro com o id especificado.
*
* 2 Abordagens possíveis. Carregar o combo todo e setar novamente o valor, ou criar uma opção local para o combo.
* Prefiro a segunda por ser MUITO mais performática e otmizar muito o carregamento do form. Para isso funcionar
* você deve além de trazer o ID do registro do combo também trazer a descrição
*/
var data = request.result.data;
if( data.nivelHierarquicoID )
//se o registro possui nivelHierarquicoID
{
/*
* crio um novo registro seguindo o protótipo do store do combo
* informando os valores devidos
*/
var novoRegistro = new this.comboNivelHierarquico.store.recordType({
nivelHierarquicoID : data.nivelHierarquicoID
,descricao : data.nivelHierarquicoDescricao
});
//adicionado no store
this.comboNivelHierarquico.store.insert(0,novoRegistro);
//e seto o valor
this.comboNivelHierarquico.setValue(data.nivelHierarquicoID);
//atualiza o store
this.comboNivelHierarquico.store.reload();
}
//tiro uma máscara
this.el.unmask();
}
Espero ter ajudado. Fico no aguardo por novos post.
Abração.
this is great! I try to translate the example code to english but it can’t work :( could you please give .zip file in english? thx a lot
Hi @evang! Good to have international comments here =D As soon as I can I’ll be translating the whole article and example ok? Regards, Bruno!
Olá Bruno,
Primeira gostaria de agradecer pelo artigo, o melhor que vi até o momento.
Gostaria de tirar uma dúvida, copiei toda a sua aplicação , mudei apenas o server-side, estou usando C#, porem qnd tento rodar ele da o seguinte erro:
b[d.xtype || e] is not a constructor
Ta tudo do jeito q está no site, é como se ele nao tivesse criando o tipo da UsuarioLista; os includes estão todos no index tb.
Agradeço pela atenção
Esse erro é justamente porque você está fazendo uso de um xtype que o Ext não reconhece. Talvez como você tenha dito, a classe UsuarioLista não esteja sendo carregada. Enfim, tente verificar na aba “Rede” do firebug se todos os arquivos estão sendo carregados. Além disso, podes comentar todo seu código, e ir descomentando aos poucos, tentando isolar o erro.
Mas com antecedência fica claro afirmar que o problema é um xtype não registrado no Ext. Abs!
Olá Bruno!
Cara eu estou utilizando o seu modelo de aplicação (que achei muito interessante) para fazer um sistema de cadastro de ramais e contatos.
Mas eu me deparei com um problema “superior” chamado CHEFE. Ele não quer que eu abra WINDOWS de jeito nenhum para o cadastro. Ele quer que o grid e o form estejam juntos em uma única tela como nesse exemplo que o Ext oferece:
DIRETORIO_EXT_BASE/examples/form/form-grid-access.html
Você tem ou sabe algum lugar onde tenha exemplo de como adaptar essa tela hibrida para esse modelo de crud que você está usando? Qualquer ajuda é bem vinda!
Olá Josenilson! Eu não tenho exemplo não cara. Mas acompanhe o seguinte: no meu exemplo quando o usuário clica no grid, nós abrimos uma janela e invocamos o método setUsuarioID para que a janela possa carregar os dados.
Para colocar as interfaces lado a lado, primeiramente a tela de cadastro deve extender de Ext.Panel, e não mais de Ext.Window. Daí visualmente você já consegue colocar o grid e o painel de cadastro lado a lado na mesma tela (layout column, hbox, etc…).
Tem mais algumas adaptações a fazer, mas eu gostei da sua idéia. Quem sabe eu aproveito ela e crio mais um tutorial. Abs, e qqer coisa estamos também no fórum!
Fala Bruno! Cara você responde rápido! No forum do Ext eu fico esperando igual um doido por um sinal de vida!
Então na verdade VISUALMENTE eu já fiz tudo o que eu preciso. Coloquei dois grids e um form na tela como queria o chefe. Eu extendi o formPanel ao invés do gridPanel. E nos items do formPanel eu adicionei um grid (xtype: ‘grid’). Mas o meu problema é que não sei como acessar o form a partir do grid que eu criei. Tentei usar o getCmp mas ele não funciona…
Tipo:
var RamalForm = Ext.extend(Ext.form.FormPanel,{
id: ‘RamalPanel’
,layout: ‘column’
,initComponent: function(){
Ext.apply(this,{
items: [
{
xtype: 'grid'
,id: 'RamalLista'
,sm: new Ext.grid.RowSelectionModel({
singleSelect: true
,listeners: {
rowselect: function(sm, row, rec) {
Ext.getCmp('RamalPanel').getForm().loadRecord(rec); //Isso não funciona! Ele não está encontrando o RAMALPANEL (nem se eu chamar por RamalForm)
}
}
})
,ds: meu_ds
,columns:[ ... ]
}
,{
xtype: ‘fieldset’
,id: ‘RamalCadastro’
,layout: ‘column’
,items: [ campos do form ]
}
]
})
}
});
Ext.reg(‘CAD_RAMAL’,RamalForm);
Eu postei o código completo no forum do Extjs:
http://www.extjs.com/forum/showthread.php?99555-How-to-navigate-between-de-components-and-pass-values-to-them&p=468749#post468749
Bem, eu escrevi um post a respeito dessa sua situação. Dê uma olhada e se tiver dúvida pode comentar lá. Abs!
Cadastro básico com grid e formulário
Fala Bruno!
Bacana o exemplo! Eu já tinha conseguido fazer isso há algum tempo seguindo a sua orientação nos comentários anteriores (extendendo o Panel ao invés do Window). E no final acabou ficando MUITO mais complexo o que o cliente queria… então na verdade agora tem TabPanels dentro de cada TabPanel e panels (com layout column) com grids e forms! Enfim, acho que virou um frankstein visual… mas é exatamente o que foi pedido.
O Ext é realmente tem uma gama muito grande de possibilidades!
Ah, falando em possibilidades eu estava reparando que em todos os exemplos que vi de layouts no estilo VIEWPORT a aba HOME tem conteudo estático. Como você faz para carregar conteudo dinamico nela?
Dear Bruno, could you please share RowEditor example. On extjs forum, people still get bug or error “f is undefined”. I try to modify your code to RowEditor but get the same issue. So please help us. thx a lot!
Primeiro de tudo parabéns pelo post
ele esta me ajudando muito
mas estou com um problema que não consigo resolver, no meu o excluir, não apaga o registro e mesmo que eu clique em No, o processo de chamar o php delete acontece
Olá Ciff! A respeito de não excluir eu não vejo como posso te ajudar, porque envolve a parte do servidor. Quanto a clicar em “Não” e excluir vc faz o seguinte:
Ext.Msg.confirm(“Atenção”,”Deseja excluir?”,function(opt){
if( opt === “no”){
return; //se respondeu não, cai fora
}
});
Abs!
Olá Bruno, muito útil este exemplo, estou fazendo minha aplicação me baseando neste layout, a pergunta é a seguinte, qndo vc inicia essa aplicação o menu pai “Cadastros” vem aberto e outros fechados, como faço para que o menu pai “Relatórios” venha aberto por padrão e os demais menus pai fechados?
Obrigado!
Olá Fabio! Sua pergunta não sumiu, ela estava aguardando aprovação ;) A respeito de trazer itens abertos, como o menu lateral se trata de um Painel com layout accordion, só de você definir o atributo activeItem:1 já é o suficiente para trazer itens expandidos ou contraídos. Claro que no lugar de “1″ você coloca o índice do painel que deseja expandir, acho que vc deve entender! Você pode saber mais na documentação de Ext.layout.AccordionLayout. Abs!
Perfeito! Era isso mesmo, desculpe o transtorno.
Uma outra questão, na listagem, vc tem aquele método ‘onDestroy’, não consegui identificar onde ele é chamado, não deveria ser chamado ao fechar a aba em questão? minha pergunta é pq na minha aplicação, eu só declarei este método desta maneira:
onDestroy: function()
{
membroLista.superclass.onDestroy().apply(this, arguments);
Ext.destroy(this.janelaMembro)
this.janelaMembro = null;
},
Porém, ao fechar a aba, o firebug me retorna o seguinte erro: membroLista.superclass.onDestroy() is undefined
[Break on this error] membroLista.superclass.onDestroy().apply(this, arguments);
Eu te adicionei no MSN para ficar mais fácil de explicar a situação.
Obrigado novamente!
Ola,
boa tarde, nossa estou impressionado msmo com esta ferramenta. A proposito, eu estou mergulhando neste universo de ExtJS e bem, andei sondando algumas IDE`s e tal. Encontrei o tal de Aptana. Só que ele nao esta configurado. Já fiz os procedimentos mais ainda sim nao rola. Você saberia me indicar uma tutor que relamente funciona a parada !!!!!
valeu e .. teu site é show de bola!
Olá Jean! O aptana é fera, eu uso ele (: As únicas coisas que eu alterei nele foi instalar o plugin Ext 2.2 (só ir em Help > Install aptana features…), tirar o auto-format de código javascript (Preferences > aptana > editors > javascript > formatting), e eu também adicionei o jslint como validador de código (Preferences > aptana > editors > javascript > validation).
Infelizmente eu não conheço tutorial, mas é fácil de configurar, você resolve rapidinho! Não entendi especificamente o que vc quer ativar nele.
Abs!
Bruno,
Parabéns pelos excelentes posts!
Olá. Comecei a trabalhar agora com essa ferramenta e não sei quase nada sobre ela. Consegui fazer o crud mas não consegui inserí-lo no viewport. Se pudesse me ajudar eu agradeceria muito. Desde já agradeço a atenção, Mayara.
Olá Mayara! Não sei de que forma posso lhe ajudar, já que não sei de que forma foi feito o CRUD. Eu publiquei no post o código completo, desde o Ext.onReady(), até a instanciação do Ext.Viewport. Você poderia tomar isso como exemplo.
Pra quem está começando, talvez seja mais interessante dar uma olha da no post sobre Cadastro básico com grid e formulário e Como abrir páginas de um menu no centro de sua aplicação.
Abs!
Muito obrigada pela ajuda..conseguir fazer o q estava precisando. Adorei o resultado! Desde já agradeço a atenção, Mayara.
Olá Bruno, desculpe incomodar novamente, mas quando dou o submit no formulário de cadastro de novo usuário, o firebug retorna o seguinte erro:
missing ) in parenthetical
[Break on this error] (”;//You have an error in your SQL sy…ear ‘WHERE matricula=12345′ at line 1)
No caso, substitui UsuarioID por matricula. Vc sabe o q pode estar dando errado?
Olá estou tentando fazer a implementação do seu exemplo, porém estou tendo dificuldades para exibir os dados no formulário. Eu clico, ele consegue pegar o ID porém não exibe o restante dos dados. Como faço para fazer o casamento entre os campos que vem do JSON e os dados a serem exibidos no form?
[]‘s
Olá Bruno..
Estou com 2 duvidas simples nesse seu exemplo:
1 – Como poderei colocar um div topo no meu cod de forma que a aplicação fique ocupando todo o espaço abaixo desse “topo”?
2 – Como fazer com que o combobox dos níveis hierárquicos não aceite que nada seje digitado nele (digitei um nível não existente e deu uns erros)?
vlw
Opa.. Outra Dúvida (liga não, sou programadr prático e ainda fico voando com alguns conecitos)..
Como adaptar esse exemplo usando o carregamento dinamico dos js?
Tem como exemplificar nesse mesmo exemplo?
Bruno,
Boa tarde, peguei seu exemplo e nao consegui rodar, aparece como se estivesse html normal.
Tema haver com o browser, estou usando IE, ou tem haver com a versão do Extjs, pois estou usando a 3.3.1?
Vlw
Bruno,
Como posso fazer a ação alterar e visualizar?
Tens algum exemplo demonstrando isso? mas utilizando o Crud….
bruno n entendil como faço pra chamar outra aba?
fiz tudo direitinho mencionado anteriormente mais n deu certo,
tem como vc me ajudar?
Belo tópico. Estou com um problema, ao criar os arquivos e no index fazer a chamada dos .js tenho esse erro ao carregar a página no firebug:
Ext.fly(d) is null
Parabéns Bruno!!! Muito bom o exemplo, principalmente para mim q estou começando agora. Seguinte, fiz tudo direitinho e o exemplo roda quase q 100%, pq:
Não está carregando os dados do BD na grid de Usuários;
Quando chamo o formulário para incluir novo usuário, o mesmo é feito com sucesso e incluído no banco de dados, mais não aparece na grid. A grid fica em branco apresentando “Nenhum registro encontrado”. Será q vc pode me ajudar? Grato desde já,
Christian Matola
Muito Bom seus posts!
Venho acompanhando tudo sobre ExtJS e ASP.NET,
gostaria de saber se vai sair um post nesta mesma ideia de crud avançado usando o EXTJS 4 e o ASP.NET.
mais uma vez parabens!!
Abraços
Olá Jhonata! Na verdade Ext JS é agnóstico quanto a linguagens server-side. Não importa se você está usando PHP, Ruby ou ASP.NET. O que importa é que a comunicação cliente – servidor tem que ser por JSON (ou xml). Por exemplo, ao submeter um formulário o Ext espera uma resposta “{success: true}”, não importa o que você está usando para criar essa resposta.
Não pretendo usar outra coisa que PHP para criar exemplos, porque dessa forma é rápida e conseguimos focar mais no Ext.