技术开发 频道

精通 Grails: 使用 Ajax 实现多对多关系

  使用 Ajax 处理大量选项

  默认的选择控件是显示实际航空公司数量的不错选择。不幸的是,对于机场情况则有所不同。我在一年中可能会到达 40 或 50 个不同的机场。依我的经验,在一个字段中提供超过 15 或 20 个选择就有点令人讨厌了。

  幸运的是,机场的 IATA 编码在行业中得到了广泛应用。我研究航班的时候会见到它们。在预订航班时也会 在收据上显示。甚至在机票上也能见到。与要求用户滚动查找数百个可能的机场相比,要求他们输入 IATA 编码一个不错的替代方法。

  回顾一下我在本文开始部分介绍的 Book 示例。Amazon.com 在其主页上提供了显示所有库存图书的选择字段了吗?没有 — 它提供了一个文本字段,您可以在其中输入图书的标题、作者,如果您喜欢,甚至还可以输入国际标准图书编号(International Standard Book Number,ISBN)。我将在此处使用相同的技巧来处理 trip-planner 应用程序中的机场。

  将控件由一个选择字段更改为一个文本字段非常简单。但是,在我继续使用这种解决方案的方法之前,我想要花一些时间处理它的语义。iata 字段是一个没有形式限制的文本字段,但是我不能对用户输入的任何值都接受(如果您写错了您的名字,应用程序不会责备您;但是如果您输错了 IATA 编码,它就需要给出警告)。我希望这种反馈回立即发生,因为没有什么事情比每次输入无效值之后重复提交整个 HTML 表单更令人沮丧。

  所以,我不希望只是为了验证一个单个字段要在服务器间往返通信整个表单,或者每次都需要将数千个机场的 IATA 编码下载到客户端。该解决方案将数据保存在服务器上,并针对每个字段执行一个细粒度的 HTTP 请求,而不是对整个表单执行一个粗粒度的请求。这种技术称为执行 Ajax (Asynchronous JavaScript + XML) 请求(参见 参考资料,获取 Ajax 的介绍)。

  要使我的 Grails 应用程序支持 Ajax,我需要对 AirportController 进行调整,以接受 Ajax 请求,还要对视图进行调整,以执行 Ajax 请求。我将从 AirportController 入手。

  AirportController 已经拥有搭建好的闭包,用于返回一个 Airport 列表,并显示一个单独的 Airport。但是,这些现有的闭包返回的值是 HTML 格式的。我将添加一个返回原始数据的新闭包。一个选择是完全将 POGO 序列化,但是我的客户机是一个 Web 浏览器。不幸的是,JavaScript — 不是 Groovy — 才是 Web 浏览器支持的语言(Mozilla Foundation,您注意到了吗?)

  Ajax 中的 x 提醒了我,我可以返回 XML。如果将 grails.converters 包导入 AirportController 中,返回 XML 只需要一行代码,如清单 11 所示:

  清单 11. 从 controller 返回 XML

import grails.converters.*

class AirportController {
  def scaffold
= Airport
  
  def getXml
= {
    render Airport.findByIata(params.iata)
as XML
  }  
}

  这种解决方案的惟一问题是,JavaScript 对 XML 的原生支持要逊于 Groovy。对象关系映射器(比如 GORM)的好处在于,它可以将数据从非原生格式(存储在关系数据库中)无缝地转换为 Groovy。这个练习的 JavaScript 版本是将 Groovy 数据转换为 JavaScript Object Notation (JSON)(参见 参考资料)。幸运的是,与转换为 XML 代码一样,可以使用一行代码转换为 JSON。在清单 12 中,我向 getJson 闭包添加了一些错误处理,但在其他方面与 getXml 闭包等效:

  清单 12. 从 controller 返回 JSON

def getJson = {
  def airport
= Airport.findByIata(params.iata)
  
  
if(!airport){
    airport
= new Airport(iata:params.iata, name:"Not found")
  }
  
  render airport
as JSON
}

  要验证 JSON 转换是否生效,可以在 Web 浏览器中输入 http://localhost:9090/trip/airport/getJson?iata=den。应该会得到清单 13 中显示的响应(您也许需要在浏览器中选择 View > Source 查看 JSON 响应)。

  清单 13. JSON 响应

{"id":1,"class":"Airport","city":
  
"Denver","country":"US","iata":
"DEN","name":"Denver International Airport","state":"CO"}

  返回一组航空公司的过程非常简单:render Airline.list() as JSON。

  现在生成了 JSON,是时候使用它了。我将把 departureAirport 的现有 注释掉,并替换为清单 14 中的 4 行代码:

  清单 14. 使用一个文本字段替换选择字段

<div id="departureAirportText">[Type an Airport IATA Code]</div>
<input type="hidden" name="departureAirport.id" value="-1"
   id
="departureAirport.id"/>          
<input type="text" name="departureAirportIata" id="departureAirportIata"/>
<input type="button" value="Find" onClick="get('departureAirport')"/>

  第一行是一个只读显示区域。注意,它具有一个 id。ID 在整个 HTML Document Object Model (DOM) 中必须是惟一的。稍后我将使用句柄 departureAirportText 写出 JSON 调用的结果。

  提交表单时,

不会被发送回服务器;但表单控件(比如输入和选择控件)会被发送回服务器。当整个表单提交到服务器时,隐藏的文本字段提供了一个存储 Airport 的 id 的位置。

 

  用户将在名为 departureAirportIata 的文本字段中输入 IATA 编码。为名称和 ID 都提供相同的值可能不是很好,但 HTML 机制需要这样做。提交表单时,名称将会传回到服务器。ID 是我调用 getJson 闭包的条件。

  最后,最后一行代码是一个按钮,单击它时,会调用一个名为 get 的 JavaScript 函数。稍后我将会展示 get 函数的实现。图 2 展示了新表单的外观:

  图 2. 改进的表单  

0
相关文章