Grails Cookbook - A collection of tutorials and examples

Grails chained select - load data on one dropdown box depending on another

This tutorial will show how to create two select boxes where the second select box's items are affected by the value of the first one. Developers encounters this commonly and it is also called chained select. An example is selecting a country, followed by selecting a state. The choices for states should be based on which country was selected.

Sample Output

Here is a sample output. We have a select box for category followed by selection for sub-category. Initially, when no items is selected on the first box, the second one is empty.

When the user chooses Color cateogory, sub-categories will show different color selection.

When the user chooses Shape cateogory, sub-categories will show different shape selection.


Test Domain Class

There are two domain classes in this example.

package asia.grails.test
class Category {
    static hasMany = [subCategories:SubCategory]
    String name
    public String toString() {
        return name
    }
}

package asia.grails.test
class SubCategory {
    static belongsTo = Category
    Category category
    String name
    public String toString() {
        return name
    }
}
This is just a simple one to many relationship.

Test Data

We populate test data via Bootstrap.groovy

import asia.grails.test.Category
import asia.grails.test.SubCategory
class BootStrap {
    def init = { servletContext ->
        if ( Category.count() == 0 ) {
            Category color = new Category(name:'Color').save()
            new SubCategory(category:color, name:'Red').save()
            new SubCategory(category:color, name:'Green').save()
            new SubCategory(category:color, name:'Blue').save()
            Category shape = new Category(name:'Shape').save()
            new SubCategory(category:shape, name:'Square').save()
            new SubCategory(category:shape, name:'Circle').save()
            Category size = new Category(name:'Size').save()
            new SubCategory(category:size, name:'Small').save()
            new SubCategory(category:size, name:'Medium').save()
            new SubCategory(category:size, name:'Large').save()
        }
    }
    def destroy = {
    }
}

We will have 3 categories: Color, Shape, and Size.

Chained Select Form

We display a form. Controller code is simple:

package asia.grails.test
class TestController {
    def form() {
    }
}

Content of form.gsp is this.

<%@ page import="asia.grails.test.Category" %>
<!DOCTYPE html>
<html>
	<head>
		<meta name="layout" content="main">
		<title>Chained Select Test</title>
		 <g:javascript library='jquery' />
	</head>
	<body>
        <div>
            <b>Category: </b>
            <g:select id="category" name="category.id" from="${Category.listOrderByName()}" optionKey="id"
                      noSelection="[null:' ']"
                      onchange="categoryChanged(this.value);" />
        </div>
        <div>
            <b>Sub-Category: </b>
            <span id="subContainer"></span>
        </div>
        <script>
            function categoryChanged(categoryId) {
                <g:remoteFunction controller="test" action="categoryChanged"
                    update="subContainer"
                    params="'categoryId='+categoryId"/>
            }
        </script>
	</body>
</html>

The second select box is rendered inside the span with id=subContainer. It is updated whenever category is changed (onchange="categoryChanged(this.value);). The method to render the sub categories is through AJAX call. The remoteFunction tag can be used to invoke a controller action asynchronously.

It is also important to include the JQuery library. This is done with this GSP code:

<g:javascript library='jquery' />

Since Grails 2.4, the Grails AJAX tags were deprecated. An alternative is to hand-code the javascript method. Here is an alternate implementation of categoryChanged function.

<script>
    function categoryChanged(categoryId) {
        jQuery.ajax({type:'POST',data:'categoryId='+categoryId, url:'/forum/test/categoryChanged',success:function(data,textStatus){jQuery('#subContainer').html(data);},error:function(XMLHttpRequest,textStatus,errorThrown){}});
    }
</script>

It is longer but still intuitive.

Here is the rest of the controller code that includes the method that will render the second select box.

package asia.grails.test
class TestController {
    def form() {
    }
    def categoryChanged(long categoryId) {
        Category category = Category.get(categoryId)
        def subCategories = []
        if ( category != null ) {
            subCategories = SubCategory.findAllByCategory(category, [order:'name'])
        }
        render g.select(id:'subCategory', name:'subCategory.id',
            from:subCategories, optionKey:'id', noSelection:[null:' ']
        )
    }
}
The controller's categoryChanged method is invoked by the AJAX function. It returns the select box HTML code and render inside the span with id=subContainer.