Last updated by
4 years ago
Page: AJAX-Driven SELECTs in GSP, Version:0
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:On our web page, we want to constrain the user to entering only Countries and Cities that our database knows about. This is a perfect use for SELECTs on our webpage. However, because City belongs to Country, we will need to change the City SELECT when the user change's the Country SELECT. Once-upon-a-time we'd either just reload the entire page in response to ONCHANGE events in the Country SELECT or we'd preload all the countries and cities into the page in Javascript.However, a modern web page can leverage AJAX and JSON to quickly and efficiently update the City select.First, in the CountryController, add the following handler:Remember to import the converters!You can test your AJAX call using curl from the command line:That was the easy part.Next, you need to wire up your SELECTs in a GSP page. Things get a little more complicated. You'll be using a AJAX library in your GSP page, so first you must include that in your page's HEAD element, eg:Next, you'll need a form with the SELECTs:The 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: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[http://dl-client.getdropbox.com/u/71682/Grails/widgetco.zip (+)].
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><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>