Search code examples
grailsgrails-5

Grails 5 change: how to inject taglib into Controller


In grails 4, if I had plugin-A that defined a taglib with static namespace = "someNamespace", I could reference that in controllers in plugin-B via something like:

render someNamespace.sometag()

In grails 4.0.3, plugin-B had a compile dependency on plugin-A, now it has an implementation dependency. Trying to run the same code, now gives the error:

"No such property: someNamespace for class: com.package.PluginBController"

Attempting to use the same namespace in the application that's running with both plugins A and B works just fine, but calling it from a Controller provided by plugin B fails. Are we missing something with the grails 5 upgrade? Does this need to be injected somehow?


Solution

  • Grails 5 change: how to inject taglib into Controller

    You can inject a taglib using its bean name like any other bean but there is no good reason to do that. The technique you show in the question is the recommended approach. We effectively add a namespace variable to your controllers which allow you to invoke tag lib implementations as if they were methods. That approach makes more sense than injecting a taglib.

    See the project at https://github.com/jeffbrown/treblataglib.

    https://github.com/jeffbrown/treblataglib/blob/8f6d46aa2d227ddb34dfa6adfa50d03b270ddd70/app/grails-app/controllers/app/DemoController.groovy

    package app
    
    class DemoController {
    
        def index() {
            render someNamespace.sometag()
        }
    }
    

    https://github.com/jeffbrown/treblataglib/blob/8f6d46aa2d227ddb34dfa6adfa50d03b270ddd70/helper/grails-app/taglib/helper/HelperTagLib.groovy

    package helper
    
    class HelperTagLib {
        static namespace = 'someNamespace'
        def sometag = { attrs ->
            out << 'This came from HelperTagLib.someTagLib'
        }
    }
    

    That appears to work:

    ~ $ mkdir working                                      
    ~ $ 
    ~ $ cd working                                         
    working $ 
    working $ git clone git@github.com:jeffbrown/treblataglib.git
    Cloning into 'treblataglib'...
    remote: Enumerating objects: 145, done.
    remote: Counting objects: 100% (145/145), done.
    remote: Compressing objects: 100% (98/98), done.
    remote: Total 145 (delta 23), reused 145 (delta 23), pack-reused 0
    Receiving objects: 100% (145/145), 860.12 KiB | 3.41 MiB/s, done.
    Resolving deltas: 100% (23/23), done.
    working $ 
    working $ cd treblataglib                                    
    treblataglib (main)$ 
    treblataglib (main)$ ./gradlew app:bootRun                              
    
    > Task :app:bootRun
    Grails application running at http://localhost:8080 in environment: development
    <============-> 95% EXECUTING [26s]
    > :app:bootRun
    

    Output:

    ~ $ http :8080/demo
    HTTP/1.1 200 
    Connection: keep-alive
    Content-Type: text/html;charset=utf-8
    Date: Mon, 08 Aug 2022 18:05:14 GMT
    Keep-Alive: timeout=60
    Transfer-Encoding: chunked
    Vary: Origin
    Vary: Access-Control-Request-Method
    Vary: Access-Control-Request-Headers
    
    This came from HelperTagLib.someTagLib
    
    

    EDIT:

    A comment below indicates that the problem manifests when a plugin is using a namespaced taglib from another plugin. I have updated the linked project to represent that (https://github.com/jeffbrown/treblataglib/commit/53de85ef7073b8f428ccf0e785097509ed966376).

    app depends on another-helper, another-helper depends on helper. helper provides a namespaced taglib. DemoController in another-helper uses that namespaced plugin.

    When run the app using Gradle it seems to work. When I build the war, that seems to work as well.