基于AJAX的自动完成
我想大家在访问某些网站的时候都曾见到过基于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节点下加入如下配置:
在页面后台代码(Default.aspx.cs)的Page_Load方法中增加下面的代码:
下面定义提供给客户端调用的方法GetSearchItems(),参数query为模糊查询的关键字值:
四、客户端实现
相对于服务器端的方法而言,客户端的处理要复杂得多。首先来分析如何根据服务器端返回的ArrayList对象展示结果。这里用到了Web编程中“层”(div)的概念,通过JavaScript和DOM创建一个新的层div,将ArrayList中的每一个条目都作为其子节点加入到div中,而每一个条目也被看作是一个div,其中具体的文本内容则是一个span对象。
除了显示待选的结果之外,下拉区域还要对键盘、鼠标事件做出响应;为了实时地显示待选结果,还需要定时更新待选结果的列表。这些功能都封装在lookup.js中。下面是lookeu.js的定义:
于此,一个基于 Ajax的自动完成功能就实现了。
本文借鉴于《ajax web2.0快速入门与项目实践》。
这本书上还使用了控件将该功能进行了封装,这样要实现Ajax的自动完成功能就更加方便了。在此就不做过多解说。
本文示例代码下载:AutoComplete.rar
---------------------------------------------------------------------------------------------------------
一、引出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<add verb="POST,GET" path="ajaxpro/*.ashx" type="AjaxPro.AjaxHandlerFactory, AjaxPro" />
4</httpHandlers>
2<!-- Register the ajax handler -->
3<add verb="POST,GET" path="ajaxpro/*.ashx" type="AjaxPro.AjaxHandlerFactory, AjaxPro" />
4</httpHandlers>
在页面后台代码(Default.aspx.cs)的Page_Load方法中增加下面的代码:
1protected void Page_Load(object sender, EventArgs e)
2{
3 AjaxPro.Utility.RegisterTypeForAjax(typeof(_Default));
4}
2{
3 AjaxPro.Utility.RegisterTypeForAjax(typeof(_Default));
4}
下面定义提供给客户端调用的方法GetSearchItems(),参数query为模糊查询的关键字值:
1[AjaxPro.AjaxMethod()]
2public ArrayList GetSearchItems(string query)
3{
4 ArrayList items = new ArrayList();
5 StringBuilder queryString = new StringBuilder();
6 queryString.Append("select employeeid,lastname,firstname,title,titleofcourtesy from dbo.Employees");
7 queryString.Append(" where firstname like '%" + query + "%'");
8
9 DataSet ds = DataBase.Instance.ReturnDataSet(queryString.ToString());
10 for (int i = 0; i < ds.Tables[0].Rows.Count; i++)
11 {
12 items.Add(ds.Tables[0].Rows[i][2].ToString());
13 }
14 return items;
15}
GetSearchItems方法返回一个ArrayList对象,它将包含所有以用户输入字符串的员工名字。2public ArrayList GetSearchItems(string query)
3{
4 ArrayList items = new ArrayList();
5 StringBuilder queryString = new StringBuilder();
6 queryString.Append("select employeeid,lastname,firstname,title,titleofcourtesy from dbo.Employees");
7 queryString.Append(" where firstname like '%" + query + "%'");
8
9 DataSet ds = DataBase.Instance.ReturnDataSet(queryString.ToString());
10 for (int i = 0; i < ds.Tables[0].Rows.Count; i++)
11 {
12 items.Add(ds.Tables[0].Rows[i][2].ToString());
13 }
14 return items;
15}
四、客户端实现
相对于服务器端的方法而言,客户端的处理要复杂得多。首先来分析如何根据服务器端返回的ArrayList对象展示结果。这里用到了Web编程中“层”(div)的概念,通过JavaScript和DOM创建一个新的层div,将ArrayList中的每一个条目都作为其子节点加入到div中,而每一个条目也被看作是一个div,其中具体的文本内容则是一个span对象。
除了显示待选的结果之外,下拉区域还要对键盘、鼠标事件做出响应;为了实时地显示待选结果,还需要定时更新待选结果的列表。这些功能都封装在lookup.js中。下面是lookeu.js的定义:
1// 下拉区背景色
2var DIV_BG_COLOR = "#EEE";
3// 高亮显示条目颜色
4var DIV_HIGHLIGHT_COLOR = "#C30";
5// 字体
6var DIV_FONT = "Arial";
7// 下拉区内补丁大小
8var DIV_PADDING = "2px";
9// 下拉区边框样式
10var DIV_BORDER = "1px solid #CCC";
11
12
13// 文本输入框
14var queryField;
15// 下拉区id
16var divName;
17// IFrame名称
18var ifName;
19// 记录上次选择的值
20var lastVal = "";
21// 当前选择的值
22var val = "";
23// 显示结果的下拉区
24var globalDiv;
25// 下拉区是否设置格式的标记
26var divFormatted = false;
27
28/**
29InitQueryCode函数必须在<body onload>事件的响应函数中调用,其中:
30queryFieldName为文本框控件的id,
31hiddenDivName为显示下拉区div的id
32*/
33function InitQueryCode (queryFieldName, hiddenDivName)
34{
35 // 指定文本输入框的onblur和onkeydown响应函数
36 queryField = document.getElementById(queryFieldName);
37 queryField.onblur = hideDiv;
38 queryField.onkeydown = keypressHandler;
39
40 // 设置queryField的autocomplete属性为"off"
41 queryField.autocomplete = "off";
42
43 // 如果没有指定hiddenDivName,取默认值"querydiv"
44 if (hiddenDivName)
45 {
46 divName = hiddenDivName;
47 }
48 else
49 {
50 divName = "querydiv";
51 }
52
53 // IFrame的name
54 ifName = "queryiframe";
55
56 // 100ms后调用mainLoop函数
57 setTimeout("mainLoop()", 100);
58}
59
60/**
61获取下拉区的div,如果没有则创建之
62*/
63function getDiv (divID)
64{
65 if (!globalDiv)
66 {
67 // 如果div在页面中不存在,创建一个新的div
68
69 if (!document.getElementById(divID))
70 {
71 var newNode = document.createElement("div");
72 newNode.setAttribute("id", divID);
73 document.body.appendChild(newNode);
74 }
75
76 // globalDiv设置为div的引用
77 globalDiv = document.getElementById(divID);
78
79 // 计算div左上角的位置
80 var x = queryField.offsetLeft;
81 var y = queryField.offsetTop + queryField.offsetHeight;
82 var parent = queryField;
83 while (parent.offsetParent)
84 {
85 parent = parent.offsetParent;
86 x += parent.offsetLeft;
87 y += parent.offsetTop;
88 }
89
90 // 如果没有对div设置格式,则为其设置相应的显示样式
91 if (!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%";
99
100 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;
105
106 divFormatted = true;
107 }
108 }
109
110 return globalDiv;
111}
112
113/**
114根据返回的结果集显示下拉区
115*/
116function showQueryDiv(resultArray)
117{
118 // 获取div的引用
119 var div = getDiv(divName);
120
121 // 如果div中有内容,则删除之
122 while (div.childNodes.length > 0)
123 div.removeChild(div.childNodes[0]);
124
125 // 依次添加结果
126 for (var i = 0; i < resultArray.length; i++)
127 {
128 // 每一个结果也是一个div
129 var result = document.createElement("div");
130 // 设置结果div的显示样式
131 result.style.cursor = "pointer";
132 result.style.padding = "2px 0px 2px 0px";
133 // 设置为未选中
134 _unhighlightResult(result);
135 // 设置鼠标移进、移出等事件响应函数
136 result.onmousedown = selectResult;
137 result.onmouseover = highlightResult;
138 result.onmouseout = unhighlightResult;
139
140 // 结果的文本是一个span
141 var result1 = document.createElement("span");
142 // 设置文本span的显示样式
143 result1.className = "result1";
144 result1.style.textAlign = "left";
145 result1.style.fontWeight = "bold";
146 result1.innerHTML = resultArray[i];
147
148 // 将span添加为结果div的子节点
149 result.appendChild(result1);
150
151 // 将结果div添加为下拉区的子节点
152 div.appendChild(result);
153 }
154
155 // 如果结果集不为空,则显示,否则不显示
156 showDiv(resultArray.length > 0);
157}
158
159/**
160用户点击某个结果时,将文本框的内容替换为结果的文本,
161并隐藏下拉区
162*/
163function selectResult()
164{
165 _selectResult(this);
166}
167
168// 选择一个条目
169function _selectResult(item)
170{
171 var spans = item.getElementsByTagName("span");
172 if (spans)
173 {
174 for (var i = 0; i < spans.length; i++)
175 {
176 if (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);
183 return;
184 }
185 }
186 }
187}
188
189/**
190当鼠标移到某个条目之上时,高亮显示该条目
191*/
192function highlightResult()
193{
194 _highlightResult(this);
195}
196
197function _highlightResult(item)
198{
199 item.style.backgroundColor = DIV_HIGHLIGHT_COLOR;
200}
201
202/**
203当鼠标移出某个条目时,正常显示该条目
204*/
205function unhighlightResult()
206{
207 _unhighlightResult(this);
208}
209
210function _unhighlightResult(item)
211{
212 item.style.backgroundColor = DIV_BG_COLOR;
213}
214
215/**
216显示/不显示下拉区
217*/
218function showDiv (show)
219{
220 var div = getDiv(divName);
221 if (show)
222 {
223 div.style.visibility = "visible";
224 }
225 else
226 {
227 div.style.visibility = "hidden";
228 }
229 //adjustiFrame();
230}
231
232/**
233隐藏下拉区
234*/
235function hideDiv ()
236{
237 showDiv(false);
238}
239
240/**
241调整IFrame的位置,这是为了解决div可能会显示在输入框后面的问题
242*/
243function adjustiFrame()
244{
245 // 如果没有IFrame,则创建之
246 if (!document.getElementById(ifName))
247 {
248 var newNode = 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 }
255
256 iFrameDiv = document.getElementById(ifName);
257 var div = getDiv(divName);
258
259 // 调整IFrame的位置与div重合,并在div的下一层
260 try
261 {
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 }
270 catch (e)
271 {
272 }
273}
274
275/**
276文本输入框的onkeydown响应函数
277*/
278function keypressHandler (evt)
279{
280 // 获取对下拉区的引用
281 var div = getDiv(divName);
282
283 // 如果下拉区不显示,则什么也不做
284 if (div.style.visibility == "hidden")
285 {
286 return true;
287 }
288
289 // 确保evt是一个有效的事件
290 if (!evt && window.event)
291 {
292 evt = window.event;
293 }
294 var key = evt.keyCode;
295
296 var KEYUP = 38;
297 var KEYDOWN = 40;
298 var KEYENTER = 13;
299 var KEYTAB = 9;
300
301 // 只处理上下键、回车键和Tab键的响应
302 if ((key != KEYUP) && (key != KEYDOWN) && (key != KEYENTER) && (key != KEYTAB))
303 {
304 return true;
305 }
306
307 var selNum = getSelectedSpanNum(div);
308 var selSpan = setSelectedSpan(div, selNum);
309
310 // 如果键入回车和Tab,则选择当前选择条目
311 if ((key == KEYENTER) || (key == KEYTAB))
312 {
313 if (selSpan)
314 {
315 _selectResult(selSpan);
316 }
317 evt.cancelBubble = true;
318 return false;
319 }
320 else //如果键入上下键,则上下移动选中条目
321 {
322 if (key == KEYUP)
323 {
324 selSpan = setSelectedSpan(div, selNum - 1);
325 }
326 if (key == KEYDOWN)
327 {
328 selSpan = setSelectedSpan(div, selNum + 1);
329 }
330 if (selSpan)
331 {
332 _highlightResult(selSpan);
333 }
334 }
335
336 // 显示下拉区
337 showDiv(true);
338 return true;
339}
340
341/**
342获取当前选中的条目的序号
343*/
344function getSelectedSpanNum(div)
345{
346 var count = -1;
347 var spans = div.getElementsByTagName("div");
348 if (spans)
349 {
350 for (var i = 0; i < spans.length; i++)
351 {
352 count++;
353 if (spans[i].style.backgroundColor != div.style.backgroundColor)
354 {
355 return count;
356 }
357 }
358 }
359
360 return -1;
361}
362
363/**
364选择指定序号的结果条目
365*/
366function setSelectedSpan(div, spanNum)
367{
368 var count = -1;
369 var thisSpan;
370 var spans = div.getElementsByTagName("div");
371 if (spans)
372 {
373 for (var i = 0; i < spans.length; i++)
374 {
375 if (++count == spanNum)
376 {
377 _highlightResult(spans[i]);
378 thisSpan = spans[i];
379 }
380 else
381 {
382 _unhighlightResult(spans[i]);
383 }
384 }
385 }
386
387 return thisSpan;
388}
389
InitQueryCode函数必须在页面的onload响应中执行,该函数最后调用setTimeout方法执行了mainLoop方法。注意,mainLoop方法并没有在lookup.js中定义,必须在包含lookup.js文件的页面文件中增加该函数的定义。于此,我们就需要在Default.aspx页面上加入如下定义:2var DIV_BG_COLOR = "#EEE";
3// 高亮显示条目颜色
4var DIV_HIGHLIGHT_COLOR = "#C30";
5// 字体
6var DIV_FONT = "Arial";
7// 下拉区内补丁大小
8var DIV_PADDING = "2px";
9// 下拉区边框样式
10var DIV_BORDER = "1px solid #CCC";
11
12
13// 文本输入框
14var queryField;
15// 下拉区id
16var divName;
17// IFrame名称
18var ifName;
19// 记录上次选择的值
20var lastVal = "";
21// 当前选择的值
22var val = "";
23// 显示结果的下拉区
24var globalDiv;
25// 下拉区是否设置格式的标记
26var divFormatted = false;
27
28/**
29InitQueryCode函数必须在<body onload>事件的响应函数中调用,其中:
30queryFieldName为文本框控件的id,
31hiddenDivName为显示下拉区div的id
32*/
33function InitQueryCode (queryFieldName, hiddenDivName)
34{
35 // 指定文本输入框的onblur和onkeydown响应函数
36 queryField = document.getElementById(queryFieldName);
37 queryField.onblur = hideDiv;
38 queryField.onkeydown = keypressHandler;
39
40 // 设置queryField的autocomplete属性为"off"
41 queryField.autocomplete = "off";
42
43 // 如果没有指定hiddenDivName,取默认值"querydiv"
44 if (hiddenDivName)
45 {
46 divName = hiddenDivName;
47 }
48 else
49 {
50 divName = "querydiv";
51 }
52
53 // IFrame的name
54 ifName = "queryiframe";
55
56 // 100ms后调用mainLoop函数
57 setTimeout("mainLoop()", 100);
58}
59
60/**
61获取下拉区的div,如果没有则创建之
62*/
63function getDiv (divID)
64{
65 if (!globalDiv)
66 {
67 // 如果div在页面中不存在,创建一个新的div
68
69 if (!document.getElementById(divID))
70 {
71 var newNode = document.createElement("div");
72 newNode.setAttribute("id", divID);
73 document.body.appendChild(newNode);
74 }
75
76 // globalDiv设置为div的引用
77 globalDiv = document.getElementById(divID);
78
79 // 计算div左上角的位置
80 var x = queryField.offsetLeft;
81 var y = queryField.offsetTop + queryField.offsetHeight;
82 var parent = queryField;
83 while (parent.offsetParent)
84 {
85 parent = parent.offsetParent;
86 x += parent.offsetLeft;
87 y += parent.offsetTop;
88 }
89
90 // 如果没有对div设置格式,则为其设置相应的显示样式
91 if (!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%";
99
100 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;
105
106 divFormatted = true;
107 }
108 }
109
110 return globalDiv;
111}
112
113/**
114根据返回的结果集显示下拉区
115*/
116function showQueryDiv(resultArray)
117{
118 // 获取div的引用
119 var div = getDiv(divName);
120
121 // 如果div中有内容,则删除之
122 while (div.childNodes.length > 0)
123 div.removeChild(div.childNodes[0]);
124
125 // 依次添加结果
126 for (var i = 0; i < resultArray.length; i++)
127 {
128 // 每一个结果也是一个div
129 var result = document.createElement("div");
130 // 设置结果div的显示样式
131 result.style.cursor = "pointer";
132 result.style.padding = "2px 0px 2px 0px";
133 // 设置为未选中
134 _unhighlightResult(result);
135 // 设置鼠标移进、移出等事件响应函数
136 result.onmousedown = selectResult;
137 result.onmouseover = highlightResult;
138 result.onmouseout = unhighlightResult;
139
140 // 结果的文本是一个span
141 var result1 = document.createElement("span");
142 // 设置文本span的显示样式
143 result1.className = "result1";
144 result1.style.textAlign = "left";
145 result1.style.fontWeight = "bold";
146 result1.innerHTML = resultArray[i];
147
148 // 将span添加为结果div的子节点
149 result.appendChild(result1);
150
151 // 将结果div添加为下拉区的子节点
152 div.appendChild(result);
153 }
154
155 // 如果结果集不为空,则显示,否则不显示
156 showDiv(resultArray.length > 0);
157}
158
159/**
160用户点击某个结果时,将文本框的内容替换为结果的文本,
161并隐藏下拉区
162*/
163function selectResult()
164{
165 _selectResult(this);
166}
167
168// 选择一个条目
169function _selectResult(item)
170{
171 var spans = item.getElementsByTagName("span");
172 if (spans)
173 {
174 for (var i = 0; i < spans.length; i++)
175 {
176 if (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);
183 return;
184 }
185 }
186 }
187}
188
189/**
190当鼠标移到某个条目之上时,高亮显示该条目
191*/
192function highlightResult()
193{
194 _highlightResult(this);
195}
196
197function _highlightResult(item)
198{
199 item.style.backgroundColor = DIV_HIGHLIGHT_COLOR;
200}
201
202/**
203当鼠标移出某个条目时,正常显示该条目
204*/
205function unhighlightResult()
206{
207 _unhighlightResult(this);
208}
209
210function _unhighlightResult(item)
211{
212 item.style.backgroundColor = DIV_BG_COLOR;
213}
214
215/**
216显示/不显示下拉区
217*/
218function showDiv (show)
219{
220 var div = getDiv(divName);
221 if (show)
222 {
223 div.style.visibility = "visible";
224 }
225 else
226 {
227 div.style.visibility = "hidden";
228 }
229 //adjustiFrame();
230}
231
232/**
233隐藏下拉区
234*/
235function hideDiv ()
236{
237 showDiv(false);
238}
239
240/**
241调整IFrame的位置,这是为了解决div可能会显示在输入框后面的问题
242*/
243function adjustiFrame()
244{
245 // 如果没有IFrame,则创建之
246 if (!document.getElementById(ifName))
247 {
248 var newNode = 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 }
255
256 iFrameDiv = document.getElementById(ifName);
257 var div = getDiv(divName);
258
259 // 调整IFrame的位置与div重合,并在div的下一层
260 try
261 {
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 }
270 catch (e)
271 {
272 }
273}
274
275/**
276文本输入框的onkeydown响应函数
277*/
278function keypressHandler (evt)
279{
280 // 获取对下拉区的引用
281 var div = getDiv(divName);
282
283 // 如果下拉区不显示,则什么也不做
284 if (div.style.visibility == "hidden")
285 {
286 return true;
287 }
288
289 // 确保evt是一个有效的事件
290 if (!evt && window.event)
291 {
292 evt = window.event;
293 }
294 var key = evt.keyCode;
295
296 var KEYUP = 38;
297 var KEYDOWN = 40;
298 var KEYENTER = 13;
299 var KEYTAB = 9;
300
301 // 只处理上下键、回车键和Tab键的响应
302 if ((key != KEYUP) && (key != KEYDOWN) && (key != KEYENTER) && (key != KEYTAB))
303 {
304 return true;
305 }
306
307 var selNum = getSelectedSpanNum(div);
308 var selSpan = setSelectedSpan(div, selNum);
309
310 // 如果键入回车和Tab,则选择当前选择条目
311 if ((key == KEYENTER) || (key == KEYTAB))
312 {
313 if (selSpan)
314 {
315 _selectResult(selSpan);
316 }
317 evt.cancelBubble = true;
318 return false;
319 }
320 else //如果键入上下键,则上下移动选中条目
321 {
322 if (key == KEYUP)
323 {
324 selSpan = setSelectedSpan(div, selNum - 1);
325 }
326 if (key == KEYDOWN)
327 {
328 selSpan = setSelectedSpan(div, selNum + 1);
329 }
330 if (selSpan)
331 {
332 _highlightResult(selSpan);
333 }
334 }
335
336 // 显示下拉区
337 showDiv(true);
338 return true;
339}
340
341/**
342获取当前选中的条目的序号
343*/
344function getSelectedSpanNum(div)
345{
346 var count = -1;
347 var spans = div.getElementsByTagName("div");
348 if (spans)
349 {
350 for (var i = 0; i < spans.length; i++)
351 {
352 count++;
353 if (spans[i].style.backgroundColor != div.style.backgroundColor)
354 {
355 return count;
356 }
357 }
358 }
359
360 return -1;
361}
362
363/**
364选择指定序号的结果条目
365*/
366function setSelectedSpan(div, spanNum)
367{
368 var count = -1;
369 var thisSpan;
370 var spans = div.getElementsByTagName("div");
371 if (spans)
372 {
373 for (var i = 0; i < spans.length; i++)
374 {
375 if (++count == spanNum)
376 {
377 _highlightResult(spans[i]);
378 thisSpan = spans[i];
379 }
380 else
381 {
382 _unhighlightResult(spans[i]);
383 }
384 }
385 }
386
387 return thisSpan;
388}
389
1<script language="javascript" src="lookup.js"></script>
2<script language="javascript">
3mainLoop = function()
4{
5 val = escape(queryField.value);
6 if (lastVal != val)
7 {
8 var response = _Default.GetSearchItems(val);
9 showQueryDiv(response.value);
10lastVal = val;
11 }
12 setTimeout('mainLoop()', 100);
13 return true;
14 }
15 </script>
由上述代码可以看到mainLoop函数每隔100ms会执行一次,它会判断当前文本输入框的值和上次提交查询的值是否相同,如果不同,它会重新向服务器发送请求进行查询,并且更新下拉区域的显示。2<script language="javascript">
3mainLoop = function()
4{
5 val = escape(queryField.value);
6 if (lastVal != val)
7 {
8 var response = _Default.GetSearchItems(val);
9 showQueryDiv(response.value);
10lastVal = val;
11 }
12 setTimeout('mainLoop()', 100);
13 return true;
14 }
15 </script>
于此,一个基于 Ajax的自动完成功能就实现了。
本文借鉴于《ajax web2.0快速入门与项目实践》。
这本书上还使用了控件将该功能进行了封装,这样要实现Ajax的自动完成功能就更加方便了。在此就不做过多解说。
本文示例代码下载:AutoComplete.rar
---------------------------------------------------------------------------------------------------------