I have a created a small app that helps me be more productive at work. It filters a list of tags by their text contents. It currently works great for finding contiguous lines of text, but I would prefer to re-implement it as a filter that works for non-contiguous binations of text.
For example, if the string inside the tag is "Appleville, MN", the current filter will find it if you type in "Apple" or "Appleville" or "App", but I want to be able to filter successfully with input such as "App MN".
Could anyone point me in the right direction? I'm not sure what exact algorithm I need to go with. Thanks!
P.S.: I've included some code below with examples of the kind of search items I am filtering, the current filter function, as well as the search field.
function filterList() {
var input, filter, ul, li, a, i, txtValue;
input = document.getElementById('myInput');
filter = input.value.toUpperCase();
ul = document.getElementById("itemList");
li = ul.getElementsByTagName('li');
for (i = 0; i < li.length; i++) {
a = li[i].getElementsByTagName("a")[0];
txtValue = a.textContent || a.innerText;
if (txtValue.toUpperCase().indexOf(filter) > -1) {
li[i].style.display = "";
} else {
li[i].style.display = "none";
}
}
}
<input type="text" id="myInput" onkeyup="filterList()" placeholder="Search list..">
<ul id="itemList">
<li onclick=applevilleMN()><a href="#">Appleville, MN</a></li>
<li onclick=orangevilleOR()><a href="#">Orangeville, OR</a></li>
<li onclick=kiwivilleCA()><a href="#">Kiwiville, CA</a></li>
<li onclick=bananavilleCA()><a href="#">Bananaville, CA</a></li>
</ul>
I have a created a small app that helps me be more productive at work. It filters a list of tags by their text contents. It currently works great for finding contiguous lines of text, but I would prefer to re-implement it as a filter that works for non-contiguous binations of text.
For example, if the string inside the tag is "Appleville, MN", the current filter will find it if you type in "Apple" or "Appleville" or "App", but I want to be able to filter successfully with input such as "App MN".
Could anyone point me in the right direction? I'm not sure what exact algorithm I need to go with. Thanks!
P.S.: I've included some code below with examples of the kind of search items I am filtering, the current filter function, as well as the search field.
function filterList() {
var input, filter, ul, li, a, i, txtValue;
input = document.getElementById('myInput');
filter = input.value.toUpperCase();
ul = document.getElementById("itemList");
li = ul.getElementsByTagName('li');
for (i = 0; i < li.length; i++) {
a = li[i].getElementsByTagName("a")[0];
txtValue = a.textContent || a.innerText;
if (txtValue.toUpperCase().indexOf(filter) > -1) {
li[i].style.display = "";
} else {
li[i].style.display = "none";
}
}
}
<input type="text" id="myInput" onkeyup="filterList()" placeholder="Search list..">
<ul id="itemList">
<li onclick=applevilleMN()><a href="#">Appleville, MN</a></li>
<li onclick=orangevilleOR()><a href="#">Orangeville, OR</a></li>
<li onclick=kiwivilleCA()><a href="#">Kiwiville, CA</a></li>
<li onclick=bananavilleCA()><a href="#">Bananaville, CA</a></li>
</ul>
Share
Improve this question
edited Sep 25, 2019 at 13:04
j08691
208k32 gold badges269 silver badges280 bronze badges
asked Sep 25, 2019 at 12:36
artifc3artifc3
771 gold badge1 silver badge7 bronze badges
3 Answers
Reset to default 4here's a modified version doing what you want.
It splits the filter string by spaces to create an array of filters, li
s arer shown only if they are valid for each substring of the array.
function filterList() {
var input, filter, ul, li, a, i, txtValue;
input = document.getElementById('myInput');
filter = input.value.toUpperCase();
ul = document.getElementById("itemList");
li = ul.getElementsByTagName('li');
for (i = 0; i < li.length; i++) {
a = li[i].getElementsByTagName("a")[0];
txtValue = a.textContent || a.innerText;
// split filter by spaces, gives ["app", "MN"] in your example
let filters = filter.split(" ")
// remove the empty filters (if your filter string
// starts or ends by a space) since they are source of errors
// Array.filter takes in parameter a function returning a boolean
// it create a new array containing only element where
// the function returned truthy value
// here we return the length of the string which is falsy (== 0) for ""
// and truthy for every other string (!= 0)
filters = filters.filter(f => f.length)
let shouldDisplay = true
// test each filter and store true only if string contains all filter
filters.forEach(filt => {
shouldDisplay = shouldDisplay && txtValue.toUpperCase().includes(filt)
})
// update visibility
// set visible if the string include all filters
// or if there is no filter
li[i].style.display = (shouldDisplay || filters.length === 0) ? "" : "none";
}
}
<ul id="itemList">
<li onclick=applevilleMN()><a href="#" >Appleville, MN</a></li>
<li onclick=orangevilleOR()><a href="#" >Orangeville, OR</a></li>
<li onclick=kiwivilleCA()><a href="#" >Kiwiville, CA</a></li>
<li onclick=bananavilleCA()><a href="#" >Bananaville, CA</a></li>
</ul>
<input type="text" id="myInput" onkeyup="filterList()" placeholder="Search list.." >
docs : Array.filter
, truthy values
, falsy values
var itensList = document.querySelectorAll('#itemList li');
function filterList() {
var valueInput = document.querySelector('#myInput').value.toLowerCase().trim();
for(var i = 0; i < itensList.length; i++){
var item = itensList[i];
var valueLi = item.innerText.toLowerCase().trim();
item.style.display = valueLi.search(new RegExp(valueInput.replace(/\s+/, '|'))) != -1 ? '' : 'none';
}
}
<input type="text" id="myInput" onkeyup="filterList()" placeholder="Search list..">
<ul id="itemList">
<li onclick=applevilleMN()><a href="#">Appleville, MN</a></li>
<li onclick=orangevilleOR()><a href="#">Orangeville, OR</a></li>
<li onclick=kiwivilleCA()><a href="#">Kiwiville, CA</a></li>
<li onclick=bananavilleCA()><a href="#">Bananaville, CA</a></li>
</ul>
I remend you to use fuse.js. Its an approximate string matching libraray, and its is lightweight and easy to set up. You only need to feed it with list of objects and use the object's key as its key.
const cities = [{
state: "MN",
city: "Appleville"
},
{
state: "OR",
city: "Orangeville"
},
{
state: "CA",
city: "Kiwiville"
},
{
state: "CA",
city: "Bananaville"
}
]
var options = {
shouldSort: true,
threshold: 0.6,
location: 0,
distance: 100,
maxPatternLength: 32,
minMatchCharLength: 2,
keys: ['state', 'city']
}
var fuse = new Fuse(cities, options)
const input = document.getElementById("myInput")
input.oninput = (event) => {
const results = fuse.search(event.target.value)
for (result of results) {
console.log(result.state, result.city)
}
}
<script src="https://cdnjs.cloudflare./ajax/libs/fuse.js/3.4.5/fuse.min.js"></script>
<ul id="itemList">
<li onclick=applevilleMN()><a href="#">Appleville, MN</a></li>
<li onclick=orangevilleOR()><a href="#">Orangeville, OR</a></li>
<li onclick=kiwivilleCA()><a href="#">Kiwiville, CA</a></li>
<li onclick=bananavilleCA()><a href="#">Bananaville, CA</a></li>
</ul>
<input type="text" id="myInput" placeholder="Search list..">
Threshold
At what point does the match algorithm give up. A threshold of 0.0 requires a perfect match (of both letters and location), a threshold of 1.0 would match anything. - fuse.js
Thrashold is the most important factor when its es to word matching. As of now, it matchs words that are 40% similar to the input. If you think its quite wide, decrease the number.