3

I am creating a simple listbox filter that takes the user input and returns the matching results in a listbox via javascript/jquery (roughly 5000+ items in listbox). Here is the code snippet:

var Listbox1 = $('#Listbox1');
var commands = document.getElementById('DatabaseCommandsHidden'); //using js for speed

$('#CommandsFilter').bind('keyup', function() {

Listbox1.children().remove();


for (var i = 0; i < commands.options.length; i++) {
    if (commands.options[i].text.toLowerCase().match($(this).val().toLowerCase())) {
        Listbox1.append($('<option></option>').val(i).html(commands.options[i].text));
    }
}
});

This works pretty well, but slows down somewhat when the 1st/2nd char's are being typed since there are so many items.

I thought a solution I could use would be to add a delay to the textbox that prevents the 'keyup' event from being called until the user stops typing. The problem is, I'm not sure how to do that, or if its even a good idea or not.

Any suggestions/help is greatly appreciated.

Darcy
  • 5,228
  • 12
  • 53
  • 79

2 Answers2

6

You can do a delay like this:

$('#CommandsFilter').keyup(function() {
  clearTimeout($.data(this, 'timer'));
  var wait = setTimeout(search, 500);
  $(this).data('timer', wait);
});

function search() {
  var temp = $("<select />");
  for (var i = 0; i < commands.options.length; i++) {
    if (commands.options[i].text.toLowerCase().match($(this).val().toLowerCase())) {
      $('<option></option>', { val: i, html: commands.options[i].text }).appendTo(temp);
    }
  }
  Listbox1.empty().append(temp.children());
}

This stores a timeout on the element you're typing in, if 500ms (adjust as needed) passes between keystrokes, a search executes. Also this appends the elements in a document fragment then into the DOM (still preserving encoding, etc). Depending on the number of items, this may be a decent performance boost as well.

Nick Craver
  • 623,446
  • 136
  • 1,297
  • 1,155
  • 1
    I don't think there is any faster method than concatenating a string and appending that chunk. – jAndy May 26 '10 at 14:03
  • @jAndy - It depends how many elements there are, currently your option doesn't set the value either, so it's cheating ;) the performance question comes up every once in a while, I actually had time to provide some samples one day here: http://stackoverflow.com/questions/2690352/which-is-better-string-html-generation-or-jquery-dom-element-creation – Nick Craver May 26 '10 at 14:10
  • Another speed boost would come from moving `$(this).val().toLowerCase()` into a variable defined outside the loop. – Tim Down May 26 '10 at 14:18
  • @Nick: fixed the cheat :p -- That DOM caching thingy, does it still work if you don't add the exact same portion of html every time? – jAndy May 26 '10 at 14:21
  • @jAndy - It caches the document fragment based on the string, so the part inside the `$(htmlHere)` needs to be consistent, since that's the key. – Nick Craver May 26 '10 at 14:24
  • Awesome. Works very well. Thanks Nick! – Darcy May 26 '10 at 15:08
  • Nick: you have a typo in `vat temp = $("");`: `vat` should be `var`. – Tim Down May 26 '10 at 15:19
2

If the commands drop-down isn't changing, I'd suggest the following (note I've dropped jQuery for better performance and compatibility). There are several improvements:

  • Timer to delay updating the filtered list once half a second has elapsed since the last keypress
  • List of command texts is pre-cached
  • Unnecessary use of match replaced with indexOf
  • Uses fast native DOM manipulation that works in all scriptable browsers since the 1990s

A quick test suggests that for a drop-down with 5000 options containing short strings, it's between 10 and 30 times faster than the jQuery equivalent in most browsers.

Code:

var commands = document.getElementById("DatabaseCommandsHidden");
var filteredDropDown = document.getElementById("Listbox1");
var filterInput = document.getElementById("CommandsFilter");
var timer;

// Create a cached list of the lower case text of the commands drop-down
var commandTexts = [], commandText;
for (var i = 0, len = commands.options.length; i < len; ++i) {
    commandText = commands.options[i].text;
    commandTexts.push({original: commandText, lower: commandText.toLowerCase()});
}

function populateFilteredDropDown() {
    timer = null;
    var val = filterInput.value.toLowerCase(), commandText;
    var opts = filteredDropDown.options;
    filteredDropDown.length = 0;
    for (var i = 0, len = commandTexts.length; i < len; ++i) {
        commandText = commandTexts[i];
        if (commandText.lower.indexOf(val) > -1) {
            opts[opts.length] = new Option(commandText.original);
        }
    }
}

filterInput.onkeyup = function() {
    if (timer) {
        window.clearTimeout(timer);
    }
    timer = window.setTimeout(populateFilteredDropDown, 500);
};
Tim Down
  • 318,141
  • 75
  • 454
  • 536
  • Your solution results in a lower case result, that's not what the OP has, he's doing a case-insensitive search, but not lower-casing the resulting options, also you need to lowercase the value, since this doesn't do a case-insensitive search either. – Nick Craver May 26 '10 at 14:46