You are on page 1of 11

This tutorial will walk through how to implement a CRUD (Create, Read, Update, Delete) DataGrid using ExtJS,

Spring MVC 3 and Hibernate 3.5. What do we usually want to do with data?

o o o o

Create (Insert) Read / Retrieve (Select) Update (Update) Delete / Destroy (Delete) Until ExtJS 3.0 we only could READ data using a dataGrid. If you wanted to update, insert or delete, you had to do some code to make these actions work. Now ExtJS 3.0 (and newest versions) introduces the ext.data.writer, and you do not need all that work to have a CRUD Grid. So What do I need to add in my code to make all these things working together? In this example, Im going to use JSON as data format exchange between the browser and the server.

ExtJS Code
First, you need an Ext.data.JsonWriter: ?

1 2 3 4 5

// The new DataWriter component. var writer = new Ext.data.JsonWriter({ encode: true, writeAllFields: true });

Where writeAllFields identifies that we want to write all the fields from the record to the database. If you have a fancy ORM then maybe you can set this to false. In this example, Im using Hibernate, and we have saveOrUpate method in this case, we need all fields to updated the object in database, so we have to ser writeAllFields to true. This is my record type declaration: ?

1 2 3 4 5 6 7 8 9 10 11 12

var Contact = Ext.data.Record.create([ {name: 'id'}, { name: 'name', type: 'string' }, { name: 'phone', type: 'string' }, { name: 'email', type: 'string' }]);

Now you need to setup a proxy like this one: ?

1 2 3 4 5 6 7 8

var proxy = new Ext.data.HttpProxy({ api: { read : 'contact/view.action', create : 'contact/create.action', update: 'contact/update.action', destroy: 'contact/delete.action' } });

FYI, this is how my reader looks like: ?

1 2 3 4 5 6 7 8

var reader = new Ext.data.JsonReader({ totalProperty: 'total', successProperty: 'success', idProperty: 'id', root: 'data', messageProperty: 'message' // <-- New "messageProperty" meta-data }, Contact);

The Writer and the proxy (and the reader) can be hooked to the store like this: ?

1 2 3 4 5

// 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 Rea

6 7 8

autoSave: false // <-- false would delay executing create, update, destroy reques some [save] buton. });

Where autosave identifies if you want the data in automatically saving mode (you do not need a save button, the app will send the actions automatically to the server). In this case, I implemented a save button, so every record with new or updated value will have a red mark on the cell left up corner). When the user alters a value in the grid, then a save event occurs (if autosave is true). Upon the save event the grid determines which cells has been altered. When we have an altered cell, then the corresponding record is sent to the server with the root from the reader around it. E.g if we read with root data, then we send back with root data. We can have several records being sent at once. When updating to the server (e.g multiple edits). And to make you life even easier, lets use the RowEditor plugin, so you can easily edit or add new records. All you have to do is to add the css and js files in your page: ?

1 2 3 4 5 6 7

<!-- Row Editor plugin css --> <link rel="stylesheet" type="text/css" href="/extjs-crud-grid/ext3.1.1/examples/ux/css/rowEditorCustom.css" /> <link rel="stylesheet" type="text/css" href="/extjs-crud-grid/ext-3.1.1/examples/sha /> <link rel="stylesheet" type="text/css" href="/extjs-crud-grid/ext3.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>

Add the plugin on you grid declaration: ?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22

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

23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68

}}, {header: "EMAIL", width: 150, sortable: true, dataIndex: 'email', editor: { xtype: 'textfield', allowBlank: false }})} ], 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' }); 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(); } }] });

69 70 71

Java code
Finally, you need some server side code.

Controller:
?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37

package com.loiane.web; @Controller public class ContactController

private ContactService contactService; @RequestMapping(value="/contact/view.action") public @ResponseBody Map<String,? extends Object> view() throws Exception { try{ List<Contact> contacts = contactService.getContactList(); return getMap(contacts); } catch (Exception e) { return getModelMapError("Error retrieving Contacts from database."); } }

@RequestMapping(value="/contact/create.action") public @ResponseBody Map<String,? extends Object> create(@RequestParam Object data) t Exception { try{ List<Contact> contacts = contactService.create(data); return getMap(contacts); } catch (Exception e) { return getModelMapError("Error trying to create contact."); } }

@RequestMapping(value="/contact/update.action") public @ResponseBody Map<String,? extends Object> update(@RequestParam Object data) t Exception {

38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83

try{ List<Contact> contacts = contactService.update(data); return getMap(contacts); } catch (Exception e) { return getModelMapError("Error trying to update contact."); } }

@RequestMapping(value="/contact/delete.action") public @ResponseBody Map<String,? extends Object> delete(@RequestParam Object data) t Exception { try{ contactService.delete(data); Map<String,Object> modelMap = new HashMap<String,Object>(3); modelMap.put("success", true); return modelMap; } catch (Exception e) { return getModelMapError("Error trying to delete contact."); } } private Map<String,Object> getMap(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 modelMap; } private Map<String,Object> getModelMapError(String msg){ Map<String,Object> modelMap = new HashMap<String,Object>(2); modelMap.put("message", msg); modelMap.put("success", false); return modelMap; }

