Bootstrap typeahead自動補全

簡單介紹

Bootstrap typeahead插件是用來完成輸入框的自動完成、模糊搜索和建議提示的功能,支持ajax數據加載,類似於jquery的流行插件Autocomplete。

typeahead的使用方式有兩種:通過數據屬性字段的方式和通過Javascript加載的方式。

1. 通過屬性字段的方式

在輸入文本框input組件裏添加data-provide="typeahead"這個屬性字段表示使用typeahead擴展插件:

<input type="text" data-provide="typeahead">

也可以通過設置autocomplete="off"來避免瀏覽器自己的自動完成功能,防止與插件使用相混。

2. 通過Javascript加載的方式

通過手動的在js中調用typeahead函數:

$('.typeahead').typeahead()

屬性選項

具體數據相關的配置通過幾個選項字段和函數控制,如下表所示:

名稱 類型 默認值 描述
source array, function [ ] 提供查詢的數據源,可以是一個字符串數組或者一個方法,該方法有兩個參數:query 輸入值和 process回調函數,回調函數可以在返回數據源的時候調用,以將數據處理成typeahead能識別的標準數據源。
items number 8 顯示在下拉菜單中的列表數量的最大值
minLength number 1 觸發autocomplete功能所需的最少輸入字符個數
matcher function case insensitive 該方法用來確定一個query怎麼匹配一個item,又一個入參item,表示與query匹配的實例,可以使用this.query來引用當前的query參數,如果query匹配成功則返回true。
sorter function exact match, case sensitive, case insensitive 該方法用於給數據源排序,有一個入參items,表示typeahead數據源實例,可以使用this.query來引用當前的query參數。
updater function returns selected item 該方法用於返回選擇的搜索項,有一個入參item,表示typeahead數據源中返回的單個實例。
highlighter function highlights all default matches 該方法用於高亮選取最終的選擇項,又一個入參item,表示typeahead數據源中返回的單個實例,返回值是一個html。


​具體的使用實例可以自行Google或借鑑我最後列出的幾個參考資料。

問題記錄

這篇文章主要記錄自己初次使用typeahead時遇到的難題,以及最終的解決方法,希望能給遇到類似問題的小夥伴們一個有用的參考。

首先,我的業務需求是輸入一個話題topic,該話題的數據源是從後臺數據庫獲取的,需要支持模糊搜索(至於左模糊、右模糊還是全模糊,就看查詢數據庫時的sql語句怎麼寫了),因此必須使用ajax異步加載的方式獲取數據,於是寫了下面這樣一個ajax函數提供數據源:

source: function (query, process) {
        return $.ajax({
            url: '/showoff/watermark/fetchTopics',
            type: 'post',
            data: {topicName: query},
            dataType: 'json',
            success: function (result) {        
                // 這裏省略resultList的處理過程,處理後resultList是一個字符串列表,
                // 經過process函數處理後成爲能被typeahead支持的字符串數組,作爲搜索的源
                return process(resultList);                                           
            }
        });
}

結果是可以行得通的,如下圖所示:


2

但這裏有個問題:
在提交表單時,我們後臺需要傳入的是話題的id,而不是搜索框裏顯示的話題name。這裏通過typeahead獲取的只有name,上面寫的ajax函數裏從後臺傳來的數據也只有name列表,該怎麼辦呢?

不難想到有下面兩個解決方案:

  1. 在提交的時候直接把話題name傳過去,然後在後臺處理邏輯裏再通過name搜索其對應的id。
  2. 將話題id和name綁定後一起傳到頁面,然後在話題輸入框下面放置一個隱藏的話題id輸入框。在搜索時只需要name作爲數據源,在選取某個name後,將其對應的id值放到隱藏的id輸入框裏面。

方案1處理起來很簡單,但同時也很low,而且可能因爲頁面多傳了個空格什麼的導致數據庫搜索失敗,容易出錯。

方案2看起來挺不錯的,可怎麼實現呢?如何在選取某個搜索值後做其它的操作?source函數做不到這點。看一下上面選項表中的幾個函數,其中有一個updater方法,該方法用來返回最終選取的某個值,顧名思義,我們也可以在方法返回之前做更新動作,比如設置某個輸入框的值。但有幾個問題:

  • topic id和name如何進行綁定?以對象的方式還是map的方式?
  • 在返回數據源時是隻返回name列表用於搜索,還是返回name和id的組合列表?如果只返回name列表,那麼在updater函數裏是取不到與name對應的id值的;如果返回組合列表,那麼搜索時顯示的就不僅是name了。
  • 是不是可以在數據源裏返回綁定的組合列表,然後自定義搜索匹配方式,item只顯示name呢?

在參考資料的幫助下,我看了下typeahead js庫裏關於上面選項表裏幾個方法的默認實現,最終得到了解決方案:

  • 後臺將topic id和topic name以對象列表的形式傳過來,到了ajax裏進行解析處理,得到一個id和name組合的json字符串數組,通過process函數處理後返回。
  • 重寫matcher、sorter、highlighter和updater這四個方法,將原來裏面的item實例全部變成item.name實例,表示要通過name進行搜索匹配、高亮和排序,而與id五官。
  • 最後,在updater方法裏將topic id的隱藏輸入框的值更新爲item.id值即可。

按照上面的思路最終實現如下,這裏貼上完整的typeahead相關的代碼:

typeahead輸入框:

<input type="text" id="topicInput" name="topicName" placeholder="請輸入話題" 
    autocomplete="off" data-provide="typeahead" />

隱藏的topic id輸入框:

<form:hidden id="topicId" name="topicId" path="labelId"/>

最終的typeahead實現js:

<script type="text/javascript">

    $('#topicInput').typeahead({
  source: function (query, process) {
        return $.ajax({
            url: '/showoff/watermark/fetchTopics',
            type: 'post',
            data: {topicName: query},
            dataType: 'json',
            success: function (result) {      
               // 這裏的數據解析根據後臺傳入格式的不同而不同  
               if(result.code == "1") {
                    var json = JSON.parse(result.data.data);                
                    var resultList = json.topicList.map(function (item) {
                        var aItem = { id: item.id, name: item.displayName };
                        return JSON.stringify(aItem);
                    });            
                    return process(resultList);                               
               } else {
                    alert(result.msg);
               }  
            }
        });
    }, 
    matcher: function (obj) {
        var item = JSON.parse(obj);
        return ~item.name.toLowerCase().indexOf(this.query.toLowerCase())
    },

    sorter: function (items) {          
        var beginswith = [], caseSensitive = [], caseInsensitive = [], item;
        while (aItem = items.shift()) {
            var item = JSON.parse(aItem);
            if (!item.name.toLowerCase().indexOf(this.query.toLowerCase()))     
                beginswith.push(JSON.stringify(item));
            else if (~item.name.indexOf(this.query)) caseSensitive.push(JSON.stringify(item));
            else caseInsensitive.push(JSON.stringify(item));
        }

        return beginswith.concat(caseSensitive, caseInsensitive)

    },

    highlighter: function (obj) {
        var item = JSON.parse(obj);
        var query = this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&')
        return item.name.replace(new RegExp('(' + query + ')', 'ig'), function ($1, match) {
            return '<strong>' + match + '</strong>'
        })
    },

    updater: function (obj) {
        var item = JSON.parse(obj);
        $('#topicId').attr('value', item.id);
        return item.name;
    }

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