views:

364

answers:

3

I'm using this code to search trough about 500 li tags.

$(function() {

 $.expr[":"].containsInCaseSensitive = function(el, i, m){
  var search = m[3];
  if (!search) return false;
  return eval("/" + search + "/i").test($(el).text());
 };  

 $('#query').focus().keyup(function(e){
  if(this.value.length > 0){
   $('ul#abbreviations li').hide();
   $('ul#abbreviations li:containsInCaseSensitive(' + this.value + ')').show();
  } else {
   $('ul#abbreviations li').show(); 
  }
  if(e.keyCode == 13) {
   $(this).val('');
   $('ul#abbreviations li').show();
  }
 });

});

And here is the HTML:

<input type="text" id="query" value=""/>
<ul id="abbreviations">
<li>ABC<span>description</span></li>
<li>BCA<span>description</span></li>
<li>ADC<span>description</span></li>
</ul>

This script is very slow with this many li tags.

How can I make it faster, and how can I search trough only the ABC text in the li, and not the span tags (without changing the html) ?

I know about the existing plugins, but I need a small implementation like this.

Here's the finished code for anyone interested

var abbrs = {};

$('ul#abbreviations li').each(function(i){
 abbrs[this.firstChild.nodeValue] = i;
});

$('#query').focus().keyup(function(e){
 if(this.value.length >= 2){
  $('ul#abbreviations li').hide();
  var filterBy = this.value.toUpperCase();
  for (var abbr in abbrs) {
      if (abbr.indexOf(filterBy) !== -1) {
         var li = abbrs[abbr];
         $('ul#abbreviations li:eq('+li+')').show();
      }
  }  
 } else {
  $('ul#abbreviations li').show(); 
 }
 if(e.keyCode == 13) {
  $(this).val('');
  $('ul#abbreviations li').show();
 } 
});
+1  A: 

For starters, I'd use new RegExp instead of the eval and see if that improved performance.

I assume you are dynamically populating the li tags. Is there a way to search the data structure from which this list is populated directly instead of searching DOM objects? If my assumption is not right, can you loop through the list in the beginning and build an array of strings which can then later be searched?

Edit: Here's how you can build the list of strings

var listTerms = [];

$("ul#abbreviations li").each(function (li) {
    listTerms.push({text : li.firstChild.nodeValue, elem : li});
});

Here's how you can search (simple loop, nothing fancy)

var exp = new RegExp(text, "i");
for(var i=0; i<listTerms.length; i++) {
    if (exp.match(listTerms[i].text)) {
        $(listTerms[i].elem).hide();
    }
}
Chetan Sastry
I'm not dynamically populating the li tags. The content is just plain HTML, nothing more. Can you show an example for the last question?
mofle
A: 

I'm not a javascript coder or that familiar with jquery, but I had a similar problem a while ago with a js eye-candy directory tree for a project spec proposal.

The regexp is obviously your bottleneck. Doesn't javascript have efficient array handling functions, instead of the full overhead of a regexp? Isn't the <li> tag already parsed into the DOM array when the HTML is parsed on document load? It should be simple to walk the DOM tree on those <li> nodes, copying them into an array, and then use a 'find_value' kind of function on the resulting array to find value.

Isn't the, indeed
Adriano Varoli Piazza
+2  A: 

Cache all items into an object first:

var abbrs = {};

$("ul#abbreviations li").each(function (i) {
    abbrs[this.firstChild.nodeValue] = this;
});

Then look for the typed text in your object:

var li = abbrs[this.value.toUpperCase()];
// show li, hide others

Update: For partial matches, you'd have to iterate through the collection:

var filterBy = this.value.toUpperCase();

for (var abbr in abbrs) {
    if (abbr.indexOf(filterBy) !== -1) {
        var li = abbrs[abbr];
        // show li
    }
}
Ates Goral
+1 Right idea, but this works correctly only if the values inside li are unique. Also you're doing an exact match.
Chetan Sastry
Ah... partial match... Well, I guess adding all the permutations of acronym substrings would be an ugly option.
Ates Goral
Some mistakes in the code, it should be: var abbrs = {}; $('ul#abbreviations li').each(function(i){ abbrs[this.firstChild.nodeValue] = i; }); -- How can I do a partial match?
mofle
Fixed code, thanks. Let me think about the partial match a bit...
Ates Goral
Thanks, that did it ;)
mofle