More ExtJS and Spring

It has been a while since I wrote my previous post which outlined a sample application using ExtJS and Spring. Since then, we’ve seen a new major version of both frameworks released, with ExtJS up to v3.3 and Spring sitting at 3.0.5.

ExtJS added a lot of cool features on the path to 3.3, but the most significant amount of cool stuff happened with the release of Spring 3.0. This release of Spring made it significantly easier to use JSON and REST, which eliminated a lot of the hackery that I had to do in my template with Spring 2.5.

I’ve been working on an updated version of my template, which makes use of all these new features, and decided to present it as a work-in-progress. The first part I want to talk about is how to get an Ext.data.JsonStore talking to a Spring MVC controller for CRUD operations. Loiane Groner wrote a pretty nice post on getting this wired in with an EditGrid, but it missed some of the real coolness with Spring 3.0.

I’ve pushed my sample application to GitHub to make it easier to get the big picture. The sample is a very simple webapp which uses a single domain object called Contact. The full project includes the Maven POM file along with a whole project which uses an HSQLDB with a full MVC stack in Spring. I’m going to ignore everything below the controller for this tutorial, but you can check out the rest in the full project.

Let’s start simple by demonstrating the GET operation, which will retrieve a list of all the contacts we have in our repository. Here is the application snippet from ContactController. Note, I’m using images so I can easily highlight sections. You can grab the raw source from the GitHub link above.

So the easy stuff first. I’ve annotated the ContactsController class with the @Controller stereotype from Spring to enable auto discovery. The @RequestMapping attribute says that this controller with catch all requests for ‘/contacts’. The getContacts() method is setup to catch any HTTP GET request, thanks the @RequestMapping annotation on the method itself. Finally, the real magic happens with the @ResponseBody annotation on the method return value.

@ResponseBody tells Spring it should not try to find a view and should just encode whatever object you are returning and send it back to the browser. Since we’re going to make an XMLHttpRequest from the browser, Spring will automagically encode the response in JSON format. The only caveat is that the Jackson JSON processer must be on the classpath, which my Maven POM file takes care of.

On the client side, the App.js file defines the Ext.data.Record, Ext.data.Reader, Ext.data.Proxy and Ext.data.Store. For simple applications, it is usually overkill to declare them like this, but I’m explicitly defining them to show how they will integrate with the Writer for updates.

For the Proxy, I’m setting restful to true, which means it will use the HTTP verbs GET, POST, PUT and DELETE for the CRUD operations. The url is set to send all those to the our controller above.

To make things interesting, I include a date field in the definition of the Record. This is used to show off one of the cool features of Jackson JSON and how it handles serialization.

Finally, I create the Store with the Proxy and Reader. I set autoSave to false, so I can manually force saves and loads from the console in Chrome.

In the current state, I haven’t wired in any widgets, so I’ll use the console to exercise the store. If you run the WAR file after generating it with Maven (mvn package), you can open up the JavaScript console in Chrome when you’re on the main page. Here’s how the store is loaded:

After executing the load() method of the store, you can see it loaded 10 items. If you enabled resource tracking, you can see the actual request looked like this:

The response is a JSON string that looks like
{"data":[{"id":10,"firstName":"Robert","lastName":"Bluegill","dob":"1975-06-04"},{"id":9,"firstName":"Lavern","lastName":"Knuckles","dob":"1980-12-16"},{"id":8,"firstName":"Kim","lastName":"Lavendar","dob":"1971-05-22"},{"id":7,"firstName":"John","lastName":"Cogsley","dob":"1965-10-15"},{"id":6,"firstName":"Larry","lastName":"Grissom","dob":"1983-04-01"},{"id":5,"firstName":"Larry","lastName":"Stewart","dob":"1981-08-08"},{"id":4,"firstName":"Jill","lastName":"Angel","dob":"1977-02-20"},{"id":3,"firstName":"Brandon","lastName":"Smythe","dob":"1968-11-10"},{"id":2,"firstName":"Susan","lastName":"Smith","dob":"1976-04-13"},{"id":1,"firstName":"John","lastName":"Smith","dob":"1984-07-03"}],"total":10,"success":true}

