控件改造中为了提高用户体验,用到ajax。
该控件主要对IDC资源进行选择操作,定名为IDC选择器。由于数据具有多层关系,于是用树来展现。
由于数据量大,所以决定在进入页面时只加载第一层,然后点击树上节点后再加载次级节点,依次类推。
在此过程中碰到很多问题,现将两个大的问题及解决方案记下。
1.树形控件选择
一开始采用微软的TreeView,发现在根上加载节点时很慢(在子节点上加节点却不慢,奇怪?),调用一次tree.add(subnode)花费100-200毫秒,有的节点甚至花费500毫秒!也就是说如果第一层有100个节点,那一次加载就要花费10秒!
比较了xtree,dhtmlxtree,最终选择dhtmlxtree.它在根上加载节点是最快的,不过也要10-50毫秒。
发现dhtmlxtree有个loadXML可以一次加载XML节点(当然格式得按它定的来),改成这样加载,发现加载140个节点15毫秒都不要。
为什么有差别呢?
看了下源代码,发现loadXML中加载节点的方法与单个加的主要区别在于:由于XML的格式是一个父节点下N个子节点,loadXML加载该XML文件时,先根据XML中定义的父节点ID到整个树中找到该节点(
var temp=dhtmlObject._globalIdStorageFind(parentId);),然后循环调用
_attachChildNode把子节点加上。
而如果按我自己的XML格式(一个一个的子节点,XML中不声明父节点ID,因为每次都是点击某个节点才去取子节点,程序中已经知道该父节点ID),这样,每次调用insertNewItem添加一个节点时都要根据传入的父节点ID找一次父节点(_globalIdStorageFind),再将子节点加上(_attachChildNode)。这大概就是原因吧。
2.ajax Get方式发送数据
研究了下《ajax in action》的代码,直接就拉过来用了,按书上的例子,用POST方式发送数据,在后端.net页中是可以收到数据的。
可是用GET方式调用其方法发送数据就不行。到网上找了相关的文章研究了下,发现该书作者在写这段代码的时候范了个低级错误,起码从这段代码可以看出来作者对http协议并没有吃透(又或者是作者想让读者先碰壁然后自己进一步学习也讲不定 :) )。
post方法是将参数写到协议体中的,而get则是直接将参数写在协议头上。于是将该段代码改造如下:

var net = new Object();
net.READY_STATE_UNINITIALIZED = 0;
net.READY_STATE_LOADING = 1;
net.READY_STATE_LOADED = 2;
net.READY_STATE_INTERACTIVE = 3;
net.READY_STATE_COMPLETE = 4;

//构造函数
net.ContentLoader = function(url,onload,onerror,method,params,contentType)


{
this.url = null;
this.req = null;
this.onload = onload;
this.onerror = (onerror) ? onerror : this.defaultError;
this.loadXMLDoc(url,method,params,contentType);
}

net.ContentLoader.prototype =


{
loadXMLDoc:function(url,method,params,contentType)

{
if(!method)

{
method = "GET";
}
if(!contentType && method == "POST")

{
contentType = "application/x-www-form-urlencoded";
}
if(window.XMLHttpRequest)

{
this.req = new XMLHttpRequest();
}
else if(window.ActiveXObject)

{
this.req = new ActiveXObject("Microsoft.XMLHTTP");
}
if(this.req)

{
try

{
//this.req.onreadystatechange = net.ContentLoader.onReadyState;
var loader = this;
this.req.onreadystatechange = function()

{
loader.onReadyState.call(loader);
}
if(method == "GET")

{
this.req.open(method,url + "?" + params,true);
this.req.send(null);
}
else

{
this.req.open(method,url,true);
if(contentType)

{
this.req.setRequestHeader("Content-Type",contentType);
}
this.req.send(params);
}
}
catch(err)

{
this.onerror.call(this);
}
}
},
onReadyState:function()

{
var req = this.req;
var ready = req.readyState;
if(ready == net.READY_STATE_COMPLETE)

{
var httpStatus = req.status;
if(httpStatus == 200 || httpStatus == 0)

{
this.onload.call(this);
}
}
},
defaultError:function()

{
//alert("error fetching data!" + "\n\n readyState:" + this.req.readyState + "\n status:" + this.req.status + "\n headers:" + this.req.getAllResponseHeaders());
alert("error fetching data!" + "\n\n readyState:" + this.req.readyState);
//alert("erro!!!test");
}
}


调用方式:
var loader1 = new net.ContentLoader(url,回调函数,错误处理函数(可以为null),"GET",strParams);
var loader1 = new net.ContentLoader(url,回调函数,null,"POST",strParams);
如果是Post后端通过Request.Form[]来取参数;Get则通过Request.QueryString[]来取。
现将网上找到的文章引用如下:
http://www.6suv.com/20070429/8303/
HTTP 协议和 GET, POST 的工作方式.
当用户在Web浏览器地址栏中输入一个带有http://前缀的URL并按下Enter后,或者在Web页面中某个以http://开头的超链接上单击鼠标,HTTP事务处理的第一个阶段--建立连接阶段就开始了.HTTP的默认端口是80.
随着连接的建立,HTTP就进入了客户向服务器发送请求的阶段.客户向服务器发送的请求是一个有特定格式的ASCII消息,其语法规则为:
< Method > < URL > < HTTP Version > <\n>
{ <Header>:<Value> <\n>}*
<\n>
{ Entity Body } |
请求消息的顶端是请求行,用于指定方法,URL和HTTP协议的版本,请求行的最后是回车换行.方法有GET,POST,HEAD,PUT,DELETE等.
在请求行之后是若干个报头(Header)行.每个报头行都是由一个报头和一个取值构成的二元对,报头和取值之间以":"分隔;报头行的最后是回车换行.常见的报头有Accept(指定MIME媒体类型),Accept_Charset(响应消息的编码方式),Accept_Encoding(响应消息的字符集),User_Agent(用户的浏览器信息)等.
在请求消息的报头行之后是一个回车换行,表明请求消息的报头部分结束.在这个\n之后是请求消息的消息实体(Entity Body).
Web服务器在收到客户请求并作出处理之后,要向客户发送应答消息.与请求消息一样,应答消息的语法规则为:
< HTTP Version> <Status Code> [<Message>]<\n>
{ <Header>:<Value> <\n> } *
<\n>
{ Entity Body } |
应答消息的第一行为状态行,其中包括了HTTP版本号,状态码和对状态码进行简短解释的消息;状态行的最后是回车换行.状态码由3位数字组成,有5类:
- 1XX 保留
- 2XX 表示成功
- 3XX 表示URL已经被移走
- 4XX 表示客户错误
- 5XX 表示服务器错误
例如:415,表示不支持改媒体类型;503,表示服务器不能访问.最常见的是200,表示成功.常见的报头有:Last_Modified(最后修改时间),Content_Type(消息内容的MIME类型),Content_Length(内容长度)等.
在报头行之后也是一个回车换行,用以表示应答消息的报头部分的结束,以及应答消息实体的开始.
下面是一个应答消息的例子:
HTTP/1.0 200 OK
Date: Moday,07-Apr-97 21:13:02 GMT
Server:NCSA/1.1
MIME_Version:1.0
Content_Type:text/html
Last_Modified:Thu Dec 5 09:28:01 1996
Coentent_Length:3107
<HTML><HEAD><TITLE>...</HTML> |
那么 GET 和 POST 有什么区别? 区别就是一个在 URL 请求里面附带了表单参数和值, 一个是在 HTTP 请求的消息实体中. 用下面的例子可以很容易的看到同样的数据通过GET和POST来发送的区别, 发送的数据是 username=张三 :
GET 方式, 浏览器键入 http://localhost?username=张三
GET /?username=%E5%BC%A0%E4%B8%89 HTTP/1.1
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-powerpoint, application/vnd.ms-excel, application/msword, */*
Accept-Language: zh-cn
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NET CLR 1.1.4322)
Host: localhost
Connection: Keep-Alive |
POST 方式:
POST / HTTP/1.1
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-powerpoint, application/vnd.ms-excel, application/msword, */*
Accept-Language: zh-cn
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NET CLR 1.1.4322)
Host: localhost
Content-Length: 28
Connection: Keep-Alive
username=%E5%BC%A0%E4%B8%89 |
比较一下上面的两段文字, 您会发现 GET 方式把表单内容放在前面的请求头中, 而 POST 则把这些内容放在请求的主体中了, 同时 POST 中把请求的 Content-Type 头设置为 application/x-www-form-urlencoded. 而发送的正文都是一样的, 可以这样来构造一个表单提交正文:
encodeURIComponent(arg1)=encodeURIComponent(value1)&encodeURIComponent(arg2)=encodeURIComponent(value2)&.....
注: encodeURIComponent 返回一个包含了 charstring 内容的新的 String 对象(Unicode 格式), 所有空格、标点、重音符号以及其他非 ASCII 字符都用 %xx 编码代替,其中 xx 等于表示该字符的十六进制数。 例如,空格返回的是 "%20" 。 字符的值大于 255 的用 %uxxxx 格式存储。参见 JavaScript 的 encodeURIComponent() 方法.
下面就讨论一下如何在 JavaScript 中执行一个 GET 或者 POST 请求. 如果您用过 Java, 那么您可能熟悉下列的用 java.net.URLConnection 类进行 POST 操作的代码(参考 Java Tip 34: POSTing via Java ):
URL url;
URLConnection urlConn;
DataOutputStream printout;
// URL of CGI-Bin or jsp, asp script.
url = new URL ("somepage");
// URL connection channel.
urlConn = url.openConnection();
// ......
// No caching, we want the real thing.
urlConn.setUseCaches (false);
// Specify the content type.
urlConn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
// Send POST output.
printout = new DataOutputStream (urlConn.getOutputStream ());
String content = "name=" + URLEncoder.encode ("Buford Early") + "&email=" + URLEncoder.encode ("buford@known-space.com");
printout.writeBytes (content);
printout.flush ();
printout.close (); |
以上的代码向 somepage 发送了一次 POST 请求, 数据为 name = Buford Early, email = buford@known-space.com.
用
JavaScript 来执行 POST/GET 请求是同样的原理, 下面的代码展示了分别用 XMLHttpRequest 对象向 somepage 用 GET 和 POST 两种方式发送和上例相同的数据的具体过程:
GET 方式
var postContent =
"name=" + encodeURIComponent("Buford Early") + "&email=" + encodeURIComponent("buford@known-space.com");
xmlhttp.open("GET", "somepage" + "?" + postContent, true);
xmlhttp.send(null); |
POST 方式
var postContent =
"name=" + encodeURIComponent("Buford Early") + "&email=" + encodeURIComponent("buford@known-space.com");
xmlhttp.open("POST", "somepage", true);
xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xmlhttp.send(postContent); |
至此希望你已经能够理解如何用 JavaScript 中的 XMLHttpRequest 对象来执行 GET/POST 操作, 剩下的工作就是您如何来构造这些提交的参数了, 最后我给出一个将现有的 form 提交代码修改为异步的 AJAX 提交的代码(注意目前作者还不知道如何让 file 上传表单域也能异步上传文件). 首先请看两个 JavaScript 函数:
// form - the form to submit // resultDivId - the division of which to display result text in, in null, then // create an element and add it to the end of the body function ajaxSubmitForm(form, resultDivId) { var elements = form.elements;// Enumeration the form elements var element; var i; var postContent = "";// Form contents need to submit for(i=0;i<elements.length;++i) { var element=elements[i]; if(element.type=="text" || element.type=="textarea" || element.type=="hidden") { postContent += encodeURIComponent(element.name) + "=" + encodeURIComponent(element.value) + "&"; } else if(element.type=="select-one"||element.type=="select-multiple") { var options=element.options,j,item; for(j=0;j<options.length;++j){ item=options[j]; if(item.selected) { postContent += encodeURIComponent(element.name) + "=" + encodeURIComponent(item.value) + "&"; } } } else if(element.type=="checkbox"||element.type=="radio") { if(element.checked) { postContent += encodeURIComponent(element.name) + "=" + encodeURIComponent(element.value) + "&"; } } else if(element.type=="file") { if(element.value != "") { postContent += encodeURIComponent(element.name) + "=" + encodeURIComponent(element.value) + "&"; } } else { postContent += encodeURIComponent(element.name) + "=" + encodeURIComponent(element.value) + "&"; } } alert(postContent); ajaxSubmit(form.action, form.method, postContent); } // url - the url to do submit // method - "get" or "post" // postContent - the string with values to be submited // resultDivId - the division of which to display result text in, in null, then // create an element and add it to the end of the body function ajaxSubmit(url, method, postContent, resultDivId) { var loadingDiv = document.getElementById('loading'); // call in new thread to allow ui to update window.setTimeout(function () { loadingDiv.innerText = "Loading...."; loadingDiv.style.display = ""; }, 1); // code for Mozilla, etc. if (window.XMLHttpRequest) { xmlhttp=new XMLHttpRequest(); } // code for IE else if (window.ActiveXObject) { xmlhttp=new ActiveXObject("Microsoft.XMLHTTP"); } if(xmlhttp) { xmlhttp.onreadystatechange = function() { // if xmlhttp shows "loaded" if (xmlhttp.readyState==4) { if(resultDivId) { document.getElementByID(resultDivId).innerHTML = xmlhttp.responseText; } else { var result = document.createElement("DIV"); result.style.border="1px solid #363636"; result.innerHTML = xmlhttp.responseText; document.body.appendChild(result); } loadingDiv.innerHTML = "Submit finnished!"; } }; if(method.toLowerCase() == "get") { xmlhttp.open("GET", url + "?" + postContent, true); xmlhttp.send(null); } else if(method.toLowerCase() == "post") { xmlhttp.open("POST", url, true); xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xmlhttp.send(postContent); } } else { loadingDiv.innerHTML = "Can't create XMLHttpRequest object, please check your web browser."; } }
|
函数 ajaxSubmitForm 将表单要提交的内容进行封装, 然后调用 ajaxSubmit 函数来执行真正的异步提交, 表单提交后所返回的结果则显示在给定的 DIV 容器中或者没有指定参数时用 DOM 对象动态生成一个 DIV 容器来显示结果并添加到页面末尾. 这样, 对原来的表单只需要改动一个地方就可以将原来的表单提交改为异步模式, 即在 form 标签里加入: onSubmit="ajaxSubmitForm(this);return false;" 即可, return false 确保表单不会被浏览器同步提交.