在本文中将给出一个例子来介绍使用 Ajax 技术从服务端获得数据的三种方法。这个例子很简单,就是两个选择框(html中的标签),通过选中第一个select的某一项后,会从服务端得到一些数据,并加载到第2个select中。
从服务端获得XML格式的数据
从服务端获得数据的最容易想到的方法就是在服务端反加一定格式的数据,一般是XML格式,然后在服务端使用XMLDocument或其他技术来读取这些数据,并生成标签中选项的格式文本(标签)。下面的addOptions函数是这个例子的核心函数,它负责根据从服务端获得的数据生成标签中的标签。在这里所使用的方法是利用了标签的innerHTML属性(仅限于firefox),如果是IE,要使用outerHTML属性(IE中标签的innerHTML属性有一些小bug,读者可以试着在IE中使用innerHTML属性,看看会发生什么情况)。addOptions方法的实现代码如下:
// 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 += "" ;
options += s;
options += ""
}
}
var id = select.id;
if(isIE())
select.outerHTML = "" + options + "";
else
select.innerHTML = options;
}
}
onReadState函数将在XMLHttpRequest对象的异步访问服务端时调用。当readyState为4时表示成功从服务端返回XML数据。这个函数的实现代码如下:
// myRequest表示XMLHttpRequest对象,selectId表示标签的id属性值
function onReadyState(myRequest, selectId)
{
if(myRequest.readyState == 4) // 4表示成功获得相应信息
{
try
{
var xml = myRequest.responseXML; // 获得XMLDocument对象
var kind = document.getElementById(selectId); // 获得对象
addOptions(kind, xml); // 向标签中加入标签
}
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个标签的数据,并将其加载到第1个标签中。让我们先看一下这个静态的html代码。
从上面代码可以看出,这两个标签分别是bigKind和smallKind,里面并没有标签,这是因为标签要在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("");
if (s == null)
{
for (String key : MyData.data.keySet())
{
out.println("" + key + "");
}
} else
{
s = java.net.URLDecoder.decode(s, "UTF-8");
System.out.println(s);
java.util.List smallKind = MyData.data.get(s);
if (smallKind != null)
{
for (String kind : smallKind)
{
out.println("" + kind + "");
}
}
}
out.println("");
} 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格式返回,类似于如下的格式:
data1
data2
如果kind参数存在,则在MyData.data中查询第2个标签(smallKind)所需要的数据。data是一个Map类型。为了方便起见,本例子并未使用数据库,而是在MyData类中定义了一个静态的Map类型变量。MyData的实现代码如下:
package database;
import java.util.*;
public class MyData {
public static Map> data;
static {
data = new HashMap>();
List eProducts = new LinkedList();
eProducts.add("手机");
eProducts.add("数码/IT");
eProducts.add("家电");
eProducts.add("电脑");
data.put("消费电子", eProducts);
List goods = new LinkedList();
goods.add("化妆");
goods.add("健康");
goods.add("玩具");
goods.add("办公/文体 ");
goods.add("童装童鞋");
goods.add("其他");
data.put("日用百货", goods);
List books = new LinkedList();
books.add("小说");
books.add("动漫");
books.add("经济");
books.add("法律");
books.add("计算机");
books.add("英语");
books.add("通讯");
books.add("其他");
data.put("图书", books);
}
}
其中data变量中的key值就是bigKind中的值,而每一个key对应的值(一个List对象就是smallKind中值的列表)。下面我们来实现当第1个标签bigKind变化时,更新smallKind标签。的onchange事件函数的代码如下:
function onChange(obj)
{
try
{
getData(encodeURI(encodeURI("../GetXML?kind=" +obj.options[obj.selectedIndex].value)), "smallKind");
}
catch(e)
{
alert(e);
}
}
这个函数是标签的onchange事件函数。obj表示标签本身。这个函数中只有一条有实际意义的语句,也就是调用了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中就必须要进行编码和解码。因此,要想跨浏览器,就需要使用本文所述的方法。
直接获得...内容的字符串
上面的获得数据的方法是从服务端获得了一个XML文档,并转换成XMLDocument对象,然后解析。这种方法虽然很好,但是操作XMLDocument对象还是有些麻烦,因此,我们可以在服务端直接反回标签所需要的标签字符串,然后将这些字符串传给对象的innerHTML或outerHTML就可以了。服务端的代码和上面的实现代码类似,只需要将去掉,然后将改为后,并使用如下的语句来设置ContentType:
response.setContentType("text/html;charset=UTF-8");
客户端可通过XMLHttpRequest对象的responseText属性获得这些含有的文本,并将其赋给innerHTML或outerHTML属性。这种方法虽然很方便,但并不灵活。如果客户端不使用标签,而是使用
从服务端返回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 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 += "" ;
myOptions += options[i];
myOptions += ""
}
}
}
var id = select.id;
if(!isIE())
select.innerHTML = myOptions;
}
在上面的addOptions方法中还有一个不同是在IE中使用了对象的options数组来添加选择项,而不是使用outerHTML。这么做的好处是可以在onLoad方法中就获得的选项值。而如果使用outerHTML在html未装载完时,标签中选择项仍然为0。这样在onLoad方法中就无法访问中的被加入项了,当然,在onchange事件中可以。
在firefox中使用innerHTML时,在html未装载完时,只要标签被装载完(也就是调用了addOptions方法后),就可以访问标签中的了。个人感觉这一点要从IE做得好。顺便说一句,笔者使用的是IE6,不知道ie7会是什么效果。
简单来说,方法有以下几种:
返回XML
返回HTML
返回Json
返回一些javascript代码,在客户端执行