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.
Reference: | Grails chained select – load data on one dropdown box depending on another from our JCG partner Jonathan Tan at the Grails cookbook blog. |