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 4d8238ca016482b483e171ebf9cbfc563a2019c8 Author: David Estes <[email protected]> AuthorDate: Thu Feb 26 07:52:03 2026 -0500 Fix method taglib compatibility regressions across fields and gsp - restore attrs-reserved binding for paginate - route namespaced method tag calls via tag output capture - add fieldValue(Map) compatibility overload - harden form fields rendering/raw handling with method dispatch Co-Authored-By: Oz <[email protected]> --- .../plugin/formfields/FormFieldsTagLib.groovy | 20 ++++++++-------- .../org/grails/taglib/TagLibraryMetaUtils.groovy | 28 ++++++++++++++++++++++ .../plugins/web/taglib/UrlMappingTagLib.groovy | 4 ++-- .../plugins/web/taglib/ValidationTagLib.groovy | 4 ++++ 4 files changed, 44 insertions(+), 12 deletions(-) diff --git a/grails-fields/grails-app/taglib/grails/plugin/formfields/FormFieldsTagLib.groovy b/grails-fields/grails-app/taglib/grails/plugin/formfields/FormFieldsTagLib.groovy index 3631c82001..a790ddea8f 100644 --- a/grails-fields/grails-app/taglib/grails/plugin/formfields/FormFieldsTagLib.groovy +++ b/grails-fields/grails-app/taglib/grails/plugin/formfields/FormFieldsTagLib.groovy @@ -247,7 +247,7 @@ class FormFieldsTagLib { widgetAttrs.remove('class') } if (hasBody(body)) { - model.widget = raw(body(model + [attrs: widgetAttrs] + widgetAttrs)) + model.widget = body(model + [attrs: widgetAttrs] + widgetAttrs)?.encodeAsRaw() } else { model.widget = renderWidget(propertyAccessor, model, widgetAttrs, widgetFolder ?: templatesFolder, theme) } @@ -389,7 +389,7 @@ class FormFieldsTagLib { out << render(template: "/templates/_fields/$template", model: attrs + [domainClass: domainClass, domainProperties: properties]) { prop -> BeanPropertyAccessor propertyAccessor = resolveProperty(bean, prop.name) Map model = buildModel(propertyAccessor, attrs, 'HTML') - out << raw(renderDisplayWidget(propertyAccessor, model, attrs, templatesFolder, theme)) + out << renderDisplayWidget(propertyAccessor, model, attrs, templatesFolder, theme)?.encodeAsRaw() } } } else { @@ -415,7 +415,7 @@ class FormFieldsTagLib { String widgetsFolderToUse = widgetFolder ?: templatesFolder if (hasBody(body)) { - model.widget = raw(body(model + [attrs: widgetAttrs] + widgetAttrs)) + model.widget = body(model + [attrs: widgetAttrs] + widgetAttrs)?.encodeAsRaw() model.value = body(model) } else { model.widget = renderDisplayWidget(propertyAccessor, model, widgetAttrs, widgetsFolderToUse, theme) @@ -426,7 +426,7 @@ class FormFieldsTagLib { if (template) { out << render(template: template.path, plugin: template.plugin, model: model + [attrs: wrapperAttrs] + wrapperAttrs) } else { - out << raw(renderDisplayWidget(propertyAccessor, model, attrs, widgetsFolderToUse, theme)) + out << renderDisplayWidget(propertyAccessor, model, attrs, widgetsFolderToUse, theme)?.encodeAsRaw() } } @@ -715,11 +715,11 @@ class FormFieldsTagLib { } } - CharSequence renderDefaultInput(Map model, Map attrs = [:]) { + protected CharSequence renderDefaultInput(Map model, Map attrs = [:]) { renderDefaultInput(null, model, attrs) } - CharSequence renderDefaultInput(BeanPropertyAccessor propertyAccessor, Map model, Map attrs = [:]) { + protected CharSequence renderDefaultInput(BeanPropertyAccessor propertyAccessor, Map model, Map attrs = [:]) { Constrained constrained = (Constrained) model.constraints attrs.name = (model.prefix ?: '') + model.property attrs.value = model.value @@ -778,7 +778,7 @@ class FormFieldsTagLib { } } - CharSequence renderDateTimeInput(Map model, Map attrs) { + protected CharSequence renderDateTimeInput(Map model, Map attrs) { attrs.precision = model.type in [java.sql.Time, LocalDateTime] ? 'minute' : 'day' if (!model.required) { attrs.noSelection = ['': ''] @@ -787,7 +787,7 @@ class FormFieldsTagLib { return g.datePicker(attrs) } - CharSequence renderStringInput(Map model, Map attrs) { + protected CharSequence renderStringInput(Map model, Map attrs) { Constrained constrained = (Constrained) model.constraints if (!attrs.type) { @@ -819,7 +819,7 @@ class FormFieldsTagLib { return g.field(attrs) } - CharSequence renderNumericInput(BeanPropertyAccessor propertyAccessor, Map model, Map attrs) { + protected CharSequence renderNumericInput(BeanPropertyAccessor propertyAccessor, Map model, Map attrs) { Constrained constrained = (Constrained) model.constraints if (!attrs.type && constrained?.inList) { @@ -992,7 +992,7 @@ class FormFieldsTagLib { buffer << render(template: '/templates/_fields/list', model: [domainClass: domainClass, domainProperties: properties]) { prop -> def propertyAccessor = resolveProperty(bean, prop.name) def model = buildModel(propertyAccessor, attrs) - out << raw(renderDisplayWidget(propertyAccessor, model, attrs, templatesFolder, theme)) + out << renderDisplayWidget(propertyAccessor, model, attrs, templatesFolder, theme)?.encodeAsRaw() } buffer.buffer } diff --git a/grails-gsp/grails-taglib/src/main/groovy/org/grails/taglib/TagLibraryMetaUtils.groovy b/grails-gsp/grails-taglib/src/main/groovy/org/grails/taglib/TagLibraryMetaUtils.groovy index bad77a6070..dd9adf4378 100644 --- a/grails-gsp/grails-taglib/src/main/groovy/org/grails/taglib/TagLibraryMetaUtils.groovy +++ b/grails-gsp/grails-taglib/src/main/groovy/org/grails/taglib/TagLibraryMetaUtils.groovy @@ -176,6 +176,34 @@ class TagLibraryMetaUtils { Object[] args = makeObjectArray(argsParam) final GroovyObject tagBean = gspTagLibraryLookup.lookupTagLibrary(namespace, name) if (tagBean != null) { + Object tagLibProp = TagMethodInvoker.getClosureTagProperty(tagBean, name) + if (tagLibProp instanceof Closure || TagMethodInvoker.hasInvokableTagMethod(tagBean, name)) { + Map attrs = [:] + Object body = null + switch (args.length) { + case 0: + break + case 1: + if (args[0] instanceof Map) { + attrs = (Map) args[0] + } else if (args[0] instanceof Closure || args[0] instanceof CharSequence) { + body = args[0] + } else { + attrs = [(name): args[0]] + } + break + case 2: + if (args[0] instanceof Map) { + attrs = (Map) args[0] + body = args[1] + } + break + } + if (addMethodsToMetaClass) { + registerMethodMissingForTags(mc, gspTagLibraryLookup, namespace, name) + } + return captureTagOutputForMethodCall(gspTagLibraryLookup, namespace, name, attrs, body) + } MetaClass tagBeanMc = tagBean.getMetaClass() final MetaMethod method = tagBeanMc.respondsTo(tagBean, name, args).find { it } if (method != null) { diff --git a/grails-gsp/plugin/src/main/groovy/org/grails/plugins/web/taglib/UrlMappingTagLib.groovy b/grails-gsp/plugin/src/main/groovy/org/grails/plugins/web/taglib/UrlMappingTagLib.groovy index 2d695e987c..181667313a 100644 --- a/grails-gsp/plugin/src/main/groovy/org/grails/plugins/web/taglib/UrlMappingTagLib.groovy +++ b/grails-gsp/plugin/src/main/groovy/org/grails/plugins/web/taglib/UrlMappingTagLib.groovy @@ -119,8 +119,8 @@ class UrlMappingTagLib implements TagLibrary { * @attr mapping The named URL mapping to use to rewrite the link * @attr fragment The link fragment (often called anchor tag) to use */ - def paginate(Map attrsMap) { - TypeConvertingMap attrs = (TypeConvertingMap) attrsMap + def paginate(Map attrs) { + attrs = (TypeConvertingMap) attrs def writer = out if (attrs.total == null) { throwTagError('Tag [paginate] is missing required attribute [total]') diff --git a/grails-gsp/plugin/src/main/groovy/org/grails/plugins/web/taglib/ValidationTagLib.groovy b/grails-gsp/plugin/src/main/groovy/org/grails/plugins/web/taglib/ValidationTagLib.groovy index 7037c317f8..6e961c48e3 100644 --- a/grails-gsp/plugin/src/main/groovy/org/grails/plugins/web/taglib/ValidationTagLib.groovy +++ b/grails-gsp/plugin/src/main/groovy/org/grails/plugins/web/taglib/ValidationTagLib.groovy @@ -122,6 +122,10 @@ class ValidationTagLib implements TagLibrary { } } + def fieldValue(Map attrs) { + fieldValue(attrs, null) + } + private Object parseForRejectedValue(bean, field) { def rejectedValue = bean for (String fieldPart in field.split('\\.')) {
