Thursday, 18 December 2008

grails + ajax: chained selects

From time to time, I run into the following problem. A User has to fill in a form containing 2 drop down lists, and I need to populate the second one with options that depend on the selection made on the first select box. This problem, commonly known as 'Chained Selects' can be easily solved using a dusting of ajax magic.

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>
<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.

11 comments:

KMR said...

hey Mo....Guess what your solution worked for me. Thanks buddy!

Mo Sayed said...

@KMR, Thanks for your kind comment. I'm glad you managed to find something useful from the blog.

Lost Soul ( Nitin Pande ) said...

Very helpful, thanks :)

John Beatty said...

I cannot get the javascript to work. Implemented all your code, and put a println in updateSelect, which doesn't get called.

I put the JS in the head, and put the template in the general views and templates folder (at least that is what netbeans calls it)

Thanks

Anonymous said...

Hey man,

So far your code have helped me a lot and I would like to thank you for that.

Would you mind helping me out a little more? I would like to know the possible alternatives and its implications of not using a template to get the desired result.

jabrantes said...

Great Jobs! I get work with two selects, but don't work for three selects. The second select don´t fire the change event. Have you any idea?

Unknown said...

Very helpful stuff; two comments though.
1) I guess your initial date for Griffins are switched (i.e. Griffin is stored a first name)
2) more important - the url in the Ajax.Updater doesn't work if deployed in a war file. In this case the property app.version must be added to the project name (e.g. familySelect-1.0)

Lombi said...

what do i have to do, that the second dropdown shows the right users when the site is first loaded? Now I have to klick on the first dropdown so that the second one changes.
thx

Romeryto said...

I used the example in my application, but have an problem because here is three selects(comboBox), Country, State and City selects. When I select Country I call the action in grails and the State select is populated. After I select the state, but the City select isn't populated. But, if I refresh the page and first I select the State select, the City select, is populated.
The select that is observed and that observe too doesn't work correctly.
What's problem in this case?

alfred said...

The other is the manner of which you or your developers implemented the application. There are several tools that you can use to automate the testing of AJAX applications.

ajax jobs

Imran Salroo said...

Hi ... I have been watching ur blogs and they are interesting ...

I am developing a grails application and came across a problem where I have to populate more than one drop downs (say dropdown2, dropdown3 etc) on selecting a particular value from dropdown3. How can I achieve that?