提交 8e261e58 编写于 作者: S Scott Andrews

SPR-3389 Nicer handling of Java 5 enums by the Spring MVC form taglib.

The form:options and form:radiobuttons tags will now render a set of options automatically if the bind target is an Enum and items are not otherwise specified.  The values of the enum are converted into form inputs where by default the form value is the enum's name() and the form label is the enum's toString().
上级 ad2cc34b
......@@ -35,6 +35,7 @@ import org.springframework.util.StringUtils;
* of '<code>checkbox</code>' or '<code>radio</code>'.
*
* @author Juergen Hoeller
* @author Scott Andrews
* @since 2.5.2
*/
public abstract class AbstractMultiCheckedElementTag extends AbstractCheckedElementTag {
......@@ -191,6 +192,11 @@ public abstract class AbstractMultiCheckedElementTag extends AbstractCheckedElem
String labelProperty =
(itemLabel != null ? ObjectUtils.getDisplayString(evaluate("itemLabel", itemLabel)) : null);
Class<?> boundType = getBindStatus().getValueType();
if (itemsObject == null && boundType != null && boundType.isEnum()) {
itemsObject = boundType.getEnumConstants();
}
if (itemsObject == null) {
throw new IllegalArgumentException("Attribute 'items' is required and must be a Collection, an Array or a Map");
}
......@@ -229,7 +235,16 @@ public abstract class AbstractMultiCheckedElementTag extends AbstractCheckedElem
String labelProperty, Object item, int itemIndex) throws JspException {
BeanWrapper wrapper = PropertyAccessorFactory.forBeanPropertyAccess(item);
Object renderValue = (valueProperty != null ? wrapper.getPropertyValue(valueProperty) : item);
Object renderValue;
if (valueProperty != null) {
renderValue = wrapper.getPropertyValue(valueProperty);
}
else if (item instanceof Enum) {
renderValue = ((Enum<?>) item).name();
}
else {
renderValue = item;
}
Object renderLabel = (labelProperty != null ? wrapper.getPropertyValue(labelProperty) : item);
writeElementTag(tagWriter, item, renderValue, renderLabel, itemIndex);
}
......
......@@ -42,7 +42,8 @@ import org.springframework.web.servlet.support.BindStatus;
* the <code>labelProperty</code>). These properties are then used when
* rendering each element of the array/{@link Collection} as an '<code>option</code>'.
* If either property name is omitted, the value of {@link Object#toString()} of
* the corresponding array/{@link Collection} element is used instead.
* the corresponding array/{@link Collection} element is used instead. However,
* if the item is an enum, {@link Enum#name()} is used as the default value.
* </p>
* <h3>Using a {@link Map}:</h3>
* <p>
......@@ -83,6 +84,7 @@ import org.springframework.web.servlet.support.BindStatus;
* @author Rob Harrop
* @author Juergen Hoeller
* @author Sam Brannen
* @author Scott Andrews
* @since 2.0
*/
class OptionWriter {
......@@ -134,6 +136,9 @@ class OptionWriter {
else if (this.optionSource instanceof Map) {
renderFromMap(tagWriter);
}
else if (this.optionSource instanceof Class && this.optionSource.getClass().isEnum()) {
renderFromEnum(tagWriter);
}
else {
throw new JspException(
"Type [" + this.optionSource.getClass().getName() + "] is not valid for option items");
......@@ -177,6 +182,14 @@ class OptionWriter {
doRenderFromCollection((Collection) this.optionSource, tagWriter);
}
/**
* Renders the inner '<code>option</code>' tags using the {@link #optionSource}.
* @see #doRenderFromCollection(java.util.Collection, TagWriter)
*/
private void renderFromEnum(final TagWriter tagWriter) throws JspException {
doRenderFromCollection(CollectionUtils.arrayToList(((Class) this.optionSource).getEnumConstants()), tagWriter);
}
/**
* Renders the inner '<code>option</code>' tags using the supplied {@link Collection} of
* objects as the source. The value of the {@link #valueProperty} field is used
......@@ -187,7 +200,16 @@ class OptionWriter {
for (Iterator it = optionCollection.iterator(); it.hasNext();) {
Object item = it.next();
BeanWrapper wrapper = PropertyAccessorFactory.forBeanPropertyAccess(item);
Object value = (this.valueProperty != null ? wrapper.getPropertyValue(this.valueProperty) : item);
Object value;
if (this.valueProperty != null) {
value = wrapper.getPropertyValue(this.valueProperty);
}
else if (item instanceof Enum) {
value = ((Enum<?>) item).name();
}
else {
value = item;
}
Object label = (this.labelProperty != null ? wrapper.getPropertyValue(this.labelProperty) : item);
renderOption(tagWriter, item, value, label);
}
......
......@@ -33,6 +33,7 @@ import org.springframework.web.util.TagUtils;
*
* @author Rob Harrop
* @author Juergen Hoeller
* @author Scott Andrews
* @since 2.0
*/
public class OptionsTag extends AbstractHtmlElementTag {
......@@ -146,7 +147,16 @@ public class OptionsTag extends AbstractHtmlElementTag {
protected int writeTagContent(TagWriter tagWriter) throws JspException {
assertUnderSelectTag();
Object items = getItems();
Object itemsObject = (items instanceof String ? evaluate("items", (String) items) : items);
Object itemsObject = null;
if (items != null) {
itemsObject = (items instanceof String ? evaluate("items", (String) items) : items);
} else {
Class<?> selectTagBoundType = ((SelectTag) findAncestorWithClass(this, SelectTag.class))
.getBindStatus().getValueType();
if (selectTagBoundType != null && selectTagBoundType.isEnum()) {
itemsObject = selectTagBoundType.getEnumConstants();
}
}
if (itemsObject != null) {
String itemValue = getItemValue();
String itemLabel = getItemLabel();
......
......@@ -971,9 +971,9 @@
</attribute>
<attribute>
<name>items</name>
<required>true</required>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
<description>The Collection, Map or array of objects used to generate the inner 'option' tags</description>
<description>The Collection, Map or array of objects used to generate the inner 'option' tags. This attribute is required unless the containing select's property for data binding is an Enum, in which case the enum's values are used.</description>
</attribute>
<attribute>
<name>itemValue</name>
......@@ -1433,9 +1433,9 @@
<!-- radiobuttons specific attributes -->
<attribute>
<name>items</name>
<required>true</required>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
<description>The Collection, Map or array of objects used to generate the 'input' tags with type 'radio'</description>
<description>The Collection, Map or array of objects used to generate the 'input' tags with type 'radio'. This attribute is required unless the property for data binding is an Enum, in which case the enum's values are used.</description>
</attribute>
<attribute>
<name>itemValue</name>
......
......@@ -24,12 +24,13 @@ import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.jsp.tagext.BodyTag;
import javax.servlet.jsp.tagext.Tag;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
import org.springframework.beans.TestBean;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockPageContext;
......@@ -43,11 +44,13 @@ import org.springframework.web.servlet.tags.RequestContextAwareTag;
/**
* @author Rob Harrop
* @author Juergen Hoeller
* @author Scott Andrews
*/
public class OptionsTagTests extends AbstractHtmlElementTagTests {
public final class OptionsTagTests extends AbstractHtmlElementTagTests {
private static final String COMMAND_NAME = "testBean";
private SelectTag selectTag;
private OptionsTag tag;
protected void onSetUp() {
......@@ -56,7 +59,13 @@ public class OptionsTagTests extends AbstractHtmlElementTagTests {
return new TagWriter(getWriter());
}
};
this.tag.setParent(new SelectTag());
selectTag = new SelectTag() {
protected TagWriter createTagWriter() {
return new TagWriter(getWriter());
}
};
selectTag.setPageContext(getPageContext());
this.tag.setParent(selectTag);
this.tag.setPageContext(getPageContext());
}
......@@ -148,16 +157,17 @@ public class OptionsTagTests extends AbstractHtmlElementTagTests {
}
public void testWithoutItems() throws Exception {
getPageContext().setAttribute(
SelectTag.LIST_VALUE_PAGE_ATTRIBUTE, new BindStatus(getRequestContext(), "testBean.country", false));
this.tag.setItemValue("isoCode");
this.tag.setItemLabel("name");
this.selectTag.setPath("testBean");
this.selectTag.doStartTag();
int result = this.tag.doStartTag();
assertEquals(Tag.SKIP_BODY, result);
String output = getOutput();
output = "<doc>" + output + "</doc>";
this.tag.doEndTag();
this.selectTag.doEndTag();
String output = getOutput();
SAXReader reader = new SAXReader();
Document document = reader.read(new StringReader(output));
Element rootElement = document.getRootElement();
......@@ -166,6 +176,62 @@ public class OptionsTagTests extends AbstractHtmlElementTagTests {
assertEquals("Incorrect number of children", 0, children.size());
}
public void testWithoutItemsEnumParent() throws Exception {
BeanWithEnum testBean = new BeanWithEnum();
testBean.setTestEnum(TestEnum.VALUE_2);
getPageContext().getRequest().setAttribute("testBean", testBean);
this.selectTag.setPath("testBean.testEnum");
this.selectTag.doStartTag();
int result = this.tag.doStartTag();
assertEquals(BodyTag.SKIP_BODY, result);
result = this.tag.doEndTag();
assertEquals(Tag.EVAL_PAGE, result);
this.selectTag.doEndTag();
String output = getWriter().toString();
SAXReader reader = new SAXReader();
Document document = reader.read(new StringReader(output));
Element rootElement = document.getRootElement();
assertEquals(2, rootElement.elements().size());
Node value1 = rootElement.selectSingleNode("option[@value = 'VALUE_1']");
Node value2 = rootElement.selectSingleNode("option[@value = 'VALUE_2']");
assertEquals("TestEnum: VALUE_1", value1.getText());
assertEquals("TestEnum: VALUE_2", value2.getText());
assertEquals(value2, rootElement.selectSingleNode("option[@selected]"));
}
public void testWithoutItemsEnumParentWithExplicitLabelsAndValues() throws Exception {
BeanWithEnum testBean = new BeanWithEnum();
testBean.setTestEnum(TestEnum.VALUE_2);
getPageContext().getRequest().setAttribute("testBean", testBean);
this.selectTag.setPath("testBean.testEnum");
this.tag.setItemLabel("enumLabel");
this.tag.setItemValue("enumValue");
this.selectTag.doStartTag();
int result = this.tag.doStartTag();
assertEquals(BodyTag.SKIP_BODY, result);
result = this.tag.doEndTag();
assertEquals(Tag.EVAL_PAGE, result);
this.selectTag.doEndTag();
String output = getWriter().toString();
SAXReader reader = new SAXReader();
Document document = reader.read(new StringReader(output));
Element rootElement = document.getRootElement();
assertEquals(2, rootElement.elements().size());
Node value1 = rootElement.selectSingleNode("option[@value = 'Value: VALUE_1']");
Node value2 = rootElement.selectSingleNode("option[@value = 'Value: VALUE_2']");
assertEquals("Label: VALUE_1", value1.getText());
assertEquals("Label: VALUE_2", value2.getText());
assertEquals(value2, rootElement.selectSingleNode("option[@selected]"));
}
protected void extendRequest(MockHttpServletRequest request) {
TestBean bean = new TestBean();
bean.setName("foo");
......
......@@ -31,8 +31,8 @@ import javax.servlet.jsp.tagext.Tag;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
import org.springframework.beans.Colour;
import org.springframework.beans.Pet;
import org.springframework.beans.TestBean;
......@@ -43,8 +43,9 @@ import org.springframework.validation.BindingResult;
/**
* @author Thomas Risberg
* @author Juergen Hoeller
* @author Scott Andrews
*/
public class RadioButtonsTagTests extends AbstractFormTagTests {
public final class RadioButtonsTagTests extends AbstractFormTagTests {
private RadioButtonsTag tag;
......@@ -405,6 +406,52 @@ public class RadioButtonsTagTests extends AbstractFormTagTests {
assertEquals("MUFTY", spanElement5.getStringValue());
}
public void testWithoutItemsEnumBindTarget() throws Exception {
BeanWithEnum testBean = new BeanWithEnum();
testBean.setTestEnum(TestEnum.VALUE_2);
getPageContext().getRequest().setAttribute("testBean", testBean);
this.tag.setPath("testEnum");
int result = this.tag.doStartTag();
assertEquals(Tag.SKIP_BODY, result);
String output = "<div>" + getOutput() + "</div>";
SAXReader reader = new SAXReader();
Document document = reader.read(new StringReader(output));
Element rootElement = document.getRootElement();
assertEquals(2, rootElement.elements().size());
Node value1 = rootElement.selectSingleNode("//input[@value = 'VALUE_1']");
Node value2 = rootElement.selectSingleNode("//input[@value = 'VALUE_2']");
assertEquals("TestEnum: VALUE_1", rootElement.selectSingleNode("//label[@for = '" + value1.valueOf("@id") + "']").getText());
assertEquals("TestEnum: VALUE_2", rootElement.selectSingleNode("//label[@for = '" + value2.valueOf("@id") + "']").getText());
assertEquals(value2, rootElement.selectSingleNode("//input[@checked]"));
}
public void testWithoutItemsEnumBindTargetWithExplicitLabelsAndValues() throws Exception {
BeanWithEnum testBean = new BeanWithEnum();
testBean.setTestEnum(TestEnum.VALUE_2);
getPageContext().getRequest().setAttribute("testBean", testBean);
this.tag.setPath("testEnum");
this.tag.setItemLabel("enumLabel");
this.tag.setItemValue("enumValue");
int result = this.tag.doStartTag();
assertEquals(Tag.SKIP_BODY, result);
String output = "<div>" + getOutput() + "</div>";
SAXReader reader = new SAXReader();
Document document = reader.read(new StringReader(output));
Element rootElement = document.getRootElement();
assertEquals(2, rootElement.elements().size());
Node value1 = rootElement.selectSingleNode("//input[@value = 'Value: VALUE_1']");
Node value2 = rootElement.selectSingleNode("//input[@value = 'Value: VALUE_2']");
assertEquals("Label: VALUE_1", rootElement.selectSingleNode("//label[@for = '" + value1.valueOf("@id") + "']").getText());
assertEquals("Label: VALUE_2", rootElement.selectSingleNode("//label[@for = '" + value2.valueOf("@id") + "']").getText());
assertEquals(value2, rootElement.selectSingleNode("//input[@checked]"));
}
public void testWithNullValue() throws Exception {
try {
this.tag.setPath("name");
......
package org.springframework.web.servlet.tags.form;
/**
* Test related data types for <code>org.springframework.web.servlet.tags.form</code> package.
*
* @author Scott Andrews
*/
public class TestTypes { }
class BeanWithEnum {
private TestEnum testEnum;
public TestEnum getTestEnum() {
return testEnum;
}
public void setTestEnum(TestEnum customEnum) {
this.testEnum = customEnum;
}
}
enum TestEnum {
VALUE_1, VALUE_2;
public String getEnumLabel() {
return "Label: " + name();
}
public String getEnumValue() {
return "Value: " + name();
}
public String toString() {
return "TestEnum: " + name();
}
}
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册