This is the typical response an Ext.data.Reader is looking for. Notice that the dates are correctly formatted as YYYY-MM-DD. This is handled via a custom attribute on the Contact domain object:

The method itself returns a Date. Without the @JsonSerialize attribute, this property would be serialized in the JSON response by calling its toString() method, which is not a desirable outcome. The ExtDateSerializer class does custom serialization of the Date object to convert it to a String using a DateFormat. The @JsonProperty annotation configures what name the property should have when serialized to a JSON object. Without this annotation, the property would have the name birthDate. I’m overriding that, specifying it should be called dob when serialized.

Now lets try to go the other direction and write (POST) a new Record from our Ext.data.Store to the ContactsController. Here is the (incorrect) Writer. We’ll get to that “incorrect” part in a minute, but first the easy stuff:

You must set encode to false, or else the Writer will try to send the payload as HTTP form data, not JSON. The error you get looks like this if you don’t set encode to false:

Note the HTTP error code is 415. Every other error you get will be a 500. It is only if you don’t set encode to false that you’ll get a 415.

Next we set writeAllFields to true. If you don’t do this, only the properties that have changed for a Record will be transmitted. We want everything server side for deserialization, so this ensures the whole object shows up at the server.

Finally, I set listful to true. This is hugely important for getting JSON deserialization to work correctly in the controller. This option says to wrap all updates inside a JSON array, even if there is only a single Record being sent. Since you can’t overload controller methods in Spring MVC controllers, we need to be able to catch both single and multi-record updates with a single method. Setting the writer to listful ensures that happens.

The controller method catching our new Record looks likes this:

The @RequestMapping annotation says that this method will catch all POST requests to the /contacts URL. The @RequestBody is new in Spring 3 and is the incoming analog to @ResponseBody. It basically tells Spring to attempt to deserialize the incoming payload into a Java object, in this case an array of Contacts. The deserialization is determined by the request headers. In this case, Spring will see it is JSON and will automagically use Jackson JSON to try and deserialize the payload to the correct object type.

Note that I return a list of Contact objects. ExtJS is expecting the return value to be a list of the same objects, post-save, with the id property set. It assumes these are in the same order that was sent, so do not shuffle the order around.

Setting listful to true in the Writer is what allows us to configure this method to accept an array of Contact objects. Even if there is only one Record coming from the Ext.data.Writer, it will be wrapped in a JSON array and deserialized into an array of Contact objects.

Now what about the birthdate property? Again, I use a Jackson JSON annotation to configure what field gets read (“dob” in this case), and how that field gets turned back in to a java.util.Date instance.

My ExtDateDeserializer class does the opposite of the ExtDateSerializer class. It uses a DateFormat to parse the string in the JSON property for dob back into a java.util.Date.

So lets try to create a new Contact and save it. Here’s the console output:

This is where we get to that “incorrect” thingy I mentioned above. The default Writer will attempt to serialize this save() request like this:

So the Writer is sending a JSON object, which contains a property called “data” which contains the array of new App.Contact records we recreated. The problem is that Spring and Jackson are looking for a simple JSON array that contains the new App.Contact records. So we need to customize the Writer to make this work right. Here is the custom Writer implementation:

I override the render method of the default Writer to directly output the JSON array of Records, and not put the wrapper object around it. Using this SpringWriter instead of the default Writer results in a payload like this:

Notice there is no longer the wrapper. When we run the console code again, we get a successful save:

So that covers reading the list of Contacts and adding a new Contact. Again, the code is available on GitHub so you can dig in to it. I’ll follow-up with another post on the PUT (update) and DELETE methods, along with wiring this in to some widgets.

