Tuesday, February 2, 2010

Using Groovy's MarkupBuilder In Grails' GSP Tags

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 TagLibs 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!

4 comments:

  1. I tend to use markup builder if I must generate html from tags, it's much cleaner.

    Another 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.

    ReplyDelete
  2. 來給你加加油~打打氣!!!更新之餘,也要注意休息哦~~ ..................................................

    ReplyDelete
  3. Dave, nice post.

    Now, 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.

    ReplyDelete