When the user chooses Shape cateogory, sub-categories will show different shape selection.
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.
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.
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.