23 July 2009

JavaScript Select/List Box Filter

I've recently built a simple filter for some HTML tables and SELECT list-boxes (originally covered here). The SELECT filter is very basic in functionality:
  • Create an expanded SELECT element (size>1) somewhere on your page and give it a unique id.
  • Create a filter textbox above or below the list-box and bind the onkeyup event to trigger a filter function.
  • The filter function takes the value from the textbox and hides/removes any list-box elements that don't match.
One method of doing this is to set the display style of individual OPTION elements withing the SELECT list-box to either 'none' or 'block'. This can be done quite easily with jQuery in only 2 lines of code, namely:

MyUtil.selectFilter = function(selectId, filter) {
$("#" + selectId).find('option').hide();
$("#" + selectId).find('option:Contains("' + filter + '")').show();
}

You would bind this to your SELECT box as follows:

<b>Filter:</b> <input type="text" onkeyup="MyUtil.selectFilter('select-list', this.value)" /><br>
<select id="select-list" size="5" style="width:200px">
<option>John Smith</option>
<option>John Doe</option>
<option>Jason Bradley</option>
<option>Bob Smith</option>
<option>Jane Doe</option>
<option>David White</option>
<option>Neal Johnson</option>
<option>Richard Bradman</option>
</select>

Whenever the user starts typing in the textbox, the option elements of the list-box are dynamically hidden by setting the CSS display property to none. This works great in FireFox but gets ignored by almost every other browser (Google Chrome, IE 6/7, Safari).

None of these other browsers respect the CSS display property on OPTION elements (Chrome and IE ignore it all together, Safari hides the text but leaves an empty row in the select box).

The only workaround for this is to dynamically add/remove individual option items from the listbox instead of setting CSS properties. This can be achieved by re-writing the filter function from above to something slightly more complex:
<
MyUtil = new Object();
MyUtil.selectFilterData = new Object();
MyUtil.selectFilter = function(selectId, filter) {
var list = document.getElementById(selectId);
if(!MyUtil.selectFilterData[selectId]) { //if we don't have a list of all the options, cache them now'
MyUtil.selectFilterData[selectId] = new Array();
for(var i = 0; i < list.options.length; i++) MyUtil.selectFilterData[selectId][i] = list.options[i];
}
list.options.length = 0; //remove all elements from the list
for(var i = 0; i < MyUtil.selectFilterData[selectId].length; i++) { //add elements from cache if they match filter
var o = MyUtil.selectFilterData[selectId][i];
if(o.text.toLowerCase().indexOf(filter.toLowerCase()) >= 0) list.add(o, null);
}
}

The basic principle now is to save a copy of all the list items into a local object cache and then dynamically populate the list-box with only matching items every time the filter function is triggered. Performance appears to be comparable if not faster than the CSS method. A working example can be found here.

UPDATE: A reader commented below that the above method does not work with multi-select list boxes - i.e. if you click on a name, then use the filter and hold ctrl and click on another name, then delete the filter, the first selection will have been removed. You can read my comment response below for why that happens, or you can use the following patched version to fix it.

9 comments:

Anonymous said...

Thanks for your post but the example page doesn't work on IE!

Anonymous said...

I like it, but can't select more than one option even when it is a multiselect list

Steps I took,
type "sm", Click on "John Smith", delete "sm" from search box, type "do" select "John Doe", delete "do", "John Smith" was unselected.

Tried it again but held the "ctrl" button before selecting John Doe, but still "John Smith" was unselected
markc09

Richard G. said...

Yup, that's because the filter actually removes any option elements from the select list that don't match the criteria, which means their selection status is also lost.

I may update the script a little to save the status in the cache somehow and post back later...

Richard G. said...

Patched. See update at end of post with link to new version.

Anonymous said...

thanks

... a neat way to unselect all?

bye

Richard G. said...

document.getElementById("select-list").selectedIndex = -1;

Anonymous said...

Like the first commenter said, doesn't work in IE.

Anonymous said...

Ok, got it to work, change list.add(o, null) to list.add(o), and make sure there's an id attribute on your option elements. Thanks again for this script!

Anonymous said...

crap, i was slightly wrong, if browser is ie, then list.add(o), otherwise, list.add(o, null), works in IE7 and Mozilla now.

Post a Comment