javascripthtmlcharat

charAt not working on element created dynamically in javascript


I am Making a name searching app in javascript. I have created all list items dynamically in javascript.

Js Fiddle

And when I use charAt(0) the app is not working nothing is displaying I want to match the first chracter of input and first character of array

names = ["Ayesha", "Alina", "Bilal", "Kaira", "Per", "Henry", "Liba", "Rimsha"]

let ulEl = document.getElementById("ul-el")
let searchInput = document.getElementById("searchInput");

function render(things) {
  let listItems = ""
  for (let i = 0; i < things.length; i++) {
    listItems += `
            <li class="names">
                    ${things[i]}
            </li>
        `
  }
  ulEl.innerHTML = listItems
}

render(names)

searchInput.addEventListener("keyup", function(event) {
  let searchQuery = event.target.value.toLowerCase().charAt(0);
  let allNamesDOMCollection = document.getElementsByClassName('names');

  console.log(allNamesDOMCollection.textContent)


  console.log(allNamesDOMCollection.textContent)
  for (let i = 0; i < allNamesDOMCollection.length; i++) {
    let currentName = allNamesDOMCollection[i].textContent.toLowerCase().charAt(0);


    if (currentName.includes(searchQuery)) {
      allNamesDOMCollection[i].style.display = "block";
    } else {
      allNamesDOMCollection[i].style.display = "none";
    }

  }
})
<h1>Search Names</h1>

<div class="container">
  <input type="text" id="searchInput" placeholder="Search">
</div>

<div class="container cont-modifier">

  <ul id="ul-el"></ul>

</div>


Solution

  • The undefined thing is because the HTMLCollection returned by getElementsByClassName has no textContent property, but since that's just in your console.log we can largely disregard that.

    The main problem is that the first character of the list items is a whitespace character because of how you build the listItems string, you need to trim() the text content if you want the first character of the name. (Or change how you build listItems.)

    I also suggest using the input event, not keyup.

    It seems odd that your search input allows multiple characters but you're only using the first, but you confirmed in a comment that's what you wanted, so I've left it that way — but see the end of the answer for how to do a full match if you prefer.

    See *** comments:

    // *** Add a declaration
    const names = ["Ayesha", "Alina", "Bilal", "Kaira", "Per", "Henry", "Liba", "Rimsha"];
    
    let ulEl = document.getElementById("ul-el");
    let searchInput = document.getElementById("searchInput");
    
    function render(things) {
        let listItems = "";
        for (let i = 0; i < things.length; i++) {
            listItems += `
                <li class="names">
                        ${things[i]}
                </li>
            `;
        }
        ulEl.innerHTML = listItems;
    }
    
    render(names);
    
    // *** Use `input`, not `keyup`
    searchInput.addEventListener("input", function(event) {
        let searchQuery = event.target.value.toLowerCase().charAt(0);
        let allNamesDOMCollection = document.getElementsByClassName('names');
    
        for (let i = 0; i < allNamesDOMCollection.length; i++) {
            // *** Remove the whitespace before getting first character
            let currentName = allNamesDOMCollection[i].textContent.trim().toLowerCase().charAt(0);
            // −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−^^^^^^^
            if (currentName.includes(searchQuery)) {
                allNamesDOMCollection[i].style.display = "block";
            } else {
                allNamesDOMCollection[i].style.display = "none";
            }
        }
    });
    <h1>Search Names</h1>
    
    <div class="container">
      <input type="text" id="searchInput" placeholder="Search">
    </div>
    
    <div class="container cont-modifier">
    
      <ul id="ul-el"></ul>
    
    </div>

    Alternatively, change how you build listItems:

    function render(things) {
        let listItems = "";
        for (let i = 0; i < things.length; i++) {
            listItems += `<li class="names">${things[i]}</li>`; // *** Remove whitespace around name
        }
        ulEl.innerHTML = listItems;
    }
    

    Live Copy:

    // *** Add a declaration
    const names = ["Ayesha", "Alina", "Bilal", "Kaira", "Per", "Henry", "Liba", "Rimsha"];
    
    let ulEl = document.getElementById("ul-el");
    let searchInput = document.getElementById("searchInput");
    
    function render(things) {
        let listItems = ""
        for (let i = 0; i < things.length; i++) {
            listItems += `<li class="names">${things[i]}</li>`; // *** Remove whitespace around name
        }
        ulEl.innerHTML = listItems;
    }
    
    render(names);
    
    // *** Use `input`, not `keyup`
    searchInput.addEventListener("input", function(event) {
        let searchQuery = event.target.value.toLowerCase().charAt(0);
        let allNamesDOMCollection = document.getElementsByClassName('names');
    
        for (let i = 0; i < allNamesDOMCollection.length; i++) {
            let currentName = allNamesDOMCollection[i].textContent.toLowerCase().charAt(0);
            if (currentName.includes(searchQuery)) {
                allNamesDOMCollection[i].style.display = "block";
            } else {
                allNamesDOMCollection[i].style.display = "none";
            }
        }
    });
    <h1>Search Names</h1>
    
    <div class="container">
      <input type="text" id="searchInput" placeholder="Search">
    </div>
    
    <div class="container cont-modifier">
    
      <ul id="ul-el"></ul>
    
    </div>


    A couple of other side notes. When deal with natural language text, in general it's best to:

    let input = `Franç`;
    let name = "François";
    console.log(name.includes(input)); // false
    input = input.normalize();
    name = name.normalize();
    console.log(name.includes(input)); // true


    In a comment you asked what you'd change if you did want to check the entire input against the entire name. To do that, just remove the two .charAt(0) calls, if you want to match anywhere in the name. If you only want to match at the start of the name, use startsWith instead of includes.

    Here's the above, using:

    const names = ["Ayesha", "Alina", "Bilal", "Kaira", "Per", "Henry", "Liba", "Rimsha"];
    
    const ulEl = document.getElementById("ul-el");
    const searchInput = document.getElementById("searchInput");
    
    function render(things) {
        let listItems = "";
        for (const thing of things) {
            listItems += `
                <li class="names">
                        ${thing}
                </li>
            `;
        }
        ulEl.innerHTML = listItems;
    }
    
    render(names);
    
    function prepForCompare(str) {
        str = str.trim().toLocaleLowerCase();
        // `normalize` is newer than most of the rest of what we're using
        if (str.normalize) {
            str = str.normalize();
        }
        return str;
    }
    
    searchInput.addEventListener("input", function(event) {
        const searchQuery = prepForCompare(event.target.value);
        const allNamesDOMCollection = document.getElementsByClassName("names");
        for (const li of allNamesDOMCollection) {
            const currentName = prepForCompare(li.textContent);
            li.classList.toggle("hidden", !currentName.includes(searchQuery));
        }
    });
    .hidden {
        display: none;
    }
    <h1>Search Names</h1>
    
    <div class="container">
      <input type="text" id="searchInput" placeholder="Search">
    </div>
    
    <div class="container cont-modifier">
    
      <ul id="ul-el"></ul>
    
    </div>