jsilva13
jsilva13

Reputation: 21

Create list dynamically from array javascript

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

Answers (5)

astalor
astalor

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

Scott Marcus
Scott Marcus

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

D Lowther
D Lowther

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

zfrisch
zfrisch

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

trincot
trincot

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

Related Questions