本文為原創(chuàng),如需轉(zhuǎn)載,請(qǐng)注明作者和出處,謝謝!
在本文中將給出一個(gè)例子來(lái)介紹使用AJAX技術(shù)從服務(wù)端獲得數(shù)據(jù)的三種方法。這個(gè)例子很簡(jiǎn)單,就是兩個(gè)選擇框(html中的<select>標(biāo)簽),通過(guò)選中第一個(gè)select的某一項(xiàng)后,會(huì)從服務(wù)端得到一些數(shù)據(jù),并加載到第2個(gè)select中。
方法一、從服務(wù)端獲得XML格式的數(shù)據(jù)
從服務(wù)端獲得數(shù)據(jù)的最容易想到的方法就是在服務(wù)端反加一定格式的數(shù)據(jù),一般是XML格式,然后在服務(wù)端使用XMLDocument或其他技術(shù)來(lái)讀取這些數(shù)據(jù),并生成<select>標(biāo)簽中選項(xiàng)的格式文本(<option>標(biāo)簽)。下面的addOptions函數(shù)是這個(gè)例子的核心函數(shù),它負(fù)責(zé)根據(jù)從服務(wù)端獲得的數(shù)據(jù)生成<select>標(biāo)簽中的<option>標(biāo)簽。在這里所使用的方法是利用了<select>標(biāo)簽的innerHTML屬性(僅限于firefox),如果是IE,要使用outerHTML屬性(IE中<select>標(biāo)簽的innerHTML屬性有一些小bug,讀者可以試著在IE中使用innerHTML屬性,看看會(huì)發(fā)生什么情況)。addOptions方法的實(shí)現(xiàn)代碼如下:
// select表示<select>對(duì)象,xml表示XMLDocument對(duì)象
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函數(shù)將在XMLHttpRequest對(duì)象的異步訪問(wèn)服務(wù)端時(shí)調(diào)用。當(dāng)readyState為4時(shí)表示成功從服務(wù)端返回XML數(shù)據(jù)。這個(gè)函數(shù)的實(shí)現(xiàn)代碼如下:
// myRequest表示XMLHttpRequest對(duì)象,selectId表示<select>標(biāo)簽的id屬性值
function onReadyState(myRequest, selectId)
{
if(myRequest.readyState == 4) // 4表示成功獲得相應(yīng)信息
{
try
{
var xml = myRequest.responseXML; // 獲得XMLDocument對(duì)象
var kind = document.getElementById(selectId); // 獲得<select>對(duì)象
addOptions(kind, xml); // 向<select>標(biāo)簽中加入<option>標(biāo)簽
}
catch(e)
{
alert("onReadyState:" + e);
}
}
}
getData函數(shù)負(fù)責(zé)向服務(wù)端發(fā)送請(qǐng)求,并設(shè)置異步事件。實(shí)現(xiàn)代碼如下:
function getData(url, selectId)
{
var myRequest = getXMLHTTPRequest(); // 獲得一個(gè)XMLHttpRequest對(duì)象
if(myRequest)
{
myRequest.onreadystatechange = function() // 接收獲得數(shù)據(jù)狀態(tài)的事件函數(shù)
{
onReadyState(myRequest, selectId);
}
try
{
myRequest.open( "post", url, true);
}
catch(e)
{
alert(e);
}
try
{
myRequest.send("");
}
catch(e)
{
alert(e);
}
}
}
現(xiàn)在本例子的核心代碼已經(jīng)實(shí)現(xiàn)完成,下一步就是在html而加載時(shí)從服務(wù)端獲得第1個(gè)<select>標(biāo)簽的數(shù)據(jù),并將其加載到第1個(gè)<select>標(biāo)簽中。讓我們先看一下這個(gè)靜態(tài)的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>
從上面代碼可以看出,這兩個(gè)<select>標(biāo)簽分別是bigKind和smallKind,里面并沒(méi)有<option>標(biāo)簽,這是因?yàn)?/span><option>標(biāo)簽要在javascript里動(dòng)態(tài)加載。下面我們先來(lái)加載bigKind中的數(shù)據(jù)。
window.onload = onLoad
function onLoad()
{
try
{
getData("../GetXML", "bigKind");
}
catch(e)
{
alert("onLoad:" + e);
}
}
其中GetXML是一個(gè)Servlet程序(讀者可以將其換成其他的服務(wù)端程序,如asp.net、php的)。下面是這個(gè)GetXML程序的實(shí)現(xiàn)代碼:
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";
}
}
不管讀者會(huì)不會(huì)java和servlet,從這個(gè)程序中的processRequest方法中都可以看出,首先會(huì)獲得請(qǐng)求參數(shù)kind,如果這個(gè)參數(shù)不存在,則返回bigKind所需要的數(shù)據(jù),以xml格式返回,類似于如下的格式:
<data>
<list>data1</list>
<list>data2</list>
</data>
如果kind參數(shù)存在,則在MyData.data中查詢第2個(gè)<select>標(biāo)簽(smallKind)所需要的數(shù)據(jù)。data是一個(gè)Map類型。為了方便起見(jiàn),本例子并未使用數(shù)據(jù)庫(kù),而是在MyData類中定義了一個(gè)靜態(tài)的Map類型變量。MyData的實(shí)現(xiàn)代碼如下:
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("手機(jī)");
eProducts.add("數(shù)碼/IT");
eProducts.add("家電");
eProducts.add("電腦");
data.put("消費(fèi)電子", 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("小說(shuō)");
books.add("動(dòng)漫");
books.add("經(jīng)濟(jì)");
books.add("法律");
books.add("計(jì)算機(jī)");
books.add("英語(yǔ)");
books.add("通訊");
books.add("其他");
data.put("圖書", books) ;
}
}
其中data變量中的key值就是bigKind中的值,而每一個(gè)key對(duì)應(yīng)的值(一個(gè)List<String>對(duì)象就是smallKind中值的列表)。下面我們來(lái)實(shí)現(xiàn)當(dāng)?shù)?/span>1個(gè)<select>標(biāo)簽bigKind變化時(shí),更新smallKind標(biāo)簽。<select>的onchange事件函數(shù)的代碼如下:
function onChange(obj)
{
try
{
getData(encodeURI(encodeURI("../GetXML?kind=" +obj.options[obj.selectedIndex].value)), "smallKind");
}
catch(e)
{
alert(e);
}
}
這個(gè)函數(shù)是<select>標(biāo)簽的onchange事件函數(shù)。obj表示<select>標(biāo)簽本身。這個(gè)函數(shù)中只有一條有實(shí)際意義的語(yǔ)句,也就是調(diào)用了getData方法,這個(gè)方法人在onLoad方法中調(diào)用getData時(shí)差不多,只是在傳送url時(shí)使用了兩個(gè)encodeURI方法。由于XMLHttpRequest方法以utf-8向服務(wù)端發(fā)送數(shù)據(jù),因此,要使用兩個(gè)encodeURI向服務(wù)端發(fā)送%xx形式的utf-8編碼,然后在服務(wù)端進(jìn)行解析。我們?cè)?/span>GetXML中的processRequest方法中可以找到如下的一條語(yǔ)句:
s = java.net.URLDecoder.decode(s, "UTF-8");
就是進(jìn)行解碼操作。
注:如果在IE中,客戶端可以不使用encodeURI對(duì)帶中文的URL進(jìn)行編碼,服務(wù)端也不用解碼。在服務(wù)端仍然可以正常顯示中文。但在firefox中就必須要進(jìn)行編碼和解碼。因此,要想跨瀏覽器,就需要使用本文所述的方法。
方法二、直接獲得<option>...</option>內(nèi)容的字符串
上面的獲得數(shù)據(jù)的方法是從服務(wù)端獲得了一個(gè)XML文檔,并轉(zhuǎn)換成XMLDocument對(duì)象,然后解析。這種方法雖然很好,但是操作XMLDocument對(duì)象還是有些麻煩,因此,我們可以在服務(wù)端直接反回<select>標(biāo)簽所需要的<option>標(biāo)簽字符串,然后將這些字符串傳給<select>對(duì)象的innerHTML或outerHTML就可以了。服務(wù)端的代碼和上面的實(shí)現(xiàn)代碼類似,只需要將<data>去掉,然后將<list>改為<option>后,并使用如下的語(yǔ)句來(lái)設(shè)置ContentType:
response.setContentType("text/html;charset=UTF-8");
客戶端可通過(guò)XMLHttpRequest對(duì)象的responseText屬性獲得這些含有<option>的文本,并將其賦給innerHTML或outerHTML屬性。這種方法雖然很方便,但并不靈活。如果客戶端不使用<select>標(biāo)簽,而是使用<table>或其他的標(biāo)簽顯示數(shù)據(jù),那么返回的這些數(shù)據(jù)就沒(méi)什么用處了。而即方便,又靈活的應(yīng)該是下面要介紹的方法。
方法三、從服務(wù)端返回javascript代碼,在客戶端使用eval函數(shù)執(zhí)行
我們可以在服務(wù)端返回類似于如下的字符串:
var options = new Array();
options.push(‘data1’);
options.push(‘data2’);
然后使用eval函數(shù)執(zhí)行上面的字符串,這樣我們?cè)?/span>javascript中就可以使用options數(shù)組了。我個(gè)人認(rèn)為,使用數(shù)組要比使用XMLDocument更容易,代碼量也更少。如果要返回更為復(fù)雜的數(shù)據(jù),也可以使用javascript中的類或其他數(shù)據(jù)結(jié)構(gòu)。根據(jù)上面的思想,新的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();
}
}
客戶端經(jīng)過(guò)改進(jìn)的addOptions函數(shù)如下:
// javascript表示從服務(wù)端返回的javascript代碼字符串
function addOptions(select, javascript)
{
if(select)
{
if(select.id == "smallKind")
{
if(isIE())
select.options.length = 0;
}
var myOptions = "";
eval(javascript); //執(zhí)行從服務(wù)端返回的javascript代碼
for(var i = 0; i < options.length ; i++) // 從options數(shù)組中取數(shù)據(jù)
{
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方法中還有一個(gè)不同是在IE中使用了<select>對(duì)象的options數(shù)組來(lái)添加選擇項(xiàng),而不是使用outerHTML。這么做的好處是可以在onLoad方法中就獲得<select>的選項(xiàng)值。而如果使用outerHTML在html未裝載完時(shí),<select>標(biāo)簽中選擇項(xiàng)仍然為0。這樣在onLoad方法中就無(wú)法訪問(wèn)<select>中的被加入項(xiàng)了,當(dāng)然,在onchange事件中可以。
在firefox中使用innerHTML時(shí),在html未裝載完時(shí),只要<select>標(biāo)簽被裝載完(也就是調(diào)用了addOptions方法后),就可以訪問(wèn)<select>標(biāo)簽中的<option>了。個(gè)人感覺(jué)這一點(diǎn)要從IE做得好。順便說(shuō)一句,筆者使用的是IE6,不知道ie7會(huì)是什么效果。如果哪位試過(guò),可以跟貼。圖1是本例的效果圖。

圖1
本來(lái)想提供asp.net的例子來(lái)著,結(jié)果不知怎么著,vs2008的asp.net設(shè)計(jì)視圖突然不響應(yīng)了,誰(shuí)知道是怎么回事啊??
新浪微博:http://t.sina.com.cn/androidguy 昵稱:李寧_Lining