Historically though JSP custom tags have always been a bit of a pain to write, they're designed to cover every variation of tag under the sun, hence the API is complicated and the tag lib descriptors verbose. Grails is all about simplicity, so how do we avoid introducing this level of complexity into our Grails applications. The answer came in the form of anonymous code blocks or closures.
I would say 90% of the tags that are written for JSP out there fall into one of three categories: simple, logical or iterative. There are those more complex tags that have relationships to each other via nesting, but the vast majority are of the aforementioned type and Grails is about making the most common cases easy, but still allowing the flexibility to scale to the more complex (thats why we still support JSP).
So what have closures done to make custom tags easier? Well Grails allows you to define a custom tag as a JavaBean property. No descriptors, no configuration, and everything is reloaded at runtime so no need to restart that application server. So lets look at an example from the tag library that ships with Grails (simplified for clarity):
class ValidationTagLib {
@Property eachError = { attrs, body ->
def errors = attrs.bean.errors
if(errors) {
errors.each { body(it) }
}
}
}
Each tag library is simply a class that ends with the convention "TagLib". The above example contains an "eachError" tag that loops through each error contained within the "bean" attribute and invokes the "body" of the tag. Note how the body of the tag itself is a closure and hence callable, the attributes are a map. To use this tag we simple call it from our GSP no need to import the tag library or anything, the error itself is available using Groovy's implicit 'it' variable which was passed to the body closure:
<g:eachError bean="${myBean}">
<p style="color:red;">${it.defaultMessage}</p>
</g:eachError >
Now thats pretty neat and simplistic, but this is where using closures for defining tags becomes really powerful. How about we want to re-use our "eachError" tag else where? Say we want to implement a default rendering tag called "renderErrors" that renders our errors as an HTML list. Well we can re-use the "eachErrors" tag to accomplish this:
@Property renderErrors = { attrs, body ->
def markup = new groovy.xml.MarkupBuilder(out)
markup.ul() {
eachError(attrs) { err ->
li( message(code:err.code) )
}
}
}
The above code creates a new MarkupBuilder instance which is used to generate an HTML list re-using the eachError tag defined previously, it actually uses a third Grails tag called "message" to retrieve the message for the error code. To call the "renderErrors" tag we simply do:
<g:renderErrors bean="${myBean}"/>
Grails users can of course customise the inbuilt tags and add brand new ones simply by adding new tag library classes in the "grails-app/taglib" directory. So there you have it, custom tags have never been easier, and your markup can remain scriptlet free without the need to invest huge amounts of time creating a custom JSP tag library.
4 comments:
Excellent, got to try our example, i don't use custom tags due to the facts you have point out, and this seems to be very friendly. I like it very much.
I'm going to a local conference of Ruby in town and want it to check upon java alikes, grails project i believe it will get further :)
Keep on the good work!
Regards
Can I somehow access to the nodes in custom tag body? for example how can I enumerate "column" nodes in class groovy code?
<g:myGrid name="mygrid">
<columns>
<column name="MyId" caption="MyColCaption1" type="int"/>
<column name="MyDate" caption="MyColCaption2" type="date"/>
</columns>
</g:myGrid>
can i use body() in MarkupBuilder instance? like this:
@Property dialog = { attrs, body ->
def markup = new groovy.xml.MarkupBuilder(out)
markup.div() {
body()
}
It is the shadow of legend Gold which make me very happy these days, my brother says sol gold is his favorite games gold he likes, he usually buy some buy shadow of legend Gold to start his game and most of the time he will win the cheap shadow of legend Gold back and give me some shadow of legend money to play the game.
Post a Comment