Jqgrid spring date binding exception
OS: Windows Vista, Framework: Jqgrid (latest), Spring (latest), JQuery (latest) I am using Jqgrid to post a form to Spring Controller to persist. When the Spring controller tries to auto bind the request parameters to domain object, it throws exception when trying to bind 'Date' data type. I am using JSon format for transfer data. The Jqgrid display the date correctly. The transfer string contains '&-quot;' characters before and after the date that causes the exception. I dont know how to remove the escape character from Jqgrid. I dont know how to intercept the string before Spring gets a chance to auto bind. Thanks for your help in advance.
public class JsonDateSerializer extends JsonSerializer<Date> {
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
AtOverride
public void serialize(Date date, JsonGenerator gen, SerializerProvider provider)
throws IOException,开发者_如何学运维 JsonProcessingException {
String formattedDate = dateFormat.format(date);
gen.writeString(formattedDate);
}
}
My controller class has initBinder method.
@InitBinder
public void initBinder(WebDataBinder binder) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
dateFormat.setLenient(false);
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
binder.registerCustomEditor(String.class, new StringTrimmerEditor(true));
}
Exception stack trace
nested exception is org.springframework.core.convert.ConversionFailedException: Unable to convert value "2010-12-01 11:10:00" from type 'java.lang.String' to type 'java.util.Date'; nested exception is java.lang.IllegalArgumentException]
org.springframework.web.bind.annotation.support.HandlerMethodInvoker.doBind(HandlerMethodInvoker.java:820)
org.springframework.web.bind.annotation.support.HandlerMethodInvoker.resolveHandlerArguments(HandlerMethodInvoker.java:359)
I will try to set-up a tutorial on my blog if I get the time today.
My JSP file is just a simple JSP file with your typical JqGrid declaration:
<script type="text/javascript">
jq(function() {
// This is the grid
jq("#grid").jqGrid({
url:'/myapp/users',
datatype: 'json',
mtype: 'GET',
colNames:['Id','Username','First Name'],
colModel:[
{name:'id',index:'id', width:55,editable:false,editoptions:{readonly:true,size:10},hidden:true},
{name:'firstName',index:'firstName', width:100,editable:true, editrules:{required:true}, editoptions:{size:10}, editrules:{required:true}},
{name:'lastName',index:'lastName', width:80, align:"right",editable:true, editrules:{required:true}, editoptions:{size:10}, editrules:{required:true}}
],
postData: {
// Here you can post extra parameters
// For example using JQuery you can retrieve values of other css elements
},
rowNum:10,
rowList:[10,20,30],
height: 200,
autowidth: true,
rownumbers: true,
pager: '#pager',
sortname: 'id',
viewrecords: true,
sortorder: "asc",
caption:"Users",
emptyrecords: "Empty records",
loadonce: false,
loadComplete: function() {
// Here you can provide extra functions after the grid is loaded completely
// Like auto-height function
},
jsonReader : {
root: "rows",
page: "page",
total: "total",
records: "records",
repeatitems: false,
cell: "cell",
id: "id"
}
});
// This is the pager
jq("#grid").jqGrid('navGrid','#pager',
{edit:false,add:false,del:false,search:true},
{ },
{ },
{ },
{
sopt:['eq', 'ne', 'lt', 'gt', 'cn', 'bw', 'ew'],
closeOnEscape: true,
multipleSearch: true,
closeAfterSearch: true }
);
// Custom Add button on the pager
jq("#grid").navButtonAdd('#pager',
{ caption:"Add",
buttonicon:"ui-icon-plus",
onClickButton: addRow,
position: "last",
title:"",
cursor: "pointer"
}
);
// Custom Edit button on the pager
jq("#grid").navButtonAdd('#pager',
{ caption:"Edit",
buttonicon:"ui-icon-pencil",
onClickButton: editRow,
position: "last",
title:"",
cursor: "pointer"
}
);
// Custom Delete button on the pager
jq("#grid").navButtonAdd('#pager',
{ caption:"Delete",
buttonicon:"ui-icon-trash",
onClickButton: deleteRow,
position: "last",
title:"",
cursor: "pointer"
}
);
// Toolbar Search
jq("#grid").jqGrid('filterToolbar',{stringResult: true,searchOnEnter : true, defaultSearch:"cn"});
});
Take note I'm using JQuery's noConflict method here. Since I have another Javascript framework that uses the $, I forced JQuery to use a different identifier for itself. I picked jq and here's the declaration:
<script type="text/javascript">
var jq = jQuery.noConflict();
</script>
The Javascript's above can be declared on the head section of your JSP. The critical part of the JqGrid declaration is the jsonReader and the datatype. Make sure the colModel name matches your model properties.
You could probably use StringTrimmerEditor's second constructor to delete those extra chars
StringTrimmerEditor(String charsToDelete, boolean emptyAsNull)
Based on the Spring docs:
charsToDelete - a set of characters to delete, in addition to trimming an input String. Useful for deleting unwanted line breaks. E.g. "\r\n\f" will delete all new lines and line feeds in a String.
I also use the latest version of JqGrid and Spring 3, but the way I handled the parameters seems to be simpler. Here's how I did it:
@Controller
@RequestMapping("/json")
public class JsonController {
@RequestMapping(method = RequestMethod.GET)
public @ResponseBody JsonResponse getAll(
@RequestParam("_search") String search,
@RequestParam(value="filters", required=false) String filters,
@RequestParam(value="datefrom", required=false) String datefrom,
@RequestParam(value="dateto", required=false) String dateto,
@RequestParam(value="page", required=false) String page,
@RequestParam(value="rows", required=false) String rows,
@RequestParam(value="sidx", required=false) String sidx,
@RequestParam(value="sord", required=false) String sord
) { ... }
All parameters are passed as normal Strings. The datefrom and dateto are custom parameters I passed from the JqGrid. The date comes from a JQuery's DatePicker and passed via JqGrid postData:
postData: {
datefrom: function() { return jq("#datepicker_from").datepicker("getDate"); },
dateto: function() { return jq("#datepicker_to").datepicker("getDate"); }
},
JsonResponse is a simple POJO that I used to map the search parameters passed by a JqGrid:
public class JsonResponse {
/**
* Current page of the query
*/
private String page;
/**
* Total pages for the query
*/
private String total;
/**
* Total number of records for the query
*/
private String records;
/**
* An array that contains the actual data
*/
private List<MyDTO> rows;
public JsonResponse() {
}
public String getPage() {
return page;
}
public void setPage(String page) {
this.page = page;
}
public String getTotal() {
return total;
}
public void setTotal(String total) {
this.total = total;
}
public String getRecords() {
return records;
}
public void setRecords(String records) {
this.records = records;
}
public List<MyDTO> getRows() {
return rows;
}
public void setRows(List<MyDTO> rows) {
this.rows = rows;
}
}
MyDTO is a simple DTO object as well.
In my Spring applicationContext.xml, I just have to declare the following:
<mvc:annotation-driven/>
And added the Jackson jar for converting from JSON to POJO and vice versa
To convert the String date to a real Date, I used the great Joda library. But of course, you can use JDK's standard Date.
If you notice on my JSP, I added custom buttons. The onClickButton calls another Javascript function. For example on the Add button, I have the addRow function. Here's the function declaration:
function addRow() {
// Get the currently selected row
jq("#grid").jqGrid('editGridRow','new',
{ url: "/myapp/users/add",
editData: {
// Here you add extra post parameters
},
recreateForm: true,
beforeShowForm: function(form) {
// Here you can add, disable, hide elements from the popup form
},
closeAfterAdd: true,
reloadAfterSubmit:false,
afterSubmit : function(response, postdata)
{
// This is a callback function that will evaluate the response sent by your Controller
var result = eval('(' + response.responseText + ')');
var errors = "";
if (result.success == false) {
// Do whatever you like if not successful
} else {
// Do whatever you like if successful
}
// only used for adding new records
var new_id = null;
// Then this will be returned back to the popup form
return [result.success, errors, new_id];
}
});
}
Take note of the url:
url: "/myapp/users/add"
This is the mapping to your Controller that handles the add request
Now, for the Jackson serialization/deserialization, you'll be suprised how easy it is.
First, make sure you have the latest Jackson library in your classpath. Next, open your Spring xml config, and add the following:
<mvc:annotation-driven/>
That's it :)
Make sure you have the latest dependency jars. I use Spring 3.0.4. There's 3.0.5 already. Also, make sure you have the latest aspectjweaver.jar. If you wanna know what's inside that mvc-annotation-driven tag, just search the web and you'll see what it contains. Basically it automatically declares an HTTPMessageConverter for JSON, and uses Jackson by default.
For the Controller, here's the mapping to the /myapp/users/add request:
@Controller
@RequestMapping("/myapp/users")
public class UserController {
@RequestMapping(value = "/add", method = RequestMethod.POST)
public @ResponseBody JsonGenericResponse addAsJson(
@RequestParam("id") String id,
@RequestParam("firstName") String firstName,
@RequestParam("lastName") String lastName
) {
// Validate input
// Process data. Call a service, etc.
// Send back a response
// JsonGenericResponse is a custom POJO
// Jackson will automatically serialize/deserialize this POJO to a JSON
// The @ResponseBody annotation triggers this behavior
JsonGenericResponse response = new JsonGenericResponse();
response.setSuccess(false);
response.setMessage("Error in server");
return response;
}
}
Here's the JsonGenericResponse:
public class JsonGenericResponse {
private Boolean success;
private List<String> message;
public JsonGenericResponse() {
message = new ArrayList<String>();
}
public Boolean getSuccess() {
return success;
}
public void setSuccess(Boolean success) {
this.success = success;
}
public List<String> getMessage() {
return message;
}
public void setMessage(String message) {
this.message.add(message);
}
}
It's just really a simple POJO.
In your JSP, to process the response, you use JavaScript in your JqGrid. The response will be sent back to whomever the caller (the add form in this case). Here's a sample JavaScript that will process the response:
if (result.success == false) {
for (var i = 0; i < result.message.length; i++) {
errors += result.message[i] + "<br/>";
}
} else {
jq("#dialog").text('Entry has been edited successfully');
jq("#dialog").dialog(
{ title: 'Success',
modal: true,
buttons: {"Ok": function() {
jq(this).dialog("close");}
}
});
}
return [result.success, errors, null];
精彩评论