随笔 - 312, 文章 - 14, 评论 - 1393, 引用 - 0
数据加载中……

AJAX从服务端获取数据的三种方法

本文为原创,如需转载,请注明作者和出处,谢谢!

    在本文中将给出一个例子来介绍使用AJAX技术从服务端获得数据的三种方法。这个例子很简单,就是两个选择框(html中的<select>标签),通过选中第一个select的某一项后,会从服务端得到一些数据,并加载到第2select中。

方法一、从服务端获得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对象的异步访问服务端时调用。当readyState4时表示成功从服务端返回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>标签分别是bigKindsmallKind,里面并没有<option>标签,这是因为<option>标签要在javascript里动态加载。下面我们先来加载bigKind中的数据。

window.onload = onLoad
function onLoad()
{                         

    
try
    {
        getData(
"../GetXML""bigKind");
               
    }
    
catch(e)
    {
        alert(
"onLoad:" + e);
    }
}


     其中GetXML是一个Servlet程序(读者可以将其换成其他的服务端程序,如asp.netphp的)。下面是这个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";
    }
}

 

   不管读者会不会javaservlet,从这个程序中的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方法中可以找到如下的一条语句:

= java.net.URLDecoder.decode(s, "UTF-8");


    就是进行解码操作。

    注:如果在IE中,客户端可以不使用encodeURI对带中文的URL进行编码,服务端也不用解码。在服务端仍然可以正常显示中文。但在firefox中就必须要进行编码和解码。因此,要想跨浏览器,就需要使用本文所述的方法。

方法二、直接获得<option>...</option>内容的字符串

    上面的获得数据的方法是从服务端获得了一个XML文档,并转换成XMLDocument对象,然后解析。这种方法虽然很好,但是操作XMLDocument对象还是有些麻烦,因此,我们可以在服务端直接反回<select>标签所需要的<option>标签字符串,然后将这些字符串传给<select>对象的innerHTMLouterHTML就可以了。服务端的代码和上面的实现代码类似,只需要将<data>去掉,然后将<list>改为<option>后,并使用如下的语句来设置ContentType

response.setContentType("text/html;charset=UTF-8");

客户端可通过XMLHttpRequest对象的responseText属性获得这些含有<option>的文本,并将其赋给innerHTMLouterHTML属性。这种方法虽然很方便,但并不灵活。如果客户端不使用<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>的选项值。而如果使用outerHTMLhtml未装载完时,<select>标签中选择项仍然为0。这样在onLoad方法中就无法访问<select>中的被加入项了,当然,在onchange事件中可以。 

    firefox中使用innerHTML时,在html未装载完时,只要<select>标签被装载完(也就是调用了addOptions方法后),就可以访问<select>标签中的<option>了。个人感觉这一点要从IE做得好。顺便说一句,笔者使用的是IE6,不知道ie7会是什么效果。如果哪位试过,可以跟贴。图1是本例的效果图。



                               图1

    本来想提供asp.net的例子来着,结果不知怎么着,vs2008asp.net设计视图突然不响应了,谁知道是怎么回事啊??





Android开发完全讲义(第2版)(本书版权已输出到台湾)

http://product.dangdang.com/product.aspx?product_id=22741502



Android高薪之路:Android程序员面试宝典 http://book.360buy.com/10970314.html


新浪微博:http://t.sina.com.cn/androidguy   昵称:李宁_Lining

posted on 2008-05-25 23:16 银河使者 阅读(7771) 评论(18)  编辑  收藏 所属分类: javaajaxjavascript 原创

评论

# re: AJAX从服务端获取数据的三种方法  回复  更多评论   

第一种方法通用性比较好,但比较复杂。
第二种方法比较直接简单。
第三种方法比较垃圾。
2008-05-26 10:28 | javajava

# re: AJAX从服务端获取数据的三种方法  回复  更多评论   

不过我认为第3种方法比较好,可以使客户端编程更容易,也更直观。如果返回的xml比较复杂,使用第一种方法,javascript代码会很多。

但在实现项目中,这三种方法可以混合使用。
2008-05-26 10:39 | 银河使者

# re: AJAX从服务端获取数据的三种方法  回复  更多评论   

innerHTML属性在IE里有一点bug,我好像还没碰到过。我用的是IE6.0
2008-05-27 01:30 | 无花果

