像百度與google,當我們往搜尋方塊輸入東西時就會出現一排列表提示使用者有什麼熱門或適合的候選詞,這種效果就叫suggest。本文將一步步教你如何設計它。
首先,用到的架構當然是我的架構mass Framework,當然你用其他架構也可以,如jQuery,沒有什麼複雜的東西。只要弄懂原理,一下子就能搞出來。想必,以後你們工作也遇到做搜尋方塊的活兒。
由於本人沒有後端,因此取用一個對象作為本機資料庫。而我現在要做的,其實遠遠比suggest進階,類似IDE的文法提示的東西。當前成品已放到github上。
好了,我們動手吧。首先是結構層,裝了FF的同學可以在百度首頁查看源碼,當輸入幾個字母時,會動態產生了那些HTML。不過怎麼也好,其成就是一個DIV放到搜尋欄的下方,裡面放了一個table,table動態存放候選詞。並且候選詞如果不是使用者輸入的部分,也就是說,JS自動補充的部分它會把它們放到一個b標籤加粗顯示出來。不過, 我覺得用table太重量化,改用了ul列表,為了讓IE6也支援掠過變色效果,我還在裡面套了一個a標籤。為了放便取詞,我還為它(a標籤),添加了一個屬性,專門用於存放補充元整後的詞彙。大抵是這個樣子:
<div id="search_wrapper"> <div> <input id="search" autocomplete="off"> </div> <div id="suggest_wrapper"> <ul id="suggest_list"> <li> <a data-value="完整的詞彙" href="javascript:void(0)"> 使用者輸入部分 <b>自動提示部分</b> </a> </li> <li> <a data-value="完整的詞彙" href="javascript:void(0)"> 使用者輸入部分 <b>自動提示部分</b> </a> </li> <!-- 更多li 最多10個 --> </ul> </div> </div>
看一看結構,其實就是兩部分,div#search_wrapper為可見,div#suggest_wrapper為“不可見”(只要裡面沒有li元素,它就不佔空間,顯示不出來了)。input搜尋方塊有個屬性autocomplete,用於關掉瀏覽器內建的提示功能。關於data-value,這種命名方法是HTML5推薦的方式,用於定義要緩衝的資料,data-*在新銳瀏覽器中會放到一個叫dataset的對象中。比如:
<div id="司徒正美" data-drink="coffee" data-meal-time="12:00">12:00</div>
我們可以通過如下方式訪問到它:
var el= document.getElementById('司徒正美'); alert( el.dataset.drink ); alert( el.dataset.mealTime );
當然,你也可以不用設定屬性,直接取a標籤的innerText或textContext。
注意:完整的詞彙 = 使用者輸入部分 + 自動提示部分。因此你不要在a標籤裡面加這麼多東西,防止出現空格什麼的,導致檢索失敗!
接著是樣式部分,不過不詳述了。很簡單:
#search_wrapper { height:50px; } #search{ width:300px; } #suggest_wrapper{ position:relative; } #suggest_list{ position:absolute; z-index:100; list-style: none; margin:0; padding:0; background:#fffafa; border:1px solid #ccc; border-bottom:0 none; } #suggest_list li a{ display: block; height:20px; width:304px; color: #000; border-bottom:1px solid #ccc; line-height:20px; text-decoration: none; } #suggest_list li a:hover, .glow_suggest { background:#ffff80; }
好了,到重點了。由於我沒有後台,要使用一個本機物件作為本機資料庫。這對象當然是個JS對象了。我們遍曆對象一般都是obj.aaa.bbb.ccc,這樣一直點下去,其實每到一個點號時,就是用for in 迴圈進行遍曆。因此我們監聽常值內容的輸入的情況,一但發生變化就取得輸入框的內容,然後在for in 迴圈中比較。如果是與這個輸入值開頭的屬性就取出來,放到一個數組中,一直取夠十個,然後把這些數組的內容拼接成上述描繪的li元素格式,一併貼到ul元素之內。當中,我們還要注意點中,如果一開始就輸入焦點號,我們就取window對象的十個屬性吧,以後遇到點號就切換這個對象。
好了,開始寫碼,由於用到我的架構,大家可以到這裡去下。在項目首頁有README,教你是怎麼安裝微型.Net伺服器與查看文檔的。一開始,你就姑且把它當成是添加了模組載入功能的jQuery,API 90%神似。我們要用到它的事件模組與屬性模組,它會把相關依賴載入好的,再添加ready參數,它就會在domReady後執行。我們選擇輸入框後為它綁定一個input事件,這是一個標準瀏覽器都支援的事件,IE下我的架構已經相容好了,用jQuery與原生的同學請用propertychange事件類比。
//by 司徒正美$.require("ready,event,attr",function(){ var search = $("#search"), hash = window, prefix = "", fixIE = NaN; search.addClass("search_target"); search.input(function(){//監聽輸入 var input = this.value,//原始值 val = input.slice( prefix.length),//比較值 output = []; //用來放置輸出內容 if( fixIE === input){ return //IE下肅使是通過程式改變輸入框裡面的值也會觸發propertychange事件,導致我們無法進行上下翻操作 } for(var prop in hash){ if( prop.indexOf( val ) === 0 ){//取得以輸入值開頭的API if( output.push( '<li><a href="javascript:void(0)" data-value="'+prefix + prop+'">'+ input + "<b>" + (prefix + prop ).slice( input.length ) +"</b></a></li>" ) == 10){ break; } } } //如果向前遇到點號,或向後取消點號 if( val.charAt(val.length - 1) === "." || (input && !val) ){ var arr = input.split("."); hash = window; for(var j = 0; j < arr.length; j++){ var el = arr[j]; if(el && hash[ el ]){ hash = hash[ el ];//重新設定要遍曆API的對象 } } prefix = input == "." ? "" : input; for( prop in hash){ if( output.push( '<li><a href="javascript:void(0)" class="search_target" data-value="'+prefix + prop+'">'+ input + "<b>" + (prefix + prop ).slice( prefix.length ) +"</b></a></li>" ) == 10){ break; } } } $("#suggest_list").html( output.join("") ); if(!input){//重設所有 hash = window; fixIE = prefix = output = []; } });});
當提示列表出來後,我們就監聽上下翻效果。也就是點擊鍵盤的方位鍵時,會上下高亮提示的條目,並且它填進搜尋方塊中。這時需要綁定keyup事件,檢查其keyCode,標準瀏覽器管它為which,可以看我的這篇博文《javascript 鍵盤事件總結》。實現原理很簡單,定義一個外圍的變數,用於存放高亮的位置(索引值),然後用上翻時就減一,用下翻時就加一,然後取得提示列表中的所有a標籤,用索引值定位到某一個a標籤中,高亮它,然後去掉原先高亮的a標籤。
//by 司徒正美$.require("ready,event,attr",function(){ var search = $("#search"), hash = window, prefix = ""; search.input(function(){//監聽輸入//..... }); var glowIndex = -1; $(document).keyup(function(e){//監聽上下翻 if(/search_target/i.test( e.target.className)){//只代理特定元素,提高效能 var upOrdown = 0 if(e.which === 38 || e.which === 104){ //up 8 upOrdown --; }else if(e.which === 40 || e.which === 98){//down 2 upOrdown ++; } if(upOrdown){ var list = $("#suggest_list a"); //轉移高亮的欄目 list.eq(glowIndex).removeClass("glow_suggest"); glowIndex += upOrdown; var el = list.eq( glowIndex ).addClass("glow_suggest"); fixIE = el.attr("data-value") search.val( fixIE ) if(glowIndex === list.length - 1){ glowIndex = -1; } } } });});
最後是斷行符號提交。我又寫到一個keyup事件中去。當然你們可以設法把兩個keyup合成一個(監聽window),我這樣寫純粹是為了教學的需要。
//by 司徒正美$.require("ready,event,attr",function(){ var search = $("#search"), hash = window, prefix = ""; search.input(function(){//監聽輸入//..... }); var glowIndex = -1; $(window).keyup(function(e){//監聽上下翻//..... }); search.keyup(function(e){//監聽提交 var input = this.value; if(input && (e.which == 13 || e.which == 108)){ //如果按下ENTER鍵 alert(input)//實際項目中,應該是進行頁面跳轉,跑到搜尋結果頁中去的! } });});
到此,suggest效果就完成了。如果下了我的架構的同學,開啟伺服器,開啟文檔首頁就能看到這個效果。而在實際項目,suggest其實更簡單些,就是當輸入框文本變化時,AJAX請求後台一個數組,然後再把它拼接成li元素的格式就行了。