ExtJS e Spring Framework: Exemplo de um CRUD Grid

25 Mar 2010
6 mins read

Este tutorial demonstra como implementar um CRUD Grid (Create, Read, Update, Delete) usando ExtJS e Spring Framework

O que geralmente queremos fazer com os dados

Até a versão 3.0 do ExtJS, podíamos apenas LER dados utilizando o componente dataGrid. Se você quisesse fazer um update, insert ou delete, você tinha que codificar funções específicas para essas ações no lado ExtJS. Com a versão 3.0 (e versões mais recentes) do ExtJS, a biblioteca javascript introduziu o ext.data.writer, e você não tem todo aquele trabalho de criar as funções específicas, pode utilizar o Writer para ter um CRUD Grid.

Mas o que é preciso para ter todas as funcionalidades funcionando apenas com o uso desse writer?

No exemplo desse tutorial, estou usando JSON como formato de dados para troca de informações entre brwoser e servidor.

Primeiro, é preciso criar um Ext.data.JsonWriter:

[code lang="js" firstline="1" toolbar="true" collapse="false" wraplines="false"]
// The new DataWriter component.
var writer = new Ext.data.JsonWriter({
encode: true,
writeAllFields: false
});
[/code]

Onde writeAllFields significa que queremos enviar todos os campos do registro para o banco de dados. identifies that we want to write all the fields from the record to the database. Se você tem uma estrutura de dados um pouco complicada ou o usuário irá fazer muitas iterações de update, é melhor deixar setado como false.

Por exemplo, Essa é a declaração da minha estrutura de dados no ExtJS:

[code lang="js" firstline="1" toolbar="true" collapse="false" wraplines="false"]
var Contact = Ext.data.Record.create([
{name: 'id'},
{
name: 'name',
type: 'string'
}, {
name: 'phone',
type: 'string'
}, {
name: 'email',
type: 'string'
}, {
name: 'birthday',
type: 'date',
dateFormat: 'm/d/Y'
}]);
[/code]

Se eu apenas atualizar o nome do contato, a aplicação irá apenas enviar o nome do contato e a id do mesmo para o servidor dizendo que foi atualizado (se o campo writeallfields estiver como false). Se tiver setado como true, irá enviar todos os campos, e o trabalho para descobrir o que sofreu alteração ficará para o server.

Agora, é necessário configurar o proxy, como esse:

[code lang="js" firstline="1" toolbar="true" collapse="false" wraplines="false"]
var proxy = new Ext.data.HttpProxy({
api: {
read : 'contact/view.action',
create : 'contact/create.action',
update: 'contact/update.action',
destroy: 'contact/delete.action'
}
});
[/code]

E só para constar, é assim que meu reader se parece:

[code lang="js" firstline="1" toolbar="true" collapse="false" wraplines="false"]
var reader = new Ext.data.JsonReader({
totalProperty: 'total',
successProperty: 'success',
idProperty: 'id',
root: 'data',
messageProperty: 'message'// <-- New "messageProperty" meta-data
},
Contact);
[/code]

O próximo passo é juntat tudo (writer, proxy e reader) no objeto store:

[code lang="js" firstline="1" toolbar="true" collapse="false" wraplines="false"]
// Typical Store collecting the Proxy, Reader and Writer together.
var store = new Ext.data.Store({
id: 'user',
proxy: proxy,
reader: reader,
writer: writer,// <-- plug a DataWriter into the store just as you would a Reader
autoSave: false // <-- false would delay executing create, update, destroy requests until specifically told to do so with some [save] buton.
});
[/code]

O autosave significa que deseja salvar as alterações automaticamente no servidor (não precisa de um botão salvar na tela, assim que o usuário atualizar, deleter ou criar um novo dado, será enviado automaticamente para o servidor). Para este exemplo, implementei um botão salvar, assim, qualquer registro ou dado que for adicionado ou alterado terá uma marcação vermelha (no canto superior esquerdo da célula), assim quando o evento (ou botão) salvar for disparado, serão enviados para o servidor os dados que sofreram alteração (marcados com o flag vermelho). Você pode fazer múltiplos updates e enviar todos para o servidor em apenas uma vez (Observe como isso foi tratado no código da classe de serviço no código fonte desse projeto).

E para deixar a vida ainda mais fácil (afinal, pra isso que usamos bibliotecas como ExtJS :D), vamos usar o plugin RowEditor, que permite a edição dos dados de forma muito simples. Tudo o que precisa fazer para usar esse plugin é primeiro adicionar os arquivos necessários na sua página HTML (ou JSP, ou outra extensão!):

[code lang="html" firstline="1" toolbar="true" collapse="false" wraplines="false"]
<!-- Row Editor plugin css -->
<link rel="stylesheet" type="text/css" href="/extjs-crud-grid/ext-3.1.1/examples/ux/css/rowEditorCustom.css" />
<link rel="stylesheet" type="text/css" href="/extjs-crud-grid/ext-3.1.1/examples/shared/examples.css" />
<link rel="stylesheet" type="text/css" href="/extjs-crud-grid/ext-3.1.1/examples/ux/css/RowEditor.css" />

<!-- Row Editor plugin js -->
<script src="/extjs-crud-grid/ext-3.1.1/examples/ux/RowEditor.js"></script>
[/code]

E adicionar o plugin na declaração do grid:

[code lang="js" firstline="1" toolbar="true" collapse="false" wraplines="false"]
var editor = new Ext.ux.grid.RowEditor({
saveText: 'Update'
});

// create grid
var grid = new Ext.grid.GridPanel({
store: store,
columns: [
{header: "NAME",
width: 170,
sortable: true,
dataIndex: 'name',
editor: {
xtype: 'textfield',
allowBlank: false
}},
{header: "PHONE #",
width: 150,
sortable: true,
dataIndex: 'phone',
editor: {
xtype: 'textfield',
allowBlank: false
}},
{header: "EMAIL",
width: 150,
sortable: true,
dataIndex: 'email',
editor: {
xtype: 'textfield',
allowBlank: false
}},
{header: "BIRTHDAY",
width: 100,
sortable: true,
dataIndex: 'birthday',
renderer: Ext.util.Format.dateRenderer('m/d/Y'),
editor: new Ext.form.DateField ({
allowBlank: false,
format: 'm/d/Y',
maxValue: (new Date())
})}
],
plugins: [editor],
title: 'My Contacts',
height: 300,
width:610,
frame:true,
tbar: [{
iconCls: 'icon-user-add',
text: 'Add Contact',
handler: function(){
var e = new Contact({
name: 'New Guy',
phone: '(000) 000-0000',
email: 'new@loianetest.com',
birthday: '01/01/2000'
});
editor.stopEditing();
store.insert(0, e);
grid.getView().refresh();
grid.getSelectionModel().selectRow(0);
editor.startEditing(0);
}
},{
iconCls: 'icon-user-delete',
text: 'Remove Contact',
handler: function(){
editor.stopEditing();
var s = grid.getSelectionModel().getSelections();
for(var i = 0, r; r = s[i]; i++){
store.remove(r);
}
}
},{
iconCls: 'icon-user-save',
text: 'Save All Modifications',
handler: function(){
store.save();
}
}]
});
[/code]

E Finalmente, precisamos de código no lado servidor. O Controller que implementei ficou assim:

[code lang="java" firstline="1" toolbar="true" collapse="false" wraplines="false"]
package com.loiane.web;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.multiaction.MultiActionController;

import com.loiane.model.Contact;
import com.loiane.service.ContactService;

public class ContactController extends MultiActionController{

private ContactService contactService;

public ModelAndView view(HttpServletRequest request,
HttpServletResponse response) throws Exception {

try{

List<Contact> contacts = contactService.getContactList();

return getModelMap(contacts);

} catch (Exception e) {

return getModelMapError("Error trying to retrieve contacts.");
}
}

public ModelAndView create(HttpServletRequest request,
HttpServletResponse response) throws Exception {

try{

Object data = request.getParameter("data");

List<Contact> contacts = contactService.create(data);

return getModelMap(contacts);

} catch (Exception e) {

return getModelMapError("Error trying to create contact.");
}
}

public ModelAndView update(HttpServletRequest request,
HttpServletResponse response) throws Exception {
try{

Object data = request.getParameter("data");

List<Contact> contacts = contactService.update(data);

return getModelMap(contacts);

} catch (Exception e) {

return getModelMapError("Error trying to update contact.");
}
}

public ModelAndView delete(HttpServletRequest request,
HttpServletResponse response) throws Exception {

try{

String data = request.getParameter("data");

contactService.delete(data);

Map<String,Object> modelMap = new HashMap<String,Object>(3);
modelMap.put("success", true);

return new ModelAndView("jsonView", modelMap);

} catch (Exception e) {

return getModelMapError("Error trying to delete contact.");
}
}

/**
* Generates modelMap to return in the modelAndView
* @param contacts
* @return
*/
private ModelAndView getModelMap(List<Contact> contacts){

Map<String,Object> modelMap = new HashMap<String,Object>(3);
modelMap.put("total", contacts.size());
modelMap.put("data", contacts);
modelMap.put("success", true);

return new ModelAndView("jsonView", modelMap);
}

/**
* Generates modelMap to return in the modelAndView in case
* of exception
* @param msg message
* @return
*/
private ModelAndView getModelMapError(String msg){

Map<String,Object> modelMap = new HashMap<String,Object>(2);
modelMap.put("message", msg);
modelMap.put("success", false);

return new ModelAndView("jsonView",modelMap);
}

/**
* Spring use - DI
* @param dadoService
*/
public void setContactService(ContactService contactService) {
this.contactService = contactService;
}

}
[/code]

Se quiser visualizar o código inteiro dessa app de exemplo (ou fazer o donwload do código completo), visite o meu repositório do GitHub: http://github.com/loiane/extjs-crud-grid

Só mais uma observação: você pode usar o dataWriter para salvar as informações que foram arrastadas para o grid com o plugin DataDrop (Lembra do plugin?). Também incluí o plugin no projeto, caso deseje testar.

Bons códigos!