14 thoughts on “More ExtJS and Spring”

  1. This look very good sample project for me but after build and deply your sample project, I see only the text “Spring-ExtJS Sample” all the rest disappear. Do I need to correct something? Thanks

  2. More… Can you give me a sample of using your app.js in datagrid like Loiane Groner? Thanks. (I’m newbie for JavaScript and ExtJS and I am a java and Flex develper. I just want to lean alternative UI framework along with Flex)

    Thanks

  3. Thanks @Tin. I’m going to add more to this, including a grid and a form, but I’ve just been in a mad sprint at work and haven’t had time to wrap up the examples. Expect to see more while I’ve got some time off for the holidays.

    -Tim

  4. could you please post the link of your old spring and ext js example?

    my previous post which outlined a sample application using ExtJS and Spring

  5. I couldn’t able to get the Object ExtData ret = new ExtData();
    May I know which Jar should need to be in class path?

  6. Hello Tim and thank you for this nice example. Can you give some hints or an example for extjs and spring security 3 ? 🙂 I’ve seen some articles with “tricks” but you seem to know spring well and I was thinking that you can give a good ajax solution :). Tnx !

  7. Hi Ahim,
    I found the original version of my Spring template which uses Spring Security and ExtJS 2. The concepts are the same. Take a look at the login.jsp and login.js files. The key is calling your form fields the correct names (j_username and j_password) ans setting the action (url) for the form to be loginProcess.

    You need to set standardSubmit to true for the form so that it does a normal form submit. If you use the AJAX submit, you’ll make your life difficult by having to write a bunch of code to deal with errors and redirects.

    Note, this uses ExtJS 2, but the concept still applies. For example, instead of FormPanel you would need use ext.form.Panel for ExtJS 4.

    Grab the code from here

  8. It’s a good example. Except, I’m not sure how you get date parameter submited like ‘1968-09-11’. No matter what I do, SpringWriter allways encodes the date like “2006-04-27T00:00:00”. I tried this on default ExtJS examples and the encoding is the same. Did you do something special to override the encoding?

  9. Thanks Bojan. Yes, I used a custom Jackson JSON serializer for the date field. Look at ExtDateSerializer.java and ExtDateDeserializer.java. It is extremely easy to custom the serialization with Jackson.

    -Tim

  10. Yes the java side is ok. The problem is when you use datecolumn in a grid. Here an example:

    App.myStore.load();
    var newContact = new App.Contact({firstName: ‘Lucky’, lastName: ‘Coder’, dob: new Date()});
    App.myStore.add(newContact);
    App.myStore.save();

    You populated the dob as string, although it is date. If you have a datecolumn the parameter is serialized from a date and the conversion creates ‘2006-04-27T00:00:00’. This however is not the same as you get from DateField from a form.

  11. Hi Tim, just followed your example. But I still struggling in how to produce paging set for extjs grid.. as the data produces thousand of record, I couldn’t figure out how your example fit in while am trying to PagedList the result set.. . Can you help me or some direction to go?

  12. I am getting the json request as:
    {“jsonObj”:[{“drugTypeKey”:”53″,”drugTypeValue”:”Drug “,”description”:”Type”,”ieFlag”:”I”,”criteEffDate”:””,”criteExpDate”:””},{“drugTypeKey”:”53″,”drugTypeValue”:””,”description”:””,”ieFlag”:”I”,”criteEffDate”:””,”criteExpDate”:””}]}

    While reading in Spring I have tried
    1. public @ResponseBody String saveDrug(@RequestParam(value=”jsonObj[]”, required=false) String[] jsonObj) {} //Giving a null
    2. public @ResponseBody String saveDrug(@RequestBody DrugListCriterion[] jsonObj) {} //Controller is not invoking. Getting error as bad request
    3. public @ResponseBody String saveDrug(@RequestParam(value=”jsonObj[]”, required=false) DrugListCriterion[] jsonObj) {} //Controller is not invoking. Getting error as bad request

    Please help me how I can read the Json request?

Leave a Reply

Your email address will not be published. Required fields are marked *

*