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, thebody
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 usemap.v
to have access to the element that is being iterated over (the variablev
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.