Saturday April 18, 2009

Grails - Invoking a tag lib from another tag lib

Grails comes with a predefined set of tags that you can use in your gsp pages. If you want to add your own tags, it is pretty simple and you can simply check the Dynamic Tag Libraries reference documentation. I created my own version of the <g:each> tag which allows you to provide a begin, end and separator attributes:

class MyTagLib {
  static namespace = 'my'
  // Equivalent to g:each but allow for begin/end and separator attributes
  def each = { attrs, body ->
    def var = attrs.var ?: "var"

    def begin = attrs.begin ?: ""
    def end = attrs.end ?: ""

    def writer = out

    if(attrs.in)
    {
      // not null and not empty (definition of truth in groovy)
      attrs.in.eachWithIndex { elt, i ->
        if(i == 0)
        {
          writer << begin
        }
        else
        {
          writer << attrs.separator
        }
        writer << body((var):elt)
      }

      writer << end
    }
    else
    {
      if(attrs.alwaysBeginEnd?.toString() == "true")
      {
        writer << begin << end
      }
    }
  }
}

Here are some examples of rendering in gsp:

<my:each in="${[1,2,3]}" var="i">${i}</my:each>
produces: 123
<my:each in="${[1,2,3]}" var="i" begin="{" end="}" separator=",">${i}</my:each>
produces: {1,2,3}
<my:each in="${[1]}" var="i" begin="{" end="}" separator=",">${i}</my:each>
produces: {1}
<my:each in="${[]}" var="i" begin="{" end="}" separator=",">${i}</my:each>
produces: 
<my:each in="${[]}" var="i" begin="{" end="}" separator="," alwaysBeginEnd="true">${i}</my:each>
produces: {}

This tag is pretty convenient as it automatically takes care of an empty list or one that has only one element to properly display the separator and the begin and end attributes. The last example shows how you can 'force' to display the begin and end attributes when the list is empty.

Now, let's say I want to create another tag which will reuse the code I already wrote. In other words, I need to call a tag from within a tag. Here is how I would do it:

def csv = { attrs, body ->
  def var = attrs.var ?: "var"
  out << my.each(in: attrs.in, var: 'v', separator: ',') { map ->
  def elt = map.v
  out << "{"
  out << body((var):elt)
  out << "}"
  }
}

And here is the rendering in gsp:

<my:csv in="${[1,2,3]}" var="i">[${i}]</my:csv>
produces: {[1]},{[2]},{[3]}

It is actually not that trivial to call a tag from within a tag (and to my knowledge it is not documented)... let's cover each details:

  • referencing another tag is used with the notation: namespace.tagName (ex: my.each)
  • simply calling the other tag is not enough and the result must be sent to the writer (ex: out << my.each(...))
  • each attribute is passed in as a map, so you simply use the groovy map notation (ex: (in: attrs.in, var: 'v', separator: ','))
  • now the really tricky part is the closure which corresponds to the children tags in gsp... the argument that you get is a map (because in the my.each code, the body closure is called with a map!). Although it makes sense, it is not that trivial because in gsp you don't see it. This is why I need to use map.v to have access to the element that is being iterated over (the variable v is because it is the one that I used in the call (my.each(..., var: 'v', ...)))

Although a little tricky to write, it is very powerful to be able to create tags that build upon other tags. There is one little caveat in how null is being handled and I opened a Jira ticket for it (GRAILS-4449) as it does not seem to be consistent.

April 18, 2009 by Yan Pujante

Posted in grails | 2 Comments »

Comments:

Wow! I had no idea how easy it was to define out owns tag libs in groovy... the way you showed us how to use a taglib from a taglib makes perfect sense... I'm beginning to like this groovy stuff....

Posted by opensas on April 19, 2009 at 07:47 AM PST #

Thanks! I couldn't remember how to get nested taglibs working though I knew I'd done it before. Totally forgot that you needed to send the call of the parent taglib that uses other taglibs to out as well.

Posted by Ted Naleid on January 28, 2010 at 09:14 AM PST #

Post a Comment:
  • HTML Syntax: Allowed