views:

2209

answers:

5

I am developing a Grails (1.0.4) app where I want to edit a collection of collections on a single page in a grid view. I got it to work quite well depending only on the indexed parameter handling of Spring MVC, except for one thing:

boolean (or, for that matter, Boolean) values in the grid can be set via checkbox, but not unset, i.e. when I check the checkbox and update, the value is set to true, but afterwards when I edit again, uncheck the checkbox and update, it remains true.

This is the GSP code of the checkbox:

<g:checkBox name="tage[${indexTag}].zuweisungen[${indexMitarb}].fixiert" value="${z.fixiert}" />

And this is the HTML that is generated:

<input type="hidden" name="tage[0].zuweisungen[0]._fixiert" />
<input type="checkbox" name="tage[0].zuweisungen[0].fixiert" checked="checked" id="tage[0].zuweisungen[0].fixiert"  />

I've found a Grails bug that describes exactly this effect, but it's marked as fixed in 1.0.2, and the problem mechanism described there (underscore in hidden field name is put in the wrong place) is not present in my case.

Any ideas what could be the reason?

+1  A: 

I would create a small sample app that demonstrates the problem and attach it to the Grails bug (or create a new one). Someone here may be able to debug your sample app or you'll have shown the bug isn't really fixed.

Ben Williams
I think you mean 'shown' instead of 'shone'. :-)
Jweede
Too lazy to do that right now :) Probably the best long-term solution, though.
Michael Borgwardt
+1  A: 

I think that the simplest workaround would be to attach a debugger and see why Grails is failing to populate the value. Considering Grails is open source you'll be able to access the source code and once you figure out the solution for it you can patch your version.

I have also found this other bug GRAILS-2861 which mentions the issue related to binding to booleans (see Marc's comment in the thread). I guess that is exactly the problem you are describing.

alexpopescu
Unfortunately, all the dynamic and metaprogramming magic going on in grails makes it almost impossible to debug unless you know exactly where to look - probably somewhere inside Spring MVC. which I don't really know.
Michael Borgwardt
The bug you linked to is a different matter, I think - basically a misunderstanding based on the different meaning of the "value" attribute in HTML and grails.
Michael Borgwardt
I must confess that I still believe that this two are related :-). By looking at the solution you've posted below it looks like the problem is the fact that the hidden or the checkbox do NOT have a value to be transmitted when the form is submitted (which is discussed on the ticket).
alexpopescu
The solution you are posting makes me think that you should look at the custom tag code and see why the value attribute is not generated. Once you'll have that attribute written in HTML I expect this issue to be fixed.
alexpopescu
I'm trying to get Mr.G and Graeme's opinion on the issue here.
alexpopescu
No, the problem is that HTML transmits nothing for an unchecked checkbox (for a checked one, it uses "on" as a default value if none is specified). The hidden field is a workaround for that.
Michael Borgwardt
And as far as I understand it, the "value" attribute in grails has a completely different meaning than in HTML - in grails it determines whether the checkbox in the form should be set or unset. In HTML it is the string that's sent to the server if the checkbox was set.
Michael Borgwardt
@Michael: unchecked checkboxes do NOT transmit if unchecked (that's HTML normal behavior), so if Grails relies on some value to be in there, all you need to do is to add a javascript function to form's onsubmit that goes through form.elements and set the hidden value for those missing it.
alexpopescu
Grails is supposed to use the hidden field to infer that the checkbox with the same name (sans underscore) was unchecked if it is not present in the request params - which works for regular fields, but for some reason fails with indexed fields.
Michael Borgwardt
A: 

Try this out, set the logs to DEBUG, frist try the first 3 if they don't show the problem up, flip them all to DEBUG:

codehaus.groovy.grails.web.servlet="error"  //  controllers
codehaus.groovy.grails.web.pages="error" //  GSP
codehaus.groovy.grails.web.sitemesh="error" //  layouts
codehaus.groovy.grails."web.mapping.filter"="error" // URL mapping
codehaus.groovy.grails."web.mapping"="error" // URL mapping
codehaus.groovy.grails.commons="info" // core / classloading
codehaus.groovy.grails.plugins="error" // plugins
codehaus.groovy.grails.orm.hibernate="error" // hibernate integration

This should allow you to see exactly when and how the parameters setting is failing and probably figure out a work around.

Xymor
Unfortunatley, I haven't found any useable hints in the log - it doesn't know how to map the "._fixiert" param, but of course that's not present in the domain model anyway, since it's just an indication that an absence of ".fixiert=on" means it should be set to false.
Michael Borgwardt
A: 

This is my own solution, basically a workaround that manually does what the grails data binding should be doing (but doesn't):

Map<String,String> checkboxes = params.findAll{def i = it.key.endsWith("._fixiert")} // all checkboxes
checkboxes.each{
    String key = it.key.substring(0, it.key.indexOf("._fixiert"))
    int tagIdx = Integer.parseInt(key.substring(key.indexOf('[')+1, key.indexOf(']')))
    int zuwIdx = Integer.parseInt(key.substring(key.lastIndexOf('[')+1, key.lastIndexOf(']')))
    if(params.get(key+".fixiert"))
    {
        dienstplanInstance.tage[tagIdx].zuweisungen[zuwIdx].fixiert = true
    }
    else
    {
        dienstplanInstance.tage[tagIdx].zuweisungen[zuwIdx].fixiert = false                    
    }
}

Works, requires no change in grails itself, but isn't reusable (probably could be made so with some extra work).

Michael Borgwardt
A: 

This is the solution a guy named Julius Huang proposed on the grails-user mailing list. It's reusable but relies on JavaScript to populate a hidden field with the "false" response for an unchecked checkbox that HTML unfortunately does not send.

I hack GSP to send "false" when uncheck the box (true -> false) with custom TagLib.

By default checkBox send nothing when uncheck, so I use the checkBox as event handler but send hidden field instead.

"params" in Controller can handle "false" -> "true" without any modification. eg. Everything remain same in Controller.

The Custom Tag Usage in GSP (sample usedfunc_F is "true"),

<jh:checkBox name="surveyList[${i}].usedfunc_F" value="${survey.usedfunc_F}"></jh:checkBox>

Here is what the Tag generate,

<input type="hidden" name="surveyList[#{i}].usedfunc_F" id="surveyList[#{i}].usedfunc_F" value="false" />
<input type="checkbox" onclick="jhtoggle('surveyList[#{i}].usedfunc_F')" checked="checked" />

The Javascript

<script type="text/javascript">
function jhtoggle(obj) {
   var jht = document.getElementById(obj);
   jht.value = (jht.value !='true' ? 'true' : 'false');
}
</script>
Michael Borgwardt