Reputation: 179
(Grails version: 2.3.11, Groovy version: 2.2.2 )
I'm new to Groovy and Grails so forgive me if I am missing something obvious. I have a command object that contains a Map, keys are Integer (although I've tried Strings and they don't work either), values are lists of Details objects:
class Details {
Integer volume
Double price
}
class TheCommand {
Map<Integer, List<Details>> details = [:].withDefault { [].withLazyDefault { new Details() } }
String location
}
I have this in my GSP:
<g:form controller="mapOfLists" action="create">
<g:textField name="location" size="7"/>
<table>
<g:each in="${(1..24)}" var="hour">
<tr>
<td>${hour}</td>
<g:each in="${(0..4)}" var="column">
<td><g:textField name="details[${hour}][${column}].price" size="4"/></td>
<td><g:textField name="details[${hour}][${column}].volume" size="3"/></td>
</g:each>
</tr>
</g:each>
</table>
<g:submitButton name="Submit" value="Submit"/>
</g:form>
And the action:
// trying the different binding approaches, none work
def create(TheCommand theCommand) {
// theCommand.hasErrors() -> false
// at this point theCommand's details is an empty map, I can see the details[x][y] values in the params object
bindData(theCommand, params)
// the bindData above didn't work, details is still an empty map
theCommand.properties['details'] = params
// the setting via properties above didn't work, details is still an empty map
}
Two questions: 1) Any ideas on what to try? I have seen there is a way to use a custom binder, but this seems like a case that grails should handle, so I'm giving this a shot before I go down that road.
2) Are there more powerful data-binders? I've stepped through the relevant SimpleDataBinder code, it appears that it only supports single-ly indexed properties
thank you very much, Seth
Upvotes: 0
Views: 1085
Reputation: 2931
I can't get it to work without resorting to custom binders :(
As for more powerful data binders, there is @BindUsing which allows you to either define a closure or implement the BindingHelper interface to format your input into whatever you need. You can even use GORM finders in there to populate properties with domain instances.
I managed to get this much:
class TheCommand {
@BindUsing({ obj, source ->
def details = new HashMap<Integer, List<Details>>().withDefault { [].withLazyDefault { new Details() } }
source['details']?.collect { mapKey, mapValue ->
if (mapKey.integer) {
mapValue.collect { arrayKey, arrayValue ->
if (arrayKey.integer) {
def detailsObj = new Details(volume:new Integer(arrayValue.volume), price: new Double(arrayValue.price))
details[new Integer(mapKey)].add(new Integer(arrayKey), detailsObj)
}
}
}
}
return details
})
Map<Integer, List<Details>> details
String location
}
It works for the following request curl http://localhost:8080/test/test/index --data "location=here&details.0.0.volume=20&details.0.0.price=2.2&details.0.1.volume=30&details.0.1.price=2"
It's powerful, but ugly (though my code has a few parts that could be implemented nicer). I don't know why a simple new Details(arrayValue)
doesn't work in there but I may be missing something obvious.
Upvotes: 2