本文聚焦 Grails 內建的 JSON 與 AJAX 支援,構建旅行規劃頁:輸入出發/目的地機場代碼,Google Maps 顯示航線,AJAX 查詢目的地附近飯店。
XMLHTTP,Mozilla 2002 標準化為 XMLHttpRequest{"city":"Denver","state":"CO","country":"US"}
// vs XML 重複標籤開銷
// 陣列語法同 Groovy List
[{"city":"Denver"},{"city":"Chicago"}]
// JavaScript 一行載入
var result = eval("(" + json + ")")
alert(result.city)
import grails.converters.*
class AirportMappingController {
def iata = {
def iata = params.id?.toUpperCase() ?: "NO IATA"
def airport = AirportMapping.findByIata(iata)
if (!airport) airport = new AirportMapping(iata: iata, name: "Not found")
render airport as JSON
}
}
測試:
{"id":328,"class":"AirportMapping","iata":"DEN","lat":"39.8584","lng":"-104.667","name":"Denver International","state":"CO"}
class AirportMappingControllerTests extends GroovyTestCase {
void testWithBadIata() {
def controller = new AirportMappingController()
controller.metaClass.getParams = {-> return ["id":"foo"] }
controller.iata()
assertTrue controller.response.contentAsString.contains('"name":"Not found"')
}
void testWithGoodIata() {
def controller = new AirportMappingController()
controller.metaClass.getParams = {-> return ["id":"den"] }
controller.iata()
assertTrue controller.response.contentAsString.contains("Denver")
}
}
Groovy MetaProgramming 直接注入 params,無需 HTTP 請求。

<script src="https://maps.google.com/maps?file=api&v=2&key=YourKeyHere"></script>
<script>
var map, usCenterPoint = new GLatLng(39.833333, -98.583333), usZoom = 4
function load() {
if (GBrowserIsCompatible()) {
map = new GMap2(document.getElementById("map"))
map.setCenter(usCenterPoint, usZoom)
map.addControl(new GLargeMapControl())
map.addControl(new GMapTypeControl())
}
}
</script>
<body onload="load()" onunload="GUnload()">
<div id="search" style="width:25%; float:left"><h1>Where to?</h1></div>
<div id="map" style="width:75%; height:100%; float:right"></div>
</body>

<g:formRemote name="from_form"
url="[controller:'airportMapping', action:'iata']"
onSuccess="addAirport(e, 0)">
From:<br/>
<input type="text" name="id" size="3"/>
<input type="submit" value="Search"/>
</g:formRemote>
<div id="airport_0"></div>
<g:formRemote name="to_form"
url="[controller:'airportMapping', action:'iata']"
onSuccess="addAirport(e, 1)">
To:<br/>
<input type="text" name="id" size="3"/>
<input type="submit" value="Search"/>
</g:formRemote>
<div id="airport_1"></div>

<g:javascript library="prototype" />
<script>
var airportMarkers = [], line
function addAirport(response, position) {
var airport = eval("(" + response.responseText + ")")
var label = airport.iata + " -- " + airport.name
var marker = new GMarker(new GLatLng(airport.lat, airport.lng), {title: label})
marker.bindInfoWindowHtml(label)
if (airportMarkers[position]) map.removeOverlay(airportMarkers[position])
if (airport.name != "Not found") {
airportMarkers[position] = marker
map.addOverlay(marker)
}
document.getElementById("airport_" + position).innerHTML = airport.name
drawLine()
showHotelsLink()
}
function drawLine() {
if (line) map.removeOverlay(line)
if (airportMarkers.length == 2) {
line = new GPolyline([airportMarkers[0].getLatLng(), airportMarkers[1].getLatLng()])
map.addOverlay(line)
}
}
function showHotelsLink() {
if (airportMarkers[1]) {
document.getElementById("hotels_link").innerHTML =
"<a href='#' onClick='loadHotels()'>Show Nearby Hotels...</a>"
}
}
</script>

Yahoo! Local Search API 回傳飯店 JSON,但瀏覽器**同源策略**阻擋直接 AJAX 呼叫。
解法:Controller 代理
class HotelController {
def near = {
def addr = "https://local.yahooapis.com/LocalSearchService/V3/localSearch?"
def qs = ["appid=YahooDemo", "query=hotel", "sort=distance", "output=json",
"latitude=${params.lat}", "longitude=${params.lng}"]
def url = new URL(addr + qs.join("&"))
render(contentType: "application/json", text: url.text)
}
}
優點:繞過同源限制、封裝外部 API 變更、未來可加入快取層。
function loadHotels() {
var url = "${createLink(controller:'hotel', action:'near')}"
url += "?lat=" + airportMarkers[1].getLatLng().lat()
url += "&lng=" + airportMarkers[1].getLatLng().lng()
new Ajax.Request(url, {
onSuccess: function(req) { showHotels(req) },
onFailure: function(req) { displayError(req) }
})
}
function showHotels(response) {
var results = eval("(" + response.responseText + ")")
var html = "<ul>"
for (var i=0; i < results.ResultSet.totalResultsReturned; i++) {
html += "<li>" + results.ResultSet.Result[i].Title + "<br/>"
html += "Distance: " + results.ResultSet.Result[i].Distance + "<br/><hr/></li>"
}
html += "</ul>"
document.getElementById("hotels").innerHTML = html
}
function displayError(response) {
var html = "status=" + response.status + "<br/>" + response.responseText
document.getElementById("hotels").innerHTML = html
}

約 150 行代碼完成:本地/遠端 JSON、Prototype AJAX、Google Maps 整合、跨域代理模式。下一篇將探討 Grails JMX 管理與監控功能。