One of the cool features of Groovy that we were not able to cover in GQuick is the MarkupBuilder
. MarkupBuilder
turns your markup language into a Groovy DSL. The markup elements become methods. If they have body content, it goes in a closure passed to the element / method. For example, an anchor tag looks something like this:
a(href:'http://somehost.com/somepath/someresource'){
//anchor text goes here
}
So, how can we use this in the GQuick example app, Tekdays.com? We could use it to render some HTML to a view from a controller action, but that would be kind of lame. If you read the book, you know that one of my favorite Grails features is custom GSP tags. We created several of them for TekDays, and they all spit out some HTML. So let's see how we can transform one of our custom tags using Groovy's MarkupBuilder
.
We'll do our conversion on the loginToggle
tag. This tag shows a Login link at the top of the page if the user is not logged in. If the user is logged in, the tag shows a welcome message on the left side of the page and a Logout link on the right. It's pretty handy. We can just drop it on any page and avoid using a bunch of messy <<g:if>
logic in our page.
This tag is introduced in Chapter 8, “Knock, Knock: Who's There? Grails Security”. Here is how it looks now:
def loginToggle = {
out << "<div>"
if (session.user){
out << "<span style='float:left;
margin:5px 0px 0px 10px'>"
out << "Welcome ${session.user}."
out << "</span><span style='float:right;
margin:5px 10px 0px 0px'>"
out << "<a href='${createLink(controller:
'tekUser', action:'logout')}'>"
out << "Logout </a></span>"
}
else{
out << "<span style='float:right;
margin:5px 10px 0px 0px'>"
out << "<a href='${createLink(controller:'tekUser',
action:'login')}'>"
out << "Login </a></span>"
}
out << "</div><br/>"
}
To use the tag, we just placed this in our main.gsp
:
<g:loginToggle />
That gave us something like this:
Well, it doesn't give us all that, but it does give us the “Welcome John Doe.” message and the Logout link. Now let's replace all those out << ...
statements with the magic of MarkupBuilder
.
def loginToggle = {
def mb = new groovy.xml.MarkupBuilder(out)
mb.div{
if (session.user){
span(style:'float:left;margin:5px 0px 0px 10px'){
mb.yield "Welcome ${session.user}."
}
span(style:'float:right;
margin:5px 10px 0px 0px'){
a(href:createLink(
controller:'tekUser',
action:'logout')){
mb.yield 'Logout'
}
}
}
else{
span(style:'float:right;
margin:5px 10px 0px 0px'){
a(href:createLink(controller:
'tekUser', action:'login')){
mb.yield 'Login'
}
}
}
}
}
In the first line, we create an instance of MarkupBuilder
using the constructor that takes a Writer
. The out
property of all TagLib
s is an instance of Writer
, so we'll pass it that. Now whatever our MarkupBuilder
produces will be automatically sent to out
.
Next we'll start creating elements. The outer element for this tag is a div
, so we'll start with that. The div
call will just take a closure. If we were setting any attributes on the div
, they would be method arguments:
div(class:"someCSSclass"){
//...
}
Note that we don't need a closing div
call -- just the closing brace.
One of the nice things about Builders is that they are just Groovy code, so you can mix in any other Groovy code along with it. You might wonder “Why doesn't it try to turn it into an HTML element?” Well, it's because Groovy is smart. It will only try to turn unrecognized symbols into elements.
So, inside of our if
block, we are calling the span()
method and passing a style
argument, followed by a closure. The closure will be the body of the generated <span>
tag. Since the body of the span
tag in this case is only text, we will use a method of MarkupBuilder
called yield()
. This method is necessary for text output in a tag that can contain other tags. Without it, the Builder would have no way to know that we want text printed literally and not turned into an element.
In the next span
the styling is a bit different to position it on the right side, and inside the closure we have another tag. This time it is an anchor tag with an href
argument. For the value of the href
, we call the createLink
tag as a method similar to the way we did before, but this time we don't have to put it in a Groovy ${}
expression. This is because we're not outputting Strings
; instead, we're calling Groovy methods. For the body of the anchor tag, we use the yield() method again.
The rest of the code does the same thing for the else
clause, so there's not much more to talk about, except to note that we don't do anything else to get all this to be rendered to our view. Just because we're binding out
to the MarkupBuilder
in the constructor, the text generated by the Builder will automatically be rendered at the completion of the method. Pretty cool.
You may have noticed that the MarkupBuilder
version of this tag is actually a tad longer than the previous version, but you'll have to admit that the readability and flexibility are improved. We also no longer have to worry about leaving out a closing tag when we are doing a bunch of nesting.
I think I'll be using MarkupBuilder
in most of my tags from now on. A special shout-out and thanks to Jerome Jahnke for suggesting this topic on the pragprog.com Grails forum.
If you've got a suggestion for a blog post related to GQuick, please leave a comment here or on the pragprog forum. Thanks!
Excellent post!
ReplyDeleteI tend to use markup builder if I must generate html from tags, it's much cleaner.
ReplyDeleteAnother of my favourites is getting a tag to dispatch to a template file using render() . Chosing between MarkupBuilder or using a template depends on the amount / type of markup involved, more complex stuff always goes in a template basic stuff I just stick in the tag using MarkupBuilder.
Templates do have the benefit though in that you can pass them onto a html designer to make design tweaks with knowing little or non groovy.
Dave, nice post.
ReplyDeleteNow, why use GSP markup at all? Compared to Markup Builders GSP markup is cumbersome at best.
Are there any performance implications to using markup builder for nearly all html markup? I know that GSP content is production reloadable; does the same hold true to markup builder generated content embedded in a GSP, or TagLib as you have done here?
I am not at all concerned about designers being able to understand markup builder layouts -- they're far, far more clear than GSPs, IMO.