Skip to content

Commit 61b5428

Browse files
committedJan 23, 2009
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.
1 parent 8e261e5 commit 61b5428

File tree

2 files changed

+121
-2
lines changed

2 files changed

+121
-2
lines changed
 

‎org.springframework.web.servlet/src/main/java/org/springframework/web/bind/WebDataBinder.java

+67-2
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,17 @@
3434
* HTML checkboxes and select options: detecting that a field was part of
3535
* the form, but did not generate a request parameter because it was empty.
3636
* A field marker allows to detect that state and reset the corresponding
37-
* bean property accordingly.
37+
* bean property accordingly. Default values, for parameters that are otherwise
38+
* not present, can specify a value for the field other then empty.
3839
*
3940
* @author Juergen Hoeller
41+
* @author Scott Andrews
4042
* @since 1.2
4143
* @see #registerCustomEditor
4244
* @see #setAllowedFields
4345
* @see #setRequiredFields
4446
* @see #setFieldMarkerPrefix
47+
* @see #setFieldDefaultPrefix
4548
* @see ServletRequestDataBinder
4649
*/
4750
public class WebDataBinder extends DataBinder {
@@ -58,9 +61,19 @@ public class WebDataBinder extends DataBinder {
5861
*/
5962
public static final String DEFAULT_FIELD_MARKER_PREFIX = "_";
6063

64+
/**
65+
* Default prefix that field default parameters start with, followed by the field
66+
* name: e.g. "!subscribeToNewsletter" for a field "subscribeToNewsletter".
67+
* <p>Default parameters differ from field markers in that they provide a default
68+
* value instead of an empty value.
69+
* @see #setFieldDefaultPrefix
70+
*/
71+
public static final String DEFAULT_FIELD_DEFAULT_PREFIX = "!";
6172

6273
private String fieldMarkerPrefix = DEFAULT_FIELD_MARKER_PREFIX;
6374

75+
private String fieldDefaultPrefix = DEFAULT_FIELD_DEFAULT_PREFIX;
76+
6477
private boolean bindEmptyMultipartFiles = true;
6578

6679

@@ -118,6 +131,32 @@ public String getFieldMarkerPrefix() {
118131
return this.fieldMarkerPrefix;
119132
}
120133

134+
/**
135+
* Specify a prefix that can be used for parameters that indicate default
136+
* value fields, having "prefix + field" as name. The value of the default
137+
* field is used when the field is not provided.
138+
* <p>Default is "!", for "!FIELD" parameters (e.g. "!subscribeToNewsletter").
139+
* Set this to null if you want to turn off the field defaults completely.
140+
* <p>HTML checkboxes only send a value when they're checked, so it is not
141+
* possible to detect that a formerly checked box has just been unchecked,
142+
* at least not with standard HTML means. A default field is especially
143+
* useful when a checkbox represents a non-boolean value.
144+
* <p>The presence of a default parameter preempts the behavior of a field
145+
* marker for the given field.
146+
* @see #DEFAULT_FIELD_DEFAULT_PREFIX
147+
* @see org.springframework.web.servlet.mvc.BaseCommandController#onBind
148+
*/
149+
public void setFieldDefaultPrefix(String fieldDefaultPrefix) {
150+
this.fieldDefaultPrefix = fieldDefaultPrefix;
151+
}
152+
153+
/**
154+
* Return the prefix for parameters that mark default fields.
155+
*/
156+
public String getFieldDefaultPrefix() {
157+
return this.fieldDefaultPrefix;
158+
}
159+
121160
/**
122161
* Set whether to bind empty MultipartFile parameters. Default is "true".
123162
* <p>Turn this off if you want to keep an already bound MultipartFile
@@ -139,16 +178,42 @@ public boolean isBindEmptyMultipartFiles() {
139178

140179

141180
/**
142-
* This implementation performs a field marker check
181+
* This implementation performs a field default and marker check
143182
* before delegating to the superclass binding process.
183+
* @see #checkFieldDefaults
144184
* @see #checkFieldMarkers
145185
*/
146186
@Override
147187
protected void doBind(MutablePropertyValues mpvs) {
188+
checkFieldDefaults(mpvs);
148189
checkFieldMarkers(mpvs);
149190
super.doBind(mpvs);
150191
}
151192

193+
/**
194+
* Check the given property values for field defaults,
195+
* i.e. for fields that start with the field default prefix.
196+
* <p>The existence of a field defaults indicates that the specified
197+
* value should be used if the field is otherwise not present.
198+
* @param mpvs the property values to be bound (can be modified)
199+
* @see #getFieldDefaultPrefix
200+
*/
201+
protected void checkFieldDefaults(MutablePropertyValues mpvs) {
202+
if (getFieldDefaultPrefix() != null) {
203+
String fieldDefaultPrefix = getFieldDefaultPrefix();
204+
PropertyValue[] pvArray = mpvs.getPropertyValues();
205+
for (PropertyValue pv : pvArray) {
206+
if (pv.getName().startsWith(fieldDefaultPrefix)) {
207+
String field = pv.getName().substring(fieldDefaultPrefix.length());
208+
if (getPropertyAccessor().isWritableProperty(field) && !mpvs.contains(field)) {
209+
mpvs.addPropertyValue(field, pv.getValue());
210+
}
211+
mpvs.removePropertyValue(pv);
212+
}
213+
}
214+
}
215+
}
216+
152217
/**
153218
* Check the given property values for field markers,
154219
* i.e. for fields that start with the field marker prefix.

‎org.springframework.web.servlet/src/test/java/org/springframework/web/bind/ServletRequestDataBinderTests.java

+54
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
* @author Rod Johnson
3535
* @author Juergen Hoeller
3636
* @author Chris Beams
37+
* @author Scott Andrews
3738
*/
3839
public class ServletRequestDataBinderTests {
3940

@@ -90,6 +91,59 @@ public void testFieldPrefixCausesFieldResetWithIgnoreUnknownFields() throws Exce
9091
assertFalse(target.isPostProcessed());
9192
}
9293

94+
@Test
95+
public void testFieldDefault() throws Exception {
96+
TestBean target = new TestBean();
97+
ServletRequestDataBinder binder = new ServletRequestDataBinder(target);
98+
99+
MockHttpServletRequest request = new MockHttpServletRequest();
100+
request.addParameter("!postProcessed", "off");
101+
request.addParameter("postProcessed", "on");
102+
binder.bind(request);
103+
assertTrue(target.isPostProcessed());
104+
105+
request.removeParameter("postProcessed");
106+
binder.bind(request);
107+
assertFalse(target.isPostProcessed());
108+
}
109+
110+
@Test
111+
public void testFieldDefaultPreemptsFieldMarker() throws Exception {
112+
TestBean target = new TestBean();
113+
ServletRequestDataBinder binder = new ServletRequestDataBinder(target);
114+
115+
MockHttpServletRequest request = new MockHttpServletRequest();
116+
request.addParameter("!postProcessed", "on");
117+
request.addParameter("_postProcessed", "visible");
118+
request.addParameter("postProcessed", "on");
119+
binder.bind(request);
120+
assertTrue(target.isPostProcessed());
121+
122+
request.removeParameter("postProcessed");
123+
binder.bind(request);
124+
assertTrue(target.isPostProcessed());
125+
126+
request.removeParameter("!postProcessed");
127+
binder.bind(request);
128+
assertFalse(target.isPostProcessed());
129+
}
130+
131+
@Test
132+
public void testFieldDefaultNonBoolean() throws Exception {
133+
TestBean target = new TestBean();
134+
ServletRequestDataBinder binder = new ServletRequestDataBinder(target);
135+
136+
MockHttpServletRequest request = new MockHttpServletRequest();
137+
request.addParameter("!name", "anonymous");
138+
request.addParameter("name", "Scott");
139+
binder.bind(request);
140+
assertEquals("Scott", target.getName());
141+
142+
request.removeParameter("name");
143+
binder.bind(request);
144+
assertEquals("anonymous", target.getName());
145+
}
146+
93147
@Test
94148
public void testWithCommaSeparatedStringArray() throws Exception {
95149
TestBean target = new TestBean();

0 commit comments

Comments
 (0)
Please sign in to comment.