The principle behind the solution is reasonably straightforward and involves attaching a listener to an HTML element such as a select field. This listener detects events triggered when a User moves the mouse, clicks on an element, etc. In our case, we're interested in the event when the User makes a selection. The listener will then fire a javascript function to perform an ajax call to the server. This call will inform the server as to the selection made on the first select element. Based on this information, the Server will create an HTML snippet containing the second select element and populate it with the correct set of options. This snippet is then sent back as a response to the ajax function, which updates the page accordingly.
To demonstrate this feature, I'm using a simple example containing 2 domain classes, Family and Member. These are listed below,
class Family{
String surname
String description
static hasMany = [members : Member]
static constraints = {
surname(blank:false)
description(blank:false)
members(nullable:true)
}
String toString()
{
return surname
}
}
and,
class Member{
String firstName
String lastName
String username
static belongsTo = Family
Family family
static constraints = {
firstName(blank:false)
lastName(blank:false)
username(blank:false)
family(nullable:true)
}
String toString()
{
return lastName + ", " + firstName
}
}
The example revolves around 2 drop down lists. The first of these lists families, e.g. Simpsons and Griffins [Family Guy]. Now I want the second drop down list to be correctly populated with the family members depending on the selection made to the first drop down list.
Step 1: Import 'prototype'
We're going to use prototype to implement this feature. Note you're not restricted to using this library. Others such as jQuery will do an equally impressive job. So the first step is to import in the library as follows,
<g:javascript library="prototype"/>
Step 2: Implement 2 drop down lists
Next we need to implement the 2 select elements as so,
<td>
<label for="family">Family:</label>
</td>
<label for="family">Family:</label>
</td>
<td>
<g:select id="familySelect" from="${Family.list().surname}" name="familySelected" value="">
</g:select>
</td>
<td valign="top" class="name">
<label for="member">Member:</label>
</td>
<td>
<div id="memberSelect">
<g:select from="${Member.list()}" name="memberSelected" value="">
</g:select>
</div>
</td>
Step 3: Hook up the ajax code
<g:javascript>
document.observe('dom:loaded', function() {
$("familySelect").observe("change", respondToSelect);
});
function respondToSelect(event)
{
new Ajax.Updater("memberSelect",
"/chainedSelect/family/updateSelect",
{method:'get', parameters: {selectedValue : $F("familySelect")} }
);
}
</g:javascript>
Here we do a couple of things; first of all we attach a listener to the 'familySelect' element to detect for any 'change' events fired when a User makes a selection on this element. Secondly we implement the ajax function that will be triggered when such an event is detected. In this instance we're using prototype's Ajax.Updater() function, which takes a number of arguments that are fully documented elsewhere. The first argument is the id of the element that will be updated by the response to the ajax function. In our case this will be a second drop down list. The second argument is a url pointing to a grails action to service our ajax request. Finally we have a set of optional parameters, the important one being 'selectedValue' which will hold the selection made by the User on the first drop down list.
Step 4: Write the grails action
def updateSelect = {
def familySelected = Family.find("from Family as family where family.surname=:surname", [surname:params.selectedValue])
render (template:"selectMember", model : ['familySelected' : familySelected])
}
This the grails action that will service the ajax call. Quite simple really. It takes the value selected from the Family dropdown list and uses it to retrieve the corresponding Domain object. This object is then supplied to a tempate file that is subsequently returned as a response to the ajax function in step 3.
Step 5: Write the HTML to update the page.
<g:select
from="${familySelected.members}"
name="memberSelected" value="">
</g:select>
This is the HTML that will be used to update the page by the ajax function defined above. Note that since I'm using templates, I need to save this in a file whose name should be prefixed with an underscore, '_'.
And here is the final result.
When we select 'Simpsons' as the Family, the second drop down is correctly populated with the members of the Simpson family.
Conversely, if we select 'Griffin' as the family, the second drop down list is populated with the members of the Griffin family.
As a final note, this example could be improved by displaying a default set of values for both selects, when the page is first loaded.
You can download the code [chainSelectFamily.zip] from here.