I've recently started working with Groovy on Grails at work. Groovy is a dynamic language that runs on the JVM (Java Virtual Machine), and Grails is a high-productivity open-source web-application framework that leverages the Groovy language. Grails follows the "convention over configuration" principle, taking away a lot of the grunt work that goes into creating web applications. It also uses proven technology like Hibernate and Spring with a consistent interface. You can have a Grails app up and ready to go with only a few lines of code. This high productivity is also due to the fact that Grails uses Groovy, which is a dynamic language and is much more expressive than Java. Finally, because Groovy runs on the JVM, it has full access to Java's rich API.
Groovy comes with a number of built-in tags which allow you to quickly create forms, form controls, or any number of other HTML constructs or elements. However, I noticed that Groovy didn't provide a built-in tag to build a SELECT that includes OPTGROUPs. OPTGROUPs allow you to group options within a drop-down select-box. But this problem can be solved because Groovy supports the creation of custom tags. After reading a bunch of documentation and looking at a few examples, I was able to create my own SELECT tag that supports OPTGROUPs. Here is an example of the usage of the tag:
<g:selectWithOptGroup name = "song.id" 
                      from = "${Song.list()}" 
                      optionKey = "id" 
                      optionValue = "songName" 
                      groupBy = "album" />
And the code to implement this tag:
import org.springframework.beans.SimpleTypeConverter
import org.springframework.web.servlet.support.RequestContextUtils as RCU
import org.codehaus.groovy.grails.commons.DomainClassArtefactHandler
class MyAppTagLib {
    def selectWithOptGroup = {attrs ->
        def messageSource = grailsAttributes.getApplicationContext().getBean("messageSource")
        def locale = RCU.getLocale(request)
        def writer = out
        def from = attrs.remove('from')
        def keys = attrs.remove('keys')
        def optionKey = attrs.remove('optionKey')
        def optionValue = attrs.remove('optionValue')
        def groupBy = attrs.remove('groupBy')
        def value = attrs.remove('value')
        def valueMessagePrefix = attrs.remove('valueMessagePrefix')
        def noSelection = attrs.remove('noSelection')
        def disabled = attrs.remove('disabled')
        Set optGroupSet = new TreeSet();
        attrs.id = attrs.id ? attrs.id : attrs.name
        if (value instanceof Collection && attrs.multiple == null) {
            attrs.multiple = 'multiple'
        }
        if (noSelection != null) {
            noSelection = noSelection.entrySet().iterator().next()
        }
        if (disabled && Boolean.valueOf(disabled)) {
            attrs.disabled = 'disabled'
        }
        // figure out the groups 
        from.each {
            optGroupSet.add(it.properties[groupBy])
        }
        writer << "<select name=\"${attrs.remove('name')}\" "
        // process remaining attributes
        outputAttributes(attrs)
        writer << '>'
        writer.println()
        if (noSelection) {
            renderNoSelectionOption(noSelection.key, noSelection.value, value)
            writer.println()
        }
        // create options from list
        if (from) {
            //iterate through group set
            for(optGroup in optGroupSet) {
                writer << " <optgroup label=\"${optGroup.encodeAsHTML()}\">"
                writer.println()
                from.eachWithIndex {el, i ->
                    if(el.properties[groupBy].equals(optGroup)) {
                        def keyValue = null
                        writer << '<option '
                        if (keys) {
                            keyValue = keys[i]
                            writeValueAndCheckIfSelected(keyValue, value, writer)
                        }
                        else if (optionKey) {
                            if (optionKey instanceof Closure) {
                                keyValue = optionKey(el)
                            }
                            else if (el != null && optionKey == 'id' && grailsApplication.getArtefact(DomainClassArtefactHandler.TYPE, el.getClass().name)) {
                                keyValue = el.ident()
                            }
                            else {
                                keyValue = el[optionKey]
                            }
                            writeValueAndCheckIfSelected(keyValue, value, writer)
                        }
                        else {
                            keyValue = el
                            writeValueAndCheckIfSelected(keyValue, value, writer)
                        }
                        writer << '>'
                        if (optionValue) {
                            if (optionValue instanceof Closure) {
                                writer << optionValue(el).toString().encodeAsHTML()
                            }
                            else {
                                writer << el[optionValue].toString().encodeAsHTML()
                            }
                        }
                        else if (valueMessagePrefix) {
                            def message = messageSource.getMessage("${valueMessagePrefix}.${keyValue}", null, null, locale)
                            if (message != null) {
                                writer << message.encodeAsHTML()
                            }
                            else if (keyValue) {
                                writer << keyValue.encodeAsHTML()
                            }
                            else {
                                def s = el.toString()
                                if (s) writer << s.encodeAsHTML()
                            }
                        }
                        else {
                            def s = el.toString()
                            if (s) writer << s.encodeAsHTML()
                        }
                        writer << '</option>'
                        writer.println()
                    }
                }
                writer << '</optgroup>'
                writer.println()
            }
        }
        // close tag
        writer << '</select>'
    }
    void outputAttributes(attrs) {
        attrs.remove('tagName') // Just in case one is left
        attrs.each {k, v ->
            out << k << "=\"" << v.encodeAsHTML() << "\" "
        }
    }
    def typeConverter = new SimpleTypeConverter()
    private writeValueAndCheckIfSelected(keyValue, value, writer) {
        boolean selected = false
        def keyClass = keyValue?.getClass()
        if (keyClass.isInstance(value)) {
            selected = (keyValue == value)
        }
        else if (value instanceof Collection) {
            selected = value.contains(keyValue)
        }
        else if (keyClass && value) {
            try {
                value = typeConverter.convertIfNecessary(value, keyClass)
                selected = (keyValue == value)
            } catch (Exception) {
                // ignore
            }
        }
        writer << "value=\"${keyValue}\" "
        if (selected) {
            writer << 'selected="selected" '
        }
    }
    def renderNoSelectionOption = {noSelectionKey, noSelectionValue, value ->
        // If a label for the '--Please choose--' first item is supplied, write it out
        out << '<option value="' << (noSelectionKey == null ? "" : noSelectionKey) << '"'
        if (noSelectionKey.equals(value)) {
            out << ' selected="selected" '
        }
        out << '>' << noSelectionValue.encodeAsHTML() << '</option>'
    }
    private String optionValueToString(def el, def optionValue) {
        if (optionValue instanceof Closure) {
            return optionValue(el).toString().encodeAsHTML()
        }
        el[optionValue].toString().encodeAsHTML()
    }
}
This is my very first attempt and so I'm sure some not-so-best practices abound. Feedback and comments are welcome.
Update
- Previously, the tag wouldn't pass through extra attributes that were defined. This has been fixed.
 - My previous version didn't take into account a bunch of stuff that Grails' actual SELECT tag does. I was able to find the Grails implementation here. I modified it to take into account OPTGROUPs.
 
					
Thank you, most helpful!
You are amazing. I was looking for optgroup and copy pasted your code and it works 🙂
Thanks
Hey Vivin, Really nice work your code helped me out a lot, I made a few modifications and now have a nice taglib for simple group by select lists.
One improvement I would suggest is to check that the group option is not null, so up on line 36ish change the code
// figure out the groups
from.each {
optGroupSet.add(it.properties[groupBy])
}
to
// figure out the groups
from.each {
if (it.properties[groupBy]){
optGroupSet.add(it.properties[groupBy])
}
}
Thanks again
wzdhck
o73nha