Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

2.0.x: backport fix for GRAILS-3468 #160

Merged
merged 6 commits into from

2 participants

@denisfalqueto

No description provided.

denisfalqueto added some commits
@denisfalqueto denisfalqueto GRAILS-3468: Add DISABLED and READONLY attributes to the form tags
Some form tags already deal correctly with disabled attribute. This
patch spreads that behavior for other tags. It also implements
the same behavior for readonly.
c560bba
@denisfalqueto denisfalqueto GRAILS-3468: Attribute value can be equal to its name
If the attribute value is equal to its name, it will be accepted as
true and will be output.
80d2f7b
@denisfalqueto denisfalqueto GRAILS-3468: Make the check for attribute value case insensitive 0b22ec5
@denisfalqueto denisfalqueto GRAILS-3468: Pass through boolean attributes
For compatibility reasons, pass through the provided attribute
value, if it is not a bool or can't be evaluated as such.
A
d29e99d
@denisfalqueto denisfalqueto GRAILS-3468: Better pass-through for non null values
String values not equal to 'false' or 'true' (case insensitive) or null will be passed
changes to the output. This will enhance compatibility with existing code.
094e02f
@denisfalqueto denisfalqueto GRAILS-3468: test cases f2857c7
@graemerocher graemerocher merged commit 86a129b into grails:2.0.x
@lhotari lhotari referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
@lhotari lhotari referenced this pull request from a commit
@lhotari lhotari revise the "escaped" characters in XML/XHTML.
Use   instead of   since it's also valid in xml.
ca17ada
@lhotari lhotari referenced this pull request from a commit
@lhotari lhotari revise the "escaped" characters in XML/XHTML.
Use   instead of   since it's also valid in xml.
ae49139
@lhotari lhotari referenced this pull request from a commit
@lhotari lhotari revise the "escaped" characters in XML/XHTML.
Use   instead of   since it's also valid in xml.
f925200
@lhotari lhotari referenced this pull request from a commit
@lhotari lhotari revise the "escaped" characters in XML/XHTML.
Use   instead of   since it's also valid in xml.
548c973
@lhotari lhotari referenced this pull request from a commit
@lhotari lhotari revise the "escaped" characters in XML/XHTML.
Use   instead of   since it's also valid in xml.
784ba93
@lhotari lhotari referenced this pull request from a commit
@lhotari lhotari revise the "escaped" characters in XML/XHTML.
Use   instead of   since it's also valid in xml.
11474d2
@lhotari lhotari referenced this pull request from a commit
@lhotari lhotari revise the "escaped" characters in XML/XHTML.
Use   instead of   since it's also valid in xml.
0f2e2e4
@lhotari lhotari referenced this pull request from a commit
@lhotari lhotari revise the "escaped" characters in XML/XHTML.
Use   instead of   since it's also valid in xml.
0ef504a
@lhotari lhotari referenced this pull request from a commit
@lhotari lhotari revise the "escaped" characters in XML/XHTML.
Use   instead of   since it's also valid in xml.
d1a6853
@lhotari lhotari referenced this pull request from a commit
@lhotari lhotari revise the "escaped" characters in XML/XHTML.
Use   instead of   since it's also valid in xml.
16c258d
@lhotari lhotari referenced this pull request from a commit
@lhotari lhotari revise the "escaped" characters in XML/XHTML.
Use   instead of   since it's also valid in xml.
3f7afe2
@lhotari lhotari referenced this pull request from a commit
@lhotari lhotari revise the "escaped" characters in XML/XHTML.
Use   instead of   since it's also valid in xml.
bf32f55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jan 27, 2012
  1. @denisfalqueto

    GRAILS-3468: Add DISABLED and READONLY attributes to the form tags

    denisfalqueto authored
    Some form tags already deal correctly with disabled attribute. This
    patch spreads that behavior for other tags. It also implements
    the same behavior for readonly.
  2. @denisfalqueto

    GRAILS-3468: Attribute value can be equal to its name

    denisfalqueto authored
    If the attribute value is equal to its name, it will be accepted as
    true and will be output.
  3. @denisfalqueto
