在本文中将给出一个例子来介绍使用AJAX
技术从服务端获得数据的三种方法。这个例子很简单,就是两个选择框(html
中的<select>
标签),通过选中第一个select
的某一项后,会从服务端得到一些数据,并加载到第2
个select
中。
方法一、从服务端获得XML
格式的数据
从服务端获得数据的最容易想到的方法就是在服务端反加一定格式的数据,一般是XML
格式,然后在服务端使用XMLDocument
或其他技术来读取这些数据,并生成<select>
标签中选项的格式文本(<option>
标签)。下面的addOptions
函数是这个例子的核心函数,它负责根据从服务端获得的数据生成<select>
标签中的<option>
标签。在这里所使用的方法是利用了<select>
标签的innerHTML
属性(
仅限于firefox)
,如果是IE
,要使用outerHTML
属性(IE
中<select>
标签的innerHTML
属性有一些小bug
,读者可以试着在IE
中使用innerHTML
属性,看看会发生什么情况)。addOptions
方法的实现代码如下:
//
select表示<select>对象,xml表示XMLDocument对象
function
addOptions(select, xml)
{
if
(select)
{
var
options
=
""
;
for
(
var
i
=
0
; i
<
xml.childNodes[
0
].childNodes.length ; i
++
)
{
if
(xml.childNodes[
0
].childNodes[i].nodeName
==
"
list
"
)
{
var
s
=
""
;
if
(isIE())
s
=
xml.childNodes[
0
].childNodes[i].text;
else
s
=
xml.childNodes[
0
].childNodes[i].textContent
options
+=
"
<option value='
"
+
s
+
"
'>
"
;
options
+=
s;
options
+=
"
</option>
"
}
}
var
id
=
select.id;
if
(isIE())
select.outerHTML
=
"
<SELECT id='
"
+
id
+
"
' onchange='onChange(this)'>
"
+
options
+
"
</SELECT>
"
;
else
select.innerHTML
=
options;
}
}
onReadState
函数将在XMLHttpRequest
对象的异步访问服务端时调用。当readyState
为4
时表示成功从服务端返回XML
数据。这个函数的实现代码如下:
//
myRequest表示XMLHttpRequest对象,selectId表示<select>标签的id属性值
function
onReadyState(myRequest, selectId)
{
if
(myRequest.readyState
==
4
)
//
4表示成功获得相应信息
{
try
{
var
xml
=
myRequest.responseXML;
//
获得XMLDocument对象
var
kind
=
document.getElementById(selectId);
//
获得<select>对象
addOptions(kind, xml);
//
向<select>标签中加入<option>标签
}
catch
(e)
{
alert(
"
onReadyState:
"
+
e);
}
}
}
getData
函数负责向服务端发送请求,并设置异步事件。实现代码如下:
function
getData(url, selectId)
{
var
myRequest
=
getXMLHTTPRequest();
//
获得一个XMLHttpRequest对象
if
(myRequest)
{
myRequest.onreadystatechange
=
function
()
//
接收获得数据状态的事件函数
{
onReadyState(myRequest, selectId);
}
try
{
myRequest.open(
"
post
"
, url,
true
);
}
catch
(e)
{
alert(e);
}
try
{
myRequest.send(
""
);
}
catch
(e)
{
alert(e);
}
}
}
现在本例子的核心代码已经实现完成,下一步就是在html
而加载时从服务端获得第1
个<select>
标签的数据,并将其加载到第1
个<select>
标签中。让我们先看一下这个静态的html
代码。
<!
DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
>
<
html
>
<
head
>
<
title
></
title
>
<
meta
http-equiv
="Content-Type"
content
="text/html; charset=UTF-8"
>
<
script
type
="text/javascript"
src
="myscript.js"
>
</
script
>
</
head
>
<
body
>
<
select
id
="bigKind"
onchange
="onChange(this)"
>
</
select
>
<
select
id
="smallKind"
>
</
select
>
</
body
>
</
html
>
从上面代码可以看出,这两个<select>
标签分别是bigKind
和smallKind
,里面并没有<option>
标签,这是因为<option>
标签要在javascript
里动态加载。下面我们先来加载bigKind
中的数据。
window.onload
=
onLoad
function
onLoad()
{
try
{
getData(
"
../GetXML
"
,
"
bigKind
"
);
}
catch
(e)
{
alert(
"
onLoad:
"
+
e);
}
}
其中
GetXML
是一个
Servlet
程序(读者可以将其换成其他的服务端程序,如
asp.net
、
php
的)。下面是这个
GetXML
程序的实现代码:
package
servlet;
import
java.io.
*
;
import
javax.servlet.
*
;
import
javax.servlet.http.
*
;
import
database.MyData;
public
class
GetXML
extends
HttpServlet
{
protected
void
processRequest(HttpServletRequest request, HttpServletResponse response)
throws
ServletException, IOException
{
response.setContentType(
"
application/xml;charset=UTF-8
"
);
PrintWriter out
=
response.getWriter();
try
{
String s
=
request.getParameter(
"
kind
"
);
out.println(
"
<data>
"
);
if
(s
==
null
)
{
for
(String key : MyData.data.keySet())
{
out.println(
"
<list>
"
+
key
+
"
</list>
"
);
}
}
else
{
s
=
java.net.URLDecoder.decode(s,
"
UTF-8
"
);
System.out.println(s);
java.util.List
<
String
>
smallKind
=
MyData.data.get(s);
if
(smallKind
!=
null
)
{
for
(String kind : smallKind)
{
out.println(
"
<list>
"
+
kind
+
"
</list>
"
);
}
}
}
out.println(
"
</data>
"
);
}
finally
{
out.close();
}
}
protected
void
doGet(HttpServletRequest request, HttpServletResponse response)
throws
ServletException, IOException
{
processRequest(request, response);
}
protected
void
doPost(HttpServletRequest request, HttpServletResponse response)
throws
ServletException, IOException
{
processRequest(request, response);
}
public
String getServletInfo()
{
return
"
Short description
"
;
}
}
不管读者会不会java
和servlet
,从这个程序中的processRequest
方法中都可以看出,首先会获得请求参数kind
,如果这个参数不存在,则返回bigKind
所需要的数据,以xml
格式返回,类似于如下的格式:
<
data
>
<
list
>
data1
</
list
>
<
list
>
data2
</
list
>
</
data
>
如果
kind
参数存在,则在
MyData.data
中查询第
2
个
<select>
标签
(smallKind)
所需要的数据。
data
是一个
Map
类型。
为了方便起见,本例子并未使用数据库,而是在
MyData
类中定义了一个静态的
Map
类型变量。
MyData
的实现代码如下:
package
database;
import
java.util.
*
;
public
class
MyData {
public
static
Map
<
String, List
<
String
>>
data;
static
{
data
=
new
HashMap
<
String, List
<
String
>>
();
List
<
String
>
eProducts
=
new
LinkedList
<
String
>
();
eProducts.add(
"
手机
"
);
eProducts.add(
"
数码/IT
"
);
eProducts.add(
"
家电
"
);
eProducts.add(
"
电脑
"
);
data.put(
"
消费电子
"
, eProducts);
List
<
String
>
goods
=
new
LinkedList
<
String
>
();
goods.add(
"
化妆
"
);
goods.add(
"
健康
"
);
goods.add(
"
玩具
"
);
goods.add(
"
办公/文体
"
);
goods.add(
"
童装童鞋
"
);
goods.add(
"
其他
"
);
data.put(
"
日用百货
"
, goods);
List
<
String
>
books
=
new
LinkedList
<
String
>
();
books.add(
"
小说
"
);
books.add(
"
动漫
"
);
books.add(
"
经济
"
);
books.add(
"
法律
"
);
books.add(
"
计算机
"
);
books.add(
"
英语
"
);
books.add(
"
通讯
"
);
books.add(
"
其他
"
);
data.put(
"
图书
"
, books) ;
}
}
其中
data
变量中的
key
值就是
bigKind
中的值,而每一个
key
对应的值(一个
List<String>
对象就是
smallKind
中值的列表)。下面我们来实现当第
1
个
<select>
标签
bigKind
变化时,更新
smallKind
标签。<select>的onchange事件函数的代码如下:
function
onChange(obj)
{
try
{
getData(encodeURI(encodeURI(
"
../GetXML?kind=
"
+
obj.options[obj.selectedIndex].value)),
"
smallKind
"
);
}
catch
(e)
{
alert(e);
}
}
这个函数是<select>
标签的onchange
事件函数。obj
表示<select>
标签本身。这个函数中只有一条有实际意义的语句,也就是调用了getData
方法,这个方法人在onLoad
方法中调用getData
时差不多,只是在传送url
时使用了两个encodeURI
方法。由于XMLHttpRequest
方法以utf-8
向服务端发送数据,因此,要使用两个encodeURI
向服务端发送%xx
形式的utf-8
编码,然后在服务端进行解析。我们在GetXML
中的processRequest
方法中可以找到如下的一条语句:
s
=
java.net.URLDecoder.decode(s,
"
UTF-8
"
);
就是进行解码操作。
注:如果在
IE
中,客户端可以不使用
encodeURI
对带中文的
URL
进行编码,服务端也不用解码。在服务端仍然可以正常显示中文。但在
firefox
中就必须要进行编码和解码。因此,要想跨浏览器,就需要使用本文所述的方法。
方法二、直接获得<option>...</option>
内容的字符串
上面的获得数据的方法是从服务端获得了一个XML
文档,并转换成XMLDocument
对象,然后解析。这种方法虽然很好,但是操作XMLDocument
对象还是有些麻烦,因此,我们可以在服务端直接反回<select>
标签所需要的<option>
标签字符串,然后将这些字符串传给<select>
对象的innerHTML
或outerHTML
就可以了。服务端的代码和上面的实现代码类似,只需要将<data>
去掉,然后将<list>
改为<option>
后,并使用如下的语句来设置ContentType
:
response.setContentType("text/html;charset=UTF-8");
客户端可通过XMLHttpRequest
对象的responseText
属性获得这些含有<option>
的文本,并将其赋给innerHTML
或outerHTML
属性。这种方法虽然很方便,但并不灵活。如果客户端不使用<select>
标签,而是使用<table>
或其他的标签显示数据,那么返回的这些数据就没什么用处了。而即方便,又灵活的应该是下面要介绍的方法。
方法三、从服务端返回javascript
代码,在客户端使用eval
函数执行
我们可以在服务端返回类似于如下的字符串:
var options = new Array();
options.push(‘data1’);
options.push(‘data2’);
然后使用
eval
函数执行上面的字符串,这样我们在
javascript
中就可以使用
options
数组了。我个人认为,使用数组要比使用
XMLDocument
更容易,代码量也更少。如果要返回更为复杂的数据,也可以使用
javascript
中的类或其他数据结构。根据上面的思想,新的
processRequest
方法的代码如下:
protected
void
processRequest(HttpServletRequest request, HttpServletResponse response)
throws
ServletException, IOException
{
response.setContentType(
"
text/html;charset=UTF-8
"
);
PrintWriter out
=
response.getWriter();
out.println(
"
var options = new Array();
"
);
try
{
String s
=
request.getParameter(
"
kind
"
);
if
(s
==
null
)
{
for
(String key : MyData.data.keySet())
{
out.println(
"
options.push('
"
+
key
+
"
');
"
);
}
}
else
{
s
=
java.net.URLDecoder.decode(s,
"
UTF-8
"
);
System.out.println(s);
java.util.List
<
String
>
smallKind
=
MyData.data.get(s);
if
(smallKind
!=
null
)
{
for
(String kind : smallKind)
{
out.println(
"
options.push('
"
+
kind
+
"
');
"
);
}
}
}
}
finally
{
out.close();
}
}
客户端经过改进的
addOptions
函数如下:
//
javascript表示从服务端返回的javascript代码字符串
function
addOptions(select, javascript)
{
if
(select)
{
if
(select.id
==
"
smallKind
"
)
{
if
(isIE())
select.options.length
=
0
;
}
var
myOptions
=
""
;
eval(javascript);
//
执行从服务端返回的javascript代码
for
(
var
i
=
0
; i
<
options.length ; i
++
)
//
从options数组中取数据
{
var
s
=
""
;
if
(isIE())
{
select.options[select.options.length]
=
new
Option(options[i], options[i]);
}
else
{
myOptions
+=
"
<option value='
"
+
options[i]
+
"
'>
"
;
myOptions
+=
options[i];
myOptions
+=
"
</option>
"
}
}
}
var
id
=
select.id;
if
(
!
isIE())
select.innerHTML
=
myOptions;
}
在上面的addOptions
方法中还有一个不同是在IE
中使用了<select>
对象的options
数组来添加选择项,而不是使用outerHTML
。这么做的好处是可以在onLoad
方法中就获得<select>
的选项值。而如果使用outerHTML
在html
未装载完时,<select>
标签中选择项仍然为0
。这样在onLoad
方法中就无法访问<select>
中的被加入项了,当然,在onchange
事件中可以。
在firefox
中使用innerHTML
时,在html
未装载完时,只要<select>
标签被装载完(也就是调用了addOptions
方法后),就可以访问<select>
标签中的<option>
了。个人感觉这一点要从IE
做得好。顺便说一句,笔者使用的是IE6
,不知道ie7
会是什么效果。如果哪位试过,可以跟贴。图1是本例的效果图
本来想提供asp.net的例子来着,结果不知怎么着,vs2008的asp.net设计视图突然不响应了,谁知道是怎么回事啊??
《银河系列原创教程》发布
《Java Web开发速学宝典》出版,欢迎定购