I was working on an existing SharePoint project which had a custom control with a large select list populated with a few thousand items. To make it easier to use the select list, a textbox was used which filtered the list as the user typed. It was ok in chrome, terrible in FF, and downright painful to use in IE. Below is my rewrite of the filter code which is instant across all browsers (IE8+, didn't have a 7 to test). The original function did the regex search on the existing list sets and removed items that did not match. To restore removed items as the search was cleared or letters removed from the search box, the rest of the options would be removed and then it would loop through a cached list of the original option set and append them to the DOM object of the select one at a time.
Some of the fixes are obvious, but the first optmization to note is that it is cheaper for browser javascript engines to delete the entire select node than it is to remove all of the individual options. The new function starts by clearing out the select list by returning a shallow copy of the original node (which will preserve events and other data from the select list without the burden of child nodes). Second, appending directly to the DOM, especially with thousands of items is a huge performance killer. Instead, document fragments should be used to create the new nodes and then append the entire set to the DOM in a single operation. Third, cache your RegExp objects! The original function created a new RegExp object to test each node with, which again is wasting valuable ms when looping through thousands of items.
That pretty much sums up the major optimization points, I have the final optimized function below with a simple example:
$.fn.filterByText = function (textbox) {
return this.each(function () {
var select = this,
options = $(select).find("option").clone(),
$textbox = $(textbox);
$textbox.on("keyup", searchFilter);
function clearOptions(select) {
var selectParentNode = select.parentNode;
var newSelect = select.cloneNode(false); // Make a shallow copy
selectParentNode.replaceChild(newSelect, select);
return newSelect;
}
function searchFilter() {
var search = $.trim($textbox.val()),
fragment = document.createDocumentFragment();
select = clearOptions(select);
if (search === "") {
for (var i = 0; i < options.length; i++) {
fragment.appendChild(options[i]);
}
} else {
var regex = new RegExp(search, "gi");
for (var i = 0; i < options.length; i++) {
if (regex.test(options[i].innerText)) {
fragment.appendChild(options[i]);
}
}
}
select.appendChild(fragment);
return false;
}
});
};
<input type="text" id="textBox" />
<select multiple id="listBox"></select>
<script type="text/javascript">
$(function () {
$("#listBox").filterByText("#textBox");
});
</script>