Commits on Jan 30, 2012
  1. @denisfalqueto

    GRAILS-3468: Pass through boolean attributes

    denisfalqueto authored
    For compatibility reasons, pass through the provided attribute
    value, if it is not a bool or can't be evaluated as such.
    A
  2. @denisfalqueto

    GRAILS-3468: Better pass-through for non null values

    denisfalqueto authored
    String values not equal to 'false' or 'true' (case insensitive) or null will be passed
    changes to the output. This will enhance compatibility with existing code.
  3. @denisfalqueto

    GRAILS-3468: test cases

    denisfalqueto authored
This page is out of date. Refresh to see the latest.
View
107 ...lugin-gsp/src/main/groovy/org/codehaus/groovy/grails/plugins/web/taglib/FormTagLib.groovy
@@ -132,16 +132,15 @@ class FormTagLib {
* @attr value the value of the checkbox
* @attr checked if evaluates to true sets to checkbox to checked
* @attr disabled if evaluates to true sets to checkbox to disabled
+ * @attr readonly if evaluates to true, sets to checkbox to read only
* @attr id DOM element id; defaults to name
*/
Closure checkBox = { attrs ->
attrs.id = attrs.id ?: attrs.name
def value = attrs.remove('value')
def name = attrs.remove('name')
- def disabled = attrs.remove('disabled')
- if (disabled && Boolean.valueOf(disabled)) {
- attrs.disabled = 'disabled'
- }
+ booleanToAttribute(attrs, 'disabled')
+ booleanToAttribute(attrs, 'readonly')
// Deal with the "checked" attribute. If it doesn't exist, we
// default to a value of "true", otherwise we use Groovy Truth
@@ -206,6 +205,25 @@ class FormTagLib {
outputAttributes(attrs, out)
out << ">" << (escapeHtml ? value.encodeAsHTML() : value) << "</textarea>"
}
+
+ /**
+ * Some attributes can be defined as Boolean values, but the html specification
+ * mandates the attribute must have the same value as its name. For example,
+ * disabled, readonly and checked.
+ */
+ private void booleanToAttribute(def attrs, String attrName) {
+ def attrValue = attrs.remove(attrName)
+ // If the value is the same as the name or if it is a boolean value,
+ // reintroduce the attribute to the map according to the w3c rules, so it is output later
+ if (Boolean.valueOf(attrValue) ||
+ (attrValue instanceof String && attrValue?.equalsIgnoreCase(attrName))) {
+ attrs.put(attrName, attrName)
+ } else if (attrValue instanceof String && !attrValue?.equalsIgnoreCase('false')) {
+ // If the value is not the string 'false', then we should just pass it on to
+ // keep compatibility with existing code
+ attrs.put(attrName, attrValue)
+ }
+ }
/**
* Check required attributes, set the id to name if no id supplied, extract bean values etc.
@@ -230,6 +248,12 @@ class FormTagLib {
attrs.value = val
}
attrs.value = attrs.value != null ? attrs.value : "" // can't use ?: since 0 is groovy false
+
+ // Some attributes can be treated as boolean, but must be converted to the
+ // expected value.
+ booleanToAttribute(attrs, 'disabled')
+ booleanToAttribute(attrs, 'checked')
+ booleanToAttribute(attrs, 'readonly')
}
/**
@@ -355,6 +379,7 @@ class FormTagLib {
*
* @attr value REQUIRED The title of the button and name of action when not explicitly defined.
* @attr action The name of the action to be executed, otherwise it is derived from the value.
+ * @attr disabled Makes the button to be disabled. Will be interpreted as a Groovy Truth
*/
Closure actionSubmit = { attrs ->
if (!attrs.value) {
@@ -372,6 +397,7 @@ class FormTagLib {
// add action and value
def value = attrs.remove('value')
def action = attrs.remove('action') ?: value
+ booleanToAttribute(attrs, 'disabled')
out << "<input type=\"submit\" name=\"_action_${action}\" value=\"${value}\" "
@@ -395,6 +421,7 @@ class FormTagLib {
* @attr value REQUIRED The title of the button and name of action when not explicitly defined.
* @attr action The name of the action to be executed, otherwise it is derived from the value.
* @attr src The source of the image to use
+ * @attr disabled Makes the button to be disabled. Will be interpreted as a Groovy Truth
*/
Closure actionSubmitImage = { attrs ->
attrs.tagName = "actionSubmitImage"
@@ -406,6 +433,7 @@ class FormTagLib {
// add action and value
def value = attrs.remove('value')
def action = attrs.remove('action') ?: value
+ booleanToAttribute(attrs, 'disabled')
out << "<input type=\"image\" name=\"_action_${action}\" value=\"${value}\" "
@@ -435,6 +463,8 @@ class FormTagLib {
* @attr years A list or range of years to display, in the order specified. i.e. specify 2007..1900 for a reverse order list going back to 1900. If this attribute is not specified, a range of years from the current year - 100 to current year + 100 will be shown.
* @attr relativeYears A range of int representing values relative to value. For example, a relativeYears of -2..7 and a value of today will render a list of 10 years starting with 2 years ago through 7 years in the future. This can be useful for things like credit card expiration dates or birthdates which should be bound relative to today.
* @attr id the DOM element id
+ * @attr disabled Makes the resulting inputs and selects to be disabled. Is treated as a Groovy Truth.
+ * @attr readonly Makes the resulting inputs and selects to be made read only. Is treated as a Groovy Truth.
*/
Closure datePicker = { attrs ->
def out = out // let x = x ?
@@ -534,12 +564,22 @@ class FormTagLib {
years = (tempyear - 100)..(tempyear + 100)
}
}
+
+ booleanToAttribute(attrs, 'disabled')
+ booleanToAttribute(attrs, 'readonly')
out.println "<input type=\"hidden\" name=\"${name}\" value=\"date.struct\" />"
// create day select
if (precision >= PRECISION_RANKINGS["day"]) {
- out.println "<select name=\"${name}_day\" id=\"${id}_day\">"
+ out.println "<select name=\"${name}_day\" id=\"${id}_day\""
+ if (attrs.disabled) {
+ out << ' disabled="disabled"'
+ }
+ if (attrs.readonly) {
+ out << ' readonly="readonly"'
+ }
+ out << '>'
if (noSelection) {
renderNoSelectionOptionImpl(out, noSelection.key, noSelection.value, '')
@@ -554,7 +594,14 @@ class FormTagLib {
// create month select
if (precision >= PRECISION_RANKINGS["month"]) {
- out.println "<select name=\"${name}_month\" id=\"${id}_month\">"
+ out.println "<select name=\"${name}_month\" id=\"${id}_month\""
+ if (attrs.disabled) {
+ out << ' disabled="disabled"'
+ }
+ if (attrs.readonly) {
+ out << ' readonly="readonly"'
+ }
+ out << '>'
if (noSelection) {
renderNoSelectionOptionImpl(out, noSelection.key, noSelection.value, '')
@@ -572,7 +619,14 @@ class FormTagLib {
// create year select
if (precision >= PRECISION_RANKINGS["year"]) {
- out.println "<select name=\"${name}_year\" id=\"${id}_year\">"
+ out.println "<select name=\"${name}_year\" id=\"${id}_year\""
+ if (attrs.disabled) {
+ out << ' disabled="disabled"'
+ }
+ if (attrs.readonly) {
+ out << ' readonly="readonly"'
+ }
+ out << '>'
if (noSelection) {
renderNoSelectionOptionImpl(out, noSelection.key, noSelection.value, '')
@@ -587,7 +641,14 @@ class FormTagLib {
// do hour select
if (precision >= PRECISION_RANKINGS["hour"]) {
- out.println "<select name=\"${name}_hour\" id=\"${id}_hour\">"
+ out.println "<select name=\"${name}_hour\" id=\"${id}_hour\""
+ if (attrs.disabled) {
+ out << ' disabled="disabled"'
+ }
+ if (attrs.readonly) {
+ out << ' readonly="readonly"'
+ }
+ out << '>'
if (noSelection) {
renderNoSelectionOptionImpl(out, noSelection.key, noSelection.value, '')
@@ -609,7 +670,14 @@ class FormTagLib {
// do minute select
if (precision >= PRECISION_RANKINGS["minute"]) {
- out.println "<select name=\"${name}_minute\" id=\"${id}_minute\">"
+ out.println "<select name=\"${name}_minute\" id=\"${id}_minute\""
+ if (attrs.disabled) {
+ out << 'disabled="disabled"'
+ }
+ if (attrs.readonly) {
+ out << 'readonly="readonly"'
+ }
+ out << '>'
if (noSelection) {
renderNoSelectionOptionImpl(out, noSelection.key, noSelection.value, '')
@@ -732,6 +800,7 @@ class FormTagLib {
* @attr valueMessagePrefix By default the value "option" element will be the result of a "toString()" call on each element in the "from" attribute list. Setting this allows the value to be resolved from the I18n messages. The valueMessagePrefix will be suffixed with a dot ('.') and then the value attribute of the option to resolve the message. If the message could not be resolved, the value is presented.
* @attr noSelection A single-entry map detailing the key and value to use for the "no selection made" choice in the select box. If there is no current selection this will be shown as it is first in the list, and if submitted with this selected, the key that you provide will be submitted. Typically this will be blank - but you can also use 'null' in the case that you're passing the ID of an object
* @attr disabled boolean value indicating whether the select is disabled or enabled (defaults to false - enabled)
+ * @attr readonly boolean value indicating whether the select is read only or editable (defaults to false - editable)
*/
Closure select = { attrs ->
if (!attrs.name) {
@@ -760,12 +829,9 @@ class FormTagLib {
if (noSelection != null) {
noSelection = noSelection.entrySet().iterator().next()
}
- def disabled = attrs.remove('disabled')
- if (disabled && Boolean.valueOf(disabled)) {
- attrs.disabled = 'disabled'
- }
-
writer << "<select name=\"${attrs.remove('name')?.encodeAsHTML()}\" "
+ booleanToAttribute(attrs, 'disabled')
+ booleanToAttribute(attrs, 'readonly')
// process remaining attributes
outputAttributes(attrs, writer)
@@ -894,16 +960,16 @@ class FormTagLib {
* @attr name REQUIRED The name of the radio button
* @attr checked boolean to indicate that the radio button should be checked
* @attr disabled boolean to indicate that the radio button should be disabled
+ * @attr readonly boolean to indicate that the radio button should not be editable
* @attr id the DOM element id
*/
Closure radio = { attrs ->
def value = attrs.remove('value')
attrs.id = attrs.id ?: attrs.name
def name = attrs.remove('name')
- def disabled = attrs.remove('disabled')
- if (disabled && Boolean.valueOf(disabled)) {
- attrs.disabled = 'disabled'
- }
+ booleanToAttribute(attrs, 'disabled')
+ booleanToAttribute(attrs, 'readonly')
+
def checked = attrs.remove('checked') ? true : false
out << "<input type=\"radio\" name=\"${name}\"${ checked ? ' checked="checked" ' : ' '}value=\"${value?.toString()?.encodeAsHTML()}\" "
// process remaining attributes
@@ -920,12 +986,17 @@ class FormTagLib {
* @attr values REQUIRED The list values for the radio buttons
* @attr value The current selected value
* @attr labels Labels for each value contained in the values list. If this is ommitted the label property on the iterator variable (see below) will default to 'Radio ' + value.
+ * @attr disabled Disables the resulting radio buttons.
+ * @attr readonly Makes the resulting radio buttons to not be editable
*/
Closure radioGroup = { attrs, body ->
def value = attrs.remove('value')
def values = attrs.remove('values')
def labels = attrs.remove('labels')
def name = attrs.remove('name')
+ booleanToAttribute(attrs, 'disabled')
+ booleanToAttribute(attrs, 'readonly')
+
values.eachWithIndex {val, idx ->
def it = new Expando()
it.radio = new StringBuilder("<input type=\"radio\" name=\"${name}\" ")
View
67 ...st-suite-web/src/test/groovy/org/codehaus/groovy/grails/web/taglib/FormTagLibTests.groovy
@@ -181,4 +181,71 @@ class FormTagLibTests extends AbstractGrailsTagTests {
assertEquals '<textarea name="testField" id="testField" >1</textarea>', sw.toString()
}
}
+
+ private void doTestBoolean(def attributes, String expected) {
+ def sw = new StringWriter()
+ withTag('textField', new PrintWriter(sw)) { tag ->
+ tag.call(attributes)
+ assertEquals expected, sw.toString()
+ }
+ }
+
+ void testBooleanAttributes() {
+ // GRAILS-3468
+ // Test readonly for string as boolean true
+ def attributes = [name: 'myfield', value: '1', readonly: 'true']
+ doTestBoolean(attributes, '<input type="text" name="myfield" value="1" id="myfield" readonly="readonly" />')
+
+ // Test readonly for string as boolean false
+ attributes = [name: 'myfield', value: '1', readonly: 'false']
+ doTestBoolean(attributes, '<input type="text" name="myfield" value="1" id="myfield" />')
+
+ // Test readonly for real boolean true
+ attributes = [name: 'myfield', value: '1', readonly: true]
+ doTestBoolean(attributes, '<input type="text" name="myfield" value="1" id="myfield" readonly="readonly" />')
+
+ // Test readonly for real boolean false
+ attributes = [name: 'myfield', value: '1', readonly: false]
+ doTestBoolean(attributes, '<input type="text" name="myfield" value="1" id="myfield" />')
+
+ // Test readonly for its default value
+ attributes = [name: 'myfield', value: '1', readonly: 'readonly']
+ doTestBoolean(attributes, '<input type="text" name="myfield" value="1" id="myfield" readonly="readonly" />')
+
+ // Test readonly for a value different from the defined in the spec
+ attributes = [name: 'myfield', value: '1', readonly: 'other value']
+ doTestBoolean(attributes, '<input type="text" name="myfield" value="1" id="myfield" readonly="other value" />')
+
+ // Test readonly for null value
+ attributes = [name: 'myfield', value: '1', readonly: null]
+ doTestBoolean(attributes, '<input type="text" name="myfield" value="1" id="myfield" />')
+
+ // Test disabled for string as boolean true
+ attributes = [name: 'myfield', value: '1', disabled: 'true']
+ doTestBoolean(attributes, '<input type="text" name="myfield" value="1" id="myfield" disabled="disabled" />')
+
+ // Test disabled for string as boolean false
+ attributes = [name: 'myfield', value: '1', disabled: 'false']
+ doTestBoolean(attributes, '<input type="text" name="myfield" value="1" id="myfield" />')
+
+ // Test disabled for real boolean true
+ attributes = [name: 'myfield', value: '1', disabled: true]
+ doTestBoolean(attributes, '<input type="text" name="myfield" value="1" id="myfield" disabled="disabled" />')
+
+ // Test disabled for real boolean false
+ attributes = [name: 'myfield', value: '1', disabled: false]
+ doTestBoolean(attributes, '<input type="text" name="myfield" value="1" id="myfield" />')
+
+ // Test disabled for its default value
+ attributes = [name: 'myfield', value: '1', disabled: 'disabled']
+ doTestBoolean(attributes, '<input type="text" name="myfield" value="1" id="myfield" disabled="disabled" />')
+
+ // Test disabled for a value different from the defined in the spec
+ attributes = [name: 'myfield', value: '1', disabled: 'other value']
+ doTestBoolean(attributes, '<input type="text" name="myfield" value="1" id="myfield" disabled="other value" />')
+
+ // Test disabled for null value
+ attributes = [name: 'myfield', value: '1', disabled: null]
+ doTestBoolean(attributes, '<input type="text" name="myfield" value="1" id="myfield" />')
+ }
}
Something went wrong with that request. Please try again.