Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to override standard behavior of ApplicationTagLib#createLink and g:link?

Tags:

grails

Background: I have grails 1.3.7 application which uses g:createLink and g:link on many pages.

Recently I decided to make big change in url mappings - introduce preceding path element.

  • Currently I have: /$controller/$action?/$id?
  • But want to have: /$regionId/$controller/$action?/$id?

It was easy to change urlMappings, but I can't figure out how to easily change the behavior how links are built throught the application.

Basically, I don't want to go through each page and change links. But want to do this in one place.

Question How to override ApplicationTagLib#createLink functionality so grails will use this implementation without the need of changes pages which use this tag (or function)?

Any help greatly appriciated!

like image 676
Sergey Karpushin Avatar asked Jun 03 '11 17:06

Sergey Karpushin


3 Answers

I had a smilar problem. Actually you can decorate g:link tag like this.

1) TagLib

import org.codehaus.groovy.grails.plugins.web.taglib.*

class OverrideDefaultTagLib {

    static namespace = "g"

    def link = { attrs, body ->

        def c = "1" // Get it from session or somewhere else

        if (attrs.params) {
            attrs.params.put("region", c)
        } else {
            attrs.params = [region: c]
        }

        def applicationTagLib = grailsApplication.mainContext.getBean('org.codehaus.groovy.grails.plugins.web.taglib.ApplicationTagLib')
            applicationTagLib.link.call(attrs, body)
        }
    }
}

2) add to UrlMappings.groovy

"/$region/$controller/$action?/$id?"{}
like image 144
sumnulu Avatar answered Jun 02 '23 21:06

sumnulu


I was unable to solve this problem in terms of OOP. I mean I can't find way how to override closure. I tried several approaches, but with no success. And documentation says that you can't override closure, you can only replace it with new implementation (please correct me if I wrong).

But (!) I was able to solve task by copy-pasting source code of ApplicationTagLib#createLink method. I think this is brutal solution, but after 8 hours of fighting with this simple task - it's acceptable.

So finally all I need to do - is define this class, grails will immediately use it for link generation (for all views, no need to change their code):

import java.text.SimpleDateFormat;

import groovy.time.*;
import java.text.*;

import org.codehaus.groovy.grails.commons.GrailsControllerClass
import org.codehaus.groovy.grails.plugins.web.taglib.ApplicationTagLib;
import org.codehaus.groovy.grails.web.mapping.UrlCreator
import org.codehaus.groovy.grails.commons.ControllerArtefactHandler
import org.springframework.web.context.request.RequestContextHolder

class OverrideTagLib extends ApplicationTagLib {

    def createLink = { attrs ->
        // get value for regionId parameter
        def regionId = regionIdFinderService.currentRegionId

        // add cutsom regionId parameter
        if (attrs) {
            if (attrs.params)
                attrs.params.put("regionId", regionId);
            else {
                attrs.params = ["regionId":regionId];
            }
        }

        // process
        def writer = getOut()
        // prefer URI attribute
        if (attrs.uri) {
            writer << handleAbsolute(attrs)
            writer << attrs.uri.toString()
        }
        else {
            // prefer a URL attribute
            def urlAttrs = attrs
            if (attrs.url instanceof Map) {
                urlAttrs = attrs.remove('url').clone()
            }
            else if (attrs.url) {
                urlAttrs = attrs.remove('url').toString()
            }

            if (urlAttrs instanceof String) {
                if (useJsessionId) {
                    writer << response.encodeURL(urlAttrs)
                }
                else {
                    writer << urlAttrs
                }
            }
            else {
                def controller = urlAttrs.containsKey("controller") ? urlAttrs.remove("controller")?.toString() : controllerName
                def action = urlAttrs.remove("action")?.toString()
                if (controller && !action) {
                    GrailsControllerClass controllerClass = grailsApplication.getArtefactByLogicalPropertyName(ControllerArtefactHandler.TYPE, controller)
                    String defaultAction = controllerClass?.getDefaultAction()
                    if (controllerClass?.hasProperty(defaultAction)) {
                        action = defaultAction
                    }
                }
                def id = urlAttrs.remove("id")
                def frag = urlAttrs.remove('fragment')?.toString()
                def params = urlAttrs.params && urlAttrs.params instanceof Map ? urlAttrs.remove('params') : [:]
                def mappingName = urlAttrs.remove('mapping')
                if (mappingName != null) {
                    params.mappingName = mappingName
                }
                if (request['flowExecutionKey']) {
                    params."execution" = request['flowExecutionKey']
                }

                if (urlAttrs.event) {
                    params."_eventId" = urlAttrs.remove('event')
                }
                def url
                if (id != null) params.id = id
                def urlMappings = applicationContext.getBean("grailsUrlMappingsHolder")
                UrlCreator mapping = urlMappings.getReverseMapping(controller,action,params)

                // cannot use jsessionid with absolute links
                if (useJsessionId && !attrs.absolute) {
                    url = mapping.createURL(controller, action, params, request.characterEncoding, frag)
                    def base = attrs.remove('base')
                    if (base) writer << base
                    writer << response.encodeURL(url)
                }
                else {
                    url = mapping.createRelativeURL(controller, action, params, request.characterEncoding, frag)
                    writer << handleAbsolute(attrs)
                    writer << url
                }
            }
        }
    }
}
like image 42
Sergey Karpushin Avatar answered Jun 02 '23 23:06

Sergey Karpushin


add regionId to params in createLink and g:link and grails is smart enough to match your urlmappings. i.e

${createLink(controller:'c',action:'a',id:1,params:[regionId:2])}
like image 29
netbrain Avatar answered Jun 02 '23 22:06

netbrain