My domain looks something like this:
ProductLine hasMany Topic(s) hasMany Subtopic(s).
On Subtopic create.gsp view I want to have two dropdowns - first for selection of ProductLine, then in the second one I want to show Topics that belong to previously selected ProductLine. How to implement this?
Chaining two drop down boxes together isn't too hard, but chaining three or more can be difficult the first time. Below I chain three drop-down boxes together, but you should be able to derive from this example how to chain any number of drop-down boxes together.
In my Load
class, when I create a new instance, I need to know who the cargo provider was, what cargo source was used, and what the cargo was. For example, imagine I have a cargo provider named ACME Rock. Then imagine ACME Rock has two locations from which they provide cargoes, 123 Somewhere Road, and 456 Nowhere Road. These locations would represent cargo sources. Finally imagine that each cargo source offers different cargoes. We will imagine that 123 Somewhere Road only produces Rock and 456 Nowhere Road only produces Dirt. With all of that said here is what the drop-down boxes in the create view (create.gsp) for my Load
class look like.
<tr class="prop">
<td valign="top" class="name">
<label for=cargoProvider"><g:message code="load.cargoProvider.label" default="Cargo Provider"/></label>
</td>
<td valign="top" class="value ${hasErrors(bean: loadInstance, field: 'cargoProvider', 'errors')}">
<g:select from="${cargoProviders}" id="cargoProvider.id" name="cargoProvider.id" noSelection="['':'-Select-']" optionKey="id" optionValue="${{it.businessName?.toString() + ': ' + it?.toString()}}" value="${loadInstance?.cargoProvider?.id}"/>
</td>
</tr>
<tr class="prop">
<td valign="top" class="name">
<label for="cargoSource"><g:message code="load.cargoSource.label" default="Cargo Source"/></label>
</td>
<td id="cargoSourceCell" valign="top" class=" value ${hasErrors(bean: loadInstance, field: 'cargoSource', 'errors')}">
<g:select from="${loadInstance?.cargoSource}" id="cargoSource.id" name="cargoSource.id" optionKey="id" value="${loadInstance?.cargoSource?.id}"/>
</td>
</tr>
<tr class="prop">
<td valign="top" class="name">
<label for="cargo"><g:message code="load.cargo.label" default="Cargo"/></label>
</td>
<td id="cargoCell" valign="top" class="value ${hasErrors(bean: loadInstance, field: 'cargo', 'errors')}">
<g:select from="${loadInstance?.cargo}" id="cargo.id" name="cargo.id" optionKey="id" value="${loadInstance?.cargo?.id}"/>
</td>
</tr>
We begin chaining these drop-down boxes together with some JavaScript located on the same page (create.gsp). Remember to put your JavaScript right before the </body>
tag which closes the body element. Please notice too that I am using jQuery, not Prototype.
<g:javascript>
$(document).ready(function() {
$("#cargoProvider\\.id").change(function() {
var cargoSourceValue = $("#cargoSource\\.id").val();
$.ajax({
url: "/truckingmanagement/load/getCargoSources",
data: "id=" + this.value,
dataType: 'html',
cache: false,
success: function(result) {
$("#cargoSourceCell").html(result);
$("#cargoSource\\.id").val(cargoSourceValue);
$("#cargoSource\\.id").trigger('change');
}
});
});
});
</g:javascript>
<g:javascript>
function updateCargoes() {
var data = ($("#cargoSource\\.id").val() == null) ? "" : $("#cargoSource\\.id").val();
var cargoValue = $("#cargo\\.id").val();
$.ajax({
url: "/truckingmanagement/load/getCargoes",
data: "id=" + data,
dataType: 'html',
cache: false,
success: function(result) {
$("#cargoCell").html(result);
$("#cargo\\.id").val(cargoValue);
}
});
}
</g:javascript>
At first glance it may seem that the updateCargoes
function isn't doing anything, but it actually is. When a selection is made in the first drop-down box the second drop-down box is populated with the HTML generated by the Grails render
statement in my Load controller. This essentially replaces the original drop-down box with a new one, thus any attributes I had originally written into the drop-down box will be lost unless they are also included in the render
statement. That is why you see onchange: 'updateCargoes();
included in the render
statement of the getCargoSources
action of my Load controller below, as well as other attributes I need for the proper execution of my application. The attributes you include will vary depending on what exactly you want to do in your view and the ones I have picked are pretty standard. Writing the attributes twice is an annoyance, but it is better than the alternative of loading the entire data set into a drop-down box when a page loads, an act that might be very inefficient depending on how much data you have.
def getCargoSources = {
if(params.id == ""){
render g.select(name: 'cargoSource.id', onchange: 'updateCargoes(); updateTotal()')
return
}
def getCargoes = {
if(params.id == ""){
render g.select(name: 'cargo.id', onchange: 'updateTotal()')
return
}
def cargoSource = Address.get(params.id)
def cargoes = Cargo.findAll("from Cargo as cargoes where cargoes.cargoSource=:cargoSource", [cargoSource: cargoSource])
render g.select(from: cargoes, name: 'cargo.id', noSelection: noSelection, onchange: 'updateTotal()', optionKey: 'id')
}
At this point your chained drop-down boxes should be working correctly.
http://blog.osx.eu/2010/04/13/creating-dependent-selects-in-grails/
http://www.grails.org/AJAX-Driven+SELECTs+in+GSP