我想大家在訪問某些網站的時候都曾見到過基於Ajax的自動完成功能,比如
一、引出Ajax的自動完成
現在要實現一個員工信息查詢的功能,即根據輸入的名字檢索員工的詳細信息。這是一個簡單的數據表查詢,在ASP.NET中實現這樣的功能是比較簡單的.
從上面可以看出,這種員工信息查詢功能還存在一些不足,比如用戶可能記不全員工的名字,只記得前面幾個字母是什麼,這樣用戶只能根據記憶猜測,一遍遍地嘗試。如果在用戶輸入的同時,輸入框下方可以給出相應的提示,輔助用戶輸入,那麼用戶進行檢索的速度和成功率就會大大提高.這就是基於Ajax的自動完成功能.
二、自動完成功能的實現
實現這樣的功能需要按以下的步驟進行。
· 服務器端提供GetSearchItems方法給客戶端,用來返回滿足條件的員工列表。
· 客戶端的輸入框需要增加onkeydown響應函數,以便即時獲取滿足條件的員工列表。
· 通過客戶端的JavaScript動態列出待選結果的列表,同時還要提供鍵盤和鼠標的響應。
三、服務器端實現
本文采用AjaxPro.NET作爲Ajax開發框架,首先爲使用AjaxPro.NET做一些準備工作。 添加對AjaxPro.dll的引用,修改Web.config配置文件,在system.web節點下加入如下配置:
2<!-- Register the ajax handler -->
3<add verb="POST,GET" path="ajaxpro/*.ashx" type="AjaxPro.AjaxHandlerFactory, AjaxPro" />
4</httpHandlers>
在頁面後臺代碼(Default.aspx.cs)的Page_Load方法中增加下面的代碼:
2{
3 AjaxPro.Utility.RegisterTypeForAjax(typeof(_Default));
4}
下面定義提供給客戶端調用的方法GetSearchItems(),參數query爲模糊查詢的關鍵字值:
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的定義:
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
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的自動完成功能就更加方便了。在此就不做過多解說。
基於AJAX的自動完成
基於AJAX的自動完成
1<httpHandlers>
1protected void Page_Load(object sender, EventArgs e)
1[AjaxPro.AjaxMethod()]
GetSearchItems方法返回一個ArrayList對象,它將包含所有以用戶輸入字符串的員工名字。 1// 下拉區背景色
InitQueryCode函數必須在頁面的onload響應中執行,該函數最後調用setTimeout方法執行了mainLoop方法。注意,mainLoop方法並沒有在lookup.js中定義,必須在包含lookup.js文件的頁面文件中增加該函數的定義。於此,我們就需要在Default.aspx頁面上加入如下定義: 1<script language="javascript" src="lookup.js"></script>
由上述代碼可以看到mainLoop函數每隔100ms會執行一次,它會判斷當前文本輸入框的值和上次提交查詢的值是否相同,如果不同,它會重新向服務器發送請求進行查詢,並且更新下拉區域的顯示。發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.