來博客園也有段時間了,真心覺得這里的人都是默默耕耘型的,所以決定搬家到這里。這個是我以前在ITeye里面寫的http://sky-yibai.iteye.com/blog/1814001,用戶名都是Sky_YiBai,現在在這里我在重新編輯啊下吧。
正文:
這幾天,幫同學解決一個問題,雖然他的問題還沒有完全解決,但在解決問題的過程中我做了這個“自動補全”的功能。雖然這個補全的功能在網上也有很多代碼,但是在我寫代碼和學習的過程中,好的較少,清晰的較少,多是轉載或是浮躁的寫一寫,我覺得這樣很容易使有問題的人產生入門易,理解難的問題。所以我下面會把自己學習和寫這個的過程盡量寫的詳細一些。
需求:
這個其實你登錄到百度首頁,我的需求主要是參考這個搜索框的“自動補全”功能,自己隨便輸入個文字,比如:“我”,看看結果理解下吧~
實現:
1.可以根據文本框的“輸入”自動完成與“輸入”相關的數據的匹配和前台展示。
2.可以通過鍵盤方向鍵實現瀏覽。
3.可以通過鼠標的點擊和鍵盤的回車選中需要的數據項並更新顯示在文本框中。
實現備注:
1.在實現的過程中,后台數據的模擬,是通過數組在js部分暫時實現,真正用到項目上我們可以通過ajax來實現,這是后話,暫不詳說。
2.至於兼容性來說,我測試過ie8、chrome、firefox、遨游、360等瀏覽器。其它的瀏覽器,尤其是ie系列,如果大家願意幫這個忙,可以幫忙測試一下。因為 我后邊還想把這個封裝成jsp的自定義標簽,所以這個還是很需要大家的幫助的,先謝謝大家了~~
綜上,廢話不多說,現在開始正是來分析下如何來實現這個功能,開整:
==========================================
分析html的結構:
1.這個通過我自己想,我想應該包括一個文本框,那下面用來顯示搜索數據的列表應該用什么呢,我想過select、ul和table,后來還是打開百度首頁,輸入了個“我”,再F12看了下它的結構,我決定就用他那個吧,詳細如下:
2.我的具體實現如下:
<body onload="initialize()"> <div id="bodyDiv" name="bodyDiv" style="position: relative; border-top:0px; margin-top:0; width:160px" > <!--<span style="color:red;font-size:5px;">*補全功能默認開啟</span>--> <input type="text" id="inputUser" name="inputUser" value="" onfocus="inputFocus()" onkeydown="inputKeydown(event)" onblur="inputBlur()" /> <div id="showUser" name="showUser"> <!-- 此處添加搜索數據的結果,使用的是table標簽 --> </div> </div> </body>
大家可以看到我的html結構,我打算用table來做具體的列表組件,這里提一句后話:我的table的id為selector,在js中通過getElementById獲得的該table並賦值給一個select變量。大家在此先不用我這么做的意義,只要先知道有這么回事兒就好。
大家應該還可以看到,我在html中綁定了幾個事件:
a.initialize(),這個函數主要用於在頁面加載完畢后立即初始化一些變量。
b.inputFocus(),這個函數主要是當焦點移動到文本框上時調用
c.inputKeydown(),這個函數主要是當焦點在文本框上時,用戶觸發了鍵盤事件,調用此函數處理
d.inputBlur(),這個事件主要是當焦點離開了文本框時,觸發該事件。
開始分析js部分:
1.initialize()函數:
我們首先要明白一件事情,當頁面加載完畢后,我們需要看到什么?一個文本框!我們不需要看到什么?那個搜索數據的列表!順便提一句,什么時候需要看到那個列表呢?那就是當文本框中輸入了數據(此處我定義的數據都是數字,所以輸入的時候若想有結果也暫時輸入數字),如果有匹配該輸入的數據,則要實時的更新並顯示列表。
我們的思路暫時理到這里,其它的留待后話再說,先看代碼:
//加載完后,將"提示"列表隱藏 function initialize(){ source=['0123','023',123,1234,212,214,'033333','0352342',1987,17563,20932]; elemCSS={ focus:{'color':'black','background':'#ccc'}, blur:{'color':'black','background':'transparent'} }; inputUse=document.getElementById("inputUser"); showUse=document.getElementById("showUser"); var bodyDiv=document.getElementById("bodyDiv"); showUse.style.display="none"; inputUse.style.width=bodyDiv.style.width; showUse.style.width=bodyDiv.style.width; inputValue=inputUse.defaultValue; }
這里着重要說的是函數中后四行的代碼。對於涉及“width”的部分,首先我們先設置用來包含table的div為隱藏狀態,這樣,table也將是隱藏的。同時將文本框的寬度和列表的寬度也進行設置,此處我用到的是最外層div的寬度。對於最后一行的代碼,我們取出了文本框的默認值(空值)賦值給一個全局變量,這個變量之后的主要作用就是用來記錄文本框之前的值,以便用這個值與文本框的當前值相比較,如果值改變了,則需要去后台查詢匹配的數據項。
2.inputFocus()函數:
大家想想初始化完之后,我們眼前出現的應該是一個文本框,而“提示”列表此時應該是隱藏的。當我們把鼠標在文本框點擊一下,頁面的焦點就會切換到文本框上,此時也就出發文本框的onfocus事件,調用inputFocus()函數,代碼如下:
//在文本框上觸發onfocus()事件 function inputFocus(){ //調用setInterval()函數每200ms刷新一次 this.timer=setInterval(function(){ if(inputUse.value!=''){ //檢查文本框的當前值與以前的值是否有變化 if(inputUse.value!=inputValue){ //如果文本框當前值與之前的值不同,記錄當前值,已被下次調用時使用 inputValue=inputUse.value; //清除上次調用顯示的結果 showUse.innerHTML=''; if(inputValue!=''){ //定義JS的RegExp對象,查詢以inputValue開頭的數據 quickExpr=RegExp('^'+inputValue); //如果數據源不為空,則調用match函數開始匹配數據 //此處如果通過ajax取數據,則適當修改數據源即可 if(source){ match(quickExpr,inputValue,source); } } } } else{ inputValue=inputUse.value; showUse.innerHTML=''; showUse.style.display="none"; } },200) }
從代碼,我們可以看出,我們的主體內容定義在了setInterval()這個函數中。為什么要這樣定義呢?因為一旦焦點在文本框中,我們就需要時刻知道文本框的內容是否發生了變化,以便匹配數據,所以我們利用setInterval函數每200m對文本框執行一次判斷。
那么該如何判斷呢,之前提到過inputValue,主要是利用這個值與文本框的當前值作比較(從代碼中你可以體會到我的意思),並且要更新inputValue的值,以備下次調用使用。如果,文本框的值與200ms之前比發生了變化,我們就要利用js的RegExp對象來訪問match()這個函數,這個函數的代碼如下:
//該函數用來查詢匹配數據 function match(quickExpr,value,source){ var table=null; var tr=null; var td=null; //創建table標簽 table=document.createElement('table'); table.id='selector'; table.style.width='100%'; //開始遍歷數組 //如果用ajax從后台取數據,我們也可組織成數組的形式返回 for(var i in source){ //再次檢驗數據是否為空並且用正則取數據 if(value.length>0 && quickExpr.exec(source[i])!=null){ //創建tr標簽 tr=document.createElement('tr'); //創建td標簽 td=document.createElement('td'); //在td中插入<a href="javascript:void(null);"><span>數據項</span></a> td.innerHTML = '<a href="javascript:void(null);">'+source[i]+'</a>'; //appendChild()在指定元素的最后一個子節點后添加節點 tr.appendChild(td); table.appendChild(tr); showUse.appendChild(table); } } //檢驗table下面的a標簽的數量,以此確定是否將“提示”列表顯示 if(showUse.getElementsByTagName('a').length){ showUse.style.display=""; }else{ showUse.style.display="none"; } }
針對match函數的代碼,我在代碼中添加的注釋相信已經對你的理解有很大的幫助,但我還要在強調的是我選擇在此創建包含“提示”列表的table,並根據查詢到的符合條件的數據量創建對應的tr,td。這個最終將形成的結構你可以參考上面我對於百度首頁的搜索框的截圖,以便更好的理解。
3.inputKeydown()函數:
當我們通過上述兩步,我們頁面上的“提示”列表已經顯示。我們必然會通過方向鍵等鍵盤按鈕訪問列表中的數據。這一個過程中,主要的變化是焦點由文本框內變化到了“提示”列表中,代碼如下:
function inputKeydown(event){ //兼容IE event = event || window.event; //如果按了down鍵 if(event.keyCode==40){ //如果“提示”列表已經顯示,則把焦點切換到列表中的第一個數據項上 if(showUse.style.display==""){ showUse.getElementsByTagName('a')[0].focus(); }else{ //如果“提示”列表未顯示,則把焦點依舊留在文本框中 inputUse.focus(); } } //如果按了up鍵 else if(event.keyCode==38){ //如果“提示”列表已經顯示,則把焦點切換到列表中的最后一個數據項上 if(showUse.style.display==""){ showUse.getElementsByTagName('a')[showUse.getElementsByTagName('a').length-1].focus(); }else{ //如果“提示”列表未顯示,則把焦點依舊留在文本框中 inputUse.focus(); } } //如果按了tab鍵,此時的情況與“百度首頁”的處理情況一樣 else if(event.keyCode==9){ showUse.innerHTML=''; showUse.style.display="none"; } }
這里主要想說的是,當"提示"列表已經展開的情況下,如果按了up或者down鍵(根據事件對象的鍵碼值判斷),則將數據項的最后一項或者第一項設置為高亮,並且將焦點真正切換到相應的高亮數據項上。
但是這里還需要着重注意的是按了tab鍵該如何處理。這里的處理方式與百度相同,就是關閉"提示"列表。
4.inputBlur函數:
這個函數的定義我們要理解下,這個事件定義的是文本框的onblur事件,該事件的含義是當焦點離開元素對象(此處就是指文本框)時調用。那么具體的內容,我們先來看看代碼:
//當焦點離開文本框時,觸發該事件 function inputBlur(){ //由於焦點已經離開了文本框,則取消setInterval clearInterval(this.timer); //記住當前有焦點的選項 var current=0; //當前table下面的a標簽的個數 var aArray=showUse.getElementsByTagName('a'); var len=aArray.length-1; var select=document.getElementById("selector"); //定義“選項”的onclick事件 var aClick = function(){ //由於“選項”上觸發了click事件,this就是指a標簽,則把a標簽包含的數據賦值給文本框 inputUse.value=this.childNodes[0].data; //將文本框的當前值更新到記錄以前值的變量中 inputValue=inputUse.value; //由於上面已經選出合適的數據項,則清空table下的內容,並關閉“提示”列表 showUse.innerHTML=''; showUse.style.display='none'; //將焦點移回文本框 inputUse.focus(); }; //定義“選項”的onfocus事件 var aFocus = function(){ for(var i=len; i>=0; i--){ //this是a,this.parentNode是td,select.children[i].children[0]是table.tr.td if(this.parentNode===select.childNodes[i].childNodes[0]){ //如果是同一個td,則將current的值置為焦點所在位置的值 current = i; break; } } //添加有焦點的效果 for(var k in elemCSS.focus){ this.style[k] = elemCSS.focus[k]; } }; //定義“選項”的onblur事件 var aBlur= function(){ //添加無焦點的效果 for(var k in elemCSS.blur) this.style[k] = elemCSS.blur[k]; }; //定義“選項”的onKeydown是事件 var aKeydown = function(event){ //兼容IE event = event || window.event; //如果在選擇數據項時按了tab鍵,此時的情況與“百度首頁”的處理情況一樣 if(event.keyCode===9){ showUse.innerHTML=''; showUse.style.display = 'none'; inputUse.focus(); } //如果按了down鍵 else if(event.keyCode==40){ //向下移動,准備移動焦點 current++; //如果當前焦點在最后一個數據項上,用戶用按了down鍵,則循環向上,回到文本框上 if(current>len){ current=-1; inputUse.focus(); }else{ select.getElementsByTagName('a')[current].focus(); } } //如果按了up鍵 else if(event.keyCode==38){ //向上移動,准備移動焦點 current--; //如果當前焦點在文本框上,用戶用按了up鍵,則循環向下,回到最后一個數據項上 if(current<0){ inputUse.focus(); }else{ select.getElementsByTagName('a')[current].focus(); } } }; //將“選項”的事件與相應的處理函數綁定 for(var i=0; i<aArray.length; i++){ aArray[i].onclick = aClick; aArray[i].onfocus = aFocus; aArray[i].onblur = aBlur; aArray[i].onkeydown = aKeydown; } }
上面代碼的內容很長,但是大家要首先抓住主要的來看。我的建議是先看最后那個for循環。我在這里把定義的四個事件綁定到了一個數組的對象上,而這個數組中的對象就是"提示"列表中每一個數據項。到此,我想大家應該應該還記得上面的inputKeydown()函數了吧,通過該函數將焦點由文本框切換到“提示”列表上,更確切的說是數據項上,此處對應的是<a>標簽。那么在數據項上的移動以及選中就要依靠上面定義的四個事件。四個事件的具體分析請大家詳細的看注釋,有些不懂的地方不妨用瀏覽器的調試來看看就明白了。
自己的收獲和感想:
我先說說技術上的吧,我最初利用table調用innerHTML並將其值設置為空,來實現"提示"列表的清除,使下次調用時僅僅顯示當次調用的正確結果。我最初代碼的測試是面向chrome的,但后來再調試兼容性時,發現在ie下,table是無法對innerTHML屬性進行設置值的,但是可讀。再糾結了一下之后,發現其實兼容性的主要問題還就是集中在這個上面,於是果斷重新寫,最終處理了這個問題。
還有一個就是z-index的使用,這里不詳述,但感興趣的同學可以翻看《程序員》雜志2013.2月版的文章:不為人知的z-index,寫的相當不錯。
其實最大的體會就是,自己動手寫。雖然網上也有很多人寫過,但我保證很多是淺嘗輒止的。就像我曾經在一個群里問了個關於焦點切換的問題,很多人都說這個東西都寫爛了,當時給我的感覺就是我這個問題問的很小白阿。但我堅持說向自己寫寫,后來其中的一個說簡單的那位看了代碼也說不會。這就說明一個問題,很多事情別人輕視了,用插件來解決了,如果你在有精力的情況下,最好還是自己動手寫寫。
我覺的比較好的方法就是,結合我這篇博客來說,我覺得大家可以先點開百度首頁自己理清思路,能理到哪兒算哪兒。然后再看看我這篇博客,加深對實現的了解。之后最好就是自己先寫寫了,其實按照這個的難度,一般人都可以寫到焦點的切換。最后,如果實在沒想法的就看看我附件只中的源碼吧。加上注釋,我相信一定可以使你較為輕松的明白問題。
最后,希望大家共同進步~~
PS:我怎么沒找到添加附件的地方。。。。,還有怎么在我編輯這個文章時,行距怎么一會兒大一會兒小啊。。。。望高手指點
這是代碼的下載地址:http://files.cnblogs.com/PurpleDream/%E8%87%AA%E5%8A%A8%E8%A1%A5%E5%85%A8.rar