This is an automated email from the ASF dual-hosted git repository. davydotcom pushed a commit to branch feature/taglib-method-actions in repository https://gitbox.apache.org/repos/asf/grails-core.git
commit f42e058c6edd0fe67119e5b45b059beaeefd7b8e Author: David Estes <[email protected]> AuthorDate: Wed Feb 25 21:59:21 2026 -0500 Bind Map tag method args by name except reserved attrs map - treat only Map parameter named attrs as full tag attributes map - allow other Map-typed parameters to bind from attribute key by parameter name - add regression tests for map-valued attribute binding and reserved attrs behavior Co-Authored-By: Oz <[email protected]> --- .../main/groovy/org/grails/taglib/TagMethodInvoker.java | 4 ++-- .../grails/web/taglib/MethodDefinedTagLibSpec.groovy | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/grails-gsp/grails-taglib/src/main/groovy/org/grails/taglib/TagMethodInvoker.java b/grails-gsp/grails-taglib/src/main/groovy/org/grails/taglib/TagMethodInvoker.java index 5669a77512..cf19e2fb4b 100644 --- a/grails-gsp/grails-taglib/src/main/groovy/org/grails/taglib/TagMethodInvoker.java +++ b/grails-gsp/grails-taglib/src/main/groovy/org/grails/taglib/TagMethodInvoker.java @@ -126,8 +126,9 @@ public final class TagMethodInvoker { Parameter[] parameters = method.getParameters(); Object[] args = new Object[parameters.length]; for (int i = 0; i < parameters.length; i++) { + String parameterName = parameters[i].getName(); Class<?> parameterType = parameters[i].getType(); - if (Map.class.isAssignableFrom(parameterType)) { + if (Map.class.isAssignableFrom(parameterType) && "attrs".equals(parameterName)) { args[i] = attrs; continue; } @@ -135,7 +136,6 @@ public final class TagMethodInvoker { args[i] = body != null ? body : TagOutput.EMPTY_BODY_CLOSURE; continue; } - String parameterName = parameters[i].getName(); Object value = attrs != null ? attrs.get(parameterName) : null; if (value == null && parameters.length == 1 && attrs != null && attrs.size() == 1) { value = attrs.values().iterator().next(); diff --git a/grails-gsp/plugin/src/test/groovy/org/grails/web/taglib/MethodDefinedTagLibSpec.groovy b/grails-gsp/plugin/src/test/groovy/org/grails/web/taglib/MethodDefinedTagLibSpec.groovy index ae636b4996..3deeb6171c 100644 --- a/grails-gsp/plugin/src/test/groovy/org/grails/web/taglib/MethodDefinedTagLibSpec.groovy +++ b/grails-gsp/plugin/src/test/groovy/org/grails/web/taglib/MethodDefinedTagLibSpec.groovy @@ -43,6 +43,15 @@ class MethodDefinedTagLibSpec extends Specification implements TagLibUnitTest<Me applyTemplate('<g:multiTypedTag first="hello" second="world" />') == 'hello-world' } + void "method tag can bind map-valued attribute to map-typed argument by parameter name"() { + expect: + applyTemplate('<g:mapValueTag config="${[k:\'v\']}" />') == 'v' + } + void "method tag still supports reserved attrs map parameter"() { + expect: + applyTemplate('<g:attrsMapTag blah="duh" />') == 'duh' + } + void "method tag can use implicit body closure"() { expect: applyTemplate('<g:bodyTag>abc</g:bodyTag>') == 'before-abc-after' @@ -100,6 +109,14 @@ class MethodTagLib { out << "${first}-${second}" } + def mapValueTag(Map config) { + out << "${config.k}" + } + + def attrsMapTag(Map attrs) { + out << "${attrs.blah}" + } + def bodyTag() { out << "before-${body()}-after" }
