模糊搜尋外掛
模糊搜尋用於搜尋引擎和資料庫中,執行與搜尋詞彙相似但不一定完全相同的結果的搜尋。這樣可以將拼寫錯誤和輸入錯誤考慮在內。它還可以讓方言的細微差異不會影響搜尋結果。一個常見的例子是用於搜尋姓氏;「Smith」和「Smythe」的發音相同,但是當使用精確比對搜尋時,輸入「Smith」將不會傳回「Smythe」。
此外掛為 DataTables 新增模糊搜尋功能。它透過精確比對和 Damerau-Levenshtein 演算法 的組合來完成此操作。
在我們深入研究之前,先預覽一下您可以從此外掛獲得什麼。以下範例以最簡單的形式初始化外掛 - 以新的模糊搜尋演算法取代 DataTables 標準的精確搜尋。
姓名 | 職位 | 辦公室 | 薪資 |
---|---|---|---|
Tiger Nixon | 系統架構師 | 愛丁堡 | $320,800 |
Garrett Winters | 會計師 | 東京 | $170,750 |
Ashton Cox | 初級技術作者 | 舊金山 | $86,000 |
Cedric Kelly | 資深 Javascript 開發人員 | 愛丁堡 | $433,060 |
Airi Satou | 會計師 | 東京 | $162,700 |
Brielle Williamson | 整合專家 | 紐約 | $372,000 |
Herrod Chandler | 銷售助理 | 舊金山 | $137,500 |
Rhona Davidson | 整合專家 | 東京 | $327,900 |
Colleen Hurst | Javascript 開發人員 | 舊金山 | $205,500 |
Sonya Frost | 軟體工程師 | 愛丁堡 | $103,600 |
Jena Gaines | 辦公室經理 | 倫敦 | $90,560 |
Quinn Flynn | 支援負責人 | 愛丁堡 | $342,000 |
Charde Marshall | 區域總監 | 舊金山 | $470,600 |
Haley Kennedy | 資深行銷設計師 | 倫敦 | $313,500 |
Tatyana Fitzpatrick | 區域總監 | 倫敦 | $385,750 |
Michael Silva | 行銷設計師 | 倫敦 | $198,500 |
Paul Byrd | 財務長 (CFO) | 紐約 | $725,000 |
Gloria Little | 系統管理員 | 紐約 | $237,500 |
Bradley Greer | 軟體工程師 | 倫敦 | $132,000 |
Dai Rios | 人事主管 | 愛丁堡 | $217,500 |
Jenette Caldwell | 開發主管 | 紐約 | $345,000 |
Yuri Berry | 行銷長 (CMO) | 紐約 | $675,000 |
Caesar Vance | 售前支援 | 紐約 | $106,450 |
Doris Wilder | 銷售助理 | 雪梨 | $85,600 |
Angelica Ramos | 執行長 (CEO) | 倫敦 | $1,200,000 |
Gavin Joyce | 開發人員 | 愛丁堡 | $92,575 |
Jennifer Chang | 區域總監 | 新加坡 | $357,650 |
Brenden Wagner | 軟體工程師 | 舊金山 | $206,850 |
Fiona Green | 營運長 (COO) | 舊金山 | $850,000 |
Shou Itou | 區域行銷 | 東京 | $163,000 |
Michelle House | 整合專家 | 雪梨 | $95,400 |
Suki Burks | 開發人員 | 倫敦 | $114,500 |
Prescott Bartlett | 技術作者 | 倫敦 | $145,000 |
Gavin Cortez | 團隊負責人 | 舊金山 | $235,500 |
Martena Mccray | 售後支援 | 愛丁堡 | $324,050 |
Unity Butler | 行銷設計師 | 舊金山 | $85,675 |
Howard Hatfield | 辦公室經理 | 舊金山 | $164,500 |
Hope Fuentes | 秘書 | 舊金山 | $109,850 |
Vivian Harrell | 財務總監 | 舊金山 | $452,500 |
Timothy Mooney | 辦公室經理 | 倫敦 | $136,200 |
Jackson Bradshaw | 總監 | 紐約 | $645,750 |
Olivia Liang | 支援工程師 | 新加坡 | $234,500 |
Bruno Nash | 軟體工程師 | 倫敦 | $163,500 |
Sakura Yamamoto | 支援工程師 | 東京 | $139,575 |
Thor Walton | 開發人員 | 紐約 | $98,540 |
Finn Camacho | 支援工程師 | 舊金山 | $87,500 |
Serge Baldwin | 資料協調員 | 新加坡 | $138,575 |
Zenaida Frank | 軟體工程師 | 紐約 | $125,250 |
Zorita Serrano | 軟體工程師 | 舊金山 | $115,000 |
Jennifer Acosta | 初級 Javascript 開發人員 | 愛丁堡 | $75,650 |
Cara Stevens | 銷售助理 | 紐約 | $145,600 |
Hermione Butler | 區域總監 | 倫敦 | $356,250 |
Lael Greer | 系統管理員 | 倫敦 | $103,500 |
Jonas Alexander | 開發人員 | 舊金山 | $86,500 |
Shad Decker | 區域總監 | 愛丁堡 | $183,000 |
Michael Bruce | Javascript 開發人員 | 新加坡 | $183,000 |
Donna Snider | 客戶支援 | 紐約 | $112,000 |
快速入門
如果您想在您的 DataTable 上使用模糊搜尋外掛,您可以透過在您的頁面上包含以下 Javascript 程式碼 (使用 script 標籤) 來完成。
最後(是的,就這麼簡單!),您需要將 fuzzySearch
初始化選項設定為 true
- 例如。
$('#myTable').DataTable({
fuzzySearch: true
})
從這裡,您會發現模糊搜尋已啟用,並且拼寫錯誤或輸入錯誤不會強制從表格中移除記錄。以布林值初始化此選項時,視覺上不會有任何變更,但是透過使用其他選項,還有額外功能的空間。
選項
類型 | 選項 | 描述 |
---|---|---|
boolean 或 object |
fuzzySearch |
在表格上啟用模糊搜尋。 |
boolean |
fuzzySearch.toggleSmart |
允許切換搜尋模式 - 只需將滑鼠游標停留在輸入元素上方,並從工具提示中選取您想要的搜尋模式。 |
column-selector |
fuzzySearch.rankColumn |
定義一個欄位,用於顯示搜尋詞彙與比對值之間的相似度。 |
number |
fuzzySearch.threshold |
設定來自 Damerau-Levenshtein 演算法的比對閾值。值介於 0 和 1 之間。較低的數字表示不太精確的比對。預設值為 0.5。 |
範例
啟用 fuzzySearch.toggleSmart
選項後,終端使用者可以在 DataTables 的正常智慧搜尋和模糊搜尋之間切換,並透過指示器顯示他們所在的搜尋模式。
$('#fuzzy-toggle').DataTable({
fuzzySearch: {
toggleSmart: true
}
});
姓名 | 職位 | 辦公室 | 薪資 |
---|---|---|---|
Tiger Nixon | 系統架構師 | 愛丁堡 | $320,800 |
Garrett Winters | 會計師 | 東京 | $170,750 |
Ashton Cox | 初級技術作者 | 舊金山 | $86,000 |
Cedric Kelly | 資深 Javascript 開發人員 | 愛丁堡 | $433,060 |
Airi Satou | 會計師 | 東京 | $162,700 |
Brielle Williamson | 整合專家 | 紐約 | $372,000 |
Herrod Chandler | 銷售助理 | 舊金山 | $137,500 |
Rhona Davidson | 整合專家 | 東京 | $327,900 |
Colleen Hurst | Javascript 開發人員 | 舊金山 | $205,500 |
Sonya Frost | 軟體工程師 | 愛丁堡 | $103,600 |
Jena Gaines | 辦公室經理 | 倫敦 | $90,560 |
Quinn Flynn | 支援負責人 | 愛丁堡 | $342,000 |
Charde Marshall | 區域總監 | 舊金山 | $470,600 |
Haley Kennedy | 資深行銷設計師 | 倫敦 | $313,500 |
Tatyana Fitzpatrick | 區域總監 | 倫敦 | $385,750 |
Michael Silva | 行銷設計師 | 倫敦 | $198,500 |
Paul Byrd | 財務長 (CFO) | 紐約 | $725,000 |
Gloria Little | 系統管理員 | 紐約 | $237,500 |
Bradley Greer | 軟體工程師 | 倫敦 | $132,000 |
Dai Rios | 人事主管 | 愛丁堡 | $217,500 |
Jenette Caldwell | 開發主管 | 紐約 | $345,000 |
Yuri Berry | 行銷長 (CMO) | 紐約 | $675,000 |
Caesar Vance | 售前支援 | 紐約 | $106,450 |
Doris Wilder | 銷售助理 | 雪梨 | $85,600 |
Angelica Ramos | 執行長 (CEO) | 倫敦 | $1,200,000 |
Gavin Joyce | 開發人員 | 愛丁堡 | $92,575 |
Jennifer Chang | 區域總監 | 新加坡 | $357,650 |
Brenden Wagner | 軟體工程師 | 舊金山 | $206,850 |
Fiona Green | 營運長 (COO) | 舊金山 | $850,000 |
Shou Itou | 區域行銷 | 東京 | $163,000 |
Michelle House | 整合專家 | 雪梨 | $95,400 |
Suki Burks | 開發人員 | 倫敦 | $114,500 |
Prescott Bartlett | 技術作者 | 倫敦 | $145,000 |
Gavin Cortez | 團隊負責人 | 舊金山 | $235,500 |
Martena Mccray | 售後支援 | 愛丁堡 | $324,050 |
Unity Butler | 行銷設計師 | 舊金山 | $85,675 |
Howard Hatfield | 辦公室經理 | 舊金山 | $164,500 |
Hope Fuentes | 秘書 | 舊金山 | $109,850 |
Vivian Harrell | 財務總監 | 舊金山 | $452,500 |
Timothy Mooney | 辦公室經理 | 倫敦 | $136,200 |
Jackson Bradshaw | 總監 | 紐約 | $645,750 |
Olivia Liang | 支援工程師 | 新加坡 | $234,500 |
Bruno Nash | 軟體工程師 | 倫敦 | $163,500 |
Sakura Yamamoto | 支援工程師 | 東京 | $139,575 |
Thor Walton | 開發人員 | 紐約 | $98,540 |
Finn Camacho | 支援工程師 | 舊金山 | $87,500 |
Serge Baldwin | 資料協調員 | 新加坡 | $138,575 |
Zenaida Frank | 軟體工程師 | 紐約 | $125,250 |
Zorita Serrano | 軟體工程師 | 舊金山 | $115,000 |
Jennifer Acosta | 初級 Javascript 開發人員 | 愛丁堡 | $75,650 |
Cara Stevens | 銷售助理 | 紐約 | $145,600 |
Hermione Butler | 區域總監 | 倫敦 | $356,250 |
Lael Greer | 系統管理員 | 倫敦 | $103,500 |
Jonas Alexander | 開發人員 | 舊金山 | $86,500 |
Shad Decker | 區域總監 | 愛丁堡 | $183,000 |
Michael Bruce | Javascript 開發人員 | 新加坡 | $183,000 |
Donna Snider | 客戶支援 | 紐約 | $112,000 |
下一個範例會加入一個欄位,用於顯示透過使用 fuzzySearch.rankColumn
選項初始化所顯示的相似度,表格會依此排序,以提供搜尋引擎可能預期的輸出。
var fsrco = $('#fuzzy-ranking').DataTable({
fuzzySearch: {
rankColumn: 3
},
sort: [[3, 'desc']]
});
fsrco.on('draw', function(){
fsrco.order([3, 'desc']);
});
姓名 | 職位 | 辦公室 | 薪資 |
---|---|---|---|
Tiger Nixon | 系統架構師 | 愛丁堡 | $320,800 |
Garrett Winters | 會計師 | 東京 | $170,750 |
Ashton Cox | 初級技術作者 | 舊金山 | $86,000 |
Cedric Kelly | 資深 Javascript 開發人員 | 愛丁堡 | $433,060 |
Airi Satou | 會計師 | 東京 | $162,700 |
Brielle Williamson | 整合專家 | 紐約 | $372,000 |
Herrod Chandler | 銷售助理 | 舊金山 | $137,500 |
Rhona Davidson | 整合專家 | 東京 | $327,900 |
Colleen Hurst | Javascript 開發人員 | 舊金山 | $205,500 |
Sonya Frost | 軟體工程師 | 愛丁堡 | $103,600 |
Jena Gaines | 辦公室經理 | 倫敦 | $90,560 |
Quinn Flynn | 支援負責人 | 愛丁堡 | $342,000 |
Charde Marshall | 區域總監 | 舊金山 | $470,600 |
Haley Kennedy | 資深行銷設計師 | 倫敦 | $313,500 |
Tatyana Fitzpatrick | 區域總監 | 倫敦 | $385,750 |
Michael Silva | 行銷設計師 | 倫敦 | $198,500 |
Paul Byrd | 財務長 (CFO) | 紐約 | $725,000 |
Gloria Little | 系統管理員 | 紐約 | $237,500 |
Bradley Greer | 軟體工程師 | 倫敦 | $132,000 |
Dai Rios | 人事主管 | 愛丁堡 | $217,500 |
Jenette Caldwell | 開發主管 | 紐約 | $345,000 |
Yuri Berry | 行銷長 (CMO) | 紐約 | $675,000 |
Caesar Vance | 售前支援 | 紐約 | $106,450 |
Doris Wilder | 銷售助理 | 雪梨 | $85,600 |
Angelica Ramos | 執行長 (CEO) | 倫敦 | $1,200,000 |
Gavin Joyce | 開發人員 | 愛丁堡 | $92,575 |
Jennifer Chang | 區域總監 | 新加坡 | $357,650 |
Brenden Wagner | 軟體工程師 | 舊金山 | $206,850 |
Fiona Green | 營運長 (COO) | 舊金山 | $850,000 |
Shou Itou | 區域行銷 | 東京 | $163,000 |
Michelle House | 整合專家 | 雪梨 | $95,400 |
Suki Burks | 開發人員 | 倫敦 | $114,500 |
Prescott Bartlett | 技術作者 | 倫敦 | $145,000 |
Gavin Cortez | 團隊負責人 | 舊金山 | $235,500 |
Martena Mccray | 售後支援 | 愛丁堡 | $324,050 |
Unity Butler | 行銷設計師 | 舊金山 | $85,675 |
Howard Hatfield | 辦公室經理 | 舊金山 | $164,500 |
Hope Fuentes | 秘書 | 舊金山 | $109,850 |
Vivian Harrell | 財務總監 | 舊金山 | $452,500 |
Timothy Mooney | 辦公室經理 | 倫敦 | $136,200 |
Jackson Bradshaw | 總監 | 紐約 | $645,750 |
Olivia Liang | 支援工程師 | 新加坡 | $234,500 |
Bruno Nash | 軟體工程師 | 倫敦 | $163,500 |
Sakura Yamamoto | 支援工程師 | 東京 | $139,575 |
Thor Walton | 開發人員 | 紐約 | $98,540 |
Finn Camacho | 支援工程師 | 舊金山 | $87,500 |
Serge Baldwin | 資料協調員 | 新加坡 | $138,575 |
Zenaida Frank | 軟體工程師 | 紐約 | $125,250 |
Zorita Serrano | 軟體工程師 | 舊金山 | $115,000 |
Jennifer Acosta | 初級 Javascript 開發人員 | 愛丁堡 | $75,650 |
Cara Stevens | 銷售助理 | 紐約 | $145,600 |
Hermione Butler | 區域總監 | 倫敦 | $356,250 |
Lael Greer | 系統管理員 | 倫敦 | $103,500 |
Jonas Alexander | 開發人員 | 舊金山 | $86,500 |
Shad Decker | 區域總監 | 愛丁堡 | $183,000 |
Michael Bruce | Javascript 開發人員 | 新加坡 | $183,000 |
Donna Snider | 客戶支援 | 紐約 | $112,000 |
深入探討 - 建構外掛
使用我們的 FuzzySearch 外掛非常簡單,因此如果您對實作細節感興趣,讓我們深入探討它的運作方式,並且我們可以研究如何建立 自訂的列式篩選外掛。
Damerau-Levenshtein 演算法
Damerau-Levenshtein 演算法 用於測量兩個序列之間的編輯距離。此演算法通常用於搜尋引擎、資料庫和拼字檢查器中,以更好地提高他們識別輸入中潛在錯誤的能力。我們不會在這裡深入探討這個演算法,我們需要知道的是它在我們的應用程式之前,已在許多應用程式中進行了嘗試和測試!
另一個很大的優點是它可在 npm 上取得,使其非常適合我們的使用案例。
npm 模組提供一個函式 (levenstein()
),該函式採用兩個字串引數並傳回具有三個值的物件,如下所示。
steps
- 兩個字串之間的 Damerau-Levenshtein 距離relative
- 步數除以最長字串的長度similarity
- 1 -relative
的值
建立此外掛的規格
在建立此外掛之前,仔細考量我們想要提供的功能非常重要。
第一個顯然是模糊搜尋功能。鑑於 DataTables 已經有一個搜尋方塊,此外掛應在搜尋表格時重複使用該方塊。這表示終端使用者的 UI 變更較少,並讓介面保持精簡。
使用者也可以在精確搜尋和模糊搜尋之間切換,這可能對使用者很有用。為此,應該在搜尋方塊附加一個圖示,該圖示能夠指示搜尋模式。當將滑鼠游標停留在搜尋方塊上方時,應顯示工具提示。此工具提示應包含兩個按鈕,以適當方式切換搜尋模式。這不應該是預設值,但使用者應該能夠使用 fuzzySearch.toggleSmart
初始化選項來啟用它。
另一個很酷的功能是表格中可以有一個欄位,顯示輸入字串與該列資料的相似程度。這不應該是預設值,但使用者應該能夠使用 fuzzySearch.rankColumn
初始化選項,指向要用於此功能的欄位。
在論壇中經常會出現按 Enter 鍵進行搜尋的需求。在 DataTables 1.11 中,我們加入了 search.return
初始化選項。鑑於一開始可能沒有任何比對,此外掛也應該與此初始化選項整合,並延遲搜尋直到按下 Enter 鍵。這不會是預設行為。
搜尋函式將使用從 levenshtein()
函式傳回的 similarity
屬性,以決定是否顯示列。此比較的閾值應該能夠使用 fuzzySearch.threshold
初始化選項來設定。
另一個有用的功能是新增一個 API 方法,該方法可以取得和設定模糊搜尋的搜尋值。
最後,當啟用 stateSave
初始化選項時,應儲存並重設搜尋模式。
建立模糊搜尋程式碼
擁有 npm 模組非常棒,但是鑑於其中的程式碼相當簡單且簡短(66 行),我們要做的第一件事是將它取出並放置在我們自己的檔案中。這將使我們省去在 plguins 中發佈的程式碼。
現在我們可以開始編寫自己的程式碼。首先,讓我們編寫一個 fuzzySearch()
函式,該函式將針對給定的列傳回布林值 pass
和 score
。pass
值表示該列是否應包含在搜尋結果中。score
值是應該顯示在由 rankColumn
指示的相似度欄位中的值(如果已啟用)。
此函式採用 3 個參數。
searchVal
已輸入到搜尋方塊中的值data
正在處理的列的資料initial
使用的fuzzySearch
初始化選項
要執行的第一個檢查是是否已定義 searchVal
。如果沒有,我們希望顯示所有列,因此我們傳回 true 和空白分數。
function fuzzySearch(searchVal, data, initial) {
// If no searchVal has been defined then return all rows.
if(searchVal === undefined || searchVal.length === 0) {
return {
pass: true,
score: ''
}
}
...
}
我們的搜尋演算法會將搜尋詞彙中的每個字詞與列資料中的每個字詞進行比較。如果每個搜尋字詞的至少一個組合都高於閾值,則應顯示該列。為此,我們分割搜尋詞彙並宣告和填入一個陣列,該陣列包含分數以及每個字詞是否通過。最初,這些值為 {pass: false, score: 0}
。如果在分割後有任何空白字詞,我們不希望考量它們,因此會從陣列中移除這些字詞。
...
// Split the searchVal into individual words.
var splitSearch = searchVal.split(/[^(a-z|A-Z|0-9)]/g);
// Array to keep scores in
var highestCollated = [];
// Remove any empty words or spaces
for(var x = 0; x < splitSearch.length; x++) {
if (splitSearch[x].length === 0 || splitSearch[x] === ' ') {
splitSearch.splice(x, 1);
x--;
}
// Aside - Add to the score collection if not done so yet for this search word
else if (highestCollated.length < splitSearch.length) {
highestCollated.push({pass: false, score: 0});
}
}
...
接下來,我們要對列資料執行一些非常類似的操作,逐一處理每個儲存格。
...
// Going to check each cell for potential matches
for(var i = 0; i < data.length; i++) {
// Convert all data points to lower case fo insensitive sorting
data[i] = data[i].toLowerCase();
// Split the data into individual words
var splitData = data[i].split(/[^(a-z|A-Z|0-9)]/g);
// Remove any empty words or spaces
for (var y = 0; y < splitData.length; y++){
if(splitData[y].length === 0 || splitData[y] === ' ') {
splitData.splice(y, 1);
x--;
}
}
...
在上面顯示的相同 for 迴圈中,我們將在搜尋方塊中的字詞與我們剛才為此儲存格識別的字詞之間進行一些比較。以下顯示了進行比較的程式碼。
...
// Check each search term word
for(var x = 0; x < splitSearch.length; x++) {
// Reset highest score
var highest = {
pass: undefined,
score: 0
};
// Against each word in the cell
for (var y = 0; y < splitData.length; y++){
// If this search Term word is the beginning of the word in
// the cell we want to pass this word
if(splitData[y].indexOf(splitSearch[x]) === 0){
var newScore =
splitSearch[x].length / splitData[y].length;
highest = {
pass: true,
score: highest.score < newScore ?
newScore :
highest.score
};
}
// Get the levenshtein similarity score for the two words
var steps =
levenshtein(splitSearch[x], splitData[y]).similarity;
// If the levenshtein similarity score is better than a
// previous one for the search word then let's store it
if(steps > highest.score) {
highest.score = steps;
}
}
// If this cell has a higher scoring word than previously found
// to the search term in the row, store it
if(highestCollated[x].score < highest.score || highest.pass) {
highestCollated[x] = {
pass: highest.pass || highestCollated.pass ?
true :
highest.score > threshold,
score: highest.score
};
}
}
}
...
最後,我們檢查搜尋字詞是否在整個列的某個位置通過。
// Check that all of the search words have passed
for(var i = 0; i < highestCollated.length; i++) {
if(!highestCollated[i].pass) {
return {
pass: false,
score: Math.round(((highestCollated.reduce((a,b) => a+b.score, 0) / highestCollated.length) * 100)) + "%"
};
}
}
// If we get to here, all scores greater than 0.5 so display the row
return {
pass: true,
score: Math.round(((highestCollated.reduce((a,b) => a+b.score, 0) / highestCollated.length) * 100)) + "%"
};
}
由於 rankColumn
選項會在表格的其中一個欄位中顯示分數,因此由於 DataTables 執行其操作的順序,無法在搜尋函式中填入欄位。相反地,我們必須為 init
事件設定監聽器。
在這個事件的監聽器中,我們會建立一個 API,並從該 API 取得模糊搜尋的初始化選項以及 DataTables 的初始化物件。如果初始化選項中沒有定義 fuzzySearch
,我們就可以在此處中止。否則,我們將繼續並識別此表格的輸入元素。
$(document).on('init.dt', function(e, settings) {
var api = new $.fn.dataTable.Api(settings);
var initial = api.init();
var initialFuzzy = initial.fuzzySearch;
// If this is not set then fuzzy searching is not enabled on the table so return.
if(!initialFuzzy) {
return;
}
// Find the input element
var input = $('div.dataTables_filter input', api.table().container())
...
接下來,我們要移除 DataTables 的預設搜尋事件,並關閉針對此表格識別出的輸入元素的監聽器。然後,我們會定義自己的函式,該函式應在輸入或按下按鍵時觸發。
// Turn off the default DataTables searching events
$(settings.nTable).off('search.dt.DT');
var fuzzySearchVal = ''; // Storage for the most recent fuzzy search value - ui or api set
var searchVal = ''; // Storage for the most recent exact search value - ui or api set
// The function that we want to run on search
var triggerSearchFunction = function(event){
...
}
input.off();
// Always add this event no matter if toggling is enabled
input.on('input keydown', triggerSearchFunction);
triggerSearchFunction()
函式會針對每一列執行 fuzzySearch()
函式,並將結果儲存在該列的 DataTables 內部屬性中。我們必須在此強調,當您建立自己的搜尋外掛程式時,不建議這樣做。然後,會呼叫 draw()
函式以觸發搜尋。
// Get the value from the input element and convert to lower case
fuzzySearchVal = input.val();
searchVal = fuzzySearchVal; // Overwrite the value for search as ui interaction
if (fuzzySearchVal !== undefined && fuzzySearchVal.length === 0) {
fuzzySearchVal = fuzzySearchVal.toLowerCase();
}
// For each row call the fuzzy search function to get result
api.rows().iterator('row', function(settings, rowIdx) {
settings.aoData[rowIdx]._fuzzySearch = fuzzySearch(fuzzySearchVal, settings.aoData[rowIdx]._aFilterData, initialFuzzy)
});
// Empty the DataTables search and replace it with our own
api.search("");
input.val(fuzzySearchVal);
api.draw();
現在,我們可以編寫在繪製時呼叫的函式。這與其他搜尋外掛程式非常相似。
如果已定義內部 _fuzzySearch
屬性,則搜尋會根據其中的傳遞值進行。如果已定義 rankColumn
,則會填入該列的分數。如果未設定 _fuzzySearch
的內部屬性,則不會設定 HTML,並且所有列都傳回 true。
$.fn.dataTable.ext.search.push(
function( settings, data, dataIndex ) {
var initial = settings.oInit.fuzzySearch;
// If fuzzy searching has not been implemented then pass all rows for this function
if (settings.aoData[dataIndex]._fuzzySearch !== undefined) {
// Read score to set the cell content and sort data
var score = settings.aoData[dataIndex]._fuzzySearch.score;
settings.aoData[dataIndex].anCells[initial.rankColumn].innerHTML = score;
// Remove '%' from the end of the score so can sort on a number
settings.aoData[dataIndex]._aSortData[initial.rankColumn] = +score.substring(0, score.length - 1);
// Return the value for the pass as decided by the fuzzySearch function
return settings.aoData[dataIndex]._fuzzySearch.pass;
}
settings.aoData[dataIndex].anCells[initial.rankColumn].innerHTML = '';
settings.aoData[dataIndex]._aSortData[initial.rankColumn] = '';
return true;
}
);
接下來,我們要整合可以切換開啟和關閉模糊搜尋的功能。這需要一些 DOM 操作,這些操作在我們稍早設定的 init
監聽器中處理,緊接在識別輸入元素之後。
var fontBold = {
'font-weight': '600',
'background-color': 'rgba(255,255,255,0.1)'
};
var fontNormal = {
'font-weight': '500',
'background-color': 'transparent'
};
var toggleDataTables = {
'border': 'none',
'background': 'none',
'font-size': '100%',
'width': '50%',
'display': 'inline-block',
'color': 'white',
'cursor': 'pointer',
'padding': '0.5em'
}
// Only going to set the toggle if it is enabled
var toggle, tooltip, exact, fuzzy, label;
if(initialFuzzy.toggleSmart) {
toggle =$('<button class="toggleSearch">Abc</button>')
.insertAfter(input)
.css({
'border': 'none',
'background': 'none',
'position': 'absolute',
'right': '0px',
'top': '4px',
'cursor': 'pointer',
'color': '#3b5e99',
'margin-top': '1px'
});
exact =$('<button class="toggleSearch">Exact</button>')
.insertAfter(input)
.css(toggleCSS)
.css(fontBold)
.attr('highlighted', true);
fuzzy =$('<button class="toggleSearch">Fuzzy</button>')
.insertAfter(input)
.css(toggleCSS);
input.css({
'padding-right': '30px'
});
label = $('<div>Search Type<div>').css({'padding-bottom': '0.5em', 'font-size': '0.8em'})
tooltip = $('<div class="fuzzyToolTip"></div>')
.css({
'position': 'absolute',
'right': '0px',
'top': '2em',
'background': 'white',
'border-radius': '4px',
'text-align': 'center',
'padding': '0.5em',
'background-color': '#16232a',
'box-shadow': '4px 4px 4px rgba(0, 0, 0, 0.5)',
'color': 'white',
'transition': 'opacity 0.25s',
'z-index': '30001'
})
.width(input.outerWidth() - 3)
.append(label).append(exact).append(fuzzy);
}
這會將圖示、按鈕和標籤插入到正確的位置,以便使用。CSS 在外掛程式中定義,因此不需要單獨的 CSS 檔案。
接下來,會定義一個函式,該函式會切換工具提示中哪個按鈕被反白顯示。這是透過新增自訂的「highlighted」屬性,以及一些上面宣告的額外 CSS 來完成的。圖示也會模糊化,以指示搜尋處於模糊模式。在函式結束時,會使用我們的 triggerSearchFunction()
呼叫來觸發搜尋。我們希望在發生切換時執行此操作,因為它通常會導致顯示不同的資料。
我們現在可以將程式碼新增至 triggerSearchFunction()
函式,以便在執行搜尋之前檢查搜尋模式。
var searchVal = '';
// If the toggle is set and isn't checkd then perform a normal search
if(toggle && !toggle.attr('blurred')) {
api.rows().iterator('row', function(settings, rowIdx) {
settings.aoData[rowIdx]._fuzzySearch = undefined;
})
api.search(input.val())
}
// Otherwise perform a fuzzy search
else {
// Get the value from the input element and convert to lower case
searchVal = input.val();
if (searchVal !== undefined && searchVal.length === 0) {
searchVal = searchVal.toLowerCase();
}
// For each row call the fuzzy search function to get result
api.rows().iterator('row', function(settings, rowIdx) {
settings.aoData[rowIdx]._fuzzySearch = fuzzySearch(searchVal, settings.aoData[rowIdx]._aFilterData, initialFuzzy)
});
// Empty the DataTables search and replace it with our own
api.search("");
input.val(searchVal);
}
api.draw();
現在,我們要將一些事件監聽器新增至我們新的 DOM 元素。為了盡量減少程式碼,我們先定義三個函式。
第一個函式 toggleFuzzy()
,透過切換按鈕的狀態並觸發搜尋函式,來變更搜尋模式是模糊還是精確。
function toggleFuzzy() {
if(toggle.attr('blurred')) {
toggle.css({'filter': 'blur(0px)'}).removeAttr('blurred');
fuzzy.removeAttr('highlighted').css(fontNormal);
exact.attr('highlighted', true).css(fontBold);
}
else {
toggle.css({'filter': 'blur(1px)'}).attr('blurred', true);
exact.removeAttr('highlighted').css(fontNormal);
fuzzy.attr('highlighted', true).css(fontBold);
}
// Whenever the search mode is changed we need to re-search
triggerSearchFunction();
}
第二個函式 highlightButton()
,接受一個參數,即要反白顯示的按鈕。如果未反白顯示,則會呼叫 toggleFuzzy 函式。
// Highlights one of the buttons in the tooltip and un-highlights the other
function highlightButton(toHighlight) {
if(!toHighlight.attr('highlighted')){
toggleFuzzy()
}
}
第三個函式 removeToolTip()
會從頁面移除工具提示。
// Removes the tooltip element
function removeToolTip() {
tooltip.remove();
}
切換圖示有三個事件監聽器。第一個監聽器位於 click
事件上,且只會呼叫 toggleFuzzy。這表示當點擊切換圖示時,搜尋模式將會變更,並且會更新結果。第二個是 mouseenter
事件。當此事件發生時,會呼叫以下函式。
function() {
tooltip
.insertAfter(toggle)
.on('mouseleave', removeToolTip);
exact.on('click', () => highlightButton(exact, fuzzy));
fuzzy.on('click', () => highlightButton(fuzzy, exact));
}
這會插入工具提示,並設定一個事件監聽器,以便在滑鼠離開時移除自身。然後,它也會設定 highlightButton 函式,以便在點擊其中一個切換按鈕時執行。
切換圖示上的最後一個事件監聽器是針對 mouseleave
,當此事件發生時,工具提示將會移除。
搜尋方塊有兩個事件監聽器。第一個監聽器用於 mouseenter
事件,且與切換圖示相同。第二個監聽器用於 mouseleave
,這與之前略有不同。
function() {
var inToolTip = false;
tooltip.on('mouseenter', () => inToolTip = true);
toggle.on('mouseenter', () => inToolTip = true);
setTimeout(function(){
if(!inToolTip) {
removeToolTip();
}
}, 50)
}
此函式會為切換圖示和工具提示設定 mouseenter 的事件監聽器。如果滑鼠在 50 毫秒內進入其中任何一個,則不會移除工具提示。否則,工具提示確實會隱藏。
這裡最後新增的是處理 stateSave
。首先,使用 state.loaded()
擷取載入的狀態。然後,針對 stateSaveParams
設定一個監聽器,以便未來可以儲存搜尋模式的目前狀態。然後,檢查目前的狀態,以查看是否將 _fuzzySearch
屬性設定為 true。如果是,則會點擊切換按鈕以變更為模糊搜尋。
var state = api.state.loaded();
api.on('stateSaveParams', function(e, settings, data) {
data._fuzzySearch = toggle.attr('blurred');
})
if(state !== null && state._fuzzySearch === 'true') {
toggle.click();
}
接下來,我們可以新增 search.return
初始化選項的功能。這涉及對 triggerSearchFunction 的最後一個變更,以檢查按下了哪個按鍵。這是一個小的變更,導致產生以下函式。
// The function that we want to run on search
var triggerSearchFunction = function(event){
// If the search is only to be triggered on return wait for that
if (!initial.search.return || event.key === "Enter") {
var searchVal = '';
// If the toggle is set and isn't checkd then perform a normal search
if(toggle && !toggle.attr('blurred')) {
api.rows().iterator('row', function(settings, rowIdx) {
settings.aoData[rowIdx]._fuzzySearch = undefined;
})
api.search(input.val())
}
// Otherwise perform a fuzzy search
else {
// Get the value from the input element and convert to lower case
searchVal = input.val();
if (searchVal !== undefined && searchVal.length === 0) {
searchVal = searchVal.toLowerCase();
}
// For each row call the fuzzy search function to get result
api.rows().iterator('row', function(settings, rowIdx) {
settings.aoData[rowIdx]._fuzzySearch = fuzzySearch(searchVal, settings.aoData[rowIdx]._aFilterData, initialFuzzy)
});
// Empty the DataTables search and replace it with our own
api.search("");
input.val(searchVal);
}
api.draw();
}
}
最後一步是實作將取得或設定模糊搜尋值的 API 方法。同樣地,我們將在 init 監聽器內執行此操作。我們透過存取 API 註冊函式來完成此操作。此函式接受兩個引數。第一個引數是在 API 執行個體內應採取的路徑,以存取 API 方法。第二個引數是呼叫 API 方法時應執行的動作。
var apiRegister = $.fn.dataTable.Api.register;
apiRegister('search.fuzzy()', function(value) {
...
})
然後,我們要新增用於擷取模糊搜尋值的行為。如果傳入的參數未定義,則這是應採取的路徑。
var apiRegister = $.fn.dataTable.Api.register;
apiRegister('search.fuzzy()', function(value) {
if(value === undefined) {
return fuzzySearchVal;
}
...
})
否則,會設定值,因此會遵循稍微不同的路徑。模糊搜尋值會輸入到輸入框中並記錄下來,同時記錄目前的搜尋值。然後,使用迭代器根據新值搜尋所有模糊搜尋詳細資訊。
var apiRegister = $.fn.dataTable.Api.register;
apiRegister('search.fuzzy()', function(value) {
if(value === undefined) {
return fuzzySearchVal;
}
else {
fuzzySearchVal = value.toLowerCase();
searchVal = api.search();
input.val(fuzzySearchVal);
// For each row call the fuzzy search function to get result
api.rows().iterator('row', function(settings, rowIdx) {
settings.aoData[rowIdx]._fuzzySearch = fuzzySearch(fuzzySearchVal, settings.aoData[rowIdx]._aFilterData, initialFuzzy)
});
return this;
}
})
最後一個環節是將最新的搜尋值新增至輸入元素。這是透過設定 search
的監聽器來完成的。
api.on('search', function(){
if(!fromPlugin) {
input.val(api.search() !== searchVal ? api.search() : fuzzySearchVal);
}
})
布林標誌 fromPlugin
用於防止當外掛程式導致搜尋時發生無限迴圈。此標誌在 triggerSearchFunction()
函式中設定,只需在每次 search
/ draw
之前將值設定為 true
,之後設定為 false
即可。然後,根據 DataTables 儲存的目前搜尋值、發生模糊搜尋時的最後搜尋值以及最後的模糊搜尋值來設定輸入值。
如果 DataTables 內儲存的目前搜尋值與我們看到的最後搜尋值不符,則表示自上次更新以來,該值已更新,因此更新的搜尋值會比較新。如果兩者相等,則模糊搜尋值會比較新,因此應顯示該值。
就是這樣。建立複雜的基於列的搜尋外掛程式所需的一切。完整檔案可在 [cdn] 上取得,因此您可以查看完整的流程以及整合在一起的所有部分。
限制
由於 FuzzySearch 執行的篩選全部在用戶端完成,因此此外掛程式不支援伺服器端處理。
意見反應
一如既往,我們很樂意聽到您如何使用 DataTable。請在論壇中給我們留言,說明您使用我們軟體的情況,或者您是否遇到任何問題,或對未來的增強功能有任何想法。我們很想知道是否有人能夠將模糊搜尋整合到他們的專案中,以及您的客戶意見反應。