首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

精通 Grails:JSON 與 AJAX 打造非同步 Web 2.0 體驗

     編輯:Java 相關知識

本文聚焦 Grails 內建的 JSON 與 AJAX 支援,構建旅行規劃頁:輸入出發/目的地機場代碼,Google Maps 顯示航線,AJAX 查詢目的地附近飯店。

歷史脈絡:從 XMLHTTP 到 AJAX 再到 JSON

  • 1999 IE5 引入 XMLHTTP,Mozilla 2002 標準化為 XMLHttpRequest
  • 2005 Google Maps 以非同步請求重新定義 Web 地圖體驗
  • Jesse James Garrett 命名「AJAX」,成為 Web 2.0 代名詞
  • XML 客戶端解析困難,Douglas Crockford 2006 推 RFC 4627 定義 JSON
  • Yahoo!、Google 相繼支援 JSON 輸出

JSON 優勢:輕量、原生 JavaScript 物件

{"city":"Denver","state":"CO","country":"US"}
// vs XML 重複標籤開銷

// 陣列語法同 Groovy List
[{"city":"Denver"},{"city":"Chicago"}]

// JavaScript 一行載入
var result = eval("(" + json + ")")
alert(result.city)

Controller 產出 JSON:一行搞定

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"}

整合測試 Controller

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 請求。

JUnit 測試通過

Google Maps 初始化

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

基本 Google Map

<g:formRemote> 非同步表單送出

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

含表單欄位的搜尋區

JavaScript 處理 JSON 回應

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

顯示兩機場與連線

遠端 JSON:跨域限制與代理模式

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 管理與監控功能。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved