SPR-2733 Improvement for handling checkboxes in web forms (patch included)

Introduced default field prefix of '!', which can be overridden with WebDataBinder#setFieldDefaultPrefix.  If a field is otherwise not present, the default value is used for the field.  Field markers for the same field are ignored.
This commit is contained in:
Scott Andrews 2009-01-23 00:03:33 +00:00
parent 8e261e5833
commit 61b5428211
2 changed files with 121 additions and 2 deletions

View File

@ -34,14 +34,17 @@ import org.springframework.web.multipart.MultipartFile;
* HTML checkboxes and select options: detecting that a field was part of * HTML checkboxes and select options: detecting that a field was part of
* the form, but did not generate a request parameter because it was empty. * the form, but did not generate a request parameter because it was empty.
* A field marker allows to detect that state and reset the corresponding * A field marker allows to detect that state and reset the corresponding
* bean property accordingly. * bean property accordingly. Default values, for parameters that are otherwise
* not present, can specify a value for the field other then empty.
* *
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Scott Andrews
* @since 1.2 * @since 1.2
* @see #registerCustomEditor * @see #registerCustomEditor
* @see #setAllowedFields * @see #setAllowedFields
* @see #setRequiredFields * @see #setRequiredFields
* @see #setFieldMarkerPrefix * @see #setFieldMarkerPrefix
* @see #setFieldDefaultPrefix
* @see ServletRequestDataBinder * @see ServletRequestDataBinder
*/ */
public class WebDataBinder extends DataBinder { public class WebDataBinder extends DataBinder {
@ -58,9 +61,19 @@ public class WebDataBinder extends DataBinder {
*/ */
public static final String DEFAULT_FIELD_MARKER_PREFIX = "_"; public static final String DEFAULT_FIELD_MARKER_PREFIX = "_";
/**
* Default prefix that field default parameters start with, followed by the field
* name: e.g. "!subscribeToNewsletter" for a field "subscribeToNewsletter".
* <p>Default parameters differ from field markers in that they provide a default
* value instead of an empty value.
* @see #setFieldDefaultPrefix
*/
public static final String DEFAULT_FIELD_DEFAULT_PREFIX = "!";
private String fieldMarkerPrefix = DEFAULT_FIELD_MARKER_PREFIX; private String fieldMarkerPrefix = DEFAULT_FIELD_MARKER_PREFIX;
private String fieldDefaultPrefix = DEFAULT_FIELD_DEFAULT_PREFIX;
private boolean bindEmptyMultipartFiles = true; private boolean bindEmptyMultipartFiles = true;
@ -118,6 +131,32 @@ public class WebDataBinder extends DataBinder {
return this.fieldMarkerPrefix; return this.fieldMarkerPrefix;
} }
/**
* Specify a prefix that can be used for parameters that indicate default
* value fields, having "prefix + field" as name. The value of the default
* field is used when the field is not provided.
* <p>Default is "!", for "!FIELD" parameters (e.g. "!subscribeToNewsletter").
* Set this to null if you want to turn off the field defaults completely.
* <p>HTML checkboxes only send a value when they're checked, so it is not
* possible to detect that a formerly checked box has just been unchecked,
* at least not with standard HTML means. A default field is especially
* useful when a checkbox represents a non-boolean value.
* <p>The presence of a default parameter preempts the behavior of a field
* marker for the given field.
* @see #DEFAULT_FIELD_DEFAULT_PREFIX
* @see org.springframework.web.servlet.mvc.BaseCommandController#onBind
*/
public void setFieldDefaultPrefix(String fieldDefaultPrefix) {
this.fieldDefaultPrefix = fieldDefaultPrefix;
}
/**
* Return the prefix for parameters that mark default fields.
*/
public String getFieldDefaultPrefix() {
return this.fieldDefaultPrefix;
}
/** /**
* Set whether to bind empty MultipartFile parameters. Default is "true". * Set whether to bind empty MultipartFile parameters. Default is "true".
* <p>Turn this off if you want to keep an already bound MultipartFile * <p>Turn this off if you want to keep an already bound MultipartFile
@ -139,16 +178,42 @@ public class WebDataBinder extends DataBinder {
/** /**
* This implementation performs a field marker check * This implementation performs a field default and marker check
* before delegating to the superclass binding process. * before delegating to the superclass binding process.
* @see #checkFieldDefaults
* @see #checkFieldMarkers * @see #checkFieldMarkers
*/ */
@Override @Override
protected void doBind(MutablePropertyValues mpvs) { protected void doBind(MutablePropertyValues mpvs) {
checkFieldDefaults(mpvs);
checkFieldMarkers(mpvs); checkFieldMarkers(mpvs);
super.doBind(mpvs); super.doBind(mpvs);
} }
/**
* Check the given property values for field defaults,
* i.e. for fields that start with the field default prefix.
* <p>The existence of a field defaults indicates that the specified
* value should be used if the field is otherwise not present.
* @param mpvs the property values to be bound (can be modified)
* @see #getFieldDefaultPrefix
*/
protected void checkFieldDefaults(MutablePropertyValues mpvs) {
if (getFieldDefaultPrefix() != null) {
String fieldDefaultPrefix = getFieldDefaultPrefix();
PropertyValue[] pvArray = mpvs.getPropertyValues();
for (PropertyValue pv : pvArray) {
if (pv.getName().startsWith(fieldDefaultPrefix)) {
String field = pv.getName().substring(fieldDefaultPrefix.length());
if (getPropertyAccessor().isWritableProperty(field) && !mpvs.contains(field)) {
mpvs.addPropertyValue(field, pv.getValue());
}
mpvs.removePropertyValue(pv);
}
}
}
}
/** /**
* Check the given property values for field markers, * Check the given property values for field markers,
* i.e. for fields that start with the field marker prefix. * i.e. for fields that start with the field marker prefix.

View File

@ -34,6 +34,7 @@ import org.springframework.mock.web.MockHttpServletRequest;
* @author Rod Johnson * @author Rod Johnson
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Chris Beams * @author Chris Beams
* @author Scott Andrews
*/ */
public class ServletRequestDataBinderTests { public class ServletRequestDataBinderTests {
@ -90,6 +91,59 @@ public class ServletRequestDataBinderTests {
assertFalse(target.isPostProcessed()); assertFalse(target.isPostProcessed());
} }
@Test
public void testFieldDefault() throws Exception {
TestBean target = new TestBean();
ServletRequestDataBinder binder = new ServletRequestDataBinder(target);
MockHttpServletRequest request = new MockHttpServletRequest();
request.addParameter("!postProcessed", "off");
request.addParameter("postProcessed", "on");
binder.bind(request);
assertTrue(target.isPostProcessed());
request.removeParameter("postProcessed");
binder.bind(request);
assertFalse(target.isPostProcessed());
}
@Test
public void testFieldDefaultPreemptsFieldMarker() throws Exception {
TestBean target = new TestBean();
ServletRequestDataBinder binder = new ServletRequestDataBinder(target);
MockHttpServletRequest request = new MockHttpServletRequest();
request.addParameter("!postProcessed", "on");
request.addParameter("_postProcessed", "visible");
request.addParameter("postProcessed", "on");
binder.bind(request);
assertTrue(target.isPostProcessed());
request.removeParameter("postProcessed");
binder.bind(request);
assertTrue(target.isPostProcessed());
request.removeParameter("!postProcessed");
binder.bind(request);
assertFalse(target.isPostProcessed());
}
@Test
public void testFieldDefaultNonBoolean() throws Exception {
TestBean target = new TestBean();
ServletRequestDataBinder binder = new ServletRequestDataBinder(target);
MockHttpServletRequest request = new MockHttpServletRequest();
request.addParameter("!name", "anonymous");
request.addParameter("name", "Scott");
binder.bind(request);
assertEquals("Scott", target.getName());
request.removeParameter("name");
binder.bind(request);
assertEquals("anonymous", target.getName());
}
@Test @Test
public void testWithCommaSeparatedStringArray() throws Exception { public void testWithCommaSeparatedStringArray() throws Exception {
TestBean target = new TestBean(); TestBean target = new TestBean();