J2EE从入门到精通40-41-Ajax

更新时间:2023-03-08 09:15:32 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

40 Ajax

AJAX 指异步JavaScript及 XML(Asynchronous JavaScript And XML)。

国内翻译常为“阿贾克斯”和阿贾克斯足球队同音。Web应用的交互如Flickr,Backpack和Google在这方面已经有质的飞跃。这个术语源自描述从基于Web的应用到基于数据的应用的转换。在基于数据的应用中,用户需求的数据如联系人列表,可以从独立于实际网页的服务端取得并且可以被动态地写入网页中,给缓慢的Web应用体验着色使之像桌面应用一样。

Ajax的核心是JavaScript对象XmlHttpRequest。该对象在Internet Explorer 5中首次引入,它是一种支持异步请求的技术。简而言之,XmlHttpRequest使您可以使用JavaScript向服务器提出请求并处理响应,而不阻塞用户。

40.1 AsynchronousJavaScript+XML

Ajax这个名词是由JesseJamesGarrett提出,在他发表的Ajax: A New Approachto Web Applications这篇文章中谈到GoogleSuggest与GoogleMaps所使用到的技术,是他们在AdaptivePath中称之为Ajax的新方法:

GoogleSuggestandGoogleMapsaretwoexamplesofanewapproachtowebapplicationsthatweatAdaptivePathhavebeencallingAjax.ThenameisshorthandforAsynchronousJavaScript+XML,anditrepresentsafundamentalshiftinwhat?spossibleontheWeb.

在文中指出,Ajax是AsynchronousJavaScript+XML的简称,这指出了Ajax的核心观念(Asynchronous)与所使用到的主要两个技术(JavaScript、XML)。

Asynchronous为非同步,要了解Ajax,必须先了解为何要非同步。

现在许多的应用程式都是在Web上展现,这托了网际网路的普及之福,然而网路当初是Web应用程式繁荣的舞台,现在却也成了限制Web应用程式发展的因素。

限制的原因来自于网路延迟的不可确定性,网路连线其实是个很耗资源的行为,程式必须序列化、通讯协定沟通、实体路由传送等动作,这些动作都很耗时间与资源,所有透过网路必需的额外操作,常足以拖慢一个系统,就Web应用程式而言,通常只能透过表单进行资料提交,在同步的情况下,使用者送出表单之后,就只能等待远端伺服器回应,在这段时间内,使用者无法作进一步的操作。

1

上图中阴影部份为送出表单之后,使用者必须等待的时间,浏览器预设是使用同步的方式送出请求并等待回应,这也可以想像成存取一个很慢的硬碟,如果这是桌面应用程式,您就可以想像这种等待有多枯燥,人们之所以可以忍受Web应用程式的这种等待,某些原因是因为无从选择而习惯了。

如果可以把请求与回应改为非同步进行,也就是发出请求后,浏览器无需苦等伺服器的回应,而可以让使用者对浏览器中的Web应用程式进行其它的操作,当伺服器终于处理完请求并送出回应,而电脑接收到回应时,再回过头来呼叫浏览器所设定的对应动作进行处理。

现在的问题是,谁来发送非同步请求,事实上有几种解决方案,在Ajax这个名词被提出之前,早就有着用IFrame的方式,也就是在HTML页面中内嵌另一个HTML页面,由内嵌的页面来发出请求,而外围的HTML页面还是可以继续让使用者进行操作,有时候IFrame的解决方式会是简单的,甚至比较好的。

不过现在谈到Ajax,都着重在XMLHttpRequest对象,您可以使用JavaScript来建立,其实Firefox、NetScape、Safari、Opera中才叫XMLHttpRequest,InternetExplorer中是Microsoft.XMLHTTP或Msxml2.XMLHTTP的ActiveX对象,不过IE7中也正名为XMLHttpRequest。

Ajax应用程式是必须由客户端、伺服端一同合作的应用程式,JavaScript是用来撰写Ajax应用程式客户端的语言,XML则是请求时或回应时,建议使用的交换资料格式,Ajax的客户端与伺服端基本上是可以独立开发的,只要协议好沟通的资料格式,伺服端不限于使用何种技术。

您可能正在使用CyberArticle破解版本,该版本可能会导致无法完整保存网页(例如不能保存Flash,图片...)。请点击这里,购买正式版本(注册费仅需人民币25元),可以解决这个问题。

40.2XMLHttpRequest

在Ajax应用程式中,如果是Mozilla/Firefox/Safari中,可以透过XMLHttpRequest来发出非同步请求,如果是在IE6或IE先前版本,则是使用ActiveXObject来发出非同步请求,为了各个不同浏览器间的相容性,必须进行测试可取得XMLHttpRequest或ActiveXObject,例如:

varxmlHttp;

