diff --git a/build-spring-framework/resources/changelog.txt b/build-spring-framework/resources/changelog.txt index ffb489777cc..e7e92dd5ddd 100644 --- a/build-spring-framework/resources/changelog.txt +++ b/build-spring-framework/resources/changelog.txt @@ -17,6 +17,8 @@ Changes in version 3.1 RC2 (2011-11-15) * fixed StandardServlet/PortletEnvironment to check for JNDI (for Google App Engine compatibility) * Use original request URI in FlashMap matching logic to account for URL rewriting * Support target request with multiple parameter values in FlahMap matching logic +* Fix issue in SimpleMappingExceptionResolver causing exception when setting "statusCodes" property +* The Form input tag allows type values other than "text" such as HTML5-specific types. Changes in version 3.1 RC1 (2011-10-11) --------------------------------------- diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/tags/form/AbstractCheckedElementTag.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/tags/form/AbstractCheckedElementTag.java index e644ecd79e8..ff1420f401b 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/tags/form/AbstractCheckedElementTag.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/tags/form/AbstractCheckedElementTag.java @@ -25,6 +25,7 @@ import javax.servlet.jsp.JspException; * * @author Thomas Risberg * @author Juergen Hoeller + * @author Rossen Stoyanchev * @since 2.5 */ public abstract class AbstractCheckedElementTag extends AbstractHtmlInputElementTag { @@ -88,6 +89,14 @@ public abstract class AbstractCheckedElementTag extends AbstractHtmlInputElement @Override protected abstract int writeTagContent(TagWriter tagWriter) throws JspException; + /** + * Flags "type" as an illegal dynamic attribute. + */ + @Override + protected boolean isValidDynamicAttribute(String localName, Object value) { + return !"type".equals(localName); + } + /** * Return the type of the HTML input element to generate: * "checkbox" or "radio". diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/tags/form/AbstractHtmlElementTag.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/tags/form/AbstractHtmlElementTag.java index 74eb84c8dcc..32b5f206538 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/tags/form/AbstractHtmlElementTag.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/tags/form/AbstractHtmlElementTag.java @@ -21,6 +21,7 @@ import java.util.Map; import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.DynamicAttributes; +import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; @@ -37,6 +38,7 @@ import org.springframework.util.StringUtils; * * @author Rob Harrop * @author Jeremy Grelle + * @author Rossen Stoyanchev * @since 2.0 */ public abstract class AbstractHtmlElementTag extends AbstractDataBoundFormElementTag implements DynamicAttributes { @@ -396,8 +398,19 @@ public abstract class AbstractHtmlElementTag extends AbstractDataBoundFormElemen if (this.dynamicAttributes == null) { this.dynamicAttributes = new HashMap(); } + if (!isValidDynamicAttribute(localName, value)) { + throw new IllegalArgumentException( + "Attribute " + localName + "=\"" + value + "\" is not allowed"); + } dynamicAttributes.put(localName, value); } + + /** + * Whether the given name-value pair is a valid dynamic attribute. + */ + protected boolean isValidDynamicAttribute(String localName, Object value) { + return true; + } /** * Writes the default attributes configured via this base class to the supplied {@link TagWriter}. diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/tags/form/HiddenInputTag.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/tags/form/HiddenInputTag.java index 7b760f5017e..c6c639b669c 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/tags/form/HiddenInputTag.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/tags/form/HiddenInputTag.java @@ -29,10 +29,20 @@ import javax.servlet.jsp.JspException; * * @author Rob Harrop * @author Juergen Hoeller + * @author Rossen Stoyanchev * @since 2.0 */ +@SuppressWarnings("serial") public class HiddenInputTag extends AbstractHtmlElementTag { + /** + * Flags "type" as an illegal dynamic attribute. + */ + @Override + protected boolean isValidDynamicAttribute(String localName, Object value) { + return !"type".equals(localName); + } + /** * Writes the HTML 'input' tag to the supplied {@link TagWriter} including the * databound value. diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/tags/form/InputTag.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/tags/form/InputTag.java index cdb9d1d6260..258da68648e 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/tags/form/InputTag.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/tags/form/InputTag.java @@ -16,20 +16,18 @@ package org.springframework.web.servlet.tags.form; -import javax.servlet.ServletRequest; -import javax.servlet.http.HttpServletRequest; import javax.servlet.jsp.JspException; -import org.springframework.web.servlet.support.RequestDataValueProcessor; - /** * Data-binding-aware JSP tag for rendering an HTML 'input' * element with a 'type' of 'text'. * * @author Rob Harrop * @author Juergen Hoeller + * @author Rossen Stoyanchev * @since 2.0 */ +@SuppressWarnings("serial") public class InputTag extends AbstractHtmlInputElementTag { public static final String SIZE_ATTRIBUTE = "size"; @@ -142,7 +140,9 @@ public class InputTag extends AbstractHtmlInputElementTag { tagWriter.startTag("input"); writeDefaultAttributes(tagWriter); - tagWriter.writeAttribute("type", getType()); + if (!hasDynamicTypeAttribute()) { + tagWriter.writeAttribute("type", getType()); + } writeValue(tagWriter); // custom optional attributes @@ -156,6 +156,10 @@ public class InputTag extends AbstractHtmlInputElementTag { return SKIP_BODY; } + private boolean hasDynamicTypeAttribute() { + return getDynamicAttributes() != null && getDynamicAttributes().containsKey("type"); + } + /** * Writes the 'value' attribute to the supplied {@link TagWriter}. * Subclasses may choose to override this implementation to control exactly @@ -163,9 +167,24 @@ public class InputTag extends AbstractHtmlInputElementTag { */ protected void writeValue(TagWriter tagWriter) throws JspException { String value = getDisplayString(getBoundValue(), getPropertyEditor()); - tagWriter.writeAttribute("value", processFieldValue(getName(), value, getType())); + String type = hasDynamicTypeAttribute() ? (String) getDynamicAttributes().get("type") : getType(); + tagWriter.writeAttribute("value", processFieldValue(getName(), value, type)); } + /** + * Flags {@code type="checkbox"} and {@code type="radio"} as illegal + * dynamic attributes. + */ + @Override + protected boolean isValidDynamicAttribute(String localName, Object value) { + if ("type".equals(localName)) { + if ("checkbox".equals(value) || "radio".equals(value)) { + return false; + } + } + return true; + } + /** * Get the value of the 'type' attribute. Subclasses * can override this to change the type of 'input' element diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/tags/form/PasswordInputTag.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/tags/form/PasswordInputTag.java index 08d9f441867..a8116aab691 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/tags/form/PasswordInputTag.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/tags/form/PasswordInputTag.java @@ -24,6 +24,7 @@ import javax.servlet.jsp.JspException; * * @author Rob Harrop * @author Rick Evans + * @author Rossen Stoyanchev * @since 2.0 */ public class PasswordInputTag extends InputTag { @@ -47,6 +48,14 @@ public class PasswordInputTag extends InputTag { this.showPassword = showPassword; } + /** + * Flags "type" as an illegal dynamic attribute. + */ + @Override + protected boolean isValidDynamicAttribute(String localName, Object value) { + return !"type".equals(localName); + } + /** * Return 'password' causing the rendered HTML 'input' * element to have a 'type' of 'password'. diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/tags/form/CheckboxTagTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/tags/form/CheckboxTagTests.java index bcb6053fc1a..c9b9d3ddf0b 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/tags/form/CheckboxTagTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/tags/form/CheckboxTagTests.java @@ -24,6 +24,8 @@ import java.util.Date; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; + +import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.Tag; import org.dom4j.Document; @@ -610,6 +612,18 @@ public class CheckboxTagTests extends AbstractFormTagTests { assertEquals("checked", checkboxElement.attribute("checked").getValue()); assertEquals("true", checkboxElement.attribute("value").getValue()); } + + public void testDynamicTypeAttribute() throws JspException { + try { + this.tag.setDynamicAttribute(null, "type", "email"); + fail("Expected exception"); + } + catch (IllegalArgumentException e) { + assertEquals("Attribute type=\"email\" is not allowed", e.getMessage()); + } + } + + private Date getDate() { Calendar cal = Calendar.getInstance(); cal.set(Calendar.YEAR, 10); diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/tags/form/CheckboxesTagTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/tags/form/CheckboxesTagTests.java index f701c5ff7c5..5a653409309 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/tags/form/CheckboxesTagTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/tags/form/CheckboxesTagTests.java @@ -28,6 +28,8 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; + +import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.Tag; import org.dom4j.Document; @@ -703,6 +705,17 @@ public class CheckboxesTagTests extends AbstractFormTagTests { assertEquals("element", spanElement.getName()); } + public void testDynamicTypeAttribute() throws JspException { + try { + this.tag.setDynamicAttribute(null, "type", "email"); + fail("Expected exception"); + } + catch (IllegalArgumentException e) { + assertEquals("Attribute type=\"email\" is not allowed", e.getMessage()); + } + } + + private Date getDate() { Calendar cal = Calendar.getInstance(); cal.set(Calendar.YEAR, 10); diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/tags/form/HiddenInputTagTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/tags/form/HiddenInputTagTests.java index 133ebecbf4f..6b842f2c5de 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/tags/form/HiddenInputTagTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/tags/form/HiddenInputTagTests.java @@ -19,6 +19,7 @@ package org.springframework.web.servlet.tags.form; import org.springframework.beans.TestBean; import org.springframework.validation.BeanPropertyBindingResult; +import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.Tag; /** @@ -70,6 +71,16 @@ public class HiddenInputTagTests extends AbstractFormTagTests { assertContainsAttribute(output, "type", "hidden"); assertContainsAttribute(output, "value", "12.34f"); } + + public void testDynamicTypeAttribute() throws JspException { + try { + this.tag.setDynamicAttribute(null, "type", "email"); + fail("Expected exception"); + } + catch (IllegalArgumentException e) { + assertEquals("Attribute type=\"email\" is not allowed", e.getMessage()); + } + } private void assertTagClosed(String output) { assertTrue(output.endsWith("/>")); diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/tags/form/InputTagTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/tags/form/InputTagTests.java index 73e6e699624..1fb2487e605 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/tags/form/InputTagTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/tags/form/InputTagTests.java @@ -18,6 +18,7 @@ package org.springframework.web.servlet.tags.form; import java.io.Writer; +import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.Tag; import org.springframework.beans.TestBean; @@ -342,7 +343,40 @@ public class InputTagTests extends AbstractFormTagTests { assertValueAttribute(output, "Rob"); } + public void testDynamicTypeAttribute() throws JspException { + this.tag.setPath("myFloat"); + this.tag.setDynamicAttribute(null, "type", "number"); + assertEquals(Tag.SKIP_BODY, this.tag.doStartTag()); + + String output = getOutput(); + assertTagOpened(output); + assertTagClosed(output); + + assertContainsAttribute(output, "type", "number"); + assertValueAttribute(output, "12.34"); + } + + public void testDynamicTypeRadioAttribute() throws JspException { + try { + this.tag.setDynamicAttribute(null, "type", "radio"); + fail("Expected exception"); + } + catch (IllegalArgumentException e) { + assertEquals("Attribute type=\"radio\" is not allowed", e.getMessage()); + } + } + + public void testDynamicTypeCheckboxAttribute() throws JspException { + try { + this.tag.setDynamicAttribute(null, "type", "checkbox"); + fail("Expected exception"); + } + catch (IllegalArgumentException e) { + assertEquals("Attribute type=\"checkbox\" is not allowed", e.getMessage()); + } + } + protected final void assertTagClosed(String output) { assertTrue("Tag not closed properly", output.endsWith("/>")); } diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/tags/form/PasswordInputTagTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/tags/form/PasswordInputTagTests.java index 5537da40a89..d3bda115ed7 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/tags/form/PasswordInputTagTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/tags/form/PasswordInputTagTests.java @@ -18,6 +18,7 @@ package org.springframework.web.servlet.tags.form; import java.io.Writer; +import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.Tag; /** @@ -77,6 +78,15 @@ public class PasswordInputTagTests extends InputTagTests { assertValueAttribute(output, ""); } + public void testDynamicTypeAttribute() throws JspException { + try { + this.getTag().setDynamicAttribute(null, "type", "email"); + fail("Expected exception"); + } + catch (IllegalArgumentException e) { + assertEquals("Attribute type=\"email\" is not allowed", e.getMessage()); + } + } protected void assertValueAttribute(String output, String expectedValue) { if (this.getPasswordTag().isShowPassword()) { diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/tags/form/RadioButtonTagTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/tags/form/RadioButtonTagTests.java index 6feeb13625b..a6a3f1c2616 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/tags/form/RadioButtonTagTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/tags/form/RadioButtonTagTests.java @@ -20,6 +20,7 @@ import java.beans.PropertyEditorSupport; import java.io.StringReader; import java.util.Collections; +import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.Tag; import org.dom4j.Document; @@ -227,6 +228,16 @@ public class RadioButtonTagTests extends AbstractFormTagTests { assertEquals("checked", checkboxElement.attribute("checked").getValue()); } + public void testDynamicTypeAttribute() throws JspException { + try { + this.tag.setDynamicAttribute(null, "type", "email"); + fail("Expected exception"); + } + catch (IllegalArgumentException e) { + assertEquals("Attribute type=\"email\" is not allowed", e.getMessage()); + } + } + private void assertTagOpened(String output) { assertTrue(output.indexOf(" -1); } diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/tags/form/RadioButtonsTagTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/tags/form/RadioButtonsTagTests.java index c604abed08b..ef10fe18212 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/tags/form/RadioButtonsTagTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/tags/form/RadioButtonsTagTests.java @@ -27,6 +27,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.Tag; import org.dom4j.Document; @@ -559,6 +560,16 @@ public final class RadioButtonsTagTests extends AbstractFormTagTests { assertEquals("element", spanElement.getName()); } + public void testDynamicTypeAttribute() throws JspException { + try { + this.tag.setDynamicAttribute(null, "type", "email"); + fail("Expected exception"); + } + catch (IllegalArgumentException e) { + assertEquals("Attribute type=\"email\" is not allowed", e.getMessage()); + } + } + private Date getDate() { Calendar cal = Calendar.getInstance(); cal.set(Calendar.YEAR, 10); diff --git a/spring-framework-reference/src/view.xml b/spring-framework-reference/src/view.xml index 55a061ee495..452fdf4d086 100644 --- a/spring-framework-reference/src/view.xml +++ b/spring-framework-reference/src/view.xml @@ -231,9 +231,11 @@ productList.url=/WEB-INF/jsp/productlist.jsp
The <literal>input</literal> tag - This tag renders an HTML 'input' tag with type 'text' using the - bound value. For an example of this tag, see . + This tag renders an HTML 'input' tag using the bound value + and type='text' by default. For an example of this tag, see . Starting with Spring 3.1 + you can use other types such HTML5-specific types like 'email', + 'tel', 'date', and others.
@@ -805,6 +807,22 @@ public String deletePet(@PathVariable int ownerId, @PathVariable int petId) { return "redirect:/owners/" + ownerId; }
+ +
+ HTML5 Tags + + Starting with Spring 3, the Spring form tag library allows entering + dynamic attributes, which means you can enter any HTML5 specific attributes. + + + In Spring 3.1, the form input tag supports entering a type attribute + other than 'text'. This is intended to allow rendering new HTML5 specific + input types such as 'email', 'date', 'range', and others. Note that + entering type='text' is not required since 'text' is the default type. + + +
+