Friday 15 November 2013

Grails Data Binding Gotcha

Grails Data Binding is one of the most powerful tools I've used in the Web MVC world. Not only does it automatically bind correctly to data types, but you can also bind to lists and maps.

So yesterday, I was building an app; and following was the code:
 1 <g:each in="${agents}" var="agent" status="i">
 2  <!-- start row -->
 3  <div class="row">
 4   <div class="span12 control-group-holder">
 5    <div class="row">
 6     <div class="span4 control-group">
 7      <g:include params="[ext_id: agent.ext_id, readonly: true]" controller="agent"
 8           action="show"/>
 9      <g:hiddenField name="agents[$i].ext_id" value="${agent.ext_id}"/>
10      <g:hiddenField name="agents[$i].version" value="${agent.version}"/>
11     </div>
12 
13     <div class="span8 control-group">
14      <g:radioGroup name="agents[$i].photoStatus" class=""
15           labels="${photoStatusValues*.name()*.toLowerCase()*.capitalize()}"
16           values="${photoStatusValues}" value="${agent.photoStatus}">
17       <label class="radio inline" style="margin-left: 0px;">${it.radio} ${it.label}</label>
18      </g:radioGroup>
19      <div style="visibility: hidden" class="photoDeniedReason">
20      <g:message code="text.admin.reviewPhotos.reason.for.rejection" /><br/>
21      <textarea name="agents[${i}].photoDeniedReason" maxlength="4000" class="rejection_reason_required"></textarea>
22      </div>
23     </div>
24    </div>
25   </div>
26  </div>
27  <hr/>
28  <!-- end row -->
29 </g:each>
This is being bound to the command object:
 1 @Validateable
 2 class BatchUpdatePhotosCommand {
 3  List<Agent> agents = new ArrayList<Agent>()
 4 }
 5 
 6 @Validateable
 7 class AgentPhotoCommand {
 8  String ext_id
 9  String version
10  String photoStatus
11 }

Except that it just would not bind. I would get:
java.lang.IndexOutOfBoundsException: Index: 0, Size: 0 at java.util.ArrayList.RangeCheck(ArrayList.java:547) at java.util.ArrayList.get(ArrayList.java:322) at grails.plugin.cache.web.filter.PageFragmentCachingFilter.doFilter(PageFragmentCachingFilter.java:195) at grails.plugin.cache.web.filter.AbstractFilter.doFilter(AbstractFilter.java:63) at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:895) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:918) at java.lang.Thread.run(Thread.java:662)
The solution was updating BatchUpdatePhotosCommand with the 'withLazyDefault' and everything worked:
 1 @Validateable
 2 class BatchUpdatePhotosCommand {
 3  List<AgentPhotoCommand> agents = new ArrayList<AgentPhotoCommand>().withLazyDefault {new AgentPhotoCommand()}
 4 }
 5 
 6 @Validateable
 7 class AgentPhotoCommand {
 8  String ext_id
 9  String version
10  String photoStatus
11 }

No comments:

Post a Comment