Creating dependent selects in Grails

Grails
In my previous post I explained how to use Ajaxify Grails Webflows, and this time I will address how to add two select boxes where the latter is depending on the input of the first. Let’s say we would like to add a select containing species, and a select containing genotypes of a particular species. As the latter is depending on the first, we would like the latter to automatically update whenever another species is selected.

First, we will create the two select elements in the view. The genotype select can be empty as it will be dynamically filled by the species select. The species select, however, is a little bit more complicated as it uses a list (speciesList) as input and contains an onChange action:

...
    <tr>
      <td class="title">Species</td>
      <td>
          <g:select
              name="species_id"
              value="${species?.id}"
              optionKey="id"
              optionValue="name"
              from="${speciesList}"
              noSelection="['':'-Choose species-']"
              onChange="${remoteFunction( action:'ajaxGetGenotypeBySpecies',
                                          params: '\'id=\'+escape(this.value)',
                                          onSuccess:'updateSelect(\'genotype_id\',data,false,false,\'default\')')}" />
      </td>
    </tr>
    <tr>
      <td class="title">Genotype</td>
      <td><g:select name="genotype_id" optionKey="id" optionValue="name" from="[]" /></td>
    </tr>
    ...

Looking at the two selects, you will see the species select will use Grails’ remoteFunction to remotely call ajaxGetGenotypeBySpecies in the controller. That particular action just fetches all genotypes for a particular species, and renders the list as a JSON object which, in turn, is given back to the calling JavaScript.

    // use Grails' converters to be able to render JSON
    import grails.converters.*

    ...

    /**
     * Fetch all genotypes for a species and render them as JSON
     * @param int species id
     * @return string JSON response
     */
    def ajaxGetGenotypeBySpecies = {
        // find species by id
        def species = Species.get(params.id)

        // fetch genotypes of a particular species
        def genotypes = Genotype.findAllBySpecies(species, [sort:'name',order:'asc'])

	// render as JSON
        render genotypes as JSON
    }

When the call has been a success, the updateSelect(\’genotype_id\’…) JavaScript function is executed, stating we would like to update the select element with genotype_id as id.

/**
 * Update one select based on another select
 *
 * @author you
 * @see     http://www.grails.org/Tag+-+remoteFunction
 * @param   string  select (form) name
 * @param   string  JSON data
 * @param   boolean keep the first option
 * @param   int     selected option
 * @param   string  if null, show this as option instead
 * @void
 */
function updateSelect(name,data,keepFirstOption,selected,presentNullAsThis) {
    var rselect = $('#'+name).get(0);
    if (data) {
        // remove old options
        var start = (keepFirstOption) ? 0 : -1;
        var i = rselect.length;
        while (i > start) {
            rselect.remove(i);
            i--;
        }

        // add new options
        $.each(items,function() {
            var i = rselect.options.length
            rselect.options[i] = new Option(
                (presentNullAsThis && this.name == null) ? presentNullAsThis : this.name,
                this.id
            );
            if (this.id == selected) rselect.options[i].selected = true;
        });
    }
}

Now that the whole chain is working, we have one last thing to do. When the user enters the page the first time, the species list is populated and the genotype list is empty. However, when he has already previously selected a species, we would like the genotype list to be populated and properly selected as well. In order to do this we will call do the following when a species is available:

    <g:if test="${species}"><script><g:remoteFunction action="ajaxGetGenotypeBySpecies" params="'id=${species.id}'" onSuccess="updateSelect('genotype_id',data,false,${genotype?.id},'default')" /></script></g:if>

And we’re done! :)

ps. Note that this is only one way to accomplish creating dependent selects. There are several other ways to accomplish the same result

Ajaxifying a Grails Webflow

Grails
The Grails framework is a very powerful MVC web framework based on technology that has proven itself (Spring, Hibernate, Java, etc). For a while now Grails has supported Spring webflows which is a great way to develop wizard like user interfaces and underlying logic. While Grails webflows work very well for full page refreshes, they do not really work in an Ajax manner to allow rendering of fractions of pages. This would be useful for a number of reasons:
- separation of templates and template elements
- limit page rendering to the things that change instead of the complete page
- reduce bandwidth and only download require JavaScript and CSS once when the complete page is loaded.
- bookmarking the wizard would only bookmark the initial wizard page
- etc

In order for a webflow to work with Ajax the webflow should be able to render and refresh only portions of a front-end. While Grails does provide a submitToRemote tag to perform an Ajax submit, the tag itself does not work with webflows. That is because a Grails webflow links form buttons to flow actions. So, if you have a submit button named ‘delete’, the delete action will be executed in the particular flow execution element. This is done by adding ‘_eventId_delete=1′ to the POST data. However, the Grails tag submitToRemote does not do that, so the webflow is not executed properly.

So, to solve this particular issue we have to create a new tag library to extend / modify the default submitToRemote tag behaviour. In the example below I have created a new tag library called myTagLib (grails create-tag-lib com.example.myTagLib) in which I extend the default tag library. It currently contains one method (‘tag’) called ajaxButton which wraps around the submitToRemote tag to add the webflow variable (_eventId_buttonName) to the Ajax call (note that I use jQuery as Ajax provider):

package com.example
import org.codehaus.groovy.grails.plugins.web.taglib.JavascriptTagLib

class MyTagLib extends JavascriptTagLib {
	// define the tag namespace (e.g.: <my:action ... />
	static namespace = "my"

