views:

183

answers:

3

Hi all,

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.

A: 

First massive performance boost should be something like

var options = '';
for (var i = 0; i < commands.options.length; i++) {
    if (commands.options[i].text.toLowerCase().match($(this).val().toLowerCase())) {
        options += '<option value=\'' + i + '\'>' + commands.options[i].text + '</option>';
    }
}
Listbox1.append(options);
jAndy
ahh yes the infamous .append....can't believe I forgot that. I didn't really notice a difference, though. Thanks Andy
Darcy
+3  A: 

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
I don't think there is any faster method than concatenating a string and appending that chunk.
jAndy
@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
Another speed boost would come from moving `$(this).val().toLowerCase()` into a variable defined outside the loop.
Tim Down
@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
@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
Awesome. Works very well. Thanks Nick!
Darcy
Nick: you have a typo in `vat temp = $("<select />");`: `vat` should be `var`.
Tim Down
@Tim - Woops, thanks! updated the answer.
Nick Craver
+1  A: 

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
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
Nick: yes, you're right. Bit of an oversight. Now fixed.
Tim Down
+1 - For a viable alternative approach :)
Nick Craver