Skip to content

Commit

Permalink
Merge branch 'debounced-search' of https://github.com/sheffieldnick/l…
Browse files Browse the repository at this point in the history
…ist.js into sheffieldnick-debounced-search
  • Loading branch information
javve committed Nov 23, 2020
2 parents 038cdfd + 071b1a7 commit ebcf141
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 36 deletions.
48 changes: 45 additions & 3 deletions __test__/search.test.js
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ describe('Search', function() {
});


describe('Specfic columns', function() {
describe('Specific columns', function() {
it('should find match in column', function() {
var result = list.search('jonny', [ 'name' ]);
expect(result.length).toEqual(1);
Expand All @@ -128,11 +128,11 @@ describe('Search', function() {
var result = list.search('jonny', [ 'born' ]);
expect(result.length).toEqual(0);
});
it('should work with columns that does not exist', function() {
it('should work with columns that do not exist', function() {
var result = list.search('jonny', [ 'pet' ]);
expect(result.length).toEqual(0);
});
it('should remove columnm option', function() {
it('should remove column option', function() {
var result = list.search('jonny', [ 'born' ]);
expect(result.length).toEqual(0);
result = list.search('jonny');
Expand All @@ -157,6 +157,48 @@ describe('Search', function() {
expect(result.length).toEqual(4);
});
});

describe('Multiple word search', function() {
it('should find jonny, hasse', function() {
var result = list.search('berg str');
expect(result.length).toEqual(2);
expect(jonny.matching()).toBe(true);
expect(martina.matching()).toBe(false);
expect(angelica.matching()).toBe(false);
expect(sebastian.matching()).toBe(false);
expect(imma.matching()).toBe(false);
expect(hasse.matching()).toBe(true);
});
it('should find martina, angelica, sebastian, hasse', function() {
var result = list.search('a e');
expect(result.length).toEqual(4);
expect(jonny.matching()).toBe(false);
expect(martina.matching()).toBe(true);
expect(angelica.matching()).toBe(true);
expect(sebastian.matching()).toBe(true);
expect(imma.matching()).toBe(false);
expect(hasse.matching()).toBe(true);
});
it('stripping whitespace should find martina', function() {
var result = list.search('martina elm ');
expect(result.length).toEqual(1);
expect(result[0]).toEqual(martina);
});
});

describe('Quoted phrase searches', function() {
it('should find martina', function() {
var result = list.search('"a e"');
expect(result.length).toEqual(1);
expect(result[0]).toEqual(martina);
});
it('quoted phrase and multiple words should find jonny', function() {
var result = list.search('" str" 1986');
expect(result.length).toEqual(1);
expect(result[0]).toEqual(jonny);
});
});

//
// describe('Special characters', function() {
// it('should escape and handle special characters', function() {
Expand Down
50 changes: 39 additions & 11 deletions docs/api.html
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ <h4>Options</h4>
<li><a href="#page">page</a></li>
<li><a href="#pagination">pagination</a></li>
<li><a href="#searchClass">searchClass</a></li>
<li><a href="#searchColumns">searchColumns</a></li>
<li><a href="#searchDelay">searchDelay</a></li>
<li><a href="#sortClass">sortClass</a></li>
<li><a href="#valueNames">valueNames</a></li>
</ul>
Expand All @@ -44,7 +46,7 @@ <h4>Methods</h4>
<li><a href="#fuzzysearch">fuzzySearch</a></li>
<li><a href="#get">get</a></li>
<li><a href="#on">on</a></li>
<li><a href="#reindex">reindex</a></li>
<li><a href="#reindex">reIndex</a></li>
<li><a href="#remove">remove</a></li>
<li><a href="#search">search</a></li>
<li><a href="#size">size</a></li>
Expand All @@ -62,11 +64,11 @@ <h3>

<ul>
<li>
<strong><a name="id" href="#id" class="anchor"></a>id</strong> or <strong><a name="element" href="#element" class="anchor"></a>element</strong> <em class="docs-parameter-description">*required</em>
<strong><a name="id" href="#id" class="anchor"></a>id</strong> or <strong><a name="element" href="#element" class="anchor"></a>element</strong> <em class="docs-parameter-description">*required</em><br>
Id the element in which the list area should be initialized. OR the actual element itself.
</li>
<li>
<p><strong><a name="options" href="#options" class="anchor"></a>options</strong>
<p><strong><a name="options" href="#options" class="anchor"></a>options</strong> <em class="docs-parameter-description">Object, default: undefined</em><br>
Some of the option parameters are required at some times</p>

<ul>
Expand Down Expand Up @@ -121,6 +123,10 @@ <h3>
What is the class of the list-container?</p></li>
<li><p><strong><a name="searchClass" href="#searchClass" class="anchor"></a>searchClass</strong> <em class="docs-parameter-description">String, default: "search"</em><br>
What is the class of the search field?</p></li>
<li><p><strong><a name="searchColumns" href="#searchColumns" class="anchor"></a>searchColumns</strong> <em class="docs-parameter-description">Array of strings, default: undefined</em><br>
Restrict searching to just these column names? Default is to search all columns.</p></li>
<li><p><strong><a name="searchDelay" href="#searchDelay" class="anchor"></a>searchDelay</strong> <em class="docs-parameter-description">Int default: 0</em><br>
Delay in milliseconds after last keypress in search field before search starts. 250&rarr;750 is good for very large lists.</p></li>
<li><p><strong><a name="sortClass" href="#sortClass" class="anchor"></a>sortClass</strong> <em class="docs-parameter-description">String, default: "sort"</em><br>
What is the class of the sort buttons?</p></li>
<li><p><strong><a name="indexAsync" href="#indexAsync" class="anchor"></a>indexAsync</strong> <em class="docs-parameter-description">Boolean, default: false</em><br>
Expand All @@ -132,11 +138,11 @@ <h3>
performance.</p></li>
<li><p><strong><a name="i" href="#i" class="anchor"></a>i</strong> <em class="docs-parameter-description">Int, default: 1</em><br>
Which item should be shown as the first one.</p></li>
<li><p><strong><a name="pagination" href="#pagination" class="anchor"></a>pagination</strong> <em class="docs-parameter-description">Boolean, default: false</em><br>
<li><p><strong><a name="pagination" href="#pagination" class="anchor"></a>pagination</strong> <em class="docs-parameter-description">Boolean, default: undefined</em><br>
Read more <a href="{{ "/docs/pagination" | relative_url }}">here</a>.</p></li>
</ul>
</li>
<li><p><strong><a name="values" href="#values" class="anchor"></a>values</strong> <em class="docs-parameter-description">Array of objects) (*optional</em>
<li><p><strong><a name="values" href="#values" class="anchor"></a>values</strong> <em class="docs-parameter-description">Array of objects, default: undefined</em><br>
Values to add to the list on initialization.</p></li>
</ul>

Expand Down Expand Up @@ -269,21 +275,43 @@ <h3><a name="methods" class="anchor" href="#methods"></a>Methods</h3>

</li>
<li>
<p><strong><a name="search" class="achor" href="#search"></a>search(searchString, columns)</strong><br>
<p><strong><a name="search" class="achor" href="#search"></a>search(searchString, columns, searchFunction)</strong><br>
Searches the list</p>

<pre><code class="javascript">itemsInList = [
{ id: 1, name: "Jonny" }
, { id: 2, name "Gustaf" }
, { id: 3, name "Jonas" }
{ id: 1, name: "Jonny Stromberg", born: 1986 }
, { id: 2, name "Jonas Arnklint", born: 1985 }
, { id: 3, name "Martina Elm", born: 1986 }
, { id: 4, name "Gustaf Lindqvist", born: 1983 }
, { id: 5, name "Jonny Strandberg", born: 1990 }
];

listObj.search('Jonny'); // Only item with name Jonny is shown (also returns this item)
listObj.search('Jonny'); // Only items with name Jonny are shown (also returns these items)

listObj.search(); // Show all items in list

listObj.search('Jonny', ['name']); // Only search in the 'name' column</code></pre>

<p>Space-separated words match in any order using logical AND. Surround a phrase in quotes for exact matches:</p>

<pre><code class="javascript">listObj.search('Jon 198'); // Items that match Jon AND 198

listObj.search('"Jonny S" 1990'); // Items that match "Jonny S" AND 1990</code></pre>

<p>Optionally your own search function can be used:</p>

<pre><code class="javascript">listObj.search('Jonny', searchFunction); // Custom search for Jonny

listObj.search('Jonny', ['name'], searchFunction); // Custom search in the 'name' column

function searchFunction(searchString, columns) {
for (var k = 0, kl = listObj.items.length; k &lt; kl; k++) {
listObj.items[k].found = false;
// Insert your custom search logic here, set found = true

}
};</code></pre>

</li>
<li>
<p><strong><a name="clear" class="achor" href="#clear"></a>clear()</strong><br>
Expand Down Expand Up @@ -345,7 +373,7 @@ <h3><a name="methods" class="anchor" href="#methods"></a>Methods</h3>
Read more <a href="{{ "/docs/fuzzysearch" | relative_url }}">here</a></p>
</li>
<li>
<p><strong><aname="on" class="achor" href="#on"></a>on(event, callback)</strong><br>
<p><strong><a name="on" class="achor" href="#on"></a>on(event, callback)</strong><br>
Execute <code>callback</code> when list have been updated (triggered by <code>update()</code>, which is used by a lot of methods). Use <code>updated</code> as the event.</p>
<h4>Avaliable events</h4>
<ul>
Expand Down
4 changes: 2 additions & 2 deletions src/fuzzy-search.js
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,10 @@ module.exports = function(list, options) {
};


events.bind(getByClass(list.listContainer, options.searchClass), 'keyup', function(e) {
events.bind(getByClass(list.listContainer, options.searchClass), 'keyup', list.utils.events.debounce(function(e) {
var target = e.target || e.srcElement; // IE have srcElement
list.search(target.value, fuzzySearch.search);
});
}, list.searchDelay));

return function(str, columns) {
list.search(str, columns, fuzzySearch.search);
Expand Down
1 change: 1 addition & 0 deletions src/index.js
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ module.exports = function(id, options, values) {
self.searched = false;
self.filtered = false;
self.searchColumns = undefined;
self.searchDelay = 0;
self.handlers = { 'updated': [] };
self.valueNames = [];
self.utils = {
Expand Down
52 changes: 32 additions & 20 deletions src/search.js
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -45,28 +45,40 @@ module.exports = function(list) {
};
var search = {
list: function() {
// Extract quoted phrases "word1 word2" from original searchString
// searchString is converted to lowercase by List.js
var words = [], phrase, ss = searchString;
while ((phrase = ss.match(/"([^"]+)"/)) !== null) {
words.push(phrase[1]);
ss = ss.substring(0,phrase.index) + ss.substring(phrase.index+phrase[0].length);
};
// Get remaining space-separated words (if any)
ss = ss.trim();
if (ss.length) words = words.concat(ss.split(/\s+/));
for (var k = 0, kl = list.items.length; k < kl; k++) {
search.item(list.items[k]);
}
},
item: function(item) {
item.found = false;
for (var j = 0, jl = columns.length; j < jl; j++) {
if (search.values(item.values(), columns[j])) {
item.found = true;
return;
}
}
},
values: function(values, column) {
if (values.hasOwnProperty(column)) {
text = list.utils.toString(values[column]).toLowerCase();
if ((searchString !== "") && (text.search(searchString) > -1)) {
return true;
var item = list.items[k];
item.found = false;
if (!words.length) continue;
for (var i = 0, il = words.length; i < il; i++) {
var word_found = false;
for (var j = 0, jl = columns.length; j < jl; j++) {
var values = item.values(), column = columns[j];
if (values.hasOwnProperty(column) && values[column] !== undefined && values[column] !== null) {
var text = (typeof values[column] !== 'string') ? values[column].toString() : values[column];
if (text.toLowerCase().indexOf(words[i]) !== -1) {
// word found, so no need to check it against any other columns
word_found = true;
break;
}
}
}
// this word not found? no need to check any other words, the item cannot match
if (!word_found) break;
}
item.found = word_found;
}
return false;
},
// Removed search.item() and search.values()
reset: function() {
list.reset.search();
list.searched = false;
Expand Down Expand Up @@ -100,13 +112,13 @@ module.exports = function(list) {
list.handlers.searchStart = list.handlers.searchStart || [];
list.handlers.searchComplete = list.handlers.searchComplete || [];

list.utils.events.bind(list.utils.getByClass(list.listContainer, list.searchClass), 'keyup', function(e) {
list.utils.events.bind(list.utils.getByClass(list.listContainer, list.searchClass), 'keyup', list.utils.events.debounce(function(e) {
var target = e.target || e.srcElement, // IE have srcElement
alreadyCleared = (target.value === "" && !list.searched);
if (!alreadyCleared) { // If oninput already have resetted the list, do nothing
searchMethod(target.value);
}
});
}, list.searchDelay));

// Used to detect click on HTML5 clear button
list.utils.events.bind(list.utils.getByClass(list.listContainer, list.searchClass), 'input', function(e) {
Expand Down
27 changes: 27 additions & 0 deletions src/utils/events.js
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,30 @@ exports.unbind = function(el, type, fn, capture){
el[i][unbind](prefix + type, fn, capture || false);
}
};

/**
* Returns a function, that, as long as it continues to be invoked, will not
* be triggered. The function will be called after it stops being called for
* `wait` milliseconds. If `immediate` is true, trigger the function on the
* leading edge, instead of the trailing.
*
* @param {Function} fn
* @param {Integer} wait
* @param {Boolean} immediate
* @api public
*/

exports.debounce = function(fn, wait, immediate){
var timeout;
return wait ? function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
if (!immediate) fn.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) fn.apply(context, args);
} : fn;
};

0 comments on commit ebcf141

Please sign in to comment.