Skip to content


Grails custom tags DIY

I’ve showed an example of how to add/edit one-to-many relationships through dynamic javascript forms in my previous post, and since you would want to add/edit the relationship through a single interface, you probably want to view that relationship in the same interface as well rather than navigating through the grails scaffolded views (which are great when u are still prototyping).

In this post I will enhance the same phone book application by showing a complete contact card when viewing a contact object and/or a contact list. To do this I will be creating my own custom tag that I’ll be reusing in the list and show pages. The final contact card will look something like this (excuse my ui design ugliness, it’s not my thing)

From this contact card, I can see all the contact details, as well as the contact’s phone numbers and types. I can click the contact name and go to the edit view, and I can click the tiny remove button to delete that contact.

You can download the source code for this post from here

Create the custom tag

The end result is to create a custom tag that will accept a contact object as an attribute and optionally a boolean to show/hide the delete button (more on that later), this should be enough for the tag to create something similar to the image above. The tag; when used; will look something like this

<pb:contactCard contact="${contactInstance}" allowRemove="${true}"/>
 

First thing we need to do is use the grails command line to create the custom tag, to do that just issue the following command

grails create-tag-lib blog.omarello.phonebook

Grails should create PhonebookTagLib.groovy in the taglib folder and PhonebookTagLibTests.groovy in the unit tests folder

Now we need to edit the PhoneBookTagLib.groovy and add a closure called contactCard, also we need to give our tag lib a namespace so we can differentiate/brand our tags. We should have something similar to this

package blog.omarello

class PhonebookTagLib {

    static namespace = "pb"

    def contactCard = {attrs ->
        def contact = attrs.contact
        def remove = attrs.allowRemove

        out << createCard(contact, remove)
    }
}

Things to note here are the namespace definition and the closure name. The combination of both will give us our custom tag syntax as shown above. We can access the attributes passed to the tag through the attrs closure parameter. In our case we need to get the contact object, and the allowRemove boolean which we will use to show/hide the delete button.
Now we need to implement the createCard method which should return an HTML string that will be written out to the stream.
The createCard method should output the contact name (first and last) as well as the contact’s nickname and then create the appropriate image resources according to the contact’s phone types and then output the list of phone numbers and pone type images.

def createCard(contact, remove){

        StringBuilder sb = new StringBuilder()

        //create a links to edit and delete the contact by setting the actions and ids
        def editLink = g.createLink(action:"edit", id:contact.id)
        def deleteLink = g.createLink(action:"delete", id:contact.id)

        //define image resources
        def cardImage = resource(dir:'images',file:'card.png')
        def mobileImage = resource(dir:'images',file:'mobile.png')
        def homeImage = resource(dir:'images',file:'home.png')
        def workImage = resource(dir:'images',file:'work.png')
        def noPhoneImage = resource(dir:'images',file:'skull.png')
        def remImage = resource(dir:'images/skin',file:'icon_delete.png')

        sb << """
            <div class="contact" id="${contact.id}">
        """

        sb << """
            <div class="ctop">
              <span class="name">
                  <a href="${editLink}">
                      ${contact.lastName}, ${contact.firstName}
                  </a>
              </span>
              <img src="${cardImage}" class="card"/>
            </div>
        """

        sb << """
            <div class="cbody">
        """

        //if remove is passed as true, show the remove button
        if(remove)
            sb << """
                <a href="${deleteLink}" onclick="return confirm('Are you sure?');">
                    <img src="${remImage}" class="rem" alt="Delete"/>
                </a>
            """

        if(contact.nickName){
            sb << """
               <div class="nickname">
                  a.k.a <em>${contact.nickName}</em>
               </div>
            """
        }

        sb << """<ul>"""
        if(contact.phones){

            contact.phones.each(){
                sb << """<li>"""

                //get image resrouce according to phone type
                if(it.type == Phone.PhoneType.H)
                    sb << """<img src="${homeImage}" alt="Home" >"""
                else if(it.type == Phone.PhoneType.M)
                    sb << """<img src="${mobileImage}" alt="Mobile" >"""
                else
                    sb << """<img src="${workImage}" alt="Work">"""

                sb << """<span class="phone">${it.number}</span>"""
                sb << """</li>"""
            }

        }else{
            sb << """<li>"""
            sb << """<img src="${noPhoneImage}" >"""
            sb << """<span class="phone">No Phone Numbers!!</span>"""
            sb << """</li>"""
        }
        sb << """</ul>"""

        sb << """
            </div>
        </div>
        """
        sb.toString()
    }

As you can see the logic is straightforward, the thing to note here is the use of existing grails tags by using the tag’s namespace (g) as an object and calling the appropriate method on it (e.g. g.createLink). Also whenever you have blocks of text or when dealing with HTML make sure you use the “”” syntax (GStrings), it will make your life easier. The rest is fairly simple. The card’s style will be controlled by a css file that we need to included in the pages we will be using our tag in.
That’s it we are done, our custom tag is ready to be tested.

Using the custom tag

We will first use our new custom tag in the contact’s show.gsp file. We will edit the file and clear everything inside the
div with class=”dialog”, we also need to remove the button’s div as well. After clearing those elements we will add our new custom tag to the gsp file like so

<%@ page import="blog.omarello.Contact" %>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <meta name="layout" content="main" />
        <g:set var="entityName" value="${message(code: 'contact.label', default: 'Contact')}" />
        <title><g:message code="default.show.label" args="[entityName]" /></title>
        <link rel="stylesheet" href="${resource(dir:'css',file:'contacts.css')}" />
    </head>
    <body>
        <div class="nav">
            <span class="menuButton"><a class="home" href="${createLink(uri: '/')}"><g:message code="default.home.label"/></a></span>
            <span class="menuButton"><g:link class="list" action="list"><g:message code="default.list.label" args="[entityName]" /></g:link></span>
            <span class="menuButton"><g:link class="list" action="phonebook">Phone Book View</g:link></span>
            <span class="menuButton"><g:link class="create" action="create"><g:message code="default.new.label" args="[entityName]" /></g:link></span>
        </div>
        <div class="body">
            <h1><g:message code="default.show.label" args="[entityName]" /></h1>
            <g:if test="${flash.message}">
            <div class="message">${flash.message}</div>
            </g:if>
            <div class="dialog">
                <pb:contactCard contact="${contactInstance}" allowRemove="${true}"/>
            </div>
        </div>
    </body>
</html>

Make note of the highlighted lines, 1st I’ve added the CSS file which will control the style of our card, 2nd I’ve used the new custom tag and passed it the contact instance to the contact attribute and true to the allowRemove attribute so that it will display the delete button. Also I’ve made a small change to the contact controller in order to remove the restriction on calling the delete action only through POST (this is just a quick change to show how things work), in order to do that I just removed the delete action from the allowedMethods map in the controller (I’ve kept the save and update)

static allowedMethods = [save: "POST", update: "POST"]

That’s it, lets go ahead and test it, grails run-app , create a contact and your show page should look something like this.

Reusing our custom tag

The whole point of creating a custom tag is to be able to re-use it (or in some cases just to show off :) ), and that’s what we will do now (reuse not show off). We will reuse the the contact card tag in a new list view that we should call a phone book view.
What we need to do is basically create a copy of list.gsp and call is phonebook.gsp, also we’ll added a new action in the Contact controller called phonebook and basically return the same results that are returned by the list action (just for example’s sake)

def phonebook = {
        params.max = Math.min(params.max ? params.int('max') : 10, 100)
        [contactInstanceList: Contact.list(params), contactInstanceTotal: Contact.count()]
    }

Now all we need to do is clear out all the code inside the div with class=”list” in the phonebook.gsp file and added the following instead

<div class="list">
    <g:each in="${contactInstanceList}" var="contactInstance">
        <pb:contactCard contact="${contactInstance}" />
    </g:each>
</div>

As you can see the loop will go over the contact list and creat contact cards for each contact using the new custom tag, without using the allowRemove attribute since there is no need to allow deleting a contact directly from the list view. Now if we point the browser to http://localhost:8080/phone-book/contact/phonebook we should see something similar to this (make sure you add some contacts first)



That’s it. As you can see, it is very easy to create a custom tag so go ahead and try it.

Note: Many thanks to Joseph Wain for the icon designs which I grabbed from here

Posted in Programming.

Tagged with , , .


0 Responses

Stay in touch with the conversation, subscribe to the RSS feed for comments on this post.



Some HTML is OK

or, reply to this post via trackback.