Last updated by renteln
2 months ago
AJAX-Driven SELECTs in GSP
A common pattern that pops up in Web development is the need to have one SELECT box change the contents of another SELECT box on the same page without doing a refresh.Take the following domains as an example:class Country {
String name
String abbr
String language static hasMany = [cities:City]
}class City {
String name
String timezone static belongsTo = [country:Country]
}import grails.converters.*class CountryController { def ajaxGetCities = {
def country = Country.get(params.id)
render country?.cities as JSON
}
}$ curl "http://localhost:8080/widgetco/country/ajaxGetCities/1"[ {"id":4,"class":"City","country":1,"name":"Dallas","timezone":"CST"}, {"id":1,"class":"City","country":1,"name":"New York","timezone":"EST"} ]"
<g:javascript library="prototype" /><form>
<g:select
optionKey="id" optionValue="name" name="country.name" id="country.name" from="${Country.list()}"
onchange="${remoteFunction(
controller:'country',
action:'ajaxGetCities',
params:'\\'id=\\' + escape(this.value)',
onComplete:'updateCity(e)')}"
></g:select>
<g:select name="city" id="city"></g:select>
</form>Where the 2 \ should be replaced by 1 \ (due to wiki syntax)![]()
If you need to pass more than 1 parameter, use the character & :params:'\\'id=\\' + escape(this.value)+\\'&id2=\\' + escape(this.value)'
In Grails 2 where JQuery is used by default, the return value is named "data" so the onComplete parameter has to beThe above GSP creates a simple form with two SELECTs and preloads the country.name SELECT from the database.To complete the wiring, you need to write some Javascript:onComplete:'updateCity(data)'
<g:javascript> function updateCity(e) {
// The response comes back as a bunch-o-JSON
var cities = eval("(" + e.responseText + ")") // evaluate JSON if (cities) {
var rselect = document.getElementById('city') // Clear all previous options
var l = rselect.length while (l > 0) {
l--
rselect.remove(l)
} // Rebuild the select
for (var i=0; i < cities.length; i++) {
var city = cities[i]
var opt = document.createElement('option');
opt.text = city.name
opt.value = city.id
try {
rselect.add(opt, null) // standards compliant; doesn't work in IE
}
catch(ex) {
rselect.add(opt) // IE only
}
}
}
}
// This is called when the page loads to initialize city
var zselect = document.getElementById('country.name')
var zopt = zselect.options[zselect.selectedIndex]
${remoteFunction(controller:"country", action:"ajaxGetCities", params:"'id=' + zopt.value", onComplete:"updateCity(e)")}</g:javascript>The above Code is for Grails 1.x where prototype is used. In Grails 2 with JQuery this has to be a bit different, especially the eval(...) is not necessary. "data" is a valid javascript object.Everything is wired up now. When you change the country.name SELECT, it will make an AJAX call to your Country controller. When the call completes, updateCity() will execute on your page, it being passed the block of JSON returned from your controller. The function evaluates the JSON and rebuilds the city SELECT.One final bit of code at the bottom of the page initializes the city SELECT to match the country.name SELECT when the page is loaded.You can download the working demo from Dropbox.There is a small typo in this example code:
<title>Demo of Selects</time>
<title>Demo of Selects</title>
"/" (controller: 'country', action: 'selectdemo')