skip to Main Content

I’m trying to program a search/filter function that searches through unordered list items with data-* attributes based on what the user types in.

<input type="text" placeholder="Search..." id="myInput" onkeyup="myFunction()">        
<ul id="myUL">
    <li><a href="#" data-keywords="photography">Digital Media Design</a></li>
    <li><a href="#" data-keywords="computers">Information Technology</a></li>
    <li><a href="#" data-keywords="coding">Programming</a></li>
</ul>

Here is the code I have so far that works only for one data-keywords item. I need help to get it to bring up search results based on multiple keywords.

<li><a href="#" data-keywords="photography photoshop illustrator premiere">Digital Media Design</a></li>

// Search functionality
function myFunction() {
    // Declare variables
    var input, filter, ul, li, a, i;
    input = document.getElementById('myInput');
    filter = input.value.toUpperCase();
    ul = document.getElementById("myUL");
    li = ul.getElementsByTagName('li');
    // Loop through all list items, and hide those who don't match the search query
    for (i = 0; i < li.length; i++) {
        a = li[i].getElementsByTagName("a")[0];
        if (a.innerHTML.toUpperCase().indexOf(filter) > -1 || $(a).data("keywords") === filter.toLocaleLowerCase()) {
            li[i].style.display = "";
        } else {
            li[i].style.display = "none";
        }
    }
}

If anybody has suggestions on how I can improve the code that would be awesome!

2

Answers


  1. This worked for me:

    // Get all li's and create an array with each li's data attributes
    const lis = document.querySelector("#myUL").children;
    
    const keywordArray = [];
    
    [...lis].forEach((li, i) => {
      keywordArray.push(li.children[0].dataset.keywords);
    });
    
    function myFunction() {
      //Show any previous hidden li's
      [...lis].forEach(li => {
        li.style.display = 'block';
      });
    
      const inputArray = document.querySelector('#myInput').value.split(' ');
      const indexes = [];
      // For each word in the input field, search through our data attribute array
      inputArray.forEach(term => {
        keywordArray.forEach((keywords, i) => {
          keywords.split(' ').forEach(keyword => {
            // If we find a match, add the index of the data attribute array, which
            // will be the same as the index of the li element
            if (keyword === term) {
              indexes.push(i);
            }
          });
        });
      });
      // If we have a match, hide every non-matching li'
      if (indexes.length) {
        [...lis].forEach((li, i) => {
          if (!indexes.includes(i)) {
            li.style.display = 'none';
          }
        });
      }
    }
    

    For clarification, do you want additional search terms to make the search more specific or less specific? Because my solution makes the search less specific as it will show any li matching any search term.

    Login or Signup to reply.
  2. I’ve made a few assumptions:

    • If multiple search terms are provided they must all match.
    • Search terms must be letters, a-z. That could easily be changed to include other characters, such as numbers, but ultimately we need some way to decide where one term ends and the next one begins.
    • Matching is based on being a substring.

    The bit that needs most explaining is that regex. It’s using a standard trick for doing an and match:

    Regular Expressions: Is there an AND operator?

    So if you search for digital photography the RegExp will be equivalent to:

    /(?=.*digital)(?=.*photography)/i
    

    If you wanted searching to be or rather than and you’d just need to tweak the RegExp accordingly. If you wanted to do a starts-with match rather than substring you could throw in b before each search term (suitably escaped as \b in the string).

    I hope the rest is pretty self-explanatory, I tried to stay close to the code in the question.

    // Search functionality
    function myFunction() {
        // Declare variables
        var input = document.getElementById('myInput'),
            filter = input.value,
            ul = document.getElementById('myUL'),
            lis = ul.getElementsByTagName('li'),
            searchTerms = filter.match(/[a-z]+/gi),
            re, index, li, a;
            
        if (searchTerms) {
            searchTerms = searchTerms.map(function(term) {
                return '(?=.*' + term + ')';
            });
            
            re = new RegExp(searchTerms.join(''), 'i');
        } else {
            re = /./;
        }
    
        // Loop through all list items, and hide those who don't match the search query
        for (index = 0; index < lis.length; index++) {
            li = lis[index];
            a = li.firstChild;
    
            if (re.test(a.innerHTML + ' ' + a.getAttribute('data-keywords'))) {
                li.style.display = '';
            } else {
                li.style.display = 'none';
            }
        }
    }
    <input type="text" placeholder="Search..." id="myInput" onkeyup="myFunction()">
    
    <ul id="myUL">
      <li><a href="#" data-keywords="photography photoshop illustrator premiere">Digital Media Design</a></li>
      <li><a href="#" data-keywords="computers">Information Technology</a></li>
      <li><a href="#" data-keywords="coding">Programming</a></li>
    </ul>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search