Consistent iteration over actualValue in Velocity and FreeMarker macros

This requires consistent exposure of an actualValue in BindStatus, even if no BindingResult available.

Issue: SPR-10837
This commit is contained in:
Juergen Hoeller 2014-01-31 22:39:20 +01:00
parent e680e34620
commit 4f60b98bca
6 changed files with 45 additions and 48 deletions

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2012 the original author or authors. * Copyright 2002-2014 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -90,7 +90,7 @@ public class BindStatus {
this.htmlEscape = htmlEscape; this.htmlEscape = htmlEscape;
// determine name of the object and property // determine name of the object and property
String beanName = null; String beanName;
int dotPos = path.indexOf('.'); int dotPos = path.indexOf('.');
if (dotPos == -1) { if (dotPos == -1) {
// property not set, only the object itself // property not set, only the object itself
@ -124,6 +124,9 @@ public class BindStatus {
this.actualValue = this.bindingResult.getRawFieldValue(this.expression); this.actualValue = this.bindingResult.getRawFieldValue(this.expression);
this.editor = this.bindingResult.findEditor(this.expression, null); this.editor = this.bindingResult.findEditor(this.expression, null);
} }
else {
this.actualValue = this.value;
}
} }
} }
else { else {
@ -143,8 +146,9 @@ public class BindStatus {
} }
if (this.expression != null && !"*".equals(this.expression) && !this.expression.endsWith("*")) { if (this.expression != null && !"*".equals(this.expression) && !this.expression.endsWith("*")) {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(target); BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(target);
this.valueType = bw.getPropertyType(this.expression);
this.value = bw.getPropertyValue(this.expression); this.value = bw.getPropertyValue(this.expression);
this.valueType = bw.getPropertyType(this.expression);
this.actualValue = this.value;
} }
this.errorCodes = new String[0]; this.errorCodes = new String[0];
this.errorMessages = new String[0]; this.errorMessages = new String[0];

View File

@ -107,7 +107,7 @@
* *
* @param path the name of the field to bind to * @param path the name of the field to bind to
* @param attributes any additional attributes for the element (such as class * @param attributes any additional attributes for the element (such as class
* or CSS styles or size * or CSS styles or size)
* *
*# *#
#macro( springFormInput $path $attributes ) #macro( springFormInput $path $attributes )
@ -124,7 +124,7 @@
* *
* @param path the name of the field to bind to * @param path the name of the field to bind to
* @param attributes any additional attributes for the element (such as class * @param attributes any additional attributes for the element (such as class
* or CSS styles or size * or CSS styles or size)
* *
*# *#
#macro( springFormPasswordInput $path $attributes ) #macro( springFormPasswordInput $path $attributes )
@ -140,7 +140,7 @@
* *
* @param path the name of the field to bind to * @param path the name of the field to bind to
* @param attributes any additional attributes for the element (such as class * @param attributes any additional attributes for the element (such as class
* or CSS styles or size * or CSS styles or size)
* *
*# *#
#macro( springFormHiddenInput $path $attributes ) #macro( springFormHiddenInput $path $attributes )
@ -156,7 +156,7 @@
* *
* @param path the name of the field to bind to * @param path the name of the field to bind to
* @param attributes any additional attributes for the element (such as class * @param attributes any additional attributes for the element (such as class
* or CSS styles or size * or CSS styles or size)
* *
*# *#
#macro( springFormTextarea $path $attributes ) #macro( springFormTextarea $path $attributes )
@ -178,16 +178,14 @@
* @param path the name of the field to bind to * @param path the name of the field to bind to
* @param options a map (value=label) of all the available options * @param options a map (value=label) of all the available options
* @param attributes any additional attributes for the element (such as class * @param attributes any additional attributes for the element (such as class
* or CSS styles or size * or CSS styles or size)
*# *#
#macro( springFormSingleSelect $path $options $attributes ) #macro( springFormSingleSelect $path $options $attributes )
#springBind($path) #springBind($path)
<select id="${status.expression}" name="${status.expression}" ${attributes}> <select id="${status.expression}" name="${status.expression}" ${attributes}>
#foreach($option in $options.keySet()) #foreach($option in $options.keySet())
<option value="${option}" <option value="${option}"
#if("$!status.value" == "$option") #if("$!status.value" == "$option") selected="selected" #end>
selected="selected"
#end>
${options.get($option)}</option> ${options.get($option)}</option>
#end #end
</select> </select>
@ -202,17 +200,15 @@
* @param path the name of the field to bind to * @param path the name of the field to bind to
* @param options a map (value=label) of all the available options * @param options a map (value=label) of all the available options
* @param attributes any additional attributes for the element (such as class * @param attributes any additional attributes for the element (such as class
* or CSS styles or size * or CSS styles or size)
*# *#
#macro( springFormMultiSelect $path $options $attributes ) #macro( springFormMultiSelect $path $options $attributes )
#springBind($path) #springBind($path)
<select multiple="multiple" id="${status.expression}" name="${status.expression}" ${attributes}> <select multiple="multiple" id="${status.expression}" name="${status.expression}" ${attributes}>
#foreach($option in $options.keySet()) #foreach($option in $options.keySet())
<option value="${option}" <option value="${option}"
#foreach($item in $status.value) #foreach($item in $status.actualValue)
#if($item == $option) #if($item == $option) selected="selected" #end
selected="selected"
#end
#end #end
>${options.get($option)}</option> >${options.get($option)}</option>
#end #end
@ -229,15 +225,13 @@
* @param separator the html tag or other character list that should be used to * @param separator the html tag or other character list that should be used to
* separate each option. Typically '&nbsp;' or '<br>' * separate each option. Typically '&nbsp;' or '<br>'
* @param attributes any additional attributes for the element (such as class * @param attributes any additional attributes for the element (such as class
* or CSS styles or size * or CSS styles or size)
*# *#
#macro( springFormRadioButtons $path $options $separator $attributes ) #macro( springFormRadioButtons $path $options $separator $attributes )
#springBind($path) #springBind($path)
#foreach($option in $options.keySet()) #foreach($option in $options.keySet())
<input type="radio" name="${status.expression}" value="${option}" <input type="radio" name="${status.expression}" value="${option}"
#if("$!status.value" == "$option") #if("$!status.value" == "$option") checked="checked" #end
checked="checked"
#end
${attributes} ${attributes}
#springCloseTag() #springCloseTag()
${options.get($option)} ${separator} ${options.get($option)} ${separator}
@ -252,15 +246,15 @@
* @param path the name of the field to bind to * @param path the name of the field to bind to
* @param options a map (value=label) of all the available options * @param options a map (value=label) of all the available options
* @param separator the html tag or other character list that should be used to * @param separator the html tag or other character list that should be used to
* separate each option. Typically '&nbsp;' or '<br>' * separate each option. Typically '&nbsp;' or '<br>'.
* @param attributes any additional attributes for the element (such as class * @param attributes any additional attributes for the element (such as class
* or CSS styles or size * or CSS styles or size)
*# *#
#macro( springFormCheckboxes $path $options $separator $attributes ) #macro( springFormCheckboxes $path $options $separator $attributes )
#springBind($path) #springBind($path)
#foreach($option in $options.keySet()) #foreach($option in $options.keySet())
<input type="checkbox" name="${status.expression}" value="${option}" <input type="checkbox" name="${status.expression}" value="${option}"
#foreach($item in $status.value) #foreach($item in $status.actualValue)
#if($item == $option) checked="checked" #end #if($item == $option) checked="checked" #end
#end #end
${attributes} #springCloseTag() ${attributes} #springCloseTag()
@ -276,7 +270,7 @@
* *
* @param path the name of the field to bind to * @param path the name of the field to bind to
* @param attributes any additional attributes for the element (such as class * @param attributes any additional attributes for the element (such as class
* or CSS styles or size * or CSS styles or size)
*# *#
#macro( springFormCheckbox $path $attributes ) #macro( springFormCheckbox $path $attributes )
#springBind($path) #springBind($path)

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2013 the original author or authors. * Copyright 2002-2014 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,27 +16,26 @@
package org.springframework.web.servlet.view.freemarker; package org.springframework.web.servlet.view.freemarker;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.FileWriter; import java.io.FileWriter;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import freemarker.template.Configuration;
import freemarker.template.SimpleHash;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.tests.sample.beans.TestBean;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.FileSystemResource;
import org.springframework.mock.web.test.MockHttpServletRequest; import org.springframework.mock.web.test.MockHttpServletRequest;
import org.springframework.mock.web.test.MockHttpServletResponse; import org.springframework.mock.web.test.MockHttpServletResponse;
import org.springframework.mock.web.test.MockServletContext; import org.springframework.mock.web.test.MockServletContext;
import org.springframework.tests.sample.beans.TestBean;
import org.springframework.util.FileCopyUtils; import org.springframework.util.FileCopyUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.context.support.StaticWebApplicationContext; import org.springframework.web.context.support.StaticWebApplicationContext;
@ -47,10 +46,7 @@ import org.springframework.web.servlet.support.RequestContext;
import org.springframework.web.servlet.theme.FixedThemeResolver; import org.springframework.web.servlet.theme.FixedThemeResolver;
import org.springframework.web.servlet.view.DummyMacroRequestContext; import org.springframework.web.servlet.view.DummyMacroRequestContext;
import freemarker.template.Configuration; import static org.junit.Assert.*;
import freemarker.template.SimpleHash;
import freemarker.template.Template;
import freemarker.template.TemplateException;
/** /**
* @author Darren Davison * @author Darren Davison
@ -69,6 +65,7 @@ public class FreeMarkerMacroTests {
private FreeMarkerConfigurer fc; private FreeMarkerConfigurer fc;
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
wac = new StaticWebApplicationContext(); wac = new StaticWebApplicationContext();
@ -219,6 +216,7 @@ public class FreeMarkerMacroTests {
} }
// TODO verify remaining output (fix whitespace) // TODO verify remaining output (fix whitespace)
@Test @Test
public void testForm9() throws Exception { public void testForm9() throws Exception {
assertEquals("<input type=\"password\" id=\"name\" name=\"name\" value=\"\" >", getMacroOutput("FORM9")); assertEquals("<input type=\"password\" id=\"name\" name=\"name\" value=\"\" >", getMacroOutput("FORM9"));
@ -272,8 +270,8 @@ public class FreeMarkerMacroTests {
assertTrue("Wrong output: " + output, output.contains("<input type=\"checkbox\" id=\"spouses0.jedi\" name=\"spouses[0].jedi\" checked=\"checked\" />")); assertTrue("Wrong output: " + output, output.contains("<input type=\"checkbox\" id=\"spouses0.jedi\" name=\"spouses[0].jedi\" checked=\"checked\" />"));
} }
private String getMacroOutput(String name) throws Exception {
private String getMacroOutput(String name) throws Exception {
String macro = fetchMacro(name); String macro = fetchMacro(name);
assertNotNull(macro); assertNotNull(macro);
@ -296,6 +294,7 @@ public class FreeMarkerMacroTests {
fred.setJedi(true); fred.setJedi(true);
darren.setSpouse(fred); darren.setSpouse(fred);
darren.setJedi(true); darren.setJedi(true);
darren.setStringArray(new String[] {"John", "Fred"});
request.setAttribute("command", darren); request.setAttribute("command", darren);
Map<String, String> names = new HashMap<String, String>(); Map<String, String> names = new HashMap<String, String>();

View File

@ -61,7 +61,7 @@ FORM7
<@spring.formRadioButtons "command.name", nameOptionMap, " ", ""/> <@spring.formRadioButtons "command.name", nameOptionMap, " ", ""/>
FORM8 FORM8
<@spring.formCheckboxes "command.spouses", nameOptionMap, " ", ""/> <@spring.formCheckboxes "command.stringArray", nameOptionMap, " ", ""/>
FORM9 FORM9
<@spring.formPasswordInput "command.name", ""/> <@spring.formPasswordInput "command.name", ""/>

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2013 the original author or authors. * Copyright 2002-2014 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -18,7 +18,6 @@ package org.springframework.web.servlet.view.velocity;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
@ -27,10 +26,10 @@ import org.apache.velocity.Template;
import org.apache.velocity.app.VelocityEngine; import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.context.Context; import org.apache.velocity.context.Context;
import org.springframework.tests.sample.beans.TestBean;
import org.springframework.mock.web.test.MockHttpServletRequest; import org.springframework.mock.web.test.MockHttpServletRequest;
import org.springframework.mock.web.test.MockHttpServletResponse; import org.springframework.mock.web.test.MockHttpServletResponse;
import org.springframework.mock.web.test.MockServletContext; import org.springframework.mock.web.test.MockServletContext;
import org.springframework.tests.sample.beans.TestBean;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.context.support.StaticWebApplicationContext; import org.springframework.web.context.support.StaticWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet; import org.springframework.web.servlet.DispatcherServlet;
@ -94,7 +93,7 @@ public class VelocityMacroTests extends TestCase {
vv.setApplicationContext(wac); vv.setApplicationContext(wac);
vv.setExposeSpringMacroHelpers(true); vv.setExposeSpringMacroHelpers(true);
Map model = new HashMap(); Map<String, Object> model = new HashMap<String, Object>();
model.put("tb", new TestBean("juergen", 99)); model.put("tb", new TestBean("juergen", 99));
vv.render(model, request, response); vv.render(model, request, response);
} }
@ -112,7 +111,7 @@ public class VelocityMacroTests extends TestCase {
vv.setApplicationContext(wac); vv.setApplicationContext(wac);
vv.setExposeSpringMacroHelpers(true); vv.setExposeSpringMacroHelpers(true);
Map model = new HashMap(); Map<String, Object> model = new HashMap<String, Object>();
model.put(VelocityView.SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE, helperTool); model.put(VelocityView.SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE, helperTool);
try { try {
@ -126,11 +125,11 @@ public class VelocityMacroTests extends TestCase {
public void testAllMacros() throws Exception { public void testAllMacros() throws Exception {
DummyMacroRequestContext rc = new DummyMacroRequestContext(request); DummyMacroRequestContext rc = new DummyMacroRequestContext(request);
Map msgMap = new HashMap(); Map<String, String> msgMap = new HashMap<String, String>();
msgMap.put("hello", "Howdy"); msgMap.put("hello", "Howdy");
msgMap.put("world", "Mundo"); msgMap.put("world", "Mundo");
rc.setMessageMap(msgMap); rc.setMessageMap(msgMap);
Map themeMsgMap = new HashMap(); Map<String, String> themeMsgMap = new HashMap<String, String>();
themeMsgMap.put("hello", "Howdy!"); themeMsgMap.put("hello", "Howdy!");
themeMsgMap.put("world", "Mundo!"); themeMsgMap.put("world", "Mundo!");
rc.setThemeMessageMap(themeMsgMap); rc.setThemeMessageMap(themeMsgMap);
@ -138,9 +137,10 @@ public class VelocityMacroTests extends TestCase {
TestBean tb = new TestBean("Darren", 99); TestBean tb = new TestBean("Darren", 99);
tb.setJedi(true); tb.setJedi(true);
tb.setStringArray(new String[] {"John", "Fred"});
request.setAttribute("command", tb); request.setAttribute("command", tb);
HashMap names = new HashMap(); Map<String, String> names = new HashMap<String, String>();
names.put("Darren", "Darren Davison"); names.put("Darren", "Darren Davison");
names.put("John", "John Doe"); names.put("John", "John Doe");
names.put("Fred", "Fred Bloggs"); names.put("Fred", "Fred Bloggs");
@ -149,7 +149,7 @@ public class VelocityMacroTests extends TestCase {
vc.setPreferFileSystemAccess(false); vc.setPreferFileSystemAccess(false);
VelocityEngine ve = vc.createVelocityEngine(); VelocityEngine ve = vc.createVelocityEngine();
Map model = new HashMap(); Map<String, Object> model = new HashMap<String, Object>();
model.put("command", tb); model.put("command", tb);
model.put("springMacroRequestContext", rc); model.put("springMacroRequestContext", rc);
model.put("nameOptionMap", names); model.put("nameOptionMap", names);

View File

@ -45,7 +45,7 @@ FORM7
#springFormRadioButtons("command.name" $nameOptionMap " " "") #springFormRadioButtons("command.name" $nameOptionMap " " "")
FORM8 FORM8
#springFormCheckboxes("command.spouses" $nameOptionMap " " "") #springFormCheckboxes("command.stringArray" $nameOptionMap " " "")
FORM9 FORM9
#springFormPasswordInput("command.name" "") #springFormPasswordInput("command.name" "")