我想大家在访问某些网站的时候都曾见到过基于Ajax的自动完成功能,比如http://www.google.com,www.56.com/.
一、引出Ajax的自动完成
现在要实现一个员工信息查询的功能,即根据输入的名字检索员工的详细信息。这是一个简单的数据表查询,在
ASP.NET中实现这样的功能是比较简单的.
从上面可以看出,这种员工信息查询功能还存在一些不足,比如用户可能记不全员工的名字,只记得前面几个字母是什么,这样用户只能根据记忆猜测,一遍遍地尝试。如果在用户输入的同时,输入框下方可以给出相应的提示,辅助用户输入,那么用户进行检索的速度和成功率就会大大提高.这就是
基于Ajax的自动完成功能.
二、自动完成功能的实现
实现这样的功能需要按以下的步骤进行。
·服务器端提供GetSearchItems方法给客户端,用来返回满足条件的员工列表。·客户端的输入框需要增加onkeydown响应函数,以便即时获取满足条件的员工列表。·通过客户端的JavaScript动态列出待选结果的列表,同时还要提供键盘和鼠标的响应。
三、服务器端实现
本文采用AjaxPro.NET作为Ajax开发框架,首先为使用AjaxPro.NET做一些准备工作。 添加对AjaxPro.dll的引用,修改Web.config配置文件,在system.web节点下加入如下配置:
1<httpHandlers>2<!--Register the ajax handler-->3<addverb="POST,GET"path="ajaxpro/*.ashx"type="AjaxPro.AjaxHandlerFactory, AjaxPro"/>4</httpHandlers>
在页面后台代码(Default.aspx.cs)的Page_Load方法中增加下面的代码:
1protectedvoidPage_Load(objectsender, EventArgs e)2{3 AjaxPro.Utility.RegisterTypeForAjax(typeof(_Default));4}
下面定义提供给客户端调用的方法GetSearchItems(),参数query为模糊查询的关键字值:
1[AjaxPro.AjaxMethod()]2publicArrayList GetSearchItems(stringquery)3{4 ArrayList items=newArrayList();5 StringBuilder queryString=newStringBuilder();6 queryString.Append("select employeeid,lastname,firstname,title,titleofcourtesy from dbo.Employees");7 queryString.Append("where firstname like '%"+query+"%'");89 DataSet ds=DataBase.Instance.ReturnDataSet(queryString.ToString());10for(inti=0; i<ds.Tables[0].Rows.Count; i++)11{12 items.Add(ds.Tables[0].Rows[i][2].ToString());13 }14returnitems;15}
GetSearchItems方法返回一个ArrayList对象,它将包含所有以用户输入字符串的员工名字。
四、客户端实现
相对于服务器端的方法而言,客户端的处理要复杂得多。首先来分析如何根据服务器端返回的ArrayList对象展示结果。这里用到了Web编程中“层”(div)的概念,通过JavaScript和DOM创建一个新的层div,将ArrayList中的每一个条目都作为其子节点加入到div中,而每一个条目也被看作是一个div,其中具体的文本内容则是一个span对象。除了显示待选的结果之外,下拉区域还要对键盘、鼠标事件做出响应;为了实时地显示待选结果,还需要定时更新待选结果的列表。这些功能都封装在lookup.js中。下面是lookeu.js的定义:
1//下拉区背景色2varDIV_BG_COLOR="#EEE";3//高亮显示条目颜色4varDIV_HIGHLIGHT_COLOR="#C30";5//字体6varDIV_FONT="Arial";7//下拉区内补丁大小8varDIV_PADDING="2px";9//下拉区边框样式10varDIV_BORDER="1px solid #CCC";111213//文本输入框14varqueryField;15//下拉区id16vardivName;17//IFrame名称18varifName;19//记录上次选择的值20varlastVal="";21//当前选择的值22varval="";23//显示结果的下拉区24varglobalDiv;25//下拉区是否设置格式的标记26vardivFormatted=false;2728/**//**29InitQueryCode函数必须在<body onload>事件的响应函数中调用,其中:30queryFieldName为文本框控件的id,31hiddenDivName为显示下拉区div的id32*/33functionInitQueryCode (queryFieldName, hiddenDivName)34{35//指定文本输入框的onblur和onkeydown响应函数36queryField=document.getElementById(queryFieldName);37 queryField.onblur=hideDiv;38 queryField.onkeydown=keypressHandler;3940//设置queryField的autocomplete属性为"off"41queryField.autocomplete="off";4243//如果没有指定hiddenDivName,取默认值"querydiv"44if(hiddenDivName)45{46 divName=hiddenDivName;47 }48else49{50 divName="querydiv";51 }5253//IFrame的name54ifName="queryiframe";5556//100ms后调用mainLoop函数57setTimeout("mainLoop()",100);58}5960/**//**61获取下拉区的div,如果没有则创建之62*/63functiongetDiv (divID)64{65if(!globalDiv)66{67//如果div在页面中不存在,创建一个新的div6869if(!document.getElementById(divID))70{71varnewNode=document.createElement("div");72 newNode.setAttribute("id", divID);73 document.body.appendChild(newNode);74 }7576//globalDiv设置为div的引用77globalDiv=document.getElementById(divID);7879//计算div左上角的位置80varx=queryField.offsetLeft;81vary=queryField.offsetTop+queryField.offsetHeight;82varparent=queryField;83while(parent.offsetParent)84{85 parent=parent.offsetParent;86 x+=parent.offsetLeft;87 y+=parent.offsetTop;88 }8990//如果没有对div设置格式,则为其设置相应的显示样式91if(!divFormatted)92{93 globalDiv.style.backgroundColor=DIV_BG_COLOR;94 globalDiv.style.fontFamily=DIV_FONT;95 globalDiv.style.padding=DIV_PADDING;96 globalDiv.style.border=DIV_BORDER;97 globalDiv.style.width="100px";98 globalDiv.style.fontSize="90%";99100 globalDiv.style.position="absolute";101 globalDiv.style.left=x+"px";102 globalDiv.style.top=y+"px";103 globalDiv.style.visibility="hidden";104 globalDiv.style.zIndex=10000;105106 divFormatted=true;107 }108 }109110returnglobalDiv;111}112113/**//**114根据返回的结果集显示下拉区115*/116functionshowQueryDiv(resultArray)117{118//获取div的引用119vardiv=getDiv(divName);120121//如果div中有内容,则删除之122while(div.childNodes.length>0)123 div.removeChild(div.childNodes[0]);124125//依次添加结果126for(vari=0; i<resultArray.length; i++)127{128//每一个结果也是一个div129varresult=document.createElement("div");130//设置结果div的显示样式131result.style.cursor="pointer";132 result.style.padding="2px 0px 2px 0px";133//设置为未选中134_unhighlightResult(result);135//设置鼠标移进、移出等事件响应函数136result.onmousedown=selectResult;137 result.onmouseover=highlightResult;138 result.onmouseout=unhighlightResult;139140//结果的文本是一个span141varresult1=document.createElement("span");142//设置文本span的显示样式143result1.className="result1";144 result1.style.textAlign="left";145 result1.style.fontWeight="bold";146 result1.innerHTML=resultArray[i];147148//将span添加为结果div的子节点149result.appendChild(result1);150151//将结果div添加为下拉区的子节点152div.appendChild(result);153 }154155//如果结果集不为空,则显示,否则不显示156showDiv(resultArray.length>0);157}158159/**//**160用户点击某个结果时,将文本框的内容替换为结果的文本,161并隐藏下拉区162*/163functionselectResult()164{165 _selectResult(this);166}167168//选择一个条目169function_selectResult(item)170{171varspans=item.getElementsByTagName("span");172if(spans)173{174for(vari=0; i<spans.length; i++)175{176if(spans[i].className=="result1")177{178 queryField.value=spans[i].innerHTML;179 lastVal=val=escape(queryField.value);180 mainLoop();181 queryField.focus();182 showDiv(false);183return;184 }185 }186 }187}188189/**//**190当鼠标移到某个条目之上时,高亮显示该条目191*/192functionhighlightResult()193{194 _highlightResult(this);195}196197function_highlightResult(item)198{199 item.style.backgroundColor=DIV_HIGHLIGHT_COLOR;200}201202/**//**203当鼠标移出某个条目时,正常显示该条目204*/205functionunhighlightResult()206{207 _unhighlightResult(this);208}209210function_unhighlightResult(item)211{212 item.style.backgroundColor=DIV_BG_COLOR;213}214215/**//**216显示/不显示下拉区217*/218functionshowDiv (show)219{220vardiv=getDiv(divName);221if(show)222{223 div.style.visibility="visible";224 }225else226{227 div.style.visibility="hidden";228 }229//adjustiFrame();230}231232/**//**233隐藏下拉区234*/235functionhideDiv ()236{237 showDiv(false);238}239240/**//**241调整IFrame的位置,这是为了解决div可能会显示在输入框后面的问题242*/243functionadjustiFrame()244{245//如果没有IFrame,则创建之246if(!document.getElementById(ifName))247{248varnewNode=document.createElement("iFrame");249 newNode.setAttribute("id", ifName);250 newNode.setAttribute("src","javascript:false;");251 newNode.setAttribute("scrolling","no");252 newNode.setAttribute("frameborder","0");253 document.body.appendChild(newNode);254 }255256 iFrameDiv=document.getElementById(ifName);257vardiv=getDiv(divName);258259//调整IFrame的位置与div重合,并在div的下一层260try261{262 iFrameDiv.style.position="absolute";263 iFrameDiv.style.width=div.offsetWidth;264 iFrameDiv.style.height=div.offsetHeight;265 iFrameDiv.style.top=div.style.top;266 iFrameDiv.style.left=div.style.left;267 iFrameDiv.style.zIndex=div.style.zIndex-1;268 iFrameDiv.style.visibility=div.style.visibility;269 }270catch(e)271{272 }273}274275/**//**276文本输入框的onkeydown响应函数277*/278functionkeypressHandler (evt)279{280//获取对下拉区的引用281vardiv=getDiv(divName);282283//如果下拉区不显示,则什么也不做284if(div.style.visibility=="hidden")285{286returntrue;287 }288289//确保evt是一个有效的事件290if(!evt&&window.event)291{292 evt=window.event;293 }294varkey=evt.keyCode;295296varKEYUP=38;297varKEYDOWN=40;298varKEYENTER=13;299varKEYTAB=9;300301//只处理上下键、回车键和Tab键的响应302if((key!=KEYUP)&&(key!=KEYDOWN)&&(key!=KEYENTER)&&(key!=KEYTAB))303{304returntrue;305 }306307varselNum=getSelectedSpanNum(div);308varselSpan=setSelectedSpan(div, selNum);309310//如果键入回车和Tab,则选择当前选择条目311if((key==KEYENTER)||(key==KEYTAB))312{313if(selSpan)314{315 _selectResult(selSpan);316 }317 evt.cancelBubble=true;318returnfalse;319 }320else//如果键入上下键,则上下移动选中条目321{322if(key==KEYUP)323{324 selSpan=setSelectedSpan(div, selNum-1);325 }326if(key==KEYDOWN)327{328 selSpan=setSelectedSpan(div, selNum+1);329 }330if(selSpan)331{332 _highlightResult(selSpan);333 }334 }335336//显示下拉区337showDiv(true);338returntrue;339}340341/**//**342获取当前选中的条目的序号343*/344functiongetSelectedSpanNum(div)345{346varcount=-1;347varspans=div.getElementsByTagName("div");348if(spans)349{350for(vari=0; i<spans.length; i++)351{352 count++;353if(spans[i].style.backgroundColor!=div.style.backgroundColor)354{355returncount;356 }357 }358 }359360return-1;361}362363/**//**364选择指定序号的结果条目365*/366functionsetSelectedSpan(div, spanNum)367{368varcount=-1;369varthisSpan;370varspans=div.getElementsByTagName("div");371if(spans)372{373for(vari=0; i<spans.length; i++)374{375if(++count==spanNum)376{377 _highlightResult(spans[i]);378 thisSpan=spans[i];379 }380else381{382 _unhighlightResult(spans[i]);383 }384 }385 }386387returnthisSpan;388}389
InitQueryCode函数必须在页面的onload响应中执行,该函数最后调用setTimeout方法执行了mainLoop方法。注意,mainLoop方法并没有在lookup.js中定义,必须在包含lookup.js文件的页面文件中增加该函数的定义。于此,我们就需要在Default.aspx页面上加入如下定义:
1<script language="javascript"src="lookup.js"></script>2<script language="javascript">3mainLoop=function()4{5 val=escape(queryField.value);6if(lastVal!=val)7{8varresponse=_Default.GetSearchItems(val);9 showQueryDiv(response.value);10lastVal=val;11 }12 setTimeout('mainLoop()',100);13returntrue;14 }15</script>
由上述代码可以看到mainLoop函数每隔100ms会执行一次,它会判断当前文本输入框的值和上次提交查询的值是否相同,如果不同,它会重新向服务器发送请求进行查询,并且更新下拉区域的显示。 于此,一个基于 Ajax的自动完成功能就实现了。
本文借鉴于《ajax web2.0快速入门与项目实践》。
这本书上还使用了控件将该功能进行了封装,这样要实现Ajax的自动完成功能就更加方便了。在此就不做过多解说。
本文示例代码下载:AutoComplete.rar
---------------------------------------------------------------------------------------------------------