datatables.net-feature-fuzzysearch 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/Readme.md ADDED
@@ -0,0 +1,24 @@
1
+
2
+ # DataTables input paging feature
3
+
4
+ The FuzzySearch plugin for DataTables which adds a _feature_ to DataTables for use in the `-init layout` option. The `fuzzySearch` feature adds a "fuzzy" option to the DataTables default search box, which the end user can activate.
5
+
6
+
7
+ ## Usage
8
+
9
+ To install with npm (substitute with your favorite package manager if you prefer):
10
+
11
+ ```
12
+ npm install datatables.net datatables.net-feature-fuzzysearch
13
+ ```
14
+
15
+ Then in your Javascript file (assuming you are using ES Modules):
16
+
17
+ ```js
18
+ import DataTable from 'datatables.net';
19
+ import 'datatables.net-feature-fuzzysearch';
20
+
21
+ new DataTable('#example', {
22
+ fuzzySearch: true
23
+ });
24
+ ```
File without changes
@@ -0,0 +1,10 @@
1
+ /*!
2
+ * Fuzzy Search for DataTables
3
+ * SpryMedia Ltd - datatables.net/license MIT license
4
+ *
5
+ * Damerau-Levenshtein function courtesy of https://github.com/tad-lispy/node-damerau-levenshtein
6
+ * BSD 2-Clause License
7
+ * Copyright (c) 2018, Tadeusz Łazurski
8
+ * All rights reserved.
9
+ */
10
+ export {};
@@ -0,0 +1,482 @@
1
+ /*!
2
+ * Fuzzy Search 3.0.0-beta.2 for DataTables
3
+ * SpryMedia Ltd - datatables.net/license MIT license
4
+ *
5
+ * Damerau-Levenshtein function courtesy of https://github.com/tad-lispy/node-damerau-levenshtein
6
+ * BSD 2-Clause License
7
+ * Copyright (c) 2018, Tadeusz Łazurski
8
+ * All rights reserved.
9
+ */
10
+
11
+ (function(factory){
12
+ if (typeof define === 'function' && define.amd) {
13
+ // AMD
14
+ define(['datatables.net'], function (dt) {
15
+ return factory(window, document, dt);
16
+ });
17
+ }
18
+ else if (typeof exports === 'object') {
19
+ // CommonJS
20
+ var cjsRequires = function (root) {
21
+ if (! root.DataTable) {
22
+ require('datatables.net')(root);
23
+ }
24
+ };
25
+
26
+ if (typeof window === 'undefined') {
27
+ module.exports = function (root) {
28
+ if (! root) {
29
+ // CommonJS environments without a window global must pass a
30
+ // root. This will give an error otherwise
31
+ root = window;
32
+ }
33
+
34
+ cjsRequires(root);
35
+ return factory(root, root.document, root.DataTable);
36
+ };
37
+ }
38
+ else {
39
+ cjsRequires(window);
40
+ module.exports = factory(window, window.document, window.DataTable);
41
+ }
42
+ }
43
+ else {
44
+ // Browser
45
+ factory(window, document, window.DataTable);
46
+ }
47
+ }(function(window, document, DataTable) {
48
+ 'use strict';
49
+
50
+ var Dom = DataTable.Dom;
51
+ var util = DataTable.util;
52
+
53
+ DataTable.ext.search.push(function (settings, data, dataIndex) {
54
+ let initial = settings.init.fuzzySearch;
55
+ let row = settings.data[dataIndex];
56
+ if (!initial) {
57
+ return true;
58
+ }
59
+ if (row) {
60
+ // If fuzzy searching has not been implemented then pass all rows for this function
61
+ if (row._fuzzySearch !== undefined) {
62
+ // Read score to set the cell content and sort data
63
+ var score = row._fuzzySearch.score;
64
+ if (util.is.plainObject(initial) &&
65
+ initial.rankColumn !== undefined) {
66
+ row.cells[initial.rankColumn].innerHTML = score;
67
+ // Remove '%' from the end of the score so can sort on a number
68
+ if (row.orderCache) {
69
+ row.orderCache[initial.rankColumn] = +score.substring(0, score.length - 1);
70
+ }
71
+ }
72
+ // Return the value for the pass as decided by the fuzzySearch function
73
+ return row._fuzzySearch.pass;
74
+ }
75
+ else if (util.is.plainObject(initial) &&
76
+ initial.rankColumn !== undefined) {
77
+ row.cells[initial.rankColumn].innerHTML = '';
78
+ if (row.orderCache) {
79
+ row.orderCache[initial.rankColumn] = '';
80
+ }
81
+ }
82
+ }
83
+ return true;
84
+ });
85
+ Dom.s(document).on('init.dt', function (e, settings) {
86
+ var api = new DataTable.Api(settings);
87
+ var initial = api.init();
88
+ var initialFuzzy = initial.fuzzySearch;
89
+ // If this is not set then fuzzy searching is not enabled on the table so return.
90
+ if (!initialFuzzy) {
91
+ return;
92
+ }
93
+ if (typeof initialFuzzy === 'object' && initialFuzzy.columns) {
94
+ initialFuzzy.columns = api
95
+ .columns(initialFuzzy.columns)
96
+ .indexes()
97
+ .toArray();
98
+ }
99
+ var fromPlugin = false;
100
+ // Find the input element
101
+ var input = Dom.s(api.table().container()).find('div.dt-search input');
102
+ var fontBold = {
103
+ 'font-weight': '600',
104
+ 'background-color': 'rgba(255,255,255,0.1)'
105
+ };
106
+ var fontNormal = {
107
+ 'font-weight': '500',
108
+ 'background-color': 'transparent'
109
+ };
110
+ var toggleCSS = {
111
+ border: 'none',
112
+ background: 'none',
113
+ 'font-size': '100%',
114
+ width: '50%',
115
+ display: 'inline-block',
116
+ color: 'white',
117
+ cursor: 'pointer',
118
+ padding: '0.5em'
119
+ };
120
+ // Only going to set the toggle if it is enabled
121
+ var toggle = Dom.c('button').classAdd('toggleSearch').text('Abc').css({
122
+ border: 'none',
123
+ background: 'none',
124
+ position: 'relative',
125
+ right: '33px',
126
+ top: '0px',
127
+ cursor: 'pointer',
128
+ color: '#3b5e99',
129
+ 'margin-top': '1px'
130
+ });
131
+ var tooltip, exact, fuzzy, label;
132
+ if (initialFuzzy === true || initialFuzzy.toggleSmart) {
133
+ toggle.insertAfter(input);
134
+ exact = Dom.c('button')
135
+ .classAdd('toggleSearch')
136
+ .text('Exact')
137
+ .insertAfter(input)
138
+ .css(toggleCSS)
139
+ .css(fontBold)
140
+ .attr('highlighted', 'true');
141
+ fuzzy = Dom.c('button')
142
+ .classAdd('toggleSearch')
143
+ .text('Fuzzy')
144
+ .insertAfter(input)
145
+ .css(toggleCSS);
146
+ input.css({
147
+ 'padding-right': '30px'
148
+ });
149
+ Dom.s(input.parent()).css('right', '-33px').css('position', 'relative');
150
+ label = Dom.c('div').text('Search Type').css({
151
+ 'padding-bottom': '0.5em',
152
+ 'font-size': '0.8em'
153
+ });
154
+ tooltip = Dom.c('div')
155
+ .classAdd('fuzzyToolTip')
156
+ .css({
157
+ position: 'absolute',
158
+ top: '2em',
159
+ background: 'white',
160
+ 'border-radius': '4px',
161
+ 'text-align': 'center',
162
+ padding: '0.5em',
163
+ 'background-color': '#16232a',
164
+ 'box-shadow': '4px 4px 4px rgba(0, 0, 0, 0.5)',
165
+ color: 'white',
166
+ transition: 'opacity 0.25s',
167
+ 'z-index': '30001',
168
+ width: input.width('outer') - 3 + 'px'
169
+ })
170
+ .append(label)
171
+ .append(exact)
172
+ .append(fuzzy);
173
+ }
174
+ function toggleFuzzy(event) {
175
+ if (toggle.attr('blurred')) {
176
+ toggle.css({ filter: 'blur(0px)' }).attrRemove('blurred');
177
+ fuzzy.attrRemove('highlighted').css(fontNormal);
178
+ exact.attr('highlighted', true).css(fontBold);
179
+ }
180
+ else {
181
+ toggle.css({ filter: 'blur(1px)' }).attr('blurred', true);
182
+ exact.attrRemove('highlighted').css(fontNormal);
183
+ fuzzy.attr('highlighted', true).css(fontBold);
184
+ }
185
+ // Whenever the search mode is changed we need to re-search
186
+ triggerSearchFunction(event);
187
+ }
188
+ // Highlights one of the buttons in the tooltip and un-highlights the other
189
+ function highlightButton(toHighlight, event) {
190
+ if (!toHighlight.attr('highlighted')) {
191
+ toggleFuzzy(event);
192
+ }
193
+ }
194
+ // Removes the tooltip element
195
+ function removeToolTip() {
196
+ tooltip.remove();
197
+ }
198
+ // Turn off the default datatables searching events
199
+ Dom.s(settings.table).off('search.dt.DT');
200
+ var fuzzySearchVal = '';
201
+ var searchVal = '';
202
+ // The function that we want to run on search
203
+ var triggerSearchFunction = function (event) {
204
+ // If the search is only to be triggered on return wait for that
205
+ if ((event.type === 'input' &&
206
+ (initial.search === undefined ||
207
+ !initial.search.return)) ||
208
+ event.key === 'Enter' ||
209
+ event.type === 'click') {
210
+ // If the toggle is set and isn't checkd then perform a normal search
211
+ if (toggle && !toggle.attr('blurred')) {
212
+ api.rows().iterator('row', function (settings, rowIdx) {
213
+ settings.data[rowIdx]._fuzzySearch = undefined;
214
+ }, false);
215
+ searchVal = input.val();
216
+ fuzzySearchVal = searchVal;
217
+ fromPlugin = true;
218
+ api.search(searchVal);
219
+ fromPlugin = false;
220
+ searchVal = '';
221
+ }
222
+ // Otherwise perform a fuzzy search
223
+ else {
224
+ // Get the value from the input element and convert to lower case
225
+ fuzzySearchVal = input.val();
226
+ searchVal = '';
227
+ if (fuzzySearchVal !== undefined &&
228
+ fuzzySearchVal.length !== 0) {
229
+ fuzzySearchVal = fuzzySearchVal.toLowerCase();
230
+ }
231
+ // For each row call the fuzzy search function to get result
232
+ api.rows().iterator('row', function (settings, rowIdx) {
233
+ var _a;
234
+ settings.data[rowIdx]._fuzzySearch = fuzzySearch(fuzzySearchVal, (_a = settings.data[rowIdx]) === null || _a === void 0 ? void 0 : _a.searchCellCache, initialFuzzy);
235
+ }, false);
236
+ fromPlugin = true;
237
+ // Empty the datatables search and replace it with our own
238
+ api.search('');
239
+ input.val(fuzzySearchVal);
240
+ fromPlugin = false;
241
+ }
242
+ fromPlugin = true;
243
+ api.draw();
244
+ fromPlugin = false;
245
+ }
246
+ };
247
+ DataTable.Api.register('search.fuzzy()', function (value) {
248
+ if (value === undefined) {
249
+ return fuzzySearchVal;
250
+ }
251
+ else {
252
+ fuzzySearchVal = value.toLowerCase();
253
+ searchVal = api.search();
254
+ input.val(fuzzySearchVal);
255
+ // For each row call the fuzzy search function to get result
256
+ api.rows().iterator('row', function (settings, rowIdx) {
257
+ var _a;
258
+ settings.data[rowIdx]._fuzzySearch = fuzzySearch(fuzzySearchVal, (_a = settings.data[rowIdx]) === null || _a === void 0 ? void 0 : _a.searchCellCache, initialFuzzy);
259
+ }, false);
260
+ // triggerSearchFunction({key: 'Enter'});
261
+ return this;
262
+ }
263
+ });
264
+ input.off();
265
+ // Set listeners to occur on toggle and typing
266
+ if (toggle) {
267
+ // Actions for the toggle button
268
+ toggle
269
+ .on('click', toggleFuzzy)
270
+ .on('mouseenter', function () {
271
+ tooltip.insertAfter(toggle).on('mouseleave', removeToolTip);
272
+ tooltip.css('left', input.position().left + 3 + 'px');
273
+ exact.on('click', event => highlightButton(exact, event));
274
+ fuzzy.on('click', event => highlightButton(fuzzy, event));
275
+ })
276
+ .on('mouseleave', removeToolTip);
277
+ // Actions for the input element
278
+ input
279
+ .on('mouseenter', function () {
280
+ tooltip.insertAfter(toggle).on('mouseleave', removeToolTip);
281
+ tooltip.css('left', input.position().left + 3 + 'px');
282
+ exact.on('click', event => highlightButton(exact, event));
283
+ fuzzy.on('click', event => highlightButton(fuzzy, event));
284
+ })
285
+ .on('mouseleave', function () {
286
+ var inToolTip = false;
287
+ tooltip.on('mouseenter', () => (inToolTip = true));
288
+ toggle.on('mouseenter', () => (inToolTip = true));
289
+ setTimeout(function () {
290
+ if (!inToolTip) {
291
+ removeToolTip();
292
+ }
293
+ }, 250);
294
+ });
295
+ var state = api.state.loaded();
296
+ api.on('stateSaveParams', function (e, settings, data) {
297
+ data._fuzzySearch = {
298
+ active: toggle.attr('blurred'),
299
+ val: input.val()
300
+ };
301
+ });
302
+ if (state !== null && state._fuzzySearch !== undefined) {
303
+ input.val(state._fuzzySearch.val);
304
+ if (state._fuzzySearch.active === 'true' &&
305
+ state.start &&
306
+ state.length) {
307
+ toggle.trigger('click');
308
+ api.page(state.start / state.length).draw('page');
309
+ }
310
+ }
311
+ }
312
+ api.on('search', function () {
313
+ if (!fromPlugin) {
314
+ input.val(api.search() !== searchVal
315
+ ? api.search()
316
+ : fuzzySearchVal);
317
+ }
318
+ });
319
+ // Always add this event no matter if toggling is enabled
320
+ input.on('input keydown', triggerSearchFunction);
321
+ });
322
+ function levenshtein(__this, that, limit) {
323
+ var thisLength = __this.length, thatLength = that.length, matrix = [];
324
+ // If the limit is not defined it will be calculate from this and that args.
325
+ limit = (limit || (thatLength > thisLength ? thatLength : thisLength)) + 1;
326
+ for (var i = 0; i < limit; i++) {
327
+ matrix[i] = [i];
328
+ matrix[i].length = limit;
329
+ }
330
+ for (i = 0; i < limit; i++) {
331
+ matrix[0][i] = i;
332
+ }
333
+ if (Math.abs(thisLength - thatLength) > (limit || 100)) {
334
+ return prepare(limit || 100);
335
+ }
336
+ if (thisLength === 0) {
337
+ return prepare(thatLength);
338
+ }
339
+ if (thatLength === 0) {
340
+ return prepare(thisLength);
341
+ }
342
+ // Calculate matrix.
343
+ var j, this_i, that_j, cost, min, t;
344
+ for (i = 1; i <= thisLength; ++i) {
345
+ this_i = __this[i - 1];
346
+ // Step 4
347
+ for (j = 1; j <= thatLength; ++j) {
348
+ // Check the jagged ld total so far
349
+ if (i === j && matrix[i][j] > 4)
350
+ return prepare(thisLength);
351
+ that_j = that[j - 1];
352
+ cost = this_i === that_j ? 0 : 1; // Step 5
353
+ // Calculate the minimum (much faster than Math.min(...)).
354
+ min = matrix[i - 1][j] + 1; // Devarion.
355
+ if ((t = matrix[i][j - 1] + 1) < min)
356
+ min = t; // Insertion.
357
+ if ((t = matrix[i - 1][j - 1] + cost) < min)
358
+ min = t; // Substitution.
359
+ // Update matrix.
360
+ matrix[i][j] =
361
+ i > 1 &&
362
+ j > 1 &&
363
+ this_i === that[j - 2] &&
364
+ __this[i - 2] === that_j &&
365
+ (t = matrix[i - 2][j - 2] + cost) < min
366
+ ? t
367
+ : min; // Transposition.
368
+ }
369
+ }
370
+ return prepare(matrix[thisLength][thatLength]);
371
+ function prepare(steps) {
372
+ var length = Math.max(thisLength, thatLength);
373
+ var relative = length === 0 ? 0 : steps / length;
374
+ var similarity = 1 - relative;
375
+ return {
376
+ steps: steps,
377
+ relative: relative,
378
+ similarity: similarity
379
+ };
380
+ }
381
+ }
382
+ function fuzzySearch(searchVal, data, initial) {
383
+ var x, y, i;
384
+ // If no searchVal has been defined then return all rows.
385
+ if (searchVal === undefined || searchVal.length === 0) {
386
+ return {
387
+ pass: true,
388
+ score: ''
389
+ };
390
+ }
391
+ var columns = initial.columns !== undefined ? initial.columns : null;
392
+ var threshold = initial.threshold !== undefined ? initial.threshold : 0.5;
393
+ // Split the searchVal into individual words.
394
+ var splitSearch = searchVal.split(/ /g);
395
+ // Array to keep scores in
396
+ var highestCollated = [];
397
+ // Remove any empty words or spaces
398
+ for (x = 0; x < splitSearch.length; x++) {
399
+ if (splitSearch[x].length === 0 || splitSearch[x] === ' ') {
400
+ splitSearch.splice(x, 1);
401
+ x--;
402
+ }
403
+ // Aside - Add to the score collection if not done so yet for this search word
404
+ else if (highestCollated.length < splitSearch.length) {
405
+ highestCollated.push({ pass: false, score: 0 });
406
+ }
407
+ }
408
+ // Going to check each cell for potential matches
409
+ for (i = 0; i < data.length; i++) {
410
+ if (columns === null || columns.includes(i)) {
411
+ // Convert all data points to lower case fo insensitive sorting
412
+ data[i] = data[i].toLowerCase();
413
+ // Split the data into individual words
414
+ var splitData = data[i].split(/ /g);
415
+ // Remove any empty words or spaces
416
+ for (y = 0; y < splitData.length; y++) {
417
+ if (splitData[y].length === 0 || splitData[y] === ' ') {
418
+ splitData.splice(y, 1);
419
+ x--;
420
+ }
421
+ }
422
+ // Check each search term word
423
+ for (x = 0; x < splitSearch.length; x++) {
424
+ // Reset highest score
425
+ var highest = {
426
+ pass: undefined,
427
+ score: 0
428
+ };
429
+ // Against each word in the cell
430
+ for (y = 0; y < splitData.length; y++) {
431
+ // If this search Term word is the beginning of the word in the cell we want to pass this word
432
+ if (splitData[y].indexOf(splitSearch[x]) === 0) {
433
+ var newScore = splitSearch[x].length / splitData[y].length;
434
+ highest = {
435
+ pass: true,
436
+ score: highest.score < newScore
437
+ ? newScore
438
+ : highest.score
439
+ };
440
+ }
441
+ // Get the levenshtein similarity score for the two words
442
+ var steps = levenshtein(splitSearch[x], splitData[y]).similarity;
443
+ // If the levenshtein similarity score is better than a previous one for the search word then var's store it
444
+ if (steps > highest.score) {
445
+ highest.score = steps;
446
+ }
447
+ }
448
+ // If this cell has a higher scoring word than previously found to the search term in the row, store it
449
+ if (highestCollated[x].score < highest.score || highest.pass) {
450
+ highestCollated[x] = {
451
+ pass: highest.pass || highestCollated[x].pass
452
+ ? true
453
+ : highest.score > threshold,
454
+ score: highest.score
455
+ };
456
+ }
457
+ }
458
+ }
459
+ }
460
+ // Check that all of the search words have passed
461
+ for (i = 0; i < highestCollated.length; i++) {
462
+ if (!highestCollated[i].pass) {
463
+ return {
464
+ pass: false,
465
+ score: Math.round((highestCollated.reduce((a, b) => a + b.score, 0) /
466
+ highestCollated.length) *
467
+ 100) + '%'
468
+ };
469
+ }
470
+ }
471
+ // If we get to here, all scores greater than 0.5 so display the row
472
+ return {
473
+ pass: true,
474
+ score: Math.round((highestCollated.reduce((a, b) => a + b.score, 0) /
475
+ highestCollated.length) *
476
+ 100) + '%'
477
+ };
478
+ }
479
+
480
+
481
+ return DataTable;
482
+ }));
File without changes
@@ -0,0 +1,10 @@
1
+ /*!
2
+ * Fuzzy Search 3.0.0-beta.2 for DataTables
3
+ * SpryMedia Ltd - datatables.net/license MIT license
4
+ *
5
+ * Damerau-Levenshtein function courtesy of https://github.com/tad-lispy/node-damerau-levenshtein
6
+ * BSD 2-Clause License
7
+ * Copyright (c) 2018, Tadeusz Łazurski
8
+ * All rights reserved.
9
+ */
10
+ (t=>{var r;"function"==typeof define&&define.amd?define(["datatables.net"],function(e){return t(window,document,e)}):"object"==typeof exports?(r=function(e){e.DataTable||require("datatables.net")(e)},"undefined"==typeof window?module.exports=function(e){return e=e||window,r(e),t(0,e.document,e.DataTable)}:(r(window),module.exports=t(window,window.document,window.DataTable))):t(window,document,window.DataTable)})(function(e,t,z){var x=z.Dom,n=z.util;function y(e,t,r){var o;if(void 0===e||0===e.length)return{pass:!0,score:""};for(var n=void 0!==r.columns?r.columns:null,a=void 0!==r.threshold?r.threshold:.5,s=e.split(/ /g),i=[],c=0;c<s.length;c++)0===s[c].length||" "===s[c]?(s.splice(c,1),c--):i.length<s.length&&i.push({pass:!1,score:0});for(o=0;o<t.length;o++)if(null===n||n.includes(o)){t[o]=t[o].toLowerCase();var l=t[o].split(/ /g);for(d=0;d<l.length;d++)0!==l[d].length&&" "!==l[d]||(l.splice(d,1),c--);for(c=0;c<s.length;c++){for(var u={pass:void 0,score:0},d=0;d<l.length;d++){0===l[d].indexOf(s[c])&&(h=s[c].length/l[d].length,u={pass:!0,score:u.score<h?h:u.score});var h=((e,t,r)=>{var o=e.length,n=t.length,a=[];r=(r||(o<n?n:o))+1;for(var s,i,c,l,u,d,h=0;h<r;h++)a[h]=[h],a[h].length=r;for(h=0;h<r;h++)a[0][h]=h;if(Math.abs(o-n)>(r||100))return f(r||100);if(0===o)return f(n);if(0===n)return f(o);for(h=1;h<=o;++h)for(i=e[h-1],s=1;s<=n;++s){if(h===s&&4<a[h][s])return f(o);c=t[s-1],u=a[h-1][s]+1,(d=a[h][s-1]+1)<u&&(u=d),(d=a[h-1][s-1]+(l=i===c?0:1))<u&&(u=d),a[h][s]=1<h&&1<s&&i===t[s-2]&&e[h-2]===c&&(d=a[h-2][s-2]+l)<u?d:u}return f(a[o][n]);function f(e){var t=Math.max(o,n),t=0===t?0:e/t;return{steps:e,relative:t,similarity:1-t}}})(s[c],l[d]).similarity;h>u.score&&(u.score=h)}(i[c].score<u.score||u.pass)&&(i[c]={pass:!(!u.pass&&!i[c].pass)||u.score>a,score:u.score})}}for(o=0;o<i.length;o++)if(!i[o].pass)return{pass:!1,score:Math.round(i.reduce((e,t)=>e+t.score,0)/i.length*100)+"%"};return{pass:!0,score:Math.round(i.reduce((e,t)=>e+t.score,0)/i.length*100)+"%"}}return z.ext.search.push(function(e,t,r){var o=e.init.fuzzySearch,e=e.data[r];if(o&&e){if(void 0!==e._fuzzySearch)return r=e._fuzzySearch.score,n.is.plainObject(o)&&void 0!==o.rankColumn&&(e.cells[o.rankColumn].innerHTML=r,e.orderCache)&&(e.orderCache[o.rankColumn]=+r.substring(0,r.length-1)),e._fuzzySearch.pass;n.is.plainObject(o)&&void 0!==o.rankColumn&&(e.cells[o.rankColumn].innerHTML="",e.orderCache)&&(e.orderCache[o.rankColumn]="")}return!0}),x.s(t).on("init.dt",function(e,t){var r,o,n,a,s,i,c,l,u,d,h,f,p=new z.Api(t),g=p.init(),v=g.fuzzySearch;function m(e){(s.attr("blurred")?(s.css({filter:"blur(0px)"}).attrRemove("blurred"),l.attrRemove("highlighted").css(a),c):(s.css({filter:"blur(1px)"}).attr("blurred",!0),c.attrRemove("highlighted").css(a),l)).attr("highlighted",!0).css(n),h(e)}function b(e,t){e.attr("highlighted")||m(t)}function w(){i.remove()}v&&("object"==typeof v&&v.columns&&(v.columns=p.columns(v.columns).indexes().toArray()),r=!1,o=x.s(p.table().container()).find("div.dt-search input"),n={"font-weight":"600","background-color":"rgba(255,255,255,0.1)"},a={"font-weight":"500","background-color":"transparent"},f={border:"none",background:"none","font-size":"100%",width:"50%",display:"inline-block",color:"white",cursor:"pointer",padding:"0.5em"},s=x.c("button").classAdd("toggleSearch").text("Abc").css({border:"none",background:"none",position:"relative",right:"33px",top:"0px",cursor:"pointer",color:"#3b5e99","margin-top":"1px"}),!0!==v&&!v.toggleSmart||(s.insertAfter(o),c=x.c("button").classAdd("toggleSearch").text("Exact").insertAfter(o).css(f).css(n).attr("highlighted","true"),l=x.c("button").classAdd("toggleSearch").text("Fuzzy").insertAfter(o).css(f),o.css({"padding-right":"30px"}),x.s(o.parent()).css("right","-33px").css("position","relative"),f=x.c("div").text("Search Type").css({"padding-bottom":"0.5em","font-size":"0.8em"}),i=x.c("div").classAdd("fuzzyToolTip").css({position:"absolute",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:o.width("outer")-3+"px"}).append(f).append(c).append(l)),x.s(t.table).off("search.dt.DT"),d=u="",h=function(e){("input"!==e.type||void 0!==g.search&&g.search.return)&&"Enter"!==e.key&&"click"!==e.type||(s&&!s.attr("blurred")?(p.rows().iterator("row",function(e,t){e.data[t]._fuzzySearch=void 0},!1),d=o.val(),u=d,r=!0,p.search(d),r=!1,d=""):(u=o.val(),d="",void 0!==u&&0!==u.length&&(u=u.toLowerCase()),p.rows().iterator("row",function(e,t){e.data[t]._fuzzySearch=y(u,null==(e=e.data[t])?void 0:e.searchCellCache,v)},!1),r=!0,p.search(""),o.val(u),r=!1),r=!0,p.draw(),r=!1)},z.Api.register("search.fuzzy()",function(e){return void 0===e?u:(u=e.toLowerCase(),d=p.search(),o.val(u),p.rows().iterator("row",function(e,t){e.data[t]._fuzzySearch=y(u,null==(e=e.data[t])?void 0:e.searchCellCache,v)},!1),this)}),o.off(),s&&(s.on("click",m).on("mouseenter",function(){i.insertAfter(s).on("mouseleave",w),i.css("left",o.position().left+3+"px"),c.on("click",e=>b(c,e)),l.on("click",e=>b(l,e))}).on("mouseleave",w),o.on("mouseenter",function(){i.insertAfter(s).on("mouseleave",w),i.css("left",o.position().left+3+"px"),c.on("click",e=>b(c,e)),l.on("click",e=>b(l,e))}).on("mouseleave",function(){var e=!1;i.on("mouseenter",()=>e=!0),s.on("mouseenter",()=>e=!0),setTimeout(function(){e||w()},250)}),f=p.state.loaded(),p.on("stateSaveParams",function(e,t,r){r._fuzzySearch={active:s.attr("blurred"),val:o.val()}}),null!==f)&&void 0!==f._fuzzySearch&&(o.val(f._fuzzySearch.val),"true"===f._fuzzySearch.active)&&f.start&&f.length&&(s.trigger("click"),p.page(f.start/f.length).draw("page")),p.on("search",function(){r||o.val(p.search()!==d?p.search():u)}),o.on("input keydown",h))}),z});
@@ -0,0 +1,10 @@
1
+ /*!
2
+ * Fuzzy Search 3.0.0-beta.2 for DataTables
3
+ * SpryMedia Ltd - datatables.net/license MIT license
4
+ *
5
+ * Damerau-Levenshtein function courtesy of https://github.com/tad-lispy/node-damerau-levenshtein
6
+ * BSD 2-Clause License
7
+ * Copyright (c) 2018, Tadeusz Łazurski
8
+ * All rights reserved.
9
+ */
10
+ import DataTable,{Dom,util}from"datatables.net";function levenshtein(e,t,r){var o=e.length,n=t.length,a=[];r=(r||(o<n?n:o))+1;for(var s,i,c,l,u,h,d=0;d<r;d++)a[d]=[d],a[d].length=r;for(d=0;d<r;d++)a[0][d]=d;if(Math.abs(o-n)>(r||100))return f(r||100);if(0===o)return f(n);if(0===n)return f(o);for(d=1;d<=o;++d)for(i=e[d-1],s=1;s<=n;++s){if(d===s&&4<a[d][s])return f(o);c=t[s-1],u=a[d-1][s]+1,(h=a[d][s-1]+1)<u&&(u=h),(h=a[d-1][s-1]+(l=i===c?0:1))<u&&(u=h),a[d][s]=1<d&&1<s&&i===t[s-2]&&e[d-2]===c&&(h=a[d-2][s-2]+l)<u?h:u}return f(a[o][n]);function f(e){var t=Math.max(o,n),t=0===t?0:e/t;return{steps:e,relative:t,similarity:1-t}}}function fuzzySearch(e,t,r){var o;if(void 0===e||0===e.length)return{pass:!0,score:""};for(var n=void 0!==r.columns?r.columns:null,a=void 0!==r.threshold?r.threshold:.5,s=e.split(/ /g),i=[],c=0;c<s.length;c++)0===s[c].length||" "===s[c]?(s.splice(c,1),c--):i.length<s.length&&i.push({pass:!1,score:0});for(o=0;o<t.length;o++)if(null===n||n.includes(o)){t[o]=t[o].toLowerCase();var l=t[o].split(/ /g);for(h=0;h<l.length;h++)0!==l[h].length&&" "!==l[h]||(l.splice(h,1),c--);for(c=0;c<s.length;c++){for(var u={pass:void 0,score:0},h=0;h<l.length;h++){0===l[h].indexOf(s[c])&&(d=s[c].length/l[h].length,u={pass:!0,score:u.score<d?d:u.score});var d=levenshtein(s[c],l[h]).similarity;d>u.score&&(u.score=d)}(i[c].score<u.score||u.pass)&&(i[c]={pass:!(!u.pass&&!i[c].pass)||u.score>a,score:u.score})}}for(o=0;o<i.length;o++)if(!i[o].pass)return{pass:!1,score:Math.round(i.reduce((e,t)=>e+t.score,0)/i.length*100)+"%"};return{pass:!0,score:Math.round(i.reduce((e,t)=>e+t.score,0)/i.length*100)+"%"}}DataTable.ext.search.push(function(e,t,r){var o=e.init.fuzzySearch,e=e.data[r];if(o&&e){if(void 0!==e._fuzzySearch)return r=e._fuzzySearch.score,util.is.plainObject(o)&&void 0!==o.rankColumn&&(e.cells[o.rankColumn].innerHTML=r,e.orderCache)&&(e.orderCache[o.rankColumn]=+r.substring(0,r.length-1)),e._fuzzySearch.pass;util.is.plainObject(o)&&void 0!==o.rankColumn&&(e.cells[o.rankColumn].innerHTML="",e.orderCache)&&(e.orderCache[o.rankColumn]="")}return!0}),Dom.s(document).on("init.dt",function(e,t){var r,o,n,a,s,i,c,l,u,h,d,f,p=new DataTable.Api(t),g=p.init(),v=g.fuzzySearch;function m(e){(s.attr("blurred")?(s.css({filter:"blur(0px)"}).attrRemove("blurred"),l.attrRemove("highlighted").css(a),c):(s.css({filter:"blur(1px)"}).attr("blurred",!0),c.attrRemove("highlighted").css(a),l)).attr("highlighted",!0).css(n),d(e)}function b(e,t){e.attr("highlighted")||m(t)}function z(){i.remove()}v&&("object"==typeof v&&v.columns&&(v.columns=p.columns(v.columns).indexes().toArray()),r=!1,o=Dom.s(p.table().container()).find("div.dt-search input"),n={"font-weight":"600","background-color":"rgba(255,255,255,0.1)"},a={"font-weight":"500","background-color":"transparent"},f={border:"none",background:"none","font-size":"100%",width:"50%",display:"inline-block",color:"white",cursor:"pointer",padding:"0.5em"},s=Dom.c("button").classAdd("toggleSearch").text("Abc").css({border:"none",background:"none",position:"relative",right:"33px",top:"0px",cursor:"pointer",color:"#3b5e99","margin-top":"1px"}),!0!==v&&!v.toggleSmart||(s.insertAfter(o),c=Dom.c("button").classAdd("toggleSearch").text("Exact").insertAfter(o).css(f).css(n).attr("highlighted","true"),l=Dom.c("button").classAdd("toggleSearch").text("Fuzzy").insertAfter(o).css(f),o.css({"padding-right":"30px"}),Dom.s(o.parent()).css("right","-33px").css("position","relative"),f=Dom.c("div").text("Search Type").css({"padding-bottom":"0.5em","font-size":"0.8em"}),i=Dom.c("div").classAdd("fuzzyToolTip").css({position:"absolute",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:o.width("outer")-3+"px"}).append(f).append(c).append(l)),Dom.s(t.table).off("search.dt.DT"),h=u="",d=function(e){("input"!==e.type||void 0!==g.search&&g.search.return)&&"Enter"!==e.key&&"click"!==e.type||(s&&!s.attr("blurred")?(p.rows().iterator("row",function(e,t){e.data[t]._fuzzySearch=void 0},!1),h=o.val(),u=h,r=!0,p.search(h),r=!1,h=""):(u=o.val(),h="",void 0!==u&&0!==u.length&&(u=u.toLowerCase()),p.rows().iterator("row",function(e,t){e.data[t]._fuzzySearch=fuzzySearch(u,null==(e=e.data[t])?void 0:e.searchCellCache,v)},!1),r=!0,p.search(""),o.val(u),r=!1),r=!0,p.draw(),r=!1)},DataTable.Api.register("search.fuzzy()",function(e){return void 0===e?u:(u=e.toLowerCase(),h=p.search(),o.val(u),p.rows().iterator("row",function(e,t){e.data[t]._fuzzySearch=fuzzySearch(u,null==(e=e.data[t])?void 0:e.searchCellCache,v)},!1),this)}),o.off(),s&&(s.on("click",m).on("mouseenter",function(){i.insertAfter(s).on("mouseleave",z),i.css("left",o.position().left+3+"px"),c.on("click",e=>b(c,e)),l.on("click",e=>b(l,e))}).on("mouseleave",z),o.on("mouseenter",function(){i.insertAfter(s).on("mouseleave",z),i.css("left",o.position().left+3+"px"),c.on("click",e=>b(c,e)),l.on("click",e=>b(l,e))}).on("mouseleave",function(){var e=!1;i.on("mouseenter",()=>e=!0),s.on("mouseenter",()=>e=!0),setTimeout(function(){e||z()},250)}),f=p.state.loaded(),p.on("stateSaveParams",function(e,t,r){r._fuzzySearch={active:s.attr("blurred"),val:o.val()}}),null!==f)&&void 0!==f._fuzzySearch&&(o.val(f._fuzzySearch.val),"true"===f._fuzzySearch.active)&&f.start&&f.length&&(s.trigger("click"),p.page(f.start/f.length).draw("page")),p.on("search",function(){r||o.val(p.search()!==h?p.search():u)}),o.on("input keydown",d))});export default DataTable;
@@ -0,0 +1,442 @@
1
+ /*!
2
+ * Fuzzy Search 3.0.0-beta.2 for DataTables
3
+ * SpryMedia Ltd - datatables.net/license MIT license
4
+ *
5
+ * Damerau-Levenshtein function courtesy of https://github.com/tad-lispy/node-damerau-levenshtein
6
+ * BSD 2-Clause License
7
+ * Copyright (c) 2018, Tadeusz Łazurski
8
+ * All rights reserved.
9
+ */
10
+
11
+ import DataTable, { Dom, util } from 'datatables.net';
12
+
13
+ DataTable.ext.search.push(function (settings, data, dataIndex) {
14
+ let initial = settings.init.fuzzySearch;
15
+ let row = settings.data[dataIndex];
16
+ if (!initial) {
17
+ return true;
18
+ }
19
+ if (row) {
20
+ // If fuzzy searching has not been implemented then pass all rows for this function
21
+ if (row._fuzzySearch !== undefined) {
22
+ // Read score to set the cell content and sort data
23
+ var score = row._fuzzySearch.score;
24
+ if (util.is.plainObject(initial) &&
25
+ initial.rankColumn !== undefined) {
26
+ row.cells[initial.rankColumn].innerHTML = score;
27
+ // Remove '%' from the end of the score so can sort on a number
28
+ if (row.orderCache) {
29
+ row.orderCache[initial.rankColumn] = +score.substring(0, score.length - 1);
30
+ }
31
+ }
32
+ // Return the value for the pass as decided by the fuzzySearch function
33
+ return row._fuzzySearch.pass;
34
+ }
35
+ else if (util.is.plainObject(initial) &&
36
+ initial.rankColumn !== undefined) {
37
+ row.cells[initial.rankColumn].innerHTML = '';
38
+ if (row.orderCache) {
39
+ row.orderCache[initial.rankColumn] = '';
40
+ }
41
+ }
42
+ }
43
+ return true;
44
+ });
45
+ Dom.s(document).on('init.dt', function (e, settings) {
46
+ var api = new DataTable.Api(settings);
47
+ var initial = api.init();
48
+ var initialFuzzy = initial.fuzzySearch;
49
+ // If this is not set then fuzzy searching is not enabled on the table so return.
50
+ if (!initialFuzzy) {
51
+ return;
52
+ }
53
+ if (typeof initialFuzzy === 'object' && initialFuzzy.columns) {
54
+ initialFuzzy.columns = api
55
+ .columns(initialFuzzy.columns)
56
+ .indexes()
57
+ .toArray();
58
+ }
59
+ var fromPlugin = false;
60
+ // Find the input element
61
+ var input = Dom.s(api.table().container()).find('div.dt-search input');
62
+ var fontBold = {
63
+ 'font-weight': '600',
64
+ 'background-color': 'rgba(255,255,255,0.1)'
65
+ };
66
+ var fontNormal = {
67
+ 'font-weight': '500',
68
+ 'background-color': 'transparent'
69
+ };
70
+ var toggleCSS = {
71
+ border: 'none',
72
+ background: 'none',
73
+ 'font-size': '100%',
74
+ width: '50%',
75
+ display: 'inline-block',
76
+ color: 'white',
77
+ cursor: 'pointer',
78
+ padding: '0.5em'
79
+ };
80
+ // Only going to set the toggle if it is enabled
81
+ var toggle = Dom.c('button').classAdd('toggleSearch').text('Abc').css({
82
+ border: 'none',
83
+ background: 'none',
84
+ position: 'relative',
85
+ right: '33px',
86
+ top: '0px',
87
+ cursor: 'pointer',
88
+ color: '#3b5e99',
89
+ 'margin-top': '1px'
90
+ });
91
+ var tooltip, exact, fuzzy, label;
92
+ if (initialFuzzy === true || initialFuzzy.toggleSmart) {
93
+ toggle.insertAfter(input);
94
+ exact = Dom.c('button')
95
+ .classAdd('toggleSearch')
96
+ .text('Exact')
97
+ .insertAfter(input)
98
+ .css(toggleCSS)
99
+ .css(fontBold)
100
+ .attr('highlighted', 'true');
101
+ fuzzy = Dom.c('button')
102
+ .classAdd('toggleSearch')
103
+ .text('Fuzzy')
104
+ .insertAfter(input)
105
+ .css(toggleCSS);
106
+ input.css({
107
+ 'padding-right': '30px'
108
+ });
109
+ Dom.s(input.parent()).css('right', '-33px').css('position', 'relative');
110
+ label = Dom.c('div').text('Search Type').css({
111
+ 'padding-bottom': '0.5em',
112
+ 'font-size': '0.8em'
113
+ });
114
+ tooltip = Dom.c('div')
115
+ .classAdd('fuzzyToolTip')
116
+ .css({
117
+ position: 'absolute',
118
+ top: '2em',
119
+ background: 'white',
120
+ 'border-radius': '4px',
121
+ 'text-align': 'center',
122
+ padding: '0.5em',
123
+ 'background-color': '#16232a',
124
+ 'box-shadow': '4px 4px 4px rgba(0, 0, 0, 0.5)',
125
+ color: 'white',
126
+ transition: 'opacity 0.25s',
127
+ 'z-index': '30001',
128
+ width: input.width('outer') - 3 + 'px'
129
+ })
130
+ .append(label)
131
+ .append(exact)
132
+ .append(fuzzy);
133
+ }
134
+ function toggleFuzzy(event) {
135
+ if (toggle.attr('blurred')) {
136
+ toggle.css({ filter: 'blur(0px)' }).attrRemove('blurred');
137
+ fuzzy.attrRemove('highlighted').css(fontNormal);
138
+ exact.attr('highlighted', true).css(fontBold);
139
+ }
140
+ else {
141
+ toggle.css({ filter: 'blur(1px)' }).attr('blurred', true);
142
+ exact.attrRemove('highlighted').css(fontNormal);
143
+ fuzzy.attr('highlighted', true).css(fontBold);
144
+ }
145
+ // Whenever the search mode is changed we need to re-search
146
+ triggerSearchFunction(event);
147
+ }
148
+ // Highlights one of the buttons in the tooltip and un-highlights the other
149
+ function highlightButton(toHighlight, event) {
150
+ if (!toHighlight.attr('highlighted')) {
151
+ toggleFuzzy(event);
152
+ }
153
+ }
154
+ // Removes the tooltip element
155
+ function removeToolTip() {
156
+ tooltip.remove();
157
+ }
158
+ // Turn off the default datatables searching events
159
+ Dom.s(settings.table).off('search.dt.DT');
160
+ var fuzzySearchVal = '';
161
+ var searchVal = '';
162
+ // The function that we want to run on search
163
+ var triggerSearchFunction = function (event) {
164
+ // If the search is only to be triggered on return wait for that
165
+ if ((event.type === 'input' &&
166
+ (initial.search === undefined ||
167
+ !initial.search.return)) ||
168
+ event.key === 'Enter' ||
169
+ event.type === 'click') {
170
+ // If the toggle is set and isn't checkd then perform a normal search
171
+ if (toggle && !toggle.attr('blurred')) {
172
+ api.rows().iterator('row', function (settings, rowIdx) {
173
+ settings.data[rowIdx]._fuzzySearch = undefined;
174
+ }, false);
175
+ searchVal = input.val();
176
+ fuzzySearchVal = searchVal;
177
+ fromPlugin = true;
178
+ api.search(searchVal);
179
+ fromPlugin = false;
180
+ searchVal = '';
181
+ }
182
+ // Otherwise perform a fuzzy search
183
+ else {
184
+ // Get the value from the input element and convert to lower case
185
+ fuzzySearchVal = input.val();
186
+ searchVal = '';
187
+ if (fuzzySearchVal !== undefined &&
188
+ fuzzySearchVal.length !== 0) {
189
+ fuzzySearchVal = fuzzySearchVal.toLowerCase();
190
+ }
191
+ // For each row call the fuzzy search function to get result
192
+ api.rows().iterator('row', function (settings, rowIdx) {
193
+ var _a;
194
+ settings.data[rowIdx]._fuzzySearch = fuzzySearch(fuzzySearchVal, (_a = settings.data[rowIdx]) === null || _a === void 0 ? void 0 : _a.searchCellCache, initialFuzzy);
195
+ }, false);
196
+ fromPlugin = true;
197
+ // Empty the datatables search and replace it with our own
198
+ api.search('');
199
+ input.val(fuzzySearchVal);
200
+ fromPlugin = false;
201
+ }
202
+ fromPlugin = true;
203
+ api.draw();
204
+ fromPlugin = false;
205
+ }
206
+ };
207
+ DataTable.Api.register('search.fuzzy()', function (value) {
208
+ if (value === undefined) {
209
+ return fuzzySearchVal;
210
+ }
211
+ else {
212
+ fuzzySearchVal = value.toLowerCase();
213
+ searchVal = api.search();
214
+ input.val(fuzzySearchVal);
215
+ // For each row call the fuzzy search function to get result
216
+ api.rows().iterator('row', function (settings, rowIdx) {
217
+ var _a;
218
+ settings.data[rowIdx]._fuzzySearch = fuzzySearch(fuzzySearchVal, (_a = settings.data[rowIdx]) === null || _a === void 0 ? void 0 : _a.searchCellCache, initialFuzzy);
219
+ }, false);
220
+ // triggerSearchFunction({key: 'Enter'});
221
+ return this;
222
+ }
223
+ });
224
+ input.off();
225
+ // Set listeners to occur on toggle and typing
226
+ if (toggle) {
227
+ // Actions for the toggle button
228
+ toggle
229
+ .on('click', toggleFuzzy)
230
+ .on('mouseenter', function () {
231
+ tooltip.insertAfter(toggle).on('mouseleave', removeToolTip);
232
+ tooltip.css('left', input.position().left + 3 + 'px');
233
+ exact.on('click', event => highlightButton(exact, event));
234
+ fuzzy.on('click', event => highlightButton(fuzzy, event));
235
+ })
236
+ .on('mouseleave', removeToolTip);
237
+ // Actions for the input element
238
+ input
239
+ .on('mouseenter', function () {
240
+ tooltip.insertAfter(toggle).on('mouseleave', removeToolTip);
241
+ tooltip.css('left', input.position().left + 3 + 'px');
242
+ exact.on('click', event => highlightButton(exact, event));
243
+ fuzzy.on('click', event => highlightButton(fuzzy, event));
244
+ })
245
+ .on('mouseleave', function () {
246
+ var inToolTip = false;
247
+ tooltip.on('mouseenter', () => (inToolTip = true));
248
+ toggle.on('mouseenter', () => (inToolTip = true));
249
+ setTimeout(function () {
250
+ if (!inToolTip) {
251
+ removeToolTip();
252
+ }
253
+ }, 250);
254
+ });
255
+ var state = api.state.loaded();
256
+ api.on('stateSaveParams', function (e, settings, data) {
257
+ data._fuzzySearch = {
258
+ active: toggle.attr('blurred'),
259
+ val: input.val()
260
+ };
261
+ });
262
+ if (state !== null && state._fuzzySearch !== undefined) {
263
+ input.val(state._fuzzySearch.val);
264
+ if (state._fuzzySearch.active === 'true' &&
265
+ state.start &&
266
+ state.length) {
267
+ toggle.trigger('click');
268
+ api.page(state.start / state.length).draw('page');
269
+ }
270
+ }
271
+ }
272
+ api.on('search', function () {
273
+ if (!fromPlugin) {
274
+ input.val(api.search() !== searchVal
275
+ ? api.search()
276
+ : fuzzySearchVal);
277
+ }
278
+ });
279
+ // Always add this event no matter if toggling is enabled
280
+ input.on('input keydown', triggerSearchFunction);
281
+ });
282
+ function levenshtein(__this, that, limit) {
283
+ var thisLength = __this.length, thatLength = that.length, matrix = [];
284
+ // If the limit is not defined it will be calculate from this and that args.
285
+ limit = (limit || (thatLength > thisLength ? thatLength : thisLength)) + 1;
286
+ for (var i = 0; i < limit; i++) {
287
+ matrix[i] = [i];
288
+ matrix[i].length = limit;
289
+ }
290
+ for (i = 0; i < limit; i++) {
291
+ matrix[0][i] = i;
292
+ }
293
+ if (Math.abs(thisLength - thatLength) > (limit || 100)) {
294
+ return prepare(limit || 100);
295
+ }
296
+ if (thisLength === 0) {
297
+ return prepare(thatLength);
298
+ }
299
+ if (thatLength === 0) {
300
+ return prepare(thisLength);
301
+ }
302
+ // Calculate matrix.
303
+ var j, this_i, that_j, cost, min, t;
304
+ for (i = 1; i <= thisLength; ++i) {
305
+ this_i = __this[i - 1];
306
+ // Step 4
307
+ for (j = 1; j <= thatLength; ++j) {
308
+ // Check the jagged ld total so far
309
+ if (i === j && matrix[i][j] > 4)
310
+ return prepare(thisLength);
311
+ that_j = that[j - 1];
312
+ cost = this_i === that_j ? 0 : 1; // Step 5
313
+ // Calculate the minimum (much faster than Math.min(...)).
314
+ min = matrix[i - 1][j] + 1; // Devarion.
315
+ if ((t = matrix[i][j - 1] + 1) < min)
316
+ min = t; // Insertion.
317
+ if ((t = matrix[i - 1][j - 1] + cost) < min)
318
+ min = t; // Substitution.
319
+ // Update matrix.
320
+ matrix[i][j] =
321
+ i > 1 &&
322
+ j > 1 &&
323
+ this_i === that[j - 2] &&
324
+ __this[i - 2] === that_j &&
325
+ (t = matrix[i - 2][j - 2] + cost) < min
326
+ ? t
327
+ : min; // Transposition.
328
+ }
329
+ }
330
+ return prepare(matrix[thisLength][thatLength]);
331
+ function prepare(steps) {
332
+ var length = Math.max(thisLength, thatLength);
333
+ var relative = length === 0 ? 0 : steps / length;
334
+ var similarity = 1 - relative;
335
+ return {
336
+ steps: steps,
337
+ relative: relative,
338
+ similarity: similarity
339
+ };
340
+ }
341
+ }
342
+ function fuzzySearch(searchVal, data, initial) {
343
+ var x, y, i;
344
+ // If no searchVal has been defined then return all rows.
345
+ if (searchVal === undefined || searchVal.length === 0) {
346
+ return {
347
+ pass: true,
348
+ score: ''
349
+ };
350
+ }
351
+ var columns = initial.columns !== undefined ? initial.columns : null;
352
+ var threshold = initial.threshold !== undefined ? initial.threshold : 0.5;
353
+ // Split the searchVal into individual words.
354
+ var splitSearch = searchVal.split(/ /g);
355
+ // Array to keep scores in
356
+ var highestCollated = [];
357
+ // Remove any empty words or spaces
358
+ for (x = 0; x < splitSearch.length; x++) {
359
+ if (splitSearch[x].length === 0 || splitSearch[x] === ' ') {
360
+ splitSearch.splice(x, 1);
361
+ x--;
362
+ }
363
+ // Aside - Add to the score collection if not done so yet for this search word
364
+ else if (highestCollated.length < splitSearch.length) {
365
+ highestCollated.push({ pass: false, score: 0 });
366
+ }
367
+ }
368
+ // Going to check each cell for potential matches
369
+ for (i = 0; i < data.length; i++) {
370
+ if (columns === null || columns.includes(i)) {
371
+ // Convert all data points to lower case fo insensitive sorting
372
+ data[i] = data[i].toLowerCase();
373
+ // Split the data into individual words
374
+ var splitData = data[i].split(/ /g);
375
+ // Remove any empty words or spaces
376
+ for (y = 0; y < splitData.length; y++) {
377
+ if (splitData[y].length === 0 || splitData[y] === ' ') {
378
+ splitData.splice(y, 1);
379
+ x--;
380
+ }
381
+ }
382
+ // Check each search term word
383
+ for (x = 0; x < splitSearch.length; x++) {
384
+ // Reset highest score
385
+ var highest = {
386
+ pass: undefined,
387
+ score: 0
388
+ };
389
+ // Against each word in the cell
390
+ for (y = 0; y < splitData.length; y++) {
391
+ // If this search Term word is the beginning of the word in the cell we want to pass this word
392
+ if (splitData[y].indexOf(splitSearch[x]) === 0) {
393
+ var newScore = splitSearch[x].length / splitData[y].length;
394
+ highest = {
395
+ pass: true,
396
+ score: highest.score < newScore
397
+ ? newScore
398
+ : highest.score
399
+ };
400
+ }
401
+ // Get the levenshtein similarity score for the two words
402
+ var steps = levenshtein(splitSearch[x], splitData[y]).similarity;
403
+ // If the levenshtein similarity score is better than a previous one for the search word then var's store it
404
+ if (steps > highest.score) {
405
+ highest.score = steps;
406
+ }
407
+ }
408
+ // If this cell has a higher scoring word than previously found to the search term in the row, store it
409
+ if (highestCollated[x].score < highest.score || highest.pass) {
410
+ highestCollated[x] = {
411
+ pass: highest.pass || highestCollated[x].pass
412
+ ? true
413
+ : highest.score > threshold,
414
+ score: highest.score
415
+ };
416
+ }
417
+ }
418
+ }
419
+ }
420
+ // Check that all of the search words have passed
421
+ for (i = 0; i < highestCollated.length; i++) {
422
+ if (!highestCollated[i].pass) {
423
+ return {
424
+ pass: false,
425
+ score: Math.round((highestCollated.reduce((a, b) => a + b.score, 0) /
426
+ highestCollated.length) *
427
+ 100) + '%'
428
+ };
429
+ }
430
+ }
431
+ // If we get to here, all scores greater than 0.5 so display the row
432
+ return {
433
+ pass: true,
434
+ score: Math.round((highestCollated.reduce((a, b) => a + b.score, 0) /
435
+ highestCollated.length) *
436
+ 100) + '%'
437
+ };
438
+ }
439
+
440
+
441
+ export default DataTable;
442
+
@@ -0,0 +1,4 @@
1
+ export default function fuzzySearch(searchVal: any, data: any, initial: any): {
2
+ pass: boolean;
3
+ score: string;
4
+ };
package/dist/search.js ADDED
@@ -0,0 +1,157 @@
1
+ function levenshtein(__this, that, limit) {
2
+ var thisLength = __this.length, thatLength = that.length, matrix = [];
3
+ // If the limit is not defined it will be calculate from this and that args.
4
+ limit = (limit || (thatLength > thisLength ? thatLength : thisLength)) + 1;
5
+ for (var i = 0; i < limit; i++) {
6
+ matrix[i] = [i];
7
+ matrix[i].length = limit;
8
+ }
9
+ for (i = 0; i < limit; i++) {
10
+ matrix[0][i] = i;
11
+ }
12
+ if (Math.abs(thisLength - thatLength) > (limit || 100)) {
13
+ return prepare(limit || 100);
14
+ }
15
+ if (thisLength === 0) {
16
+ return prepare(thatLength);
17
+ }
18
+ if (thatLength === 0) {
19
+ return prepare(thisLength);
20
+ }
21
+ // Calculate matrix.
22
+ var j, this_i, that_j, cost, min, t;
23
+ for (i = 1; i <= thisLength; ++i) {
24
+ this_i = __this[i - 1];
25
+ // Step 4
26
+ for (j = 1; j <= thatLength; ++j) {
27
+ // Check the jagged ld total so far
28
+ if (i === j && matrix[i][j] > 4)
29
+ return prepare(thisLength);
30
+ that_j = that[j - 1];
31
+ cost = this_i === that_j ? 0 : 1; // Step 5
32
+ // Calculate the minimum (much faster than Math.min(...)).
33
+ min = matrix[i - 1][j] + 1; // Devarion.
34
+ if ((t = matrix[i][j - 1] + 1) < min)
35
+ min = t; // Insertion.
36
+ if ((t = matrix[i - 1][j - 1] + cost) < min)
37
+ min = t; // Substitution.
38
+ // Update matrix.
39
+ matrix[i][j] =
40
+ i > 1 &&
41
+ j > 1 &&
42
+ this_i === that[j - 2] &&
43
+ __this[i - 2] === that_j &&
44
+ (t = matrix[i - 2][j - 2] + cost) < min
45
+ ? t
46
+ : min; // Transposition.
47
+ }
48
+ }
49
+ return prepare(matrix[thisLength][thatLength]);
50
+ function prepare(steps) {
51
+ var length = Math.max(thisLength, thatLength);
52
+ var relative = length === 0 ? 0 : steps / length;
53
+ var similarity = 1 - relative;
54
+ return {
55
+ steps: steps,
56
+ relative: relative,
57
+ similarity: similarity
58
+ };
59
+ }
60
+ }
61
+ export default function fuzzySearch(searchVal, data, initial) {
62
+ var x, y, i;
63
+ // If no searchVal has been defined then return all rows.
64
+ if (searchVal === undefined || searchVal.length === 0) {
65
+ return {
66
+ pass: true,
67
+ score: ''
68
+ };
69
+ }
70
+ var columns = initial.columns !== undefined ? initial.columns : null;
71
+ var threshold = initial.threshold !== undefined ? initial.threshold : 0.5;
72
+ // Split the searchVal into individual words.
73
+ var splitSearch = searchVal.split(/ /g);
74
+ // Array to keep scores in
75
+ var highestCollated = [];
76
+ // Remove any empty words or spaces
77
+ for (x = 0; x < splitSearch.length; x++) {
78
+ if (splitSearch[x].length === 0 || splitSearch[x] === ' ') {
79
+ splitSearch.splice(x, 1);
80
+ x--;
81
+ }
82
+ // Aside - Add to the score collection if not done so yet for this search word
83
+ else if (highestCollated.length < splitSearch.length) {
84
+ highestCollated.push({ pass: false, score: 0 });
85
+ }
86
+ }
87
+ // Going to check each cell for potential matches
88
+ for (i = 0; i < data.length; i++) {
89
+ if (columns === null || columns.includes(i)) {
90
+ // Convert all data points to lower case fo insensitive sorting
91
+ data[i] = data[i].toLowerCase();
92
+ // Split the data into individual words
93
+ var splitData = data[i].split(/ /g);
94
+ // Remove any empty words or spaces
95
+ for (y = 0; y < splitData.length; y++) {
96
+ if (splitData[y].length === 0 || splitData[y] === ' ') {
97
+ splitData.splice(y, 1);
98
+ x--;
99
+ }
100
+ }
101
+ // Check each search term word
102
+ for (x = 0; x < splitSearch.length; x++) {
103
+ // Reset highest score
104
+ var highest = {
105
+ pass: undefined,
106
+ score: 0
107
+ };
108
+ // Against each word in the cell
109
+ for (y = 0; y < splitData.length; y++) {
110
+ // If this search Term word is the beginning of the word in the cell we want to pass this word
111
+ if (splitData[y].indexOf(splitSearch[x]) === 0) {
112
+ var newScore = splitSearch[x].length / splitData[y].length;
113
+ highest = {
114
+ pass: true,
115
+ score: highest.score < newScore
116
+ ? newScore
117
+ : highest.score
118
+ };
119
+ }
120
+ // Get the levenshtein similarity score for the two words
121
+ var steps = levenshtein(splitSearch[x], splitData[y]).similarity;
122
+ // If the levenshtein similarity score is better than a previous one for the search word then var's store it
123
+ if (steps > highest.score) {
124
+ highest.score = steps;
125
+ }
126
+ }
127
+ // If this cell has a higher scoring word than previously found to the search term in the row, store it
128
+ if (highestCollated[x].score < highest.score || highest.pass) {
129
+ highestCollated[x] = {
130
+ pass: highest.pass || highestCollated[x].pass
131
+ ? true
132
+ : highest.score > threshold,
133
+ score: highest.score
134
+ };
135
+ }
136
+ }
137
+ }
138
+ }
139
+ // Check that all of the search words have passed
140
+ for (i = 0; i < highestCollated.length; i++) {
141
+ if (!highestCollated[i].pass) {
142
+ return {
143
+ pass: false,
144
+ score: Math.round((highestCollated.reduce((a, b) => a + b.score, 0) /
145
+ highestCollated.length) *
146
+ 100) + '%'
147
+ };
148
+ }
149
+ }
150
+ // If we get to here, all scores greater than 0.5 so display the row
151
+ return {
152
+ pass: true,
153
+ score: Math.round((highestCollated.reduce((a, b) => a + b.score, 0) /
154
+ highestCollated.length) *
155
+ 100) + '%'
156
+ };
157
+ }
@@ -0,0 +1,36 @@
1
+
2
+ import DataTables, { Api, ColumnSelector } from 'datatables.net';
3
+
4
+ export default DataTables;
5
+
6
+
7
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
8
+ * DataTables' types integration
9
+ */
10
+ declare module 'datatables.net' {
11
+ interface Options {
12
+ fuzzySearch?:
13
+ | boolean
14
+ | {
15
+ columns?: ColumnSelector;
16
+ rankColumn?: number;
17
+ threshold?: number;
18
+ toggleSmart?: boolean;
19
+ };
20
+ }
21
+
22
+ interface Row {
23
+ _fuzzySearch?: any;
24
+ }
25
+
26
+ interface StateLoad {
27
+ _fuzzySearch: {
28
+ active: 'true' | 'false';
29
+ val: any;
30
+ };
31
+ }
32
+
33
+ interface ApiSearch<T> {
34
+ fuzzy: (input: string) => Api;
35
+ }
36
+ }
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "datatables.net-feature-fuzzysearch",
3
+ "version": "1.0.0",
4
+ "description": "DataTables plugin: Fuzzy search",
5
+ "main": "dist/dataTables.fuzzySearch.js",
6
+ "module": "dist/dataTables.fuzzySearch.mjs",
7
+ "style": "dist/dataTables.fuzzySearch.css",
8
+ "types": "dist/types.d.ts",
9
+ "files": [
10
+ "dist/*.css",
11
+ "dist/*.js",
12
+ "dist/*.mjs",
13
+ "dist/*.d.ts"
14
+ ],
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/DataTables/Plugins.git"
18
+ },
19
+ "keywords": [
20
+ "DataTables",
21
+ "plugins",
22
+ "search"
23
+ ],
24
+ "author": "SpryMedia Ltd",
25
+ "license": "MIT",
26
+ "bugs": {
27
+ "url": "https://datatables.net/forums"
28
+ },
29
+ "homepage": "https://datatables.net",
30
+ "dependencies": {
31
+ "datatables.net": "^3.0.0-0"
32
+ }
33
+ }