functioncreateXMLHttpRequest(){

if(window.XMLHttpRequest){//如果可以取得XMLHttpRequest xmlHttp=newXMLHttpRequest();//Mozilla、Firefox、Safari }

elseif(window.ActiveXObject){//如果可以取得ActiveXObject

xmlHttp=newActiveXObject(\

2

} }

这段程式码可以满足大部份的浏览器,您可以再进一步检查是否真正生成了对象,例如: if(xmlHttp){ //dorequest } else{

alert(\您的浏览器不支援这个Ajax程式的功能\}

在建立XMLHttpRequest之后,则可以使用以下的几种方法:

voidopen(stringmethod,stringurl,booleanasynch,stringusername,stringpassword) 开启对伺服端的连结;method为请求方式(GET、POST);url为伺服端位址,如果是GET的话,可加上请求参数与值;asynch为非同步设定,预设是true,表示使用非同步方式。

voidsend(content)

对伺服端传送请求,content这以放XML、输入串流、字串、JSON格式的内容,放进去会放在POST本体中发送。

voidsetRequestHeader(stringheader,stringvalue) 为HTTP请求设定一个给定的header设定值。 voidabort()

用来中断请求。

stringgetAllResponseHeaders()

传回一个字串,其中包含HTTP请求的所有回应标头。 stringgetResponseHeader(stringheader)

传回一个字串,其中包含指定的回应标头值。 XMLHttpRequest包括以下几个标准属性: onreadystatechange

参考至callback函式,readyState每次改变时,都会呼叫onreadystatechange所参考的函式。 readyState

会有0到4的数值,分别表示不同的请求状态: 0=未初始化的连线(uninitialized),还没呼叫open() 1=载入中(loading),呼叫open(),还没呼叫send() 2=已载入(loaded),呼叫send(),请求header/status准备好 3=互动中(interactive),正在与伺服器互动中 4=请求完成(completed),完成请求 responseText

伺服器传来的请求回应文字,会设定给这个属性。 responseXML

伺服器传来的请求回应如果是XML,会成为DOM设定给这个属性。 status

伺服器回应的状态码,例如200是OK,404为NotFound… statusText

伺服器回应的状态文字。

一个基本的Ajax请求可以是以下的片段: functionstartRequest(){

createXMLHttpRequest();//建立非同步请求对象

xmlHttp.onreadystatechange=handleStateChange;//设定callback函式

3

xmlHttp.open(\开启连结 xmlHttp.send(null);//传送请求 }

functionhandleStateChange(){//在这边处理非同步回应 … }

当每次readyState改变时,都会呼叫以上程式片段中设定的handleStateChange()函式,通常会在请求完成进行处理,所以您可以如以下的程式片段来处理回应:

functionhandleStateChange(){

if(xmlHttp.readyState==4){//测试状态是否请求完成 if(xmlHttp.status==200){//如果伺服端回应OK

alert(\伺服器回应\这边只取得回应文字 } } }

40.3AjaxGossip:Hello!Ajax!

来编写您第一个Ajax程序,使用非同步的方式向服务端取得数据,并加以显示,首先请准备一个HTML网页:

HelloAjaxEx-1.html

Hello!Ajax!Examples...

这个HTML网页会取得JavaScript文件,而按下按钮后,会执行startRequest()函式,JavaScript文件如下所示:

HelloAjaxEx-1.js varxmlHttp;

functioncreateXMLHttpRequest(){ if(window.XMLHttpRequest){ xmlHttp=newXMLHttpRequest(); }

elseif(window.ActiveXObject){

xmlHttp=newActiveXObject(\} }

functionstartRequest(){ createXMLHttpRequest();

4

xmlHttp.onreadystatechange=handleStateChange; xmlHttp.open(\xmlHttp.send(null); }

functionhandleStateChange(){ if(xmlHttp.readyState==4){ if(xmlHttp.status==200){

alert(\服务端回应:\} } }

在startRequest()中会建立XMLHttpRequest,并发出非同步请求取得HelloAjaxEx-1.txt,在当中只是简单的文字信息,注意如果当中要编写中文,则文字文件必须储存为UTF8,假设HelloAjaxEx1-1.txt如下编写:

HelloAjaxEx-1.txt

这是非同步请求的回应文字 您可以按下链结来观看结果。

您可以结合DOM来显示取得的回应文字,不必使用对话方块或重清(Refresh)网页,例如在网页中设定一个:

HelloAjaxEx-2.html

Hello!Ajax!Examples...

而HelloAjaxEx-2.js可以改写如下: HelloAjaxEx-2.js varxmlHttp;

functioncreateXMLHttpRequest(){ if(window.XMLHttpRequest){ xmlHttp=newXMLHttpRequest(); }

elseif(window.ActiveXObject){

xmlHttp=newActiveXObject(\} }

5

functionstartRequest(){ createXMLHttpRequest();

xmlHttp.onreadystatechange=handleStateChange; xmlHttp.open(\xmlHttp.send(null); }

functionhandleStateChange(){ if(xmlHttp.readyState==4){ if(xmlHttp.status==200){

document.getElementById(\xmlHttp.responseText; } } }

在这边为了简化范例,直接使用DOM对象的innerHTML属性,您可以按链结观看结果。

40.4发送请求参数

如果在请求时,要连带发送相关参数,若是使用GET的方式发送参数,则将参数附加在URL上即可,例如:

varurlAndqueryString=\xmlHttp.open(\xmlHttp.send(null);

如果发送请求时使用POST,那么将要发送的资料塞到send()中即可,例如: varurl=\

varqueryString=\xmlHttp.open(“POST\

xmlHttp.setRequestHeader(\xmlHttp.send(queryString);

由于塞到POST本体中的资料有可能是表单的name/value,也有可能是XML、JSON等格式,您必须告诉伺服端如何剖析表单本体内容,这可以设定Content-Type的header来告知,以name/value或JSON格式来说,就要设定Content-Type为application/x-www-form-urlencoded,如果是XML文件的话,则要设定text/xml。

以下以简单的实例示范如何以GET及POST发送请求参数,假设您用以下的Servlet来处理请求:

GetPostServlet.java

packageonlyfun.caterpillar; importjava.io.IOException; importjava.io.PrintWriter;

importjavax.servlet.ServletException;

importjavax.servlet.http.HttpServletRequest; importjavax.servlet.http.HttpServletResponse;

publicclassGetPostServletextendsjavax.servlet.http.HttpServletimplements

6

javax.servlet.Servlet{ publicGetPostServlet(){ super(); }

protectedvoiddoGet(HttpServletRequestrequest,

HttpServletResponseresponse)throwsServletException,IOException{ doResponse(request,response,\}

protectedvoiddoPost(HttpServletRequestrequest,

HttpServletResponseresponse)throwsServletException,IOException{ doResponse(request,response,\}

privatevoiddoResponse(HttpServletRequestrequest, HttpServletResponseresponse,Stringmethod) throwsServletException,IOException{

Stringname=request.getParameter(\response.setContentType(\PrintWriterout=response.getWriter(); out.println(method+\out.flush(); out.close(); } }

回应只是简单的传回发送的请求参数值,并加上GET或POST表示接收到何种请求,假设您使用以下的网页来发送请求:

GETPOSTEx-1.html

\

GET、POST

网页上分别有GET与POST两个按钮,按下后分别由GETPOSTEx-1.js中的doGetRequest()或doPostRequest()来发送GET、POST请求,假设GETPOSTEx-1.js撰写如下:

GETPOSTEx-1.js varxmlHttp;

7

functioncreateXMLHttpRequest(){ if(window.XMLHttpRequest){ xmlHttp=newXMLHttpRequest(); }

elseif(window.ActiveXObject){

xmlHttp=newActiveXObject(\} }

functioncreateQueryString(){//建立请求参数

return\取得文字方块值 }

functiondoGetRequest(){

varurl=\createXMLHttpRequest();

xmlHttp.onreadystatechange=handleStateChange; xmlHttp.open(\xmlHttp.send(null); }

functiondoPostRequest(){

varurl=\避免快取 createXMLHttpRequest();

xmlHttp.onreadystatechange=handleStateChange; xmlHttp.open(\

xmlHttp.setRequestHeader(\xmlHttp.send(createQueryString()); }

functionhandleStateChange(){ if(xmlHttp.readyState==4){ if(xmlHttp.status==200){

document.getElementById(\xmlHttp.responseText; } } }

由于POST时,网址URL并不会有所变化,在某些浏览器,如果请求的URL是相同的,则会利用快取中的资料,为了避免资料快取,则您可以故意加上一个timeStamp请求参数,附上当时系统的时间,如此每次请求时URL就不会相同。

完成以上程式后,在网页文字方块中输入\并按下GET按钮,则会传回\并显示在网页上,按下POST按钮,则会传回\并显示在网页上。

41.5XML传送与接收

Ajax客户端与服务端之间,可以使用XML作为资料传送、沟通的格式,Ajax客户端若要发送

8

XML,基本上就是将XML作为字串,塞在POST本体中发送,例如:

HandleXMLEx-1.js varxmlHttp;

functioncreateXMLHttpRequest(){ if(window.XMLHttpRequest){ xmlHttp=newXMLHttpRequest(); }

elseif(window.ActiveXObject){

xmlHttp=newActiveXObject(\} }

functionprepareXML(){ varxml=\

varoptions=document.getElementById(\for(vari=0;i

xml=xml+\} }

xml=xml+\returnxml; }

functionhandleSkills(){ varxml=prepareXML();

varurl=\createXMLHttpRequest();

xmlHttp.onreadystatechange=handleStateChange; xmlHttp.open(\

xmlHttp.setRequestHeader(\xmlHttp.send(xml); }

在上例中,将客户端于多选单中选择的资料,以下列的XML格式送出:

java vb csharp

而服务端接收到XML之后,针对XML加以分析,取得每个节点资料,如果使用Servlet作为服务器端,可以使用org.w3c.dom下所提供的DOM操作相关类别来剖析XML的内容,例如在以下的XMLServlet中,将取得的XML加以剖析,并以另一个XML文件重新发送回客户端。

XMLServlet.java

packageonlyfun.caterpillar; importjava.io.*;

importjavax.servlet.*; importjavax.servlet.http.*;

9

importjavax.xml.parsers.DocumentBuilder;

importjavax.xml.parsers.DocumentBuilderFactory; importjavax.xml.parsers.ParserConfigurationException; importorg.w3c.dom.Document; importorg.w3c.dom.NodeList; importorg.xml.sax.SAXException;

publicclassXMLServletextendsjavax.servlet.http.HttpServlet implementsjavax.servlet.Servlet{ protectedvoiddoPost(

HttpServletRequestrequest,HttpServletResponseresponse) throwsServletException,IOException{

Stringxml=readXMLFromRequestBody(request); DocumentxmlDoc=null; try{

DocumentBuilderbuilder=

DocumentBuilderFactory.newInstance().newDocumentBuilder(); xmlDoc=builder.parse(

newByteArrayInputStream(xml.getBytes())); }

catch(ParserConfigurationExceptione){ System.out.println(e); }

catch(SAXExceptione){ System.out.println(e); }

StringresponseXML=prepareXMLResponse(xmlDoc);

responseXML=responseXML+\response.setContentType(\response.getWriter().print(responseXML); }

privateStringreadXMLFromRequestBody(HttpServletRequestrequest){ StringBufferxml=newStringBuffer(); try{

BufferedReaderreader=request.getReader(); Stringline=null;

while((line=reader.readLine())!=null){ xml.append(line); } }

catch(Exceptione){

System.out.println(\读取有误…\

10

}

returnxml.toString(); }

privateStringprepareXMLResponse(DocumentxmlDoc){

NodeListselectedSkills=xmlDoc.getElementsByTagName(\

StringBufferxml=newStringBuffer(); xml.append(\

for(inti=0;i

Stringskill=selectedSkills.item(i).getFirstChild().getNodeValue(); xml.append(\xml.append(skill);

xml.append(\}

returnxml.toString(); } }

这个Servlet只是简单的示范如何剖析XML,并重新使用以下的XML格式,将选择的资料发送回客户端:

java vb csharp

将资料以新的XML格式发送回客户端,纯綷只是要示范客户端取得XML文件时,如果加以剖析处理,客户端接收到的资料如果是XML文件,则可以使用XMLHttpRequest的responseXML属性,尝试取得代表XML文件的DOM对象,之后就可以利用DOM的各种操作方法来剖析文件内容,例如:

functionhandleStateChange(){ if(xmlHttp.readyState==4){ if(xmlHttp.status==200){

varxmlDoc=xmlHttp.responseXML;//取得DOM对象

varresponses=xmlDoc.getElementsByTagName(\varout=\

for(vari=0;i

out=out+\}

document.getElementById(\} } }

在这边您所看到的三段JavaScript片段,组成HandleXMLEx-1.js,可以搭配以下的HTML来完成一个完整的客户端/伺服端程式:

11

HandleXMLEx-1.html

\

处理XML


如果执行这个程式,客户端所选择的资料以XML送出,伺服端加以剖析,并以另一个XML传回,客户端再剖析XML并显示标签中的文字,这是一个基本的XML传送与接收的例子。

40.6JSON传送与接收

在JSON中我们已经介绍过其基本格式,与XML相同,JSON只是一个文字格式,只要客户端与伺服端可以剖析它,就可以利用它作为传送资料的格式,但它是JavaScript的核心特性之一,所以在JavaScript中使用JSON作为资料格式很方便,您还可以在http://www.json.org找到处理JSON的程式库,包括客户端JavaScriptlibrary与伺服端的library。

相较于XML,JSON在资料表示时更为简洁,例如一个表示帐户的资料,XML中可能如下表示:

123456 Justin 1000

而使用JSON可以这么表示: varaccount={ number:\name:\balance:\};

12

您可以下载http://www.json.org/json.js,并将之加入至网页之中:

json.js中有JSONparser与JSONstringifier,例如它扩充了对象,增加了toJSONString()函式,您可以直接如下传回对象的JSON表示法:

functionAccount(number,name,balance){ this.number=number; this.name=name; this.balance=balance; }

varaccount=newAccount(123456,\alert(account.toJSONString()); 您可以按下执行范例观看结果。

您可以把JSON表示法以POST发送至伺服端,例如: HandleJSONEx-2.js varxmlHttp;

functioncreateXMLHttpRequest(){ if(window.XMLHttpRequest){ xmlHttp=newXMLHttpRequest(); }

elseif(window.ActiveXObject){

xmlHttp=newActiveXObject(\} }

functionprepareJSON(){ varnumber=document.getElementById(\ varname=document.getElementById(\ varbalance=document.getElementById(\ varaccount=newObject(); account.number=number; account.name=name; account.balance=balance; returnaccount.toJSONString(); }

functionhandleAccount(){ varjson=prepareJSON();

varurl=\createXMLHttpRequest();

xmlHttp.onreadystatechange=handleStateChange; xmlHttp.open(\

xmlHttp.setRequestHeader(\\xmlHttp.send(json); }

functionhandleStateChange(){

13

if(xmlHttp.readyState==4){ if(xmlHttp.status==200){

document.getElementById(\} } }

这个程式使用者输入的帐号资讯包装为对象,并使用JSON扩充的toJSONString()将之转换为JSON格式,之后POST至伺服端,伺服端可以至JSONinJava下载JSON伺服端的Parser,您可以撰写以下的程式:

JSONServlet.java

packageonlyfun.caterpillar; importjava.io.*;

importjavax.servlet.ServletException;

importjavax.servlet.http.HttpServletRequest; importjavax.servlet.http.HttpServletResponse; importorg.json.*;

publicclassJSONServletextendsjavax.servlet.http.HttpServlet implementsjavax.servlet.Servlet{ publicJSONServlet(){ super(); } protectedvoiddoPost(HttpServletRequestrequest,

HttpServletResponseresponse)throwsServletException,IOException{ Stringjson=readJSONString(request);

JSONObjectjsonObject=null; StringresponseText=null; try{

jsonObject=newJSONObject(json);

responseText=\帐号\\名称\余额\+jsonObject.getString(\}

catch(JSONExceptione){ e.printStackTrace(); }

response.setCharacterEncoding(\response.setContentType(\response.getWriter().print(responseText); }

privateStringreadJSONString(HttpServletRequestrequest){ StringBufferjson=newStringBuffer(); Stringline=null;

14

try{

BufferedReaderreader=request.getReader(); while((line=reader.readLine())!=null){ json.append(line); } }

catch(Exceptione){

System.out.println(e.toString()); }

returnjson.toString(); } }

这个Servlet剖析JSON,并将资料取出后再传回文字回应。

如果将JSON字串传回给客户端浏览器,则可以使用eval()将之运算为一个JavaScript对象以进行操作。

40.7输入验证

表单资料的验证可以分为客户端验证与伺服端验证,为了安全起见,避免客户端验证被跳过,伺服端验证往往是必须的。

当使用者在表单中输入资料时,在过去往往只能靠使用者完成资料输入,再按下“送出”按钮,才可以进行伺服端验证,现在可以利用非同步请求方式,在使用者某些栏位输入完成之后,就以非同步方式在背景发送请求给伺服端进行验证,若有误再以DOM更新页面讯息,使用者可以即时发现先前错误的输入。

举个例子来说,有个网页必须输入帐户号码与姓名,帐户号码是XXX-XXXXXX-X的格式,X为数字:

InputValidateEx-1.html

\

输入验证

帐户号码:

当使用者在帐户号码输入完成,转而输入姓名时,这时会触发onchange事件而执行validate()函式,您可以在validate()函式中发送号码栏位,以非同步方式向伺服端要求验证:

InputValidateEx-1.js varxmlHttp;

functioncreateXMLHttpRequest(){

15

if(window.XMLHttpRequest){ xmlHttp=newXMLHttpRequest(); }

elseif(window.ActiveXObject){

xmlHttp=newActiveXObject(\} }

functionvalidate(){

varnumber=document.getElementById(\varurl=\createXMLHttpRequest();

xmlHttp.onreadystatechange=handleStateChange; xmlHttp.open(\xmlHttp.send(null); }

functionhandleStateChange(){ if(xmlHttp.readyState==4){ if(xmlHttp.status==200){ varpass=xmlHttp.responseXML.getElementsByTagName( \

varmessage=xmlHttp.responseXML.getElementsByTagName( \ if(pass===\color=\

document.getElementById(\\} else{ document.getElementById(\} } } }

伺服端会传回XML作为回应,当中包括以下讯息:

false

输入的格式有误

所以程式上设定,当格式错误时为false,此时以红字在网页上显示错误讯息,如果正确则什么都不作。

您可以搭配以下这个简单的Servlet来进行验证: ValidateServlet.java

packageonlyfun.caterpillar; importjava.io.IOException; importjava.io.PrintWriter;

16

importjavax.servlet.ServletException;

importjavax.servlet.http.HttpServletRequest; importjavax.servlet.http.HttpServletResponse;

publicclassValidateServletextendsjavax.servlet.http.HttpServlet implementsjavax.servlet.Servlet{

protectedvoiddoGet(HttpServletRequestrequest,HttpServletResponseresponse) throwsServletException,IOException{ booleanpass=request.getParameter(

\

response.setContentType(\

response.setHeader(\response.setCharacterEncoding(\

Stringmessage=\输入的格式有误\

if(pass){

message=\!\}

PrintWriterout=response.getWriter(); out.println(\

out.println(\

out.println(\out.println(\out.close(); } }

40.8动态清单

除了要求使用者按下按钮之外,还有什么更直觉的方式可以取得资料?以这个观点出发,可以想到许多非同步请求回应的使用方式,例如,当使用者选择书籍类别完成后,触发onchange事件,自动将使用者的选择送出,取得下一个选单的资料并呈现在网页上,这个功能在桌面应用程式上很常见,但在Web应用程式来说,以前并不常见。

例如您设计的网页如下: DynamicListeEx-1.html

\

动态载入清单

17

种类…



书籍…

当使用者选择完书籍种类之后,另一个选单就会自动填上符合该重类的书籍,来看看JavaScript如何撰写:

DynamicListEx-1.js varxmlHttp;

window.onload=refreshBooklList; functioncreateXMLHttpRequest(){ if(window.XMLHttpRequest){ xmlHttp=newXMLHttpRequest(); }

elseif(window.ActiveXObject){

xmlHttp=newActiveXObject(\} }

functionrefreshBooklList(){

vartype=document.getElementById(\varurl=\

createXMLHttpRequest();

xmlHttp.onreadystatechange=handleStateChange; xmlHttp.open(\xmlHttp.send(null); }

functionhandleStateChange(){ if(xmlHttp.readyState==4){ if(xmlHttp.status==200){ clearBookList(); updateBookList(); }

18

} }

//清除上一次的显示结果 functionclearBookList(){

varbooks=document.getElementById(\while(books.childNodes.length>0){

books.removeChild(books.childNodes[0]); } }

//以回应更新资料

functionupdateBookList(){

varresults=xmlHttp.responseXML.getElementsByTagName(\varbooks=document.getElementById(\

varoption=null;

for(vari=0;i

option=document.createElement(\

option.appendChild(document.createTextNode(results[i].firstChild.nodeValue)); books.appendChild(option); } }

从伺服端要传回以下的XML,表示根据种类的查询结果:

Java学习笔记 C++学习笔记

JSP/Servlet学习笔记 Ajax学习笔记

您可以使用以下这个简单的Servlet模拟资料查询并传回XML回应的过程:RefreshBookServlet.java packageonlyfun.caterpillar; importjava.util.*;

importjava.io.IOException;

importjavax.servlet.ServletException;

importjavax.servlet.http.HttpServletRequest; importjavax.servlet.http.HttpServletResponse;

publicclassRefreshBookServletextendsjavax.servlet.http.HttpServlet implementsjavax.servlet.Servlet{

privatestaticMapinMemoryDB=newHashMap();

publicvoidinit()throwsServletException{ inMemoryDB.put(\

newString[]{\学习笔记\电脑图学\常见程式演算\设计模式\inMemoryDB.put(\

newString[]{\学习笔记\学习笔记\\学习笔记\学习笔记\

19

inMemoryDB.put(\

newString[]{\学习笔记\学习笔记\\学习笔记\学习笔记\}

protectedvoiddoGet(HttpServletRequestrequest,HttpServletResponseresponse) throwsServletException,IOException{ Stringtype=request.getParameter(\

StringBufferxml=newStringBuffer(\String[]books=(String[])inMemoryDB.get(type); for(inti=0;i

xml.append(\

response.setCharacterEncoding(\response.setContentType(\

response.getWriter().write(xml.toString()); response.getWriter().close(); } }

40.9自动表单填写

当使用者在您的网页上填写资料时,如果您的网站资料库中已经有该使用者的资料,那么您可以贴心的在他填写完部份栏位后,根据这些栏位,自动帮他显示其它相关的资料,以避免他仍要填写重复的资料。

举个例子来说,有个员工管理的网页: AutoFilledEx-1.html

\

自动表单填写

编号:

20

名称:

薪水:

住址:

当输入人员填完员工编号,如果资料库中有该编号的员工资料,那您可以使用非同步请求取得名称、薪水与住址资讯,并直接显示在网页上,这样输入人员就不用重复输入这些资料,来看看AutoFilledEx-1.js的内容:

AutoFilledEx-1.js varxmlHttp;

functioncreateXMLHttpRequest(){ if(window.ActiveXObject){

xmlHttp=newActiveXObject(\}

elseif(window.XMLHttpRequest){ xmlHttp=newXMLHttpRequest(); } }

functionreadEmployee(){

varid=document.getElementById(\

//如果没有输入id就清除栏位 if(id===\clearEmployee(); return; }

varurl=\

createXMLHttpRequest();

xmlHttp.onreadystatechange=handleStateChange; xmlHttp.open(\xmlHttp.send(null); }

21

functionhandleStateChange(){ if(xmlHttp.readyState==4){ if(xmlHttp.status==200){ updateEmployee(); } } }

functionupdateEmployee(){ varxml=xmlHttp.responseXML;

document.getElementById(\ xml.getElementsByTagName(\ document.getElementById(\ xml.getElementsByTagName(\document.getElementById(\ xml.getElementsByTagName(\ }

functionclearEmployee(){

document.getElementById(\document.getElementById(\document.getElementById(\}

当这个JavaScript使用id以非同步方式方式查询伺服端时,伺服端必须使用以下的XML格式来回应:

001

Justin 30000

NTU-M8-419

例如您可以使用以下这个简单的Servlet来搭配这个客户端网页: EmployeeServlet.java

packageonlyfun.caterpillar; importjava.io.IOException; importjava.io.PrintWriter; importjava.util.HashMap; importjava.util.Map;

importjavax.servlet.ServletException;

importjavax.servlet.http.HttpServletRequest; importjavax.servlet.http.HttpServletResponse; publicclassEmployeeServlet

extendsjavax.servlet.http.HttpServletimplementsjavax.servlet.Servlet{ privateMapinMemoryDB;

publicvoidinit()throwsServletException{

inMemoryDB=newHashMap();

22

Employeee1=newEmployee(\Employeee2=newEmployee(\

inMemoryDB.put(e1.id,e1); inMemoryDB.put(e2.id,e2); }

privateEmployeefindEmployee(Stringid){ Employeeemployee=inMemoryDB.get(id);

if(employee==null){

employee=newEmployee(id,null,0,null); }

returnemployee; }

protectedvoiddoGet(HttpServletRequestrequest,HttpServletResponseresponse) throwsServletException,IOException{

Employeeemployee=findEmployee(request.getParameter(\

response.setContentType(\

response.setHeader(\

PrintWriterout=response.getWriter(); out.println(\

out.println(\

out.println(\out.println(\out.println(\out.println(\out.close(); }

privateclassEmployee{ Stringid; Stringname; intsalary; Stringaddress;

Employee(Stringid,Stringname,intsalary,Stringaddress){ this.id=id;

this.name=name; this.salary=salary; this.address=address;

23

} } }

40.10网页聊天室

网页聊天的基本原理很简单,在使用者发送讯息给伺服端时,同时取回新的聊天讯息,在使用者没有发送讯息,同时查询伺服端是否有新的讯息,并显示在页面中。

不过重点就在于取得讯息或重新取得讯息的方式,在过去,是在让浏览器定时重新整理网页,每一次除了新的讯息之外,往往伴随着大量重复的HTML标签等内容。

如果使用非同步请求,取得XML回应讯息,并动态更新页面中显示聊天讯息的部份,这么一来,就可以节省掉下载重复页面内容的频宽,使用者的画面也会更稳定,不会因为重新整理而发生闪烁的感觉。

例如,您可以写一个简单的聊天页面: ChatRoomEx-1.html

\

聊天室

输入讯息:

聊天室讯息:

您可以在这个页面中的栏位中输入文字,而下方会有个显示讯息的区域,每次的新讯息将只在该区域更新,页面中其余的部份不用变动,所以不用重复下载,来看一下JavaScript的部份:

ChatRoomEx-1.js varxmlHttp;

functioncreateXMLHttpRequest(){ if(window.ActiveXObject){

xmlHttp=newActiveXObject(\}

elseif(window.XMLHttpRequest){ xmlHttp=newXMLHttpRequest(); }

24

}

functionsendMessage(){

varmsg=document.getElementById(\

//使用者只是随意按下传送钮,但文字栏位中没有文字 if(msg===\

//那就重新整理讯息区好了 refreshMessage(); return; }

//传送讯息

varparam=\//ajax请求

ajaxRequest(param); //清空文字栏位

document.getElementById(\}

//定时查询用这个

functionqueryMessage(){ varparam=\ajaxRequest(param); }

functionajaxRequest(param){

varurl=\createXMLHttpRequest();

xmlHttp.onreadystatechange=refreshMessage; xmlHttp.open(\

xmlHttp.setRequestHeader(\xmlHttp.send(param); }

functionrefreshMessage(){ if(xmlHttp.readyState==4){ if(xmlHttp.status==200){ //处理显示讯息的表格区域

vartable_body=document.getElementById(\ varlength=table_body.childNodes.length; vari;

for(i=0;i

//先移除原有的列(row)

table_body.removeChild(table_body.childNodes[0]); }

//处理取回的讯息

varmessages=xmlHttp.responseXML.getElementsByTagName(\ length=messages.length; for(i=length-1;i>=0;i--){ varmessage=messages[i].firstChild.data;

25

//在表格中新增一列来排列讯息 varrow=createRow(message); table_body.appendChild(row); }

//下次2秒后会再查询一下有无新讯息 setTimeout(queryMessage,2000); } } }

functioncreateRow(message){

varrow=document.createElement(\varcell=document.createElement(\

varcell_data=document.createTextNode(message); cell.appendChild(cell_data); row.appendChild(cell); returnrow; }

伺服端必须传回以下的XML格式,表示目前伺服端所管理的聊天室中可取得的讯息:

聊天讯息一 聊天讯息二 聊天讯息三

以下是个简单的聊天室Servlet: ChatRoomServlet.java packageonlyfun.caterpillar; importjava.io.IOException; importjava.io.PrintWriter; importjava.util.LinkedList; importjava.util.List;

importjavax.servlet.ServletException;

importjavax.servlet.http.HttpServletRequest; importjavax.servlet.http.HttpServletResponse; publicclassChatRoomServletextends

javax.servlet.http.HttpServletimplementsjavax.servlet.Servlet{

privatestaticLinkedListmessages=newLinkedList();

privateListaddMessage(Stringtext){ if(text!=null&&text.trim().length()>0){ messages.addFirst(newMessage(text)); while(messages.size()>10){ messages.removeLast(); } }

returnmessages; }

26

privateListgetMessages(){ returnmessages; }

protectedvoiddoPost(HttpServletRequestrequest,HttpServletResponseresponse) throwsServletException,IOException{ Listlist=null;

if(\Stringmsg=request.getParameter(\//中文处理

msg=newString(msg.getBytes(\list=addMessage(msg); }

elseif(\list=getMessages(); }

response.setContentType(\

response.setHeader(\response.setCharacterEncoding(\

PrintWriterout=response.getWriter(); out.println(\for(inti=0;i

out.println(\}

out.println(\out.close(); } }

Message.java

packageonlyfun.caterpillar; publicclassMessage{ privateStringtext;

publicMessage(Stringnewtext){ text=newtext;

if(text.length()>256){ text=text.substring(0,256); }

text=text.replace('<','['); text=text.replace('&','_'); }

publicStringgetText(){

27

returntext; } }

40.11进度列

如果伺服端收到客户端请求后,必须处理一段时间可以有回应,这时也许可以在客户端设计一个进度列,显示目前伺服端对于请求处理的进度,方式就是让客户端定时以非同步方式取得伺服端的请求进度,并以进度列显示在网页之上。

在这边举个最简单的例子,假设网页中有个按钮,按下去之后会作某些事: ProgressBarEX-1.html

\

进度列


ProgressBarEx-1.js中在按下按钮后,会暂时锁定按钮无法使用,并定时向伺服端取得进度,进度列是一个1像素宽、20像素高的图片,藉由改变它在网页中显示的宽度来模拟进度列的增长:

ProgressBarEx-1.js varxmlHttp;

functioncreateXMLHttpRequest(){ if(window.XMLHttpRequest){ xmlHttp=newXMLHttpRequest(); }

elseif(window.ActiveXObject){

xmlHttp=newActiveXObject(\} }

functiondoSomething(){

document.getElementById(\ pollProgress(); }

functionpollProgress(){

varurl=\createXMLHttpRequest();

28

xmlHttp.onreadystatechange=handleStateChange; xmlHttp.open(\xmlHttp.send(null); }

functionhandleStateChange(){ if(xmlHttp.readyState==4){ if(xmlHttp.status==200){ varpercent=xmlHttp.responseXML.getElementsByTagName( \ varprogressbar=document.getElementById(\ progressbar.innerHTML=

\ +percent+\ if(percent!=100){ //每两秒查询一次进度

setTimeout(pollProgress,2000); } else{ document.getElementById(\ } } } }

伺服端必须传回以下的XML格式表示进度:

10

您可以搭配下面这个超级简单的Servlet来摸拟伺服端的进度处理: ProgressServlet.java

packageonlyfun.caterpillar; importjava.io.IOException;

importjavax.servlet.ServletException;

importjavax.servlet.http.HttpServletRequest; importjavax.servlet.http.HttpServletResponse; importjava.io.*;

importjavax.servlet.http.*;

//超级简单的Servlet,只是用来测试客户端的Progressbarorz.. publicclassProgressServletextendsHttpServlet{ privateintcounter;

protectedvoiddoGet(HttpServletRequestrequest,HttpServletResponseresponse) throwsServletException,IOException{

PrintWriterout=response.getWriter(); response.setContentType(\

29

response.setHeader(\out.println(\out.println(\out.println(10*counter++); out.println(\out.println(\out.close();

if(counter>10){ counter=0; } } }

40.12提示文字

如果您的页面中有很多产品,对于每个产品您会希望有产品说明,但每个产品都会有大量的文字说明,使用者也许只对一两项产品有兴趣,却得下载其它产品的大量文字说明。

您可以使用非同步请求,在使用者将滑鼠指向产品时,从伺服端下载产品说明并显示,这样就可以节省大量的说明文字下载。

例如我的Gossip首页就是一个例子,以下是简化后的HTML内容: TipsEx-1.html

\

提示文字

\ll>

src=\

30

滑鼠指到书的图片上时,会取得书籍说明,滑鼠移开图片,说明会消失,您可以使用以下的JavaScript来达到这个功能:

TipsEx-1.js varxmlHttp; varbook;

functioncreateXMLHttpRequest(){ if(window.ActiveXObject){ xmlHttp=newActiveXObject(\}

elseif(window.XMLHttpRequest){ xmlHttp=newXMLHttpRequest(); } }

functiongetBookData(element){ createXMLHttpRequest(); book=element;

//懒得写Servlet了,直接取得.txt的文字XD varurl=element.id+\xmlHttp.open(\

xmlHttp.onreadystatechange=callback; xmlHttp.send(null); }

functioncallback(){

if(xmlHttp.readyState==4){ if(xmlHttp.status==200){ //计算提示文字位置 setTipsPosition(); //建立表格列(Row),并设定取得的文字 varrow=createRow(xmlHttp.responseText); document.getElementById(\ } } }

//清除说明

functionclearData(){

varbookDataBody=document.getElementById(\bookDataBody.removeChild(bookDataBody.childNodes[0]); document.getElementById(\}

functioncreateRow(data){

vartxtNode=document.createTextNode(data);

varcell=document.createElement(\cell.setAttribute(\cell.setAttribute(\cell.appendChild(txtNode);

31

varrow=document.createElement(\row.appendChild(cell); returnrow; }

functionsetTipsPosition(){ //计算说明的X座标

varend=book.offsetWidth+50; vartop=calculateOffset(book)+25;

varpopup=document.getElementById(\popup.style.border=\popup.style.left=end+\popup.style.top=top+\}

//计算说明的Y座标

functioncalculateOffset(field){ varoffset=0; while(field){ offset+=field.offsetTop; field=field.offsetParent; }

returnoffset; }

可以看一下执行结果。

40.13自动完成

想知道什么是自动完成(AutoComplete),可以试着在GoogleSuggest打几个字就知道了,事实上这个功能在桌面应用程式很常见,像是字典查询程式。

其基本原理就是,当您在打字时触发键盘事件,例如onkeyup事件,向伺服端发出非同步请求…

例如打了一个a,接着伺服端从资料库查询出a开头的所有资料,然后传回至客户端,客户端此时执行callback函式,将结果以一个表格来模拟下拉式选单,显示出所有可能的建议选项…

32

接着使用者可以从中选择选项,利用滑鼠事件,将使用者选择的项目放置至输入栏位中… 就某些程度来说,这有些类似动态清单,只不过这次是以每个使用者键入的文字来查询,而不是下拉式选单中加以设定的选项,而且为了美观,会需要使用DOM、CSS等,将画面模拟的跟视窗程式中的选单画面一样。

而且您必须考虑到打字者打字速度的问题,如果有个使用者打字速度很快,一秒钟可以按下键盘10次,那就是一秒钟发出10次请求,那么以像Google这样百万人次的伺服端来说,处理请求的负担会很重,您可以在客户端设定,大概一秒钟(或一个您觉得适当的时间)才将使用者目前已键入的文字送出至伺服端查询。

因为使用者查询的资料可能在上一次请求传回的结果集合中,所以另一个减少向伺服端请求的方法之一,就是将上次查询到的资料快取一份在客户端,如果使用者继续键入文字,那么先在快取中查询是否有符合的,如果有的话就直接用,如果没有再向伺服端查询。

程式撰写起来会有些复杂,有些程式库或框架,就直接提供有自动完成的元件,您只有略为遵守元件的规范(例如回应的XML格式),就可以拥有自动完成的功能。

事实上,手工编写Ajax应用程式实在很辛苦,有一个好用的程式库或框架,对于撰写Ajax应用程式来说是绝对必要的,在了解Ajax基本原理之后,接下来就是选择适合的程式库与框架。

41进阶JavaScript

41.1在网页中使用JavaScript

要在网页中使用JavaScript,您可以使用

33

您可以只撰写type属性或只撰写language属性,在HTML4中,建议撰写type属性设定,事实上,language的设定也可以是VBScript,如果没有撰写language设定,预设就是\,language属性上还可以指定版本,例如:

在language中指定了\,则当中的JavaScript必须要支援JavaScript1.5的浏览器才可以,其它浏览器则加以忽略。

不过一般来说,并不建议将JavaScript原始码撰写在HTML网页之中,而是将之储存为一个.js的档案,然后在HTML网页中引用它,例如:

被引用的.js档案,不再需要撰写

Top