84 85 86 87 88 89 90 91 92 93 94

@Autowired public void setContactService(ContactService contactService) { this.contactService = contactService; } }

Some observations: In Spring 3, we can get the objects from requests directly in the method parameters using @RequestParam. I dont know why, but it did not work with ExtJS. I had to leave as an Object and to the JSON-Object parser myself. That is why Im using a Util class to parser the object from request into my POJO class. If you know how I can replace Object parameter from controller methods, please, leave a comment, because Id really like to know that!

Service Class:
?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26

package com.loiane.service; @Service public class ContactService { private ContactDAO contactDAO; private Util util; @Transactional(readOnly=true) public List<Contact> getContactList(){ return contactDAO.getContacts(); } @Transactional public List<Contact> create(Object data){ List<Contact> newContacts = new ArrayList<Contact>(); List<Contact> list = util.getContactsFromRequest(data); for (Contact contact : list){ newContacts.add(contactDAO.saveContact(contact)); } return newContacts; } @Transactional

27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72

public List<Contact> update(Object data){ List<Contact> returnContacts = new ArrayList<Contact>(); List<Contact> updatedContacts = util.getContactsFromRequest(data); for (Contact contact : updatedContacts){ returnContacts.add(contactDAO.saveContact(contact)); } return returnContacts; } @Transactional public void delete(Object data){ //it is an array - have to cast to array object if (data.toString().indexOf('[') > -1){ List<Integer> deleteContacts = util.getListIdFromJSON(data); for (Integer id : deleteContacts){ contactDAO.deleteContact(id); } } else { //it is only one object - cast to object/bean Integer id = Integer.parseInt(data.toString()); contactDAO.deleteContact(id); } } @Autowired public void setContactDAO(ContactDAO contactDAO) { this.contactDAO = contactDAO; } @Autowired public void setUtil(Util util) { this.util = util; } }

Contact Calss POJO:


?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45

package com.loiane.model; @JsonAutoDetect @Entity @Table(name="CONTACT") public class Contact { private private private private int id; String name; String phone; String email;

@Id @GeneratedValue @Column(name="CONTACT_ID") public int getId() { return id; } public void setId(int id) { this.id = id; } @Column(name="CONTACT_NAME", nullable=false) public String getName() { return name; } public void setName(String name) { this.name = name; } @Column(name="CONTACT_PHONE", nullable=false) public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } @Column(name="CONTACT_EMAIL", nullable=false) public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } }

46 47 48 49 50

DAO Class:
?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
?

package com.loiane.dao; @Repository public class ContactDAO implements IContactDAO{ private HibernateTemplate hibernateTemplate; @Autowired public void setSessionFactory(SessionFactory sessionFactory) { hibernateTemplate = new HibernateTemplate(sessionFactory); } @SuppressWarnings("unchecked") @Override public List<Contact> getContacts() { return hibernateTemplate.find("from Contact"); } @Override public void deleteContact(int id){ Object record = hibernateTemplate.load(Contact.class, id); hibernateTemplate.delete(record); } @Override public Contact saveContact(Contact contact){ hibernateTemplate.saveOrUpdate(contact); return contact; } }

Util Class:
1 2 3 4 5 6
package com.loiane.util; @Component public class Util { public List<Contact> getContactsFromRequest(Object data){

7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43

List<Contact> list; //it is an array - have to cast to array object if (data.toString().indexOf('[') > -1){ list = getListContactsFromJSON(data); } else { //it is only one object - cast to object/bean Contact contact = getContactFromJSON(data); list = new ArrayList<Contact>(); list.add(contact); } return list; } private Contact getContactFromJSON(Object data){ JSONObject jsonObject = JSONObject.fromObject(data); Contact newContact = (Contact) JSONObject.toBean(jsonObject, Contact.class); return newContact; } ) private List<Contact> getListContactsFromJSON(Object data){ JSONArray jsonArray = JSONArray.fromObject(data); List<Contact> newContacts = (List<Contact>) JSONArray.toCollection(jsonArray,Contact.class); return newContacts; } public List<Integer> getListIdFromJSON(Object data){ JSONArray jsonArray = JSONArray.fromObject(data); List<Integer> idContacts = (List<Integer>) JSONArray.toCollection(jsonArray,Integer.class); return idContacts; } }

If you want to see all the code (complete project will all the necessary files to run this app), download it from my GitHub repository: http://github.com/loiane/extjs-crud-grid-spring-hibernate This was a requested post. Ive got a lot of comments from my previous CRUD Grid example and some emails. I made some adjustments to current code, but the idea is still the same. I hope I was able answer all the questions. Happy coding!