来源:www.cncfan.com | 2006-3-15 | (有6361人读过)
我们将创建一个包含三个INPUT字段(Zip,City和State)的HTML页面。我们将保证,只要用户输入邮政区号的前三个数字,该页面上的字段就会用第一个匹配的状态值填充。一旦用户输入了所有五位邮政区号数,我们将立即决定和填充相应的城市。如果邮政区号无效(在服务器的数据库没有找到),那么我们将把邮政区号的边界设置为红色。这样的可视化线索有助于用户并且在现代浏览器中已经成为一种标准(作为一实例,当Firefox找到一个HTML页面中的匹配关键字时,它会高亮与你在浏览器查找域输入的内容一致的部分)。
让我们首先创建一个简单的包含三个INPUT字段的HTML:zip,city和state。请注意,一旦一个字符输入进邮政区号字段域中,即调用方法zipChanged()。JavaScript函数zipChanged()(见下)在当zip长度为3时调用函数updateState(),而在当zip长度为5时调用函数up-dateCity()。而updateCity()和updateState()把大部分的工作代理到另一个函数ask()。 Zip:<input id="zipcode" type="text" maxlength="5" onKeyUp="zipChanged()" style="width:60"/> City: <input id="city" disabled maxlength="32" style="width:160"/> State:<input id="state" disabled maxlength="2" style="width:30"/> <script src="xmlhttp.js"></script> <script> var zipField = null; function zipChanged(){ zipField = document.getElementById("zipcode") var zip = zipField.value; zip.length == 3?updateState(zip):zip.length == 5?updateCity(zip):""; } function updateState(zip) { var stateField = document.getElementById("state"); ask("resolveZip.jsp?lookupType=state&zip="+zip, stateField, zipField); } function updateCity(zip) { var cityField = document.getElementById("city"); ask("resolveZip.jsp? lookupType=city&zip="+zip, cityField, zipField); } </script>
函数ask()与服务器进行通讯并分配一个回调函数来处理服务器的响应(见下列代码)。后面,我们将分析具有双重特点的resolveZip.jsp的内容-它根据zip字段中的字符数查找city或state信息。重要的是,ask()使用了具有异步特点的XmlHttpRequest,这样填充state和city字段或着色zip字段边界就可以不必减慢数据入口而得以实现。首先,我们调用request.open()-它用服务器打开套接字频道,使用一个HTTP动词(GET或POST)作为第一个参数并且以数据提供者的URL作为第二个参数。request.open()的最后一个参数被设置为true-它指示该请求的异步特性。注意,该请求还没有被提交。随着对request.send()的调用,开始提交-这可以为POST提供任何必要的有效载荷。在使用异步请求时,我们必须使用request.onreadystatechanged属性来分配请求的回调函数。(如果请求是同步的话,我们应该能够在调用request.send之后立即处理结果,但是我们也有可能阻断用户,直到该请求完成为止。) HTTPRequest = function () { var xmlhttp=null; try { xmlhttp = new ActiveXObject("Msxml2.XMLHTTP"); } catch (_e) { try { xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); } catch (_E) { } } if (!xmlhttp && typeof XMLHttpRequest != 'undefined') { try { xmlhttp = new XMLHttpRequest(); } catch (e) { xmlhttp = false; } } return xmlhttp; }
function ask(url, fieldToFill, lookupField) { var http = new HTTPRequest(); http.open("GET", url, true); http.onreadystatechange = function (){ handleHttpResponse(http, fieldToFill,lookupField)}; http.send(null); }
function handleHttpResponse(http, fieldToFill, lookupField) { if (http.readyState == 4) { result = http.responseText; if ( -1 != result.search("null") ) { lookupField.style.borderColor = "red"; fieldToFill.value = ""; } else { lookupField.style.borderColor = ""; fieldToFill.value = result; } } }
为ask()所使用的HttpRequest()函数(见上)是一跨浏览器的XMLHTTPRequest的一个实例的构造器;稍后我们将分析它。到目前为止,请注意对于handleResponse()的调用是如何用一匿名函数包装的-这个函数是function(){handleHttpResponse(http,fieldToFill, lookupField)}。
该函数的代码是动态创建的并且在每次我们给http.onreadstatechange属性赋值时被编译。结果,JavaScript创建一个指向上下文(所有的变量都可以存取正在结束的方法-ask())的指针。这样以来,匿名函数和handleResponse()就能够被保证充分存取所有的上下文宿主的变量,直至到匿名函数的参考被垃圾回收站收集为止。换句话说,无论何时我们的匿名函数被调用,它都能无缝地参考request,fieldToFill和lookupField变量,就象它们是全局的一样。而且,每次ask()调用都将创建环境的一个独立拷贝,并且此时这些变量中保存有该函数将结束时的值。
现在,让我们分析一下函数handleResponse()。既然它能够在请求处理的不同状态下激活,那么该函数将忽略所有的情形-除了该请求处理完成之外-这相应于request.readyState属性等于4("Completed")。此时,该函数读取服务器的响应文本。与它的名字所暗示的相反,XmlHttpRequest的输入和输出都不必限于XML格式。特别地,我们的resolveZip.jsp(见源码中的列表1)返回普通文本。如果返回值为"unknown",那么该函数将假定邮政区号是无效的并且把查找字段(zip)边界颜色置为红色。否则,返回值被用于填充字段state或city,并且zip的边界被赋予一种缺省颜色。
XMLHttpRequest-传输对象
让我们返回到我们的XMLHTTPRequest的跨浏览器实现。最后一个列表包含一个HttpRequest()函数-它向上兼容于IE5.0和Mozilla 1.8/FireFox。为简化起见,我们只创建一个微软XMLHTTPRequest对象,而且如果创建失败,我们假定它是Firefox/Mozilla。
该函数的核心是XMLHTTPRequest-这是一个本机浏览器对象,它为包括HTTP协议的任何东西与服务器之间的通讯提供方便。它允许指定任何HTTP动词,头部和有效载荷,并且能够以异步或同步方式工作。不需要下载也不需要安装任何插件-尽管在IE的情形下,XMLHTTPRequest是一个集成到浏览器内部的ActiveX。因而,"Run ActiveX Control and Plugins"默认IE权限应该正好适合使用它。
最重要的是,XMLHTTPRequest允许一个到服务器的RPC风格的编程查询而不需要任何页面刷新。它以一种可预测的,可控制的方式来实现此-提供了到HTTP协议的所有细节的完整存取-包括头部和数据的任何定制格式。在以后的文章中,我们将向你展示其它一些业界协议-你可以在这些传输协议(如Web服务和XML-RPC)之上运行-它们极大地简化大规模应用程序的开发和维护。
五.服务器端逻辑
最后,服务器端的resolveZip.jsp被从函数ask()中调用(见所附源码中的列表1)。这个resolveZip.jsp在两种由当前的邮政区号长度所区分的独立的场所下被调用(见zipChanged()函数)。请求参数lookupType的值或者是state或者是city。为简化起见,我们将假定,两个文件state.properties和city.properties都位于服务器中C驱动器的根目录下。resolveZip.jsp逻辑负责用适当的预装载的文件返回查找值。 我们的支持AJAX的页面现在已经准备好了。
六.远程脚本技术-一种可选方法
一些更旧的AJAX实现是基于所谓的远程脚本技术。这种思想是,用户的行为导致经由IFRAME对服务器进行查询,而服务器用JavaScript作出响应,该脚本一旦到达客户端立即被执行。这与XMLHttpRequest方法相比存在较大的区别,在后者情况下,服务器响应数据而客户端解释数据。其好处是这种解决方案支持更旧的浏览器。
基于IFRAME示例的HTML部分(见所附源码中的列表2)与我们在XMLHTTPRequest场合下所用的极相似,但是这次我们将引入另外一个IFRAME元素-controller: Zip:<input id="zipcode" type="text" maxlength="5" onKeyUp="zipChanged()" style="width:60" size="20"/> City: <input id="city" disabled maxlength="32" style="width:160" size="20"/> State:<input id="state" disabled maxlength="2" style="width:30" size="20"/> <iframe id="controller" style="visibility:hidden;width:0;height:0"></iframe>
我们保持每次击键都调用zipChanged()一次,但是这一次,从zipChanged()中被调用的函数ask()(见所附源码中的列表3)负责设置IFRAME的src属性,而不是调用一个XMLHTTPRequest: function ask(url, fieldToFill, lookupField){ var controller = document.getElementById("controller"); controller.src= url+"&field="+fieldToFill.id+"&zip="+lookupField.id; }
服务器端逻辑由一个粗略的resolveZip.jsp(见所附源码中的列表4)所描述。它与它的XMLHTTPRequest对应物相区别-它返回JavaScript语句,这些语句设置变量字段lookup和city的全局值,而且一旦它到达浏览器即从全局窗口的执行上下文中调用函数response()。
函数response()是一修改版本的handleResponse()-这一函数可以免于处理未完成的请求(详见本文所附源码中的列表2)。
七. 难题
为简化起见,让我们"俯看"一下在我们的示例代码中的一些重要的问题:
1.事实-XMLHTTPRequest对象实例和回调函数调用在被使用以后并没被破坏-在每次调用后这有可能导致内存泄漏。适当编写的代码应该破坏或重用对象池中的这些实例。而且,客户端必须使用与服务器软件相同的对象管理技术。
2.在大多数情况下,错误往往得不到有效处理。例如,在方法ask()中对request.open()的调用可能引发一个异常,这是必须要捕获和处理的,即使在浏览器中没有设置JavaScript异常自动捕获功能。而handleResponse()函数又是另外一个例子。它必须要为可能的服务器端和通讯错误而检查headers和responseText值。在发生错误的情况下,它必须尽力恢复并/或者报告错误。正确开发的AJAX应用程序要尽可能避免"提交"松散的数据,因为往往存在线路断开和其它低级通讯的问题-所以这些程序必须建立一个强壮的和自恢复的框架为此提供支持。
3.当前服务器端框架提供相当多的功能-它们可以与一种自由刷新方法和谐相处。例如,让我们考虑一个定制的在指定时间内的服务器端认证的问题。在这种情况下,我们必须拦截到XMLHTTPRequest调用的安全系统响应,显示登录屏幕,然后在用户被认证后重新发出请求。
所有的这些问题只是一些典型的用低级API工作的任何应用程序代码,而且所有这些问题都能被解决。好消息是,解决这些问题所需要的技术十分相似于大多数Java开发技术,如Web服务,定制标签和XML/XSLT。唯一的区别在于,现在这些技术以下列形式用于客户端:
·Web服务-使用SOAP/REST/RPC等简单通讯标准
·客户端定制标签-打包丰富的客户端控件并集成AJAX功能
·数据操作-基于XML和基于XSLT技术
八. 小结
AJAX方法能够向人们提供一种与桌面应用程序相同的丰富的互联网体验。但是,我们必须有选择地使用AJAX技术,如当你仍在线购物时,你绝对不想让你的信用卡通过后台处理就悄悄地开始付款。AJAX会成为一种持续的动力吗?我们当然希望这样。在过去的五年时间内我们一直在努力开发AJAX应用程序并且能证明它是健全并且很有效的。然而,它要求一个开发者必须精通大量技术而不是在传统的"click-refresh"Web应用程序中所使用的那些。
|