	/**
	 * ajaxButton tag, this is a modified version of the default
	 * grails submitToRemote tag to work with grails webflows.
	 * Usage is identical to submitToRemote with the only exception
	 * that a 'name' form element attribute is required. E.g.
	 * <my:ajaxButton name="myAction" value="myButton ... />
	 *
	 * @see http://www.grails.org/WebFlow
	 * @see http://www.grails.org/Tag+-+submitToRemote
	 * @param Map attributes
	 * @param Closure body
	 */
	def ajaxButton = {attrs, body ->
		// get the jQuery version
		def jQueryVersion = grailsApplication.getMetadata()['plugins.jquery']

		// fetch the element name from the attributes
		def elementName = attrs['name'].replaceAll(/ /, "_")

		// generate a normal submitToRemote button
		def button = submitToRemote(attrs, body)

		/**
		 * as of now (grails 1.2.0 and jQuery 1.3.2.4) the grails webflow does
		 * not properly work with AJAX as the submitToRemote button does not
		 * handle and submit the form properly. In order to support webflows
		 * this method modifies two parts of a 'normal' submitToRemote button:
		 *
		 * 1) replace 'this' with 'this.form' as the 'this' selector in a button
		 *    action refers to the button and / or the action upon that button.
		 *    However, it should point to the form the button is part of as the
		 *    the button should submit the form data.
		 * 2) prepend the button name to the serialized data. The default behaviour
		 *    of submitToRemote is to remove the element name altogether, while
		 *    the grails webflow expects a parameter _eventId_BUTTONNAME to execute
		 *    the appropriate webflow action. Hence, we are going to prepend the
		 *    serialized formdata with an _eventId_BUTTONNAME parameter.
		 */
		if (jQueryVersion =~ /^1.([1|2|3]).(.*)/) {
			// fix for older jQuery plugin versions
			button = button.replaceFirst(/data\:jQuery\(this\)\.serialize\(\)/, "data:\'_eventId_${elementName}=1&\'+jQuery(this.form).serialize()")
		} else {
			// as of jQuery plugin version 1.4.0.1 submitToRemote has been modified and the
			// this.form part has been fixed. Consequently, our wrapper has changed as well...
			button = button.replaceFirst(/data\:jQuery/, "data:\'_eventId_${elementName}=1&\'+jQuery")
		}

		// render button
		out << button
	}
}

Now that we have this tag library in place we can add a webflow compatible submitToRemote button by adding the following tag:

<my:ajaxButton name="next" value="next »" url="[controller:'wizard',action:'pages']" update="[success:'wizardPage',failure:'wizardError']" class="prevnext" />

As the new ajaxButton tag is a wrapper of the submitToRemote tag it supports all arguments the submitToRemote tag supports, but in contrast it does not ignore the name argument. In fact, the name argument is required as it is responsible for executing webflow actions. The only thing which remains is to make sure the webflow only renders portions, and not complete pages.

Observe the following WizardController code:

package com.example

class WizardController {
    def index = {
      redirect(action:'pages')
    }

    def pagesFlow = {
      // render the main wizard page
      mainPage {
	 render(view:"/wizard/index")
         on("next") {
            println "next page!"
          }.to "pageTwo"
       }

       // render page one
       pageOne {
         render(view:"_one")
         on("next") {
           println "next page!"
         }.to "pageTwo"
       }

       // render page two
       pageTwo {
         render(view:"_two")
         on("next") {
             println "next page!"
         }.to "pageThree"
         on("previous") {
            println "previous page!"
         }.to "pageOne"
	}
    }
}

As seen in the webflow we have a two paged wizard. The mainpage renders the initial wizard page (index.gsp), while the pageOne and pageTwo actions only render portions of a page (respectively: pages/_one.gsp and pages/_two.gsp). We could split this up into the following four templates. Note that you can seperate templates even more, but for this example I will stick to these four:

index.gsp

<html>
 <head>
  <meta name="layout" content="main" />
 </head>
 <body>
  <g:render template="common/wizard"/>
 </body>
</html>

common/_wizard.gsp

  <div id="wizard" class="wizard">
    <h1>Proof of concept AJAXified  Webflow Wizard</h1>
    <g:form action="pages" name="_wizard" >
    <div id="wizardPage">
      <g:render template="pages/one"/>
    </div>
    <div id="wizardError" class="error"/>
    </g:form>
  </div>

pages/_one.gsp

...my form content for page 1...<br />
<my:ajaxButton name="next" value="next »" url="[controller:'wizard',action:'pages']" update="[success:'wizardPage',failure:'wizardError']" class="prevnext" />

pages/_two.gsp

...my form content for page 2...<br />
<my:ajaxButton name="previous" value="« prev" url="[controller:'wizard',action:'pages']" update="[success:'wizardPage',failure:'wizardError']" class="prevnext" />

As you can see, index.gsp will render default layout (add your CSS here) and renders common/_wizard.gsp. Wizard.gsp will, on it’s turn, render the first web flow template (pages/_one.gsp) which includes a form and the ajaxButton we created in the tag library above. This button will now use and Ajax call to submit to our webflow and execute the on(“next”) action in the pageOne part of our webflow, which will make the logic jump to the pageTwo part. Upon entry pageTwo will render pages/_two.gsp and return the rendered content to the Ajax call.

The ajaxButton contains update=”[success:'wizardPage'...]” which makes the Ajax call refresh the wizardPage div with the result of the Ajax call. Hence, the wizardPage div will now contain the rendered content of pages/_two.gsp with it’s own form.

Hence, we have Ajaxified our Grails webflow :)