7

What I'm looking for:

I'm working on creating an easy way for a user to search a list of people, and for results to instantly display below the search field. The results MUST display "close" results, rather than exact. For example: User searches for "Mr. Smith" and The following existing result is displayed: "John Smith" (since there is no "Mr. Smith" entry, it displayed one with the keyword "smith")

What I have:

I have a working code that lets the user enter some characters and all divs that include a string matching the input is displayed (see in jsfiddle: http://jsfiddle.net/891nvajb/5/ Code is also below) Unfortunately, this only displays results that match EXACTLY.

<body>
 <input type="text" id="edit_search" onkeyup="javascript: find_my_div();">
 <input type="button" onClick="javascript: find_my_div();" value="Find">
        
<script>

function gid(a_id) {
 return document.getElementById (a_id) ;
}

  function close_all(){
  
   for (i=0;i<999; i++) {
    var o = gid("user_"+i);
    if (o) {
     o.style.display = "none";
    }
   }
  
  }


  function find_my_div(){ 
   close_all(); 
   
 var o_edit = gid("edit_search");
 var str_needle = edit_search.value;
 str_needle = str_needle.toUpperCase();
  
 if (str_needle != "") {
  for (i=0;i<999; i++) {
    var o = gid("user_"+i);
    if (o) { 
     
     var str_haystack = o.innerHTML.toUpperCase();
     if (str_haystack.indexOf(str_needle) ==-1) {
      // not found, do nothing
     }
     else{
      o.style.display = "block";
      } 
     }
    }
   }
  
  }
</script>
    
<div id="user_0" style="display:none">Andy Daulton<br/>Owner Nissan<br/><br/></div>
<div id="user_1" style="display:none">Doug Guy<br/>Bug Collector<br/><br/></div>
<div id="user_2" style="display:none">Sam Hilton<br/>Famous Celebrity in Hollywood<br/><br/></div>
<div id="user_3" style="display:none">Don Grey<br/>Old man<br/><br/></div>
<div id="user_4" style="display:none">Amy Hinterly<br/>Cook<br/><br/></div>
<div id="user_5" style="display:none">Gary Doll<br/>Racecar Driver<br/><br/></div>
<div id="user_6" style="display:none">Tod Akers<br/>Football Player<br/><br/></div>
<div id="user_7" style="display:none">Greg Barkley<br/>Interior designer<br/><br/></div>
<div id="user_8" style="display:none">Alen Simmons<br/>8th place winner<br/><br/></div>
Steve
  • 111
  • 2
  • 6
  • I am not able to reproduce the "exact match" behavior in your JSFIDDLE which you claim is working....can you give an example of what you searched for when it worked for you..... – Phani Oct 21 '14 at 18:35
  • 1
    The embedded code seems to work; "ton" returns both Daulton and Hilton...? – Malk Oct 21 '14 at 18:38
  • You just want to give priority to several matching words over fewer? Or Mr. Smoth should should also return John Smith? – juvian Oct 21 '14 at 18:39
  • @Phani Example: "dy da" will bring up "Andy Daulton" because that string of characters is included in that result. Whereas if you type "andy smith" nothing will come up, even though "andy" is in the result. So it's not looking at keywords individually (which Is what I want), but rather just the exact string that's input. – Steve Oct 21 '14 at 18:40
  • @juvian if "smooth" was a keyword in John Smith's result, then he would come up, but otherwise no. – Steve Oct 21 '14 at 18:41

2 Answers2

4

Split the words in the search string with a regex like

searchString.split(/\W/);

and do a OR search over each of the words in the resulting array.

Updated fiddle

var searchStrings = str_needle.split(/\W/);

for (var i = 0, len = searchStrings.length; i < len; i++) {
    var currentSearch = searchStrings[i].toUpperCase();
    if (currentSearch !== "") {
        nameDivs = document.getElementsByClassName("name");
        for (var j = 0, divsLen = nameDivs.length; j < divsLen; j++) {
            if (nameDivs[j].textContent.toUpperCase().indexOf(currentSearch) !== -1) {
                nameDivs[j].style.display = "block";
            }
        }
    }
}
Matt R
  • 724
  • 5
  • 14
  • Ah brilliant! Such a simple fix, thank you! I might even get rid of the "onKeyUp" funcionality to improve user experience so all the results don't show again with the start of each new word Thank you! – Steve Oct 21 '14 at 19:10
  • I wanted to upvote but didn't want to ruin your 666 reputation :> – SirDerpington Nov 14 '18 at 08:50
1

One further approach, is as follows:

function gid(a_id) {
  return document.getElementById(a_id);
}

function close_all() {

  // applies the Array.prototype.forEach() method to the array-like nodeList
  // returned by document.querySelectorAll() (the string passed to which finds all
  // elements with an id that starts with ('^=') the string 'user_':
  [].forEach.call(document.querySelectorAll('[id^=user_]'), function(div) {
    // 'div' is the array element (the node) itself:
    div.style.display = 'none';
  });

}


function find_my_div() {
  close_all();

  // getting the trimmed lower-cased string from the input element, split
  // on white-space characters to create an array:
  var keywords = gid('edit_search').value.trim().toLowerCase().split(/\s+/),
    // as above, selecting all elements whose id starts with the string 'user_':
    haystack = document.querySelectorAll('[id^="user_"]'),
    // working out whether text is accessed by node.textContent, or node.innerText:
    textProp = 'textContent' in document.body ? 'textContent' : 'innerText',
    // an initialised variable, for later:
    userWords,

    // filters the haystack (the divs whose id starts with 'user_'):
    found = [].filter.call(haystack, function(user) {
      // assigns the lower-cased string to the created-variable:
      userWords = user[textProp].toLowerCase();
      // returns those div elements whose text contains some of
      // the words returned, earlier, as the keywords:
      return keywords.some(function (word) {
        return userWords.indexOf(word) > -1;
      });
    });

  // iterates over the found elements, and shows them:
  [].forEach.call(found, function(user) {
    user.style.display = 'block';
  });

}

<body>
  <input type="text" id="edit_search" onkeyup="javascript: find_my_div();">
  <input type="button" onClick="javascript: find_my_div();" value="Find">

  <script>
    function gid(a_id) {
      return document.getElementById(a_id);
    }

    function close_all() {

      [].forEach.call(document.querySelectorAll('[id^=user_]'), function(div) {
        div.style.display = 'none';
      });

    }


    function find_my_div() {
      close_all();
      var keywords = gid('edit_search').value.trim().toLowerCase().split(/\s+/),
        haystack = document.querySelectorAll('[id^="user_"]'),
        textProp = 'textContent' in document.body ? 'textContent' : 'innerText',
        userWords,
        found = [].filter.call(haystack, function(user) {
          userWords = user[textProp].toLowerCase();
          return keywords.some(function (word) {
            return userWords.indexOf(word) > -1;
          });
        });
      console.log(found);
      [].forEach.call(found, function(user) {
        user.style.display = 'block';
      });

    }
  </script>

  <div id="user_0" style="display:none">Andy Daulton
    <br/>Owner Nissan
    <br/>
    <br/>
  </div>
  <div id="user_1" style="display:none">Doug Guy
    <br/>Bug Collector
    <br/>
    <br/>
  </div>
  <div id="user_2" style="display:none">Sam Hilton
    <br/>Famous Celebrity in Hollywood
    <br/>
    <br/>
  </div>
  <div id="user_3" style="display:none">Don Grey
    <br/>Old man
    <br/>
    <br/>
  </div>
  <div id="user_4" style="display:none">Amy Hinterly
    <br/>Cook
    <br/>
    <br/>
  </div>
  <div id="user_5" style="display:none">Gary Doll
    <br/>Racecar Driver
    <br/>
    <br/>
  </div>
  <div id="user_6" style="display:none">Tod Akers
    <br/>Football Player
    <br/>
    <br/>
  </div>
  <div id="user_7" style="display:none">Greg Barkley
    <br/>Interior designer
    <br/>
    <br/>
  </div>
  <div id="user_8" style="display:none">Alen Simmons
    <br/>8th place winner
    <br/>
    <br/>
  </div>

References:

David Thomas
  • 249,100
  • 51
  • 377
  • 410