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:
parent
8e261e5833
commit
61b5428211
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue