簡單介紹
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);
}
});
}
結果是可以行得通的,如下圖所示:
但這裏有個問題:
在提交表單時,我們後臺需要傳入的是話題的id,而不是搜索框裏顯示的話題name。這裏通過typeahead獲取的只有name,上面寫的ajax函數裏從後臺傳來的數據也只有name列表,該怎麼辦呢?
不難想到有下面兩個解決方案:
- 在提交的時候直接把話題name傳過去,然後在後臺處理邏輯裏再通過name搜索其對應的id。
- 將話題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>