基於AJAX的自動完成

基於AJAX的自動完成

     我想大家在訪問某些網站的時候都曾見到過基於Ajax的自動完成功能,比如    


一、引出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>

    在頁面後臺代碼(Default.aspx.cs)的Page_Load方法中增加下面的代碼
1protected void Page_Load(object sender, EventArgs e)
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對象,它將包含所有以用戶輸入字符串的員工名字。

四、客戶端實現
    相對於服務器端的方法而言,客戶端的處理要複雜得多。首先來分析如何根據服務器端返回的ArrayList對象展示結果。這裏用到了Web編程中“層”(div)的概念,通過JavaScriptDOM創建一個新的層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頁面上加入如下定義:
 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會執行一次,它會判斷當前文本輸入框的值和上次提交查詢的值是否相同,如果不同,它會重新向服務器發送請求進行查詢,並且更新下拉區域的顯示。
    於此,一個基於 Ajax的自動完成功能就實現了。

本文借鑑於《ajax web2.0快速入門與項目實踐》。
這本書上還使用了控件將該功能進行了封裝,這樣要實現Ajax的自動完成功能就更加方便了。在此就不做過多解說。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章