模糊搜索插件
模糊搜索用于搜索引擎和数据库中,执行搜索以匹配结果,这些结果与搜索词相似,但不一定完全相同。这允许考虑拼写错误和错字。它还允许方言的细微变化不影响搜索结果。一个常用的例子是姓氏搜索;“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 上使用模糊搜索插件,您可以通过在页面上的 script 标签中包含以下 javascript 来实现
最后,(是的,它就是这么简单!)您需要将fuzzySearch
初始化选项设置为true
- 例如:
$('#myTable').DataTable({
fuzzySearch: true
})
从这里您将发现模糊搜索已启用,并且拼写错误或错字不会强制从表格中删除记录。在将此选项初始化为布尔值时,视觉上不会发生任何变化,但通过使用其他选项,可以扩展更多功能。
选项
类型 | 选项 | 描述 |
---|---|---|
boolean 或 object |
fuzzySearch |
在表格上启用模糊搜索。 |
布尔值 |
fuzzySearch.toggleSmart |
允许切换搜索模式 - 只需将鼠标悬停在输入元素上,然后从工具提示中选择所需的搜索模式。 |
列选择器 |
fuzzySearch.rankColumn |
定义一列用于显示搜索词和匹配值之间的相似度。 |
数字 |
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()
),它接受两个字符串参数并返回一个包含三个值的 Object,如下所示。
steps
- 两个字符串之间的 Damerau-Levenshtein 距离relative
- 步数除以最长字符串的长度similarity
- 1 -relative
的值
创建此插件的规范
在创建插件之前,仔细考虑我们想要提供的功能非常重要。
第一个显然是模糊搜索功能。鉴于 DataTables 已经有一个搜索框,此插件在搜索表格时应该重用它。这意味着最终用户需要更改的 UI 较少,并且可以保持界面简洁美观。
用户可能还需要能够在精确搜索和模糊搜索之间切换。为此,应将一个图标附加到搜索框,该图标能够指示搜索模式。将鼠标悬停在搜索框上时,应显示一个工具提示。此工具提示应包含两个按钮,用于适当地切换搜索模式。这不应该默认为启用状态,但用户应该能够通过使用fuzzySearch.toggleSmart
初始化选项来启用它。
另一个很酷的功能是在表格中添加一列,显示输入字符串与该行中的数据的相似程度。这不应该默认为启用状态,但用户应该能够通过使用fuzzySearch.rankColumn
初始化选项来指定要使用哪一列。
回车搜索是在论坛中经常出现的问题。在 DataTables 1.11 中,我们添加了search.return
初始化选项。鉴于最初可能没有任何匹配项,因此插件还应与该初始化选项集成,并在按下 Enter 键后延迟搜索。这不会是默认行为。
搜索函数将使用从levenshtein()
函数返回的similarity
属性来决定是否显示行。此比较的阈值应该可以通过使用fuzzySearch.threshold
初始化选项来设置。
另一个有用的功能是添加一个 API 方法,该方法可以获取和设置模糊搜索的搜索值。
最后,当stateSave
初始化选项启用时,搜索模式应保存并在重置时恢复。
创建模糊搜索代码
拥有 npm 模块很棒,但鉴于其中的代码相当简单且简短(66 行),我们要做的第一件事就是将其提取出来并放置在我们自己的文件中。这将节省我们捆绑代码以在插件中发布。
现在我们可以开始编写我们自己的代码了。让我们首先编写一个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,并从中获取模糊搜索的初始化选项和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
属性,则根据其中的pass值进行搜索。如果已定义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。请在论坛中给我们留言,告诉我们您对我们软件的使用情况,或者如果您遇到任何问题,或者对未来的增强功能有任何想法。我们很想了解人们是否能够将模糊搜索集成到他们的项目中以及您的客户反馈。