Home > Enterprise >  How can I successively filter the choices in a list on a letter by letter basis?
How can I successively filter the choices in a list on a letter by letter basis?

Time:06-03

My apologies if this has been answered elsewhere; if so, I was unable to query the database in a way that returned the result I need. Here's my question:

Let's say I have an unordered list of state names.

etc., Louisiana, Maine, Massachusetts, Michigan, Mississippi, Missouri, Montana, New Hampshire, etc.

And I want to display a search box that filters the list to a single choice as successive unique letters are typed in. In other words, by just typing in an "M", you eliminate "Louisiana" and "New Hampshire" from the list. By adding an "o" to the "M" you eliminate everything but "Montana." But typing in an "a" after the "M" the list still offers two choices. In order to reduce the list to a single choice you have to type a third letter. I've found a script that will filter by upper case letter, but nothing more. If a second letter is typed in to further reduce the number of choices, the list disappears entirely.

Here (from w3schools with some modification) are the elements:

function myFunction() {
  var input, filter, ul, li, a, i, txtValue;
  input = document.getElementById("myInput");
  filter = input.value.toUpperCase();
  ul = document.getElementById("myUL");
  li = ul.getElementsByTagName("li");
  for (i = 0; i < li.length; i  ) {
    a = li[i].getElementsByTagName("a")[0];
    txtValue = a.textContent || a.innerText;
    if (txtValue.indexOf(filter) > -1) {
      li[i].style.display = "";
    } else {
      li[i].style.display = "none";
    }
  }
}
* {
  box-sizing: border-box;
}

#myInput {
  background-image: url('/css/searchicon.png');
  background-position: 10px 12px;
  background-repeat: no-repeat;
  width: 100%;
  font-size: 16px;
  padding: 12px 20px 12px 40px;
  border: 1px solid #ddd;
  margin-bottom: 12px;
}

#myUL {
  list-style-type: none;
  padding: 0;
  margin: 0;
}

#myUL li a {
  border: 1px solid #ddd;
  margin-top: -1px;
  /* Prevent double borders */
  background-color: #f6f6f6;
  padding: 12px;
  text-decoration: none;
  font-size: 18px;
  color: black;
  display: block
}

#myUL li a:hover:not(.header) {
  background-color: #eee;
}
<input type="text" id="myInput" onkeyup="myFunction()" placeholder="Search for names.." title="Type in a name">

<ul id="myUL">
  <li><a href="#">Louisiana</a></li>
  <li><a href="#">Maine</a></li>
  <li><a href="#">Massachusetts</a></li>
  <li><a href="#">Michigan</a></li>
  <li><a href="#">Mississippi</a></li>
  <li><a href="#">Missouri</a></li>
  <li><a href="#">Montana</a></li>
  <li><a href="#">New Hampshire</a></li>
</ul>

Their script does offer the option of sorting by any letter in the name instead of just the uppercase (presumably first) letter. But this returns all names wherein the typed letter appears at all. Thus, typing an "i" (in a list of all state names) would display all states with that letter anywhere in the name. However, this method eventually does reduce the list to a single choice as more letters are typed in. I would like both of these methods to work together i.e., I want the first typed letter to filter the list to states with only that first letter, the second typed letter to reduce it to only those states with the same first two letters, etc. I hope that makes sense.

Thanks in advance for any help.

CodePudding user response:

I couldn't quite get what you were asking so I added a 2 options. One that checks the beggining of the state and one that check every state that start with any of the letters you've put in

function myFunction() {
var input, filter, ul, li, a, i, txtValue;
input = document.getElementById("myInput");
filter = input.value.toUpperCase().split('');
ul = document.getElementById("myUL");
li = ul.getElementsByTagName("li");
for (i = 0; i < li.length; i  ) {
    if (filter.length === 0) {
    li[i].style.display = "";

  } else {
    a = li[i].getElementsByTagName("a")[0];
    txtValue = a.textContent.toUpperCase() || a.innerText.toUpperCase();
    // If you want to search from the first letter onwards
    /* if (txtValue.substr(0, filter.length) == filter)  {
     li[i].style.display = "";
    } else {
      li[i].style.display = "none";
    } */ 
    
    // If you want to search by the first letter no matter how many charachters you put in
    if (txtValue[0].split('').some((char) => filter.includes(char))) {
        li[i].style.display = "";
    } else {
        li[i].style.display = "none";
    }
  }
}

}

CodePudding user response:

Is more algorithmic problem than javascript problem, so u can add aditional tag to your question. I used foreach loop to simplified code, u can change it if u want. Okej your code check if txtValue(string variable) contain second string 'filter' txtValue.indexOf(filter) > -1, here u can read more about indexOf method. You need to check, which option of your list start with 'filter'. Simplest solution for your will be:

function myFunction() {
    var value = document.getElementById("myInput").value.toUpperCase();
    var myUL = document.getElementById("myUL");
    for (var elem of myUL.getElementsByTagName("li")) {
        if (elem.innerText.toUpperCase().startsWith(value)) {
          elem.style.display = "";
        } else {
          elem.style.display = "none";
        }
    }
}

but i don't think it is a good search engine. U can search "Fuzzy search algorithm" to achieve smth more sophisticated.

CodePudding user response:

Try to research the built in string and array methods in JavaScript.

You could do something like this:

HTML

<h2>
Available names:
</h2>

<p>
'Tom', 'Linda', 'James', 'Allen', 'Matt'
</p>

<h3>
Try to search for one.
</h3>
<input type="text" />
<button type="button">
Search
</button>

<h3>
Names:
</h3>
<div id="output">
  No search yet
</div>   <!-- or even use an <output> tag! -->

JavaScript

const names = ["Tom", "Linda", "James", "Allen", "Matt", "Adam"];

const input = document.querySelector("input");
const button = document.querySelector("button");
const output = document.querySelector("#output");

const renderNamesToHTML = function(names) {
  output.innerHTML = '';
  output.innerHTML  = names.map((n) => n.toString()).join(', ');
};

const filterNamesToStartWith = e => renderNamesToHTML(
  names.filter((name) => name.toLowerCase().startsWith(input.value.toLowerCase()))
)

const filterNamesToInclude = e => renderNamesToHTML(
  names.filter((name) => name.toLowerCase().indexOf(input.value.toLowerCase()) > -1)
)

button.addEventListener("click", filterNamesToInclude);

Try running it in JSFiddle!

Edit: I noticed I actually left it not fully working, sorry. Should've tried it properly myself, anyway it should be working well now and I shortened the code a bit as well.

CodePudding user response:

Hi @ean i have added single line comment on bellow code its just compare upper case string because when your typing on textbox its you have one line on JavaScript its converting in upper case and string.indexOf comparing upper and lower case character

function myFunction() {
  var input, filter, ul, li, a, i, txtValue;
  input = document.getElementById("myInput");
  filter = input.value.toUpperCase();
  ul = document.getElementById("myUL");
  li = ul.getElementsByTagName("li");
  for (i = 0; i < li.length; i  ) {
      a = li[i].getElementsByTagName("a")[0];
      // txtValue = a.textContent || a.innerText;
      txtValue = a.innerText.toUpperCase();
      if (txtValue.indexOf(filter) > -1) {
        li[i].style.display = "";
      } else {
        li[i].style.display = "none";
      }
  }
}
  • Related