# re: AJAX从服务端获取数据的三种方法  回复  更多评论   

并不是所有的html标签的innerHTML都有bug。我碰到的是<select>标签的innerHTML有一些bug。

不信通过getElementById获得<select>对象后,使用下面的代码试试,看看会发生什么效果

var select = getElementById("selectid");
select.innerHTML = "<option>abc</option><option>ddd</option>";

上面的代码是出不来的选项的,不知怎么着,IE6把第一个<option>弄没了。
只有用下面的代码才好使:


var select = getElementById("selectid");
select.outerHTML = "<select id='selectid' ><option>abc</option><option>ddd</option></select>";
2008-05-27 08:49 | 银河使者

# re: AJAX从服务端获取数据的三种方法  回复  更多评论   

你试试重装一下你的软件看看能不能正常,楼主
2008-05-27 17:58 | 懒人

# re: AJAX从服务端获取数据的三种方法  回复  更多评论   

重装软件可是最后一招。 这最后一招杀手锏我可不想这么快就用。 还是研究一下vs2008的设计视图为什么不好使吧。要是一不好使就重装,那不是要把人弄疯了。vs2008很大的,如果重装还不行,是不是要重装xp啊。^-^
2008-05-27 21:58 | 银河使者

# re: AJAX从服务端获取数据的三种方法  回复  更多评论   

@银河使者
思路不错,不过是不是安装的时候哪个选项给漏掉了
2008-05-29 11:04 | 懒人

# re: AJAX从服务端获取数据的三种方法  回复  更多评论   

我一般安装任何软件都是完全安装的,没办法,硬盘就是大。^-^,要说漏掉是不可能的,我都是全选的。单位的机器也是按着同样的方法安的,没有问题,就是家里的dell笔记本出这种事。昨天安了个vs2008的patch,也不好使。
2008-05-29 12:39 | 银河使者

# re: AJAX从服务端获取数据的三种方法  回复  更多评论   

ajax不就是后台发送请求,接收返回的结果,然后解析,不是吗?
2008-05-29 18:15 | stanley_xu

# re: AJAX从服务端获取数据的三种方法  回复  更多评论   

ajax从原理上讲很简单。其实任何技术从基本原理上看都很简单。但实现起来,确是另外一回事。
2008-05-29 18:45 | 银河使者

# re: AJAX从服务端获取数据的三种方法  回复  更多评论   

LZ說的是,實現起來的感覺.......
現在的AJAX框架技術太多了,搞得我不知用哪種好,唉!
2008-05-30 16:00 | hulu

# re: AJAX从服务端获取数据的三种方法  回复  更多评论   

只要适合自己的,就是好的。
2008-05-30 20:45 | 银河使者

# re: AJAX从服务端获取数据的三种方法[未登录]  回复  更多评论   

下次能不能写全了再给放上来,文章代码内容不全,让我调试了半天才搞定。
2008-07-19 19:39 | eric

# re: AJAX从服务端获取数据的三种方法  回复  更多评论   

本文的只是代码片段,为了演示这个功能,本文已经假设读者对javascript比较熟悉,因此,并没有列出所有的代码。如果那么,会无法突出重点,让想看这部分内容的读者不容易找到了。在以后的文章,我会提供源代码供读者下载学习。谢谢关注本文。
2008-07-19 19:51 | 银河使者

# re: AJAX从服务端获取数据的三种方法  回复  更多评论   

JSON更爽.
2008-07-28 18:37 | Beniao

# re: AJAX从服务端获取数据的三种方法  回复  更多评论   

json里面这些都自动处理了,请看我的这篇文章
http://www.blogjava.net/nokiaguy/archive/2008/07/04/212515.html
2008-07-28 20:34 | 银河使者

# re: AJAX从服务端获取数据的三种方法  回复  更多评论   

i8里面第二个select选择的时候就没有了。不知道是怎么回事。我用的outerHTML
2009-03-24 22:50 | liuhua

# re: AJAX从服务端获取数据的三种方法  回复  更多评论   

@liuhua
我在ie6下测的,it7和ie8没测过,也许是不兼容的问题吧。哎~
2009-03-25 08:27 | 银河使者

只有注册用户登录后才能发表评论。


网站导航: