Criando uma Aplicação CRUD com ExtJS 4 MVC: Parte 5

Parte 5 da série de posts explicando passo a passo como criar uma aplicação simples com ExtJS 4 usando o MVC.

extjs-4-mvc-parte5

Este tutorial está dividido em 5 partes – hoje vamos ver a quinta e última parte (demorou, mas saiu! rs):

  1. Preparando o Ambiente e montando o Projeto (Parte 1)
  2. Criando o Model e Store (Parte 2)
  3. Criando a View – Grid (Parte 3)
  4. Criando a View – Formulário (Parte 4)
  5. Criando o Controller (Parte 5)

A quinta parte inclui:

  1. Criando o Controller
  2. Abrindo a Janela para Edição do Contato
  3. Adicionar um Contato
  4. Salvar um Contato
  5. Deletar um Contato

1 - Criando o Controller

Bem, note que até agora apenas criamos as Views, Models e Stores. Se a gente executar o projeto e clicar em algum botão, nada vai funcionar. Isso é porque essa parte onde o usuário "fala" com a aplicação e a aplicação responde com alguma ação ainda não foi implementada e vamos fazer isso agora.

Para criar o Controller, vamos criar um arquivo chamado Contatos.js dentro da pasta app/controller:

[code lang="js" firstline="1" toolbar="true" collapse="false" wraplines="false"]
Ext.define('ExtMVC.controller.Contatos', {
extend: 'Ext.app.Controller',

stores: ['Contatos'],

models: ['Contato'],

views: ['contato.Formulario', 'contato.Grid'],

refs: [{
ref: 'contatoPanel',
selector: 'panel'
},{
ref: 'contatoGrid',
selector: 'grid'
}
],

init: function() {
this.control({
'contatogrid dataview': {
itemdblclick: this.editarContato
},
'contatogrid button[action=add]': {
click: this.editarContato
},
'contatogrid button[action=delete]': {
click: this.deleteContato
},
'contatoform button[action=save]': {
click: this.updateContato
}
});
},

editarContato: function(grid, record) {
var edit = Ext.create('ExtMVC.view.contato.Formulario').show();

if(record){
edit.down('form').loadRecord(record);
}
},

updateContato: function(button) {
var win = button.up('window'),
form = win.down('form'),
record = form.getRecord(),
values = form.getValues();

var novo = false;

if (values.id > 0){
record.set(values);
} else{
record = Ext.create('ExtMVC.model.Contato');
record.set(values);
this.getContatosStore().add(record);
novo = true;
}

win.close();
this.getContatosStore().sync();

if (novo){ //faz reload para atualziar
this.getContatosStore().load();
}
},

deleteContato: function(button) {

var grid = this.getContatoGrid(),
record = grid.getSelectionModel().getSelection(),
store = this.getContatosStore();

store.remove(record);
this.getContatosStore().sync();

//faz reload para atualziar
this.getContatosStore().load();
}
});

[/code]

Na linha 1 temos o nome da classe. ExtMVC é o nosso namespace, controller é o nome do pacote e Contatos é o nome do arquivo que criamos dentro do pacote controller (app/controller).

Nas linhas 4, 6 e 8 temos a declaração de todas as stores, models e views que esse Controller irá se preocupar. O Controller automaticamente cria métodos get para essas declarações. Mas para ser sincera, nunca vi muita gente usando isso na prática. Mas vamos ver como isso funciona também nesse post.

Nas linhas 10 a 17 temos a declaração das ref, que são referências. Vamos entender como isso funciona.

Às vezes queremos pegar a referência de algum componente. Isso pode ser facilmente feito usando o ComponentQuery. Por exemplo, digamos que queremos pegar a referência do Grid que criamos. Esse grid foi declarado com o xtype contatogrid (alias). Para obter a referência podemos usar Ext.ComponentQuery.query('contatogrid')[0]. Lembre-se que o método query do ComponentQuery retorna um array de objetos que atendem a busca. Como temos apenas 1 instância desse grid na aplicação, podemos pegar a posição zero do array diretamente. Os selectors do JQuery funcionam de maneira bem parecida.

Agora imagine que você precisa pegar a referência desse Grid várias vezes nesse mesmo controller. O código não vai ficar tão elegante. E por isso podemos criar uma referência. Assim sempre que precisamos podemos usar a referência ao invés da busca do componente usado o ComponentQuery.

E para criar a referência precisamos de um nome para a referência (ref) e um selector que será usado para fazer a busca desse componente (selector). Geralmente usamos o xtype do componente como selector para facilitar. No caso da referência contatoGrid, depois podemos usar o seguinte código para pegar a referência do componente: this.getContatoGrid().

E nas linhas 19 a 34 temos as declarações de todos os selectors e eventos que queremos ouvir. O controller só tomará alguma ação se o evento estiver declarado. Vamos ver um a um para entender melhor como funciona:

Na linha 21 por exemplo. Queremos que o método/função editarContato seja executado caso o usuário dê um duplo clique em alguma linha da Grid que criamos. Para isso, temos que ouvir o evento itemdblclick da View do Grid (contatogrid dataview). Nesse caso ajuda muito se você souber quais componentes formam um grid por exemplo. O componente GridPanel é a apenas o container, a carcaça do componente. Todas as linhas e colunas ficam dentro de outro componente chamado DataView.

Na linha 24 estamos escutando o evento click to botão Add. Note que o botão Add é o botão (componente cujo xtype é button) que foi declarado dentro do grid cujo xtype é contatogrid e esse botão tem uma config action cujo valor é add. Dessa maneira estamos sendo bem específicos e não estamos dando nenhuma informação ao Ext JS que o leve a um componente que possa ter informação igual. Estamos deixando bem claro que esse botão é único.

Bem mais do que usar esses selectors no control ou ref do Controller, também podemos usar esses mesmos selectors no ComponentQuery (que é usado por trá para buscar os componentes). Usar a config id de um componente do ExtJS é uma péssima prática pois você precisa ter certeza de que essa id é única em toda a sua aplicação. E isso fica ainda mais complicado se você estiver trabalhando em equipe. Uma solução é criar a sua própria config como fizemos com a action (essa config não existe no ExtJS) ou você pode simplesmente usar a config itemId. O itemId precisa ser único no escopo, ou seja, você pode ter um botão com itemId add no form Contato, e ter um botão com itemId add no form Cliente. Assim você garante que para aquele escopo esse botão é unico. Se você for usar itemId, o selector ficaria assim: contatogrid button#add (muito similar ao JQuery). E isso é o que acho legal no ExtJS: várias maneiras de se fazer a mesma coisa. Você pode usar a aquela que você se sentir mais confortável.

Na linha 27 e 31 temos a mesma lógica, só que estamos escutando o evento click dos botões delete e save.

2 - Abrindo a Janela para Edição do Contato

Já estamos escutando o evento itemdblclick do DataView do Grid. Agora precisamos implementar o método/função editarContato (linhas 36 a 42).

Se a gente for na documentação e olhar o evento itemdblclick, vamos ver que esse evento recebe os seguintes parâmetros: this, record, item, index, e, eOpts; sendo que this é a própria view. Nós só estamos interessados nos 2 primeiros parâmetros, mas é uma boa prática declarar todos. Como estamos reusando esse método em outro evento também, vamos declarar apenas os 2 primeiros parâmetros.

Na linha 37 estamos então criando o Form que está dentro de uma Window e mostrando essa Janela para edição.

Na linha 39 estamos verificando se o parâmetro record está vazio/null. Caso negativo, vamos carregar os dados do registro no Form (linha 40). Isso só irá funcionar caso o evento disparado seja o itemdblclick. E é claro, o nome dos campos do Model precisam ser os mesmos dos campos do Form para isso funcionar.

Nós não vamos nos preocupar em salvar o contato agora. A única coisa que queremos é abrir o formulário e carregar os dados da linha que o usuário selecionou.

Segue um screenshot do formulário:

extjs-mvc-crud-form

3 - Adicionar um Contato

Para adicionar um contato, esperamos que o usuário clique no botão Add e com isso o evento click do botão Add seja disparado e o método editarContato seja executado. Note que é o mesmo método que usamos para abrir a janela para editar o contato.

Caso o evento click seja disparado, o parâmetro record não será verdadeiro, e com isso o form não irá carregar os dados, ou seja, a única coisa que será feita é a janela que será aberta para o usuário digitar as informações (linha 37).

4 - Salvar um Contato

Não importa se o contato é novo ou o usuário editou. Quando o usuário clicar no botão Salvar o método updateContato será executado.

Note que o primeiro parâmetro passado para esse método é a própria referência do botão que o usuário clicou. Isso vai nos ajudar a pegar as referências de outros componentes que precisamos.

Na linha 45 estamos pegando a referência da Janela onde está o Form. Como o botão foi declarado dentro da Window, ao chamar o método up('window') o ExtJS vai buscar a primeira Window que encontrar na hierarquia. Precisamos da referência da Janela para poder pegar a referência do formulário (linha 46).

E como o formulário está dentro da Janela, podemos descer na hierarquia e pegar a referência do form (linha 46).

Ao chamar o método getRecord() do form (linha 47), estamos pegando os valores do Model que foram carregados no form (caso o usuário tenha feito edição).

E ao chamar o método getValues() do form (linha 48), estamos pegando todos os valores que estão no form no formato JSON. Isso vai ser útil mais para frente caso o usuário criou um novo contato.

Na linha 50 criamos uma flag apenas para nos ajudar a identificar se foi uma criação ou edição.

Se a gente  olhar a parte 4 desse tutorial, no Form criarmos um campo hidden que é o id. Esse id vai nos ajudar a dizer se é um registro novo ou uma edição. Se ele for 0 é porque é novo e se tiver algum valor é porque é uma edição. Tendo dito isso, vamos olha a linha 52. Como o values é um objeto JSON, podemos acessar diretamente o id e verificar se é maior que 0 (zero). Se sim, vamos setar os valores do Form no Model que carregamos no form (linha 53). Caso contrário (é um contato novo), precisamos criar um novo Model (linha 55), setar os valores (linha 56) e adicionar esse novo Model na store (linha 57) e é claro, marcar a flag como verdadeira (linha 58).

Como eu sei que posso chamar esse método this.getContatosStore()? Lembra que no começo do Controller declaramos stores: ['Contatos']?  Então o método fica this.get + nome da store (Contatos) + Store.

O próximo passo seria fechar a Janela, afina não queremos que o usuário faça isso sozinho (linha 61).

E depois é só chamar o método sync da Store para sincronizar/salvar os dados no servidor (linha 62). Se for um contato novo, adicionar esse contato e vai chamar a url de create da store. E caso seja uma edição, como o Model já estava na Store, quando setamos os valores (linha 53), os novos valores foram setados e o registro foi marcado como dirty (sujo) e nesse caso a store irá chamar a url de update.

E caso seja um registro novo, vamos dar um reload na store só para ter os últimos valores do servidor (linhas 64 e 65).

5 - Deletar um Contato

E para deletar um contato vamos declarar o método deleteContato.

Como é a lógica? O usuário seleciona um registro do grid e clica no botão delete. Para isso, precisamos:

Pegar a referência do grid (linha 71), usando a ref que criamos antes. Com a referência do grid conseguimos pegar as linhas selecionadas do grid (linha 72). O retorno do método grid.getSelectionModel().getSelection() é um array de Models. E por último, também precisamos da referência da store (linha 73).

Basta a gente chamar o método remove da Store passando os Models que queremos remover (linha 75) e sincronizar com o servidor (linha 76). A store irá chamar a url delete.

E se ainda quiser, pode fazer um refresh na store para pegar uma atualização do servidor (linha 79), mas é opcional.

Conclusão

Com esse post concluimos o nosso CRUD.

Se vocês querem um screencast desse tutorial com detalhes extras (às vezes escrever limita um pouco a gente), só deixar nos comentários. :)

Até a próxima! :)