Subrata
Subrata

Reputation: 57

Jquery + regex script to match words in any order for making live search

So i just came accross a jquery live search script that uses regex to filter down and show the list items which matches the input text and hides rest.

The problem is, it matches phrases and in ascending order only.

Suppose if my list is:

<ul class="my_ul">
   <li><a>Nvidia gtx 970 vs amd rx 390</a></li>
   <li><a>Nvidia gtx 1060 vs nvidia gtx 970</a></li>
</ul>

Now if i enter: 1060 gtx nvidia970, it should return the second li, and ignore any extra spaces. (Means should match single words only in any order ignoring spaces)

For now i am using this script:

$("#search-input").keyup(function(){

  // Retrieve the input field text and reset the count to zero
  var filter = $(this).val(), count = 0;

  // Loop through the comment list
  $(".my_ul li").each(function(){

      // If the list item does not contain the text phrase fade it out
     if ($(this).text().search(new RegExp(filter, "i")) < 0) {

          $(this).fadeOut(200);

      // Show the list item if the phrase matches and increase the count by 1
      } else {
          $(this).show();

          count++;
      }
  });

  // Update the count
  var numberItems = count;
  $("#filter-count").text("Number of Comments = "+count);
  });

But this only matches if the words are in order, For example: gtx 1060 vs nvidia gtx 970,

Here is jsfiddle: https://jsfiddle.net/xpvt214o/257804/

Please solve this for me, i am a beginner and just came accross regex so can't figure it out by replacing the regex using some of the codes here is stactoverflow. Please help, thanks :)

Upvotes: 2

Views: 1888

Answers (2)

wp78de
wp78de

Reputation: 18950

The problem to solve here is separating numbers and letters in the filter var. Indeed, a regex comes handy to do so:

filter.match(/[a-zA-Z]+|[0-9]+/g).join("|")

This matches either letters or numbers and joins the resulting array to a string again with a regex alternation as the separator. This allows us to search for numbers and letters independently.

However, there's a problem: Since numbers and letters are separated you may get unexpected results, eg. search for Phantom90, both the line with 390 and Phantom9 will show up. To improve you could add an "exact phrase" search in quoted strings as used in search engines; or wrap the separated elements with a lookahead to filter strings that contain each element. The later approach is shown below:

Sample Code

$("#search-input").keyup(function(){
  // Retrieve the input field text and reset the count to zero
  var filter = $(this).val(), count = 0;
  // Loop through the comment list
  $(".my_ul li").each(function(){
      var pattern = filter.trim().match(/[a-zA-Z]+|[0-9]+/g) || "";  // null coalescing: "" if null
      if(pattern) {
        pattern = pattern.map(function(el) { 
          return '(?=.*' + el + ')';
        });
        //join if there is something in the array
        pattern = pattern.join("");
     }
     //console.log(pattern.join("")); 
     // If the list item does not contain the text phrase fade it out
     if ($(this).text().search(new RegExp(pattern, "i")) < 0) {
          $(this).fadeOut(200);
      // Show the list item if the phrase matches and increase the count by 1
      } else {
          $(this).show();
          count++;
      }
  });

  // Update the count
  var numberItems = count;
  $("#filter-count").text("Number of Comments = "+count);
  });
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input placeholder="Search Me" id="search-input" type="text" /> 
<ul class="my_ul">
   <li><a>Nvidia gtx 970 vs amd rx 390</a></li>
   <li><a>Nvidia gtx 1060 vs nvidia gtx 970</a></li>
   <li><a>Phaeton vs Phantom9</a></li>
</ul>

Remarks:

    • You may not want to split your input into letters and numbers but numbers and not numbers, i.e. \d+|\D+ instead.
  • some special characters in your input filter will break the script, escape them as shown here.

Upvotes: 1

Ruben Bohnet
Ruben Bohnet

Reputation: 402

If I understand your requirements, this is easy, you simply need a global flag, telling your regex to not return after the first match, than you apply a logic Or to all your filters.

This is the "Regex-Solution" to your problem, since I don't know enough about JS, someone with more savvy with JS might be able to give you a more JS-centric solution.

You should end up with this result:

/(GTX|AVD|1060|970)/g

To archive that:

  1. Split your filter input along its whitespaces.
  2. Build a string that starts with /(.
  3. Concatenate the first word.
  4. Put | followed by your words till you have inputted all words.
  5. Close with /).
  6. Add g to the flags you set in the constructor.

See the regex in effect here:

const regex = /(GTX|AVD|1060|970)/g;
const str = `Bla AVD trd GTX 1060 df fggg`;
let m;

while ((m = regex.exec(str)) !== null) {
    // This is necessary to avoid infinite loops with zero-width matches
    if (m.index === regex.lastIndex) {
        regex.lastIndex++;
    }
    
    // The result can be accessed through the `m`-variable.
    m.forEach((match, groupIndex) => {
        if(groupIndex == 0) {
          console.log(`Found match, group ${groupIndex}: ${match}`);
        }
    });
}

Upvotes: 0

Related Questions