Reputation: 21
I need to create a list from an array. I pass an input value and recreate the list with all the matches I found. if there's no match I need to show the "no results" message.
var arr = [{"name": "Jack"}, {"name": "Jake"}];
var txtName = document.querySelector("#txtName");
txtName.addEventListener('keyup', function(e){
arr.forEach(function(item){
if(item.name.toLowerCase().indexOf(txtName.value) > -1){
myList.innerHTML +=
`<li>${item.name}</li>`
return
}else{
myList.innerHTML = `<li>No results </li>`
}
});
});
<input type="text" id="txtName">
<ul id="myList"></ul>
Upvotes: 1
Views: 1643
Reputation: 290
<input type="text" id="txtName" onkeyup="filter(this.value)">
<ul id="myList"></ul>
<script type="text/javascript">
var arr = [{
"name": "Jack"
}, {
"name": "Jake"
}];
var txtName = document.querySelector("#txtName");
var filter = function(val) {
var regex = new RegExp("^" + val, "i");
var result = arr.filter(function(obj) {
return obj.name.search(regex) > -1;
});
if (result.length == 0) {
myList.innerHTML = 'No results';
} else {
myList.innerHTML = '';
result.forEach(function(item) {
myList.innerHTML += `<li>${item.name}</li>`;
});
}
}
</script>
Upvotes: 0
Reputation: 65806
Don't write to .innerHTML
more than you need to. It causes the DOM to update and you should only do that when you have to. Instead, build up a string and then inject the string once it's complete. In your case, you are writing "No Results" if one particular iteration of the loop doesn't find a match, but other iterations might, so only write "no results" after you know there weren't any. You can use a simple flag variable to keep track of whether matches were found.
Clear the results as you write new data and know that you can't return
from a .forEach()
Also, if you are going to convert one string to lower case, you need to also convert the other so that you are comparing lower to lower.
And, change keyup
to input
so that it will work on devices that don't support keyboard events (i.e. mobile devices), .
var arr = [{name: "Jack"}, {name: "Jake"}];
var txtName = document.getElementById("txtName");
txtName.addEventListener('input', function(e){
myList.innerHTML = ""; // Clear out old results
var output = ""; // This will be the results
var foundItems = false; // "Flag" that keeps track of whether matches were found
// You can't return from a .forEach loop
arr.forEach(function(item){
// Compare lower case against lower case
if(item.name.toLowerCase().indexOf(txtName.value.toLowerCase()) > -1){
output += `<li>${item.name}</li>`
foundItems = true; // Set flag
}
});
// Check flag
if(foundItems){
myList.innerHTML = output; // Flag is up, use found data
} else {
// Flag is still down, no results
myList.innerHTML = `<li>No results </li>`;
}
});
<input type="text" id="txtName">
<ul id="myList"></ul>
Upvotes: 1
Reputation: 1619
You want to clear/rewrite the html for your list for every event. Then filter down your results to a matching set, if no match is found end the event while adding your no result item this stops processing on empty results or no match. Only iterate if you have matches, so save that bit for after your empty check.
const arr = [{"name": "Jack"}, {"name": "Jake"}];
const txtName = document.querySelector("#txtName");
const myList = document.querySelector('#myList');
const findName = e => {
myList.innerHTML = "";
const matches = arr.filter(val => val.name.toLowerCase().indexOf(e.target.value) > -1);
if (matches.length === 0 || e.target.value == '') {
myList.innerHTML = '<li>No Results</li>';
return;
}
matches.forEach(val => {
let current = document.createElement('li')
current.textContent = val.name;
myList.appendChild(current);
});
}
txtName.addEventListener('keyup', findName);
<input type="text" id="txtName">
<ul id="myList"></ul>
Upvotes: 0
Reputation: 8660
You can create a container for your results(in the snippet it's a div called myResults) then repopulate the container when text is entered.
I'm not sure what type of functionality you wanted when it comes to matching. Currently it'll match if a
is entered in since Jack
and Jake
both contain a
. However, if nothing is found it will display that result.
The way this works is that we have a function that creates a new list every time something is entered into the box. It determines if any of the names include the text box value, if they do it appends them as list items to the list. At the end of the function it uses the childElementCount
property to determine if any list items were added. If they weren't we know nothing matched, and we set the text to No Results Found
.
var arr = [{
"name": "Jack"
}, {
"name": "Jake"
}];
var txtName = document.querySelector("#txtName"),
results = document.querySelector("#myResults");
txtName.addEventListener('keyup', function(e) {
results.innerHTML = "";
results.appendChild(createList(this.value));
});
function createList(value) {
let create = (tag, props = {}) => Object.assign(document.createElement(tag), props),
appendTo = (p, c) => {
if (p) p.appendChild(c);
return p;
},
list = create("ul");
list.id = "myList";
for (let value_obj of arr) {
if (value_obj.name.toLowerCase().indexOf(value.toLowerCase()) > -1) {
appendTo(list, create("li", {
textContent: value_obj.name
}));
}
}
if(list.childElementCount == 0) list.textContent = "No Results Found";
return list;
}
<input type="text" id="txtName">
<div id="myResults"></div>
Upvotes: 0
Reputation: 350147
You should only decide to output "No results" at the end of the loop. You cannot know whether it is appropriate to output this at some arbitrary iteration of the loop, so you are wiping out good results that might already have been output.
Here is a corrected version:
var arr = [{"name": "Jack"}, {"name": "Jake"}];
var txtName = document.querySelector("#txtName");
txtName.addEventListener('keyup', function(e){
var lowText = txtName.value.toLowerCase();
var matches = arr.filter(function(item){
return item.name.toLowerCase().indexOf(lowText) > -1;
}).map(function (item) {
return `<li>${item.name}</li>`;
}).join('');
myList.innerHTML = matches || `<li>No results </li>`;
});
<input type="text" id="txtName">
<ul id="myList"></ul>
If you want an empty input to give "No results" instead of all items, then change the definition of matches
to:
var matches = lowText.length && arr.filter( // ...etc
Upvotes: 2