From 982ece595c5dc0f9f0c8b6c0c37f2db8539790a8 Mon Sep 17 00:00:00 2001 From: Jeremy Grelle Date: Mon, 21 Sep 2009 02:48:00 +0000 Subject: [PATCH] SPR-5931 - Allow non-standard attributes in
tags --- .../tags/form/AbstractHtmlElementTag.java | 42 +++++++++++++-- .../main/resources/META-INF/spring-form.tld | 13 +++++ .../servlet/tags/form/CheckboxTagTests.java | 30 +++++++++++ .../servlet/tags/form/CheckboxesTagTests.java | 54 +++++++++++++++++++ .../web/servlet/tags/form/ErrorsTagTests.java | 35 ++++++++++++ .../web/servlet/tags/form/FormTagTests.java | 7 +++ .../web/servlet/tags/form/InputTagTests.java | 7 +++ .../web/servlet/tags/form/LabelTagTests.java | 28 ++++++++++ .../web/servlet/tags/form/OptionTagTests.java | 26 +++++++++ .../servlet/tags/form/OptionsTagTests.java | 38 +++++++++++++ .../tags/form/PasswordInputTagTests.java | 1 + .../tags/form/RadioButtonTagTests.java | 24 +++++++++ .../tags/form/RadioButtonsTagTests.java | 54 +++++++++++++++++++ .../web/servlet/tags/form/SelectTagTests.java | 19 +++++++ .../servlet/tags/form/TextareaTagTests.java | 19 +++++++ 15 files changed, 394 insertions(+), 3 deletions(-) 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 2954d16f22..2062110b08 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 @@ -16,20 +16,31 @@ package org.springframework.web.servlet.tags.form; +import java.util.HashMap; +import java.util.Map; + import javax.servlet.jsp.JspException; +import javax.servlet.jsp.tagext.DynamicAttributes; +import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; /** * Base class for databinding-aware JSP tags that render HTML element. Provides * a set of properties corresponding to the set of HTML attributes that are common - * across elements. + * across elements. + * + *

Additionally, this base class allows for rendering non-standard attributes + * as part of the tag's output. These attributes are accessible to subclasses if + * needed via the {@link AbstractHtmlElementTag#getDynamicAttributes() dynamicAttributes} + * map. * * @author Rob Harrop + * @author Jeremy Grelle * @since 2.0 */ -public abstract class AbstractHtmlElementTag extends AbstractDataBoundFormElementTag { +public abstract class AbstractHtmlElementTag extends AbstractDataBoundFormElementTag implements DynamicAttributes{ public static final String CLASS_ATTRIBUTE = "class"; @@ -97,6 +108,8 @@ public abstract class AbstractHtmlElementTag extends AbstractDataBoundFormElemen private String onkeyup; private String onkeydown; + + private Map dynamicAttributes; /** @@ -369,7 +382,23 @@ public abstract class AbstractHtmlElementTag extends AbstractDataBoundFormElemen protected String getOnkeydown() { return this.onkeydown; } - + + /** + * Get the map of dynamic attributes. + */ + protected Map getDynamicAttributes() { + return this.dynamicAttributes; + } + + /** + * {@inheritDoc} + */ + public void setDynamicAttribute(String uri, String localName, Object value ) throws JspException { + if (this.dynamicAttributes == null) { + this.dynamicAttributes = new HashMap(); + } + dynamicAttributes.put(localName, value); + } /** * Writes the default attributes configured via this base class to the supplied {@link TagWriter}. @@ -383,6 +412,7 @@ public abstract class AbstractHtmlElementTag extends AbstractDataBoundFormElemen /** * Writes the optional attributes configured via this base class to the supplied {@link TagWriter}. + * The set of optional attributes that will be rendered includes any non-standard dynamic attributes. * Called by {@link #writeDefaultAttributes(TagWriter)}. */ protected void writeOptionalAttributes(TagWriter tagWriter) throws JspException { @@ -403,6 +433,12 @@ public abstract class AbstractHtmlElementTag extends AbstractDataBoundFormElemen writeOptionalAttribute(tagWriter, ONKEYPRESS_ATTRIBUTE, getOnkeypress()); writeOptionalAttribute(tagWriter, ONKEYUP_ATTRIBUTE, getOnkeyup()); writeOptionalAttribute(tagWriter, ONKEYDOWN_ATTRIBUTE, getOnkeydown()); + + if (!CollectionUtils.isEmpty(this.dynamicAttributes)) { + for (String attr : this.dynamicAttributes.keySet()) { + tagWriter.writeOptionalAttributeValue(attr, getDisplayString(this.dynamicAttributes.get(attr))); + } + } } /** diff --git a/org.springframework.web.servlet/src/main/resources/META-INF/spring-form.tld b/org.springframework.web.servlet/src/main/resources/META-INF/spring-form.tld index 0726f98b4a..953476bf59 100644 --- a/org.springframework.web.servlet/src/main/resources/META-INF/spring-form.tld +++ b/org.springframework.web.servlet/src/main/resources/META-INF/spring-form.tld @@ -190,6 +190,7 @@ false true + true @@ -383,6 +384,7 @@ false true + true @@ -582,6 +584,7 @@ false true + true @@ -794,6 +797,7 @@ false true + true @@ -944,6 +948,7 @@ false true + true @@ -1089,6 +1094,7 @@ false true + true @@ -1258,6 +1264,7 @@ false true + true @@ -1445,6 +1452,7 @@ false true + true @@ -1614,6 +1622,7 @@ false true + true @@ -1801,6 +1810,7 @@ false true + true @@ -1982,6 +1992,7 @@ false true + true @@ -2119,6 +2130,7 @@ false true + true @@ -2252,6 +2264,7 @@ false true + true \ No newline at end of file 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 0d81c67f52..0ad6553f74 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 @@ -39,6 +39,7 @@ import org.springframework.validation.BindingResult; /** * @author Rob Harrop * @author Juergen Hoeller + * @author Jeremy Grelle */ public class CheckboxTagTests extends AbstractFormTagTests { @@ -75,6 +76,35 @@ public class CheckboxTagTests extends AbstractFormTagTests { assertEquals("checked", checkboxElement.attribute("checked").getValue()); assertEquals("true", checkboxElement.attribute("value").getValue()); } + + public void testWithSingleValueBooleanObjectCheckedAndDynamicAttributes() throws Exception { + String dynamicAttribute1 = "attr1"; + String dynamicAttribute2 = "attr2"; + + this.tag.setPath("someBoolean"); + this.tag.setDynamicAttribute(null, dynamicAttribute1, dynamicAttribute1); + this.tag.setDynamicAttribute(null, dynamicAttribute2, dynamicAttribute2); + + int result = this.tag.doStartTag(); + assertEquals(Tag.SKIP_BODY, result); + String output = getOutput(); + + // wrap the output so it is valid XML + output = "" + output + ""; + + SAXReader reader = new SAXReader(); + Document document = reader.read(new StringReader(output)); + Element rootElement = document.getRootElement(); + assertEquals("Both tag and hidden element not rendered", 2, rootElement.elements().size()); + Element checkboxElement = (Element) rootElement.elements().get(0); + assertEquals("input", checkboxElement.getName()); + assertEquals("checkbox", checkboxElement.attribute("type").getValue()); + assertEquals("someBoolean", checkboxElement.attribute("name").getValue()); + assertEquals("checked", checkboxElement.attribute("checked").getValue()); + assertEquals("true", checkboxElement.attribute("value").getValue()); + assertEquals(dynamicAttribute1, checkboxElement.attribute(dynamicAttribute1).getValue()); + assertEquals(dynamicAttribute2, checkboxElement.attribute(dynamicAttribute2).getValue()); + } public void testWithSingleValueBooleanChecked() throws Exception { this.tag.setPath("jedi"); 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 3bf0bcfce8..cc333d7c64 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 @@ -46,6 +46,7 @@ import org.springframework.validation.BindingResult; * @author Mark Fisher * @author Juergen Hoeller * @author Benjamin Hoffmann + * @author Jeremy Grelle */ public class CheckboxesTagTests extends AbstractFormTagTests { @@ -99,6 +100,59 @@ public class CheckboxesTagTests extends AbstractFormTagTests { assertEquals("baz", checkboxElement3.attribute("value").getValue()); assertEquals("baz", spanElement3.getStringValue()); } + + public void testWithMultiValueArrayAndDynamicAttributes() throws Exception { + String dynamicAttribute1 = "attr1"; + String dynamicAttribute2 = "attr2"; + + this.tag.setPath("stringArray"); + this.tag.setItems(new Object[] {"foo", "bar", "baz"}); + this.tag.setDynamicAttribute(null, dynamicAttribute1, dynamicAttribute1); + this.tag.setDynamicAttribute(null, dynamicAttribute2, dynamicAttribute2); + + int result = this.tag.doStartTag(); + assertEquals(Tag.SKIP_BODY, result); + + String output = getOutput(); + + // wrap the output so it is valid XML + output = "" + output + ""; + SAXReader reader = new SAXReader(); + Document document = reader.read(new StringReader(output)); + Element spanElement1 = (Element) document.getRootElement().elements().get(0); + Element checkboxElement1 = (Element) spanElement1.elements().get(0); + assertEquals("input", checkboxElement1.getName()); + assertEquals("checkbox", checkboxElement1.attribute("type").getValue()); + assertEquals("stringArray", checkboxElement1.attribute("name").getValue()); + assertEquals("checked", checkboxElement1.attribute("checked").getValue()); + assertEquals("foo", checkboxElement1.attribute("value").getValue()); + assertEquals("foo", spanElement1.getStringValue()); + assertEquals(dynamicAttribute1, checkboxElement1.attribute(dynamicAttribute1).getValue()); + assertEquals(dynamicAttribute2, checkboxElement1.attribute(dynamicAttribute2).getValue()); + + Element spanElement2 = (Element) document.getRootElement().elements().get(1); + Element checkboxElement2 = (Element) spanElement2.elements().get(0); + assertEquals("input", checkboxElement2.getName()); + assertEquals("checkbox", checkboxElement2.attribute("type").getValue()); + assertEquals("stringArray", checkboxElement2.attribute("name").getValue()); + assertEquals("checked", checkboxElement2.attribute("checked").getValue()); + assertEquals("bar", checkboxElement2.attribute("value").getValue()); + assertEquals("bar", spanElement2.getStringValue()); + assertEquals(dynamicAttribute1, checkboxElement2.attribute(dynamicAttribute1).getValue()); + assertEquals(dynamicAttribute2, checkboxElement2.attribute(dynamicAttribute2).getValue()); + + Element spanElement3 = (Element) document.getRootElement().elements().get(2); + Element checkboxElement3 = (Element) spanElement3.elements().get(0); + assertEquals("input", checkboxElement3.getName()); + assertEquals("checkbox", checkboxElement3.attribute("type").getValue()); + assertEquals("stringArray", checkboxElement3.attribute("name").getValue()); + assertNull("not checked", checkboxElement3.attribute("checked")); + assertEquals("baz", checkboxElement3.attribute("value").getValue()); + assertEquals("baz", spanElement3.getStringValue()); + assertEquals(dynamicAttribute1, checkboxElement3.attribute(dynamicAttribute1).getValue()); + assertEquals(dynamicAttribute2, checkboxElement3.attribute(dynamicAttribute2).getValue()); + + } public void testWithMultiValueArrayWithDelimiter() throws Exception { this.tag.setDelimiter("
"); diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/tags/form/ErrorsTagTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/tags/form/ErrorsTagTests.java index 9d2e5faafc..ccea7230e9 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/tags/form/ErrorsTagTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/tags/form/ErrorsTagTests.java @@ -40,6 +40,7 @@ import org.springframework.web.servlet.tags.RequestContextAwareTag; * @author Rick Evans * @author Juergen Hoeller * @author Mark Fisher + * @author Jeremy Grelle */ public class ErrorsTagTests extends AbstractFormTagTests { @@ -159,6 +160,40 @@ public class ErrorsTagTests extends AbstractFormTagTests { assertBlockTagContains(output, "Default Message"); assertBlockTagContains(output, "Too Short"); } + + public void testWithErrorsAndDynamicAttributes() throws Exception { + String dynamicAttribute1 = "attr1"; + String dynamicAttribute2 = "attr2"; + + this.tag.setDynamicAttribute(null, dynamicAttribute1, dynamicAttribute1); + this.tag.setDynamicAttribute(null, dynamicAttribute2, dynamicAttribute2); + + // construct an errors instance of the tag + TestBean target = new TestBean(); + target.setName("Rob Harrop"); + Errors errors = new BeanPropertyBindingResult(target, COMMAND_NAME); + errors.rejectValue("name", "some.code", "Default Message"); + errors.rejectValue("name", "too.short", "Too Short"); + + exposeBindingResult(errors); + + int result = this.tag.doStartTag(); + assertEquals(BodyTag.EVAL_BODY_BUFFERED, result); + + result = this.tag.doEndTag(); + assertEquals(Tag.EVAL_PAGE, result); + + String output = getOutput(); + assertElementTagOpened(output); + assertElementTagClosed(output); + + assertContainsAttribute(output, "id", "name.errors"); + assertContainsAttribute(output, dynamicAttribute1, dynamicAttribute1); + assertContainsAttribute(output, dynamicAttribute2, dynamicAttribute2); + assertBlockTagContains(output, "
"); + assertBlockTagContains(output, "Default Message"); + assertBlockTagContains(output, "Too Short"); + } public void testWithEscapedErrors() throws Exception { // construct an errors instance of the tag diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/tags/form/FormTagTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/tags/form/FormTagTests.java index 54175918a3..9bf708967f 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/tags/form/FormTagTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/tags/form/FormTagTests.java @@ -26,6 +26,7 @@ import org.springframework.mock.web.MockHttpServletRequest; * @author Rick Evans * @author Juergen Hoeller * @author Scott Andrews + * @author Jeremy Grelle */ public class FormTagTests extends AbstractHtmlElementTagTests { @@ -68,6 +69,8 @@ public class FormTagTests extends AbstractHtmlElementTagTests { String autocomplete = "off"; String cssClass = "myClass"; String cssStyle = "myStyle"; + String dynamicAttribute1 = "attr1"; + String dynamicAttribute2 = "attr2"; this.tag.setName(name); this.tag.setCssClass(cssClass); @@ -81,6 +84,8 @@ public class FormTagTests extends AbstractHtmlElementTagTests { this.tag.setOnsubmit(onsubmit); this.tag.setOnreset(onreset); this.tag.setAutocomplete(autocomplete); + this.tag.setDynamicAttribute(null, dynamicAttribute1, dynamicAttribute1); + this.tag.setDynamicAttribute(null, dynamicAttribute2, dynamicAttribute2); int result = this.tag.doStartTag(); assertEquals(Tag.EVAL_BODY_INCLUDE, result); @@ -110,6 +115,8 @@ public class FormTagTests extends AbstractHtmlElementTagTests { assertContainsAttribute(output, "autocomplete", autocomplete); assertContainsAttribute(output, "id", commandName); assertContainsAttribute(output, "name", name); + assertContainsAttribute(output, dynamicAttribute1, dynamicAttribute1); + assertContainsAttribute(output, dynamicAttribute2, dynamicAttribute2); } public void testWithActionFromRequest() throws Exception { 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 b91543b7c3..73e6e69962 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 @@ -29,6 +29,7 @@ import org.springframework.web.servlet.tags.NestedPathTag; /** * @author Rob Harrop * @author Rick Evans + * @author Jeremy Grelle */ public class InputTagTests extends AbstractFormTagTests { @@ -149,6 +150,8 @@ public class InputTagTests extends AbstractFormTagTests { String onselect = "doSelect()"; String readonly = "true"; String autocomplete = "off"; + String dynamicAttribute1 = "attr1"; + String dynamicAttribute2 = "attr2"; this.tag.setId(id); this.tag.setPath("name"); @@ -179,6 +182,8 @@ public class InputTagTests extends AbstractFormTagTests { this.tag.setOnselect(onselect); this.tag.setReadonly(readonly); this.tag.setAutocomplete(autocomplete); + this.tag.setDynamicAttribute(null, dynamicAttribute1, dynamicAttribute1); + this.tag.setDynamicAttribute(null, dynamicAttribute2, dynamicAttribute2); assertEquals(Tag.SKIP_BODY, this.tag.doStartTag()); @@ -217,6 +222,8 @@ public class InputTagTests extends AbstractFormTagTests { assertContainsAttribute(output, "onselect", onselect); assertContainsAttribute(output, "readonly", "readonly"); assertContainsAttribute(output, "autocomplete", autocomplete); + assertContainsAttribute(output, dynamicAttribute1, dynamicAttribute1); + assertContainsAttribute(output, dynamicAttribute2, dynamicAttribute2); } public void testWithNestedBind() throws Exception { diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/tags/form/LabelTagTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/tags/form/LabelTagTests.java index 5cc598d1f1..ac8a8bf379 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/tags/form/LabelTagTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/tags/form/LabelTagTests.java @@ -27,6 +27,7 @@ import org.springframework.web.servlet.tags.NestedPathTag; * @author Rob Harrop * @author Rick Evans * @author Juergen Hoeller + * @author Jeremy Grelle */ public class LabelTagTests extends AbstractFormTagTests { @@ -70,6 +71,33 @@ public class LabelTagTests extends AbstractFormTagTests { assertTrue(output.startsWith("