How to filter select list options

Here’s a jQuery extension method to filter the elements of a select list (option tags). It binds to a textbox, and as you type in the textbox the select list gets filtered to match what you are typing.

jQuery.fn.filterByText = function(textbox, selectSingleMatch) {
  return this.each(function() {
    var select = this;
    var options = [];
    $(select).find('option').each(function() {
      options.push({value: $(this).val(), text: $(this).text()});
    });
    $(select).data('options', options);
    $(textbox).bind('change keyup', function() {
      var options = $(select).empty().scrollTop(0).data('options');
      var search = $.trim($(this).val());
      var regex = new RegExp(search,'gi');

      $.each(options, function(i) {
        var option = options[i];
        if(option.text.match(regex) !== null) {
          $(select).append(
             $('<option>').text(option.text).val(option.value)
          );
        }
      });
      if (selectSingleMatch === true && 
          $(select).children().length === 1) {
        $(select).children().get(0).selected = true;
      }
    });
  });
};

Parameters:

  • textbox
    This could be a jQuery selector, a jQuery object, or a DOM object.
  • selectSingleMatch
    This is optional, if you set it to true, when the filtered list includes only one item, that item will be automatically selected.

For example:

$(function() {
  $('#select').filterByText($('#textbox'), true);
});  

Live example (or in a new window):

You can play around with it on jsbin: http://jsbin.com/egogeh/edit

What it does:

When you invoke the function on a select object, it finds all child option tags and saves their text and values into an array. This array is saved to the select object itself as a HTML5 custom data attribute, called data-options. Then, when a change or keyup event fires on the associated textbox, the select list is emptied, and for any entries in the array that match the search text a new option element is created.

Edit 2012-10-15: Added ScrollTop(0) to improve the way it works with long lists, thanks to a comment by xarel below.

Leave a Reply

Your email address will not be published. Required fields are marked *


  1. Hi, Thats a good one…. Thank you.. I have a question if you could help me with it will be great..

    I am creating a small troubleshooting guide which is created in Jquery and HTML. When user selects a step this is designed to give next step to try and from there next one in a flow chart style.

    I want to add a textbox to which the selections will auto populate. After troubleshooting they should be able to copy all selected items from the text box. Can anyone help me with a code which will auto populate all user selected items from the flow into a text box.

    Please find the code I used for the troubleshooting guide. Any help will highly appreciated.. Thanks in advance..

    $(‘document’).ready(function(){
    var count = 0;
    $(‘#questions’).hide();
    $(‘#answers’).hide();
    $(‘#questions tr:nth-child(2)’).attr(‘id’,’currow’);
    var q1 = $(‘#currow td:nth-child(2)’).html();
    var q3 = ” + q1 + ” ;
    var a1 = $(‘#currow td:nth-child(3)’).html();
    var r1 = q3 + a1 +”;
    $(‘#showquestion’).html(r1);

    $(‘li’).live(‘click’,function(){
    $(this).addClass(‘selected’).siblings().removeClass(‘selected’);
    var target = $(this).attr(‘id’);
    var parid = $(this).parent().parent().attr(‘id’);
    var parnum = parseInt(parid.slice(1,3));
    count = count + 1;
    var ps = $(‘#showquestion div’).length;
    $(‘#showquestion div’).each(function() {
    var divid = $(this).attr(‘id’);
    var divnum = parseInt(divid.slice(1,3));
    if(divnum > parnum)
    $(this).remove()
    })
    $(‘td’ ).each(function(){
    var qnum = $(this).text();
    if(qnum == target) {
    var q = $(this).next(‘td’).html();
    var q2 = ” + q + ”;
    var a = $(this).next(‘td’).next(‘td’).html();
    var qs = $(‘#showquestion’).html();
    var r = qs + q2 + a +”;
    $(‘#showquestion’).html(r);
    window.scrollBy(0,400);
    }
    })
    })
    })

    Support System

    No
    Question/Heading
    Answers

    1
    Question 1

    Option 1
    Option 2
    Option 3

    2
    Level 2 – from question 1 Option 1

    Option 1
    Option 2

    3
    Level 2 – from question 1 – no

    Option 1
    Option 2
    Option 3
    Option 4

    4
    Level 3 – from level 2 option 1

    Option 1
    Option 2
    Option 3
    Option 4

    Text Field:

  2. This is awesome! Just the solution I had been looking for. Came across other filter functions but it wouldn’t work on my Struts2 select list. Thank you for posting an optimal solution.

  3. Thank you so much, I have been looking for this. It works great and simple.

  4. Algunas veces tengo que hacer doble click para que me seleccion el list box, alguna sugerencia???

  5. Thanks, this is what I actually needed, but I have many Select multiple box options in my page which I would like to use this script for filtering. Already it is working in one select Option. How can I make this (global) be used by all the select multiple options instead of creating many scripts, I need to have one script be used by all other Select multiple options for filtering.
    Auto Filter

    =======================
    Auto Filter

    ============================
    Auto Filter

    =================================
    Auto Filter

  6. You can use it on many select multiple boxes. You just need to bind each one, for example:

    $('#select').filterByText($('#textbox'), true);
    $('#select2').filterByText($('#textbox2'), true);

  7. Hi.
    Great script, thank you for posting it. The site I’m working on has literally thousands of products and categories, so this is something of a life-saver where auto-complete isn’t possible.
    There was one small niggle in our case – so many options in the select element that after the first keypress there was a significant pause while the script found every option with that letter in.
    At line 9 I added:
    $(select).data('options', options);
    $(textbox).bind('change keyup', function() { // this is line 9
    if (textbox.value.length > 1) {

    And then of course added another } to what then became line 27.

    That makes the filter only kick in once there are at least two characters in the text field.
    Thanks again 🙂

  8. Hi there,
    I am currently missing one feature, which is the saving of already selected options.

    Let me explain that: I have a very large list of possible selections and want a simple way to filter the list, select the one option I want, reset the filter and select the next one (using the control key). What it does for now is that it let’s me filter the options, select one or more which are currently in that selection, but if I reset the filter or write a bit more in the filter, the selection I made previously gets deleted.

    Is there a way to fix that?

    Thanks,
    Mathis

  9. Mathis… You need to use jquery to create a copy of your select element, then remove all the options from the original. Then create a button that adds options back in from the copy and select them. Obviously the filter would be attached to the copy, not the empty original.

  10. Hi, this works great, thank you very much!
    One question: My option tags have a class for different formatting, such as:
    value1

    When filtering, the class gets lost. Is there an easy way to preserve it?
    Yes, I am a complete newbie to jquery…

  11. You can make the following two modifications to preserve the class(es) on the option tags:

    line 7: options.push({value: $(this).val(), text: $(this).text(), className: $(this).attr('class')});
    line 19: $('<option>').text(option.text).val(option.value).addClass(option.className)

    See it in action here: http://jsbin.com/egogeh/216/edit

  12. Works perfectly, thank you!!
    Ok, one more question: I have a large list, if I start typing while I am at end of the list, and the filter result contains just a few entries, the option list window is stuck at the lower end and won’t show any entries. I have to move the scrollbar a bit to make the items appear.
    I tried $(select).scrollTop() at various positions, but could not see any effect. Is scrollTop() the correct method? And where would be a good place to execute it?
    Thank you very much for your help!

  13. You can add .scrollTop(0) to line 11 to get the select list to scroll to the top:

    var options = $(select).empty().scrollTop(0).data('options');

    Live: http://jsbin.com/egogeh/218/edit

    And thanks for letting me know about this – I had never tried it with a long list like that! 🙂

  14. Dear Lessan,

    this works excellent, too. Thank you very, very much for your help, you saved me hours of trial and error. Also, thanks for that jsbin link, nice way to play and learn with jquery.
    Best regards,
    x

  15. Thanks for help ,
    I want to some more help .I want to give search option in under select box.After click on select box user is able to see all field name in option as well as can see search input box in under select box and after enter any keyword shorting is perform .
    This all work shall be happen with ajax .

  16. Thanks for help ,
    I want to some more help .I want to give search option in under select box.After click on select box user is able to see all field name in option as well as can see search input box in under select box and after enter any keyword shorting is perform .
    This all work shall be happen with ajax .

  17. Sweet! but here is my situation i have optgroup for each option and I want to show optgroup along with matching option every time it filter. Please help me how can i do it?

  18. Works great for initial, HTML-embedded selectlists.

    I populate my selectlist using AJAX in a dynamic way. The filter works after invoking on the initial HTML-embedded selectlist. How can I solve this ?

  19. You will need to figure out how to invoke a command after your selectlist has been populated using AJAX. There, simply place the call to initialize the function:

       
    $('#select').filterByText($('#textbox'), true); 
    
  20. 1) The page shows a selectlist in the HTML. The filter works perfect!
    2) When I re-populatie this selectlist with new options, using AJAX, then the filter re-populates the selectlist with the options that were initially present. My AJAX-filled selectlist is gone !

  21. 1) The page shows a selectlist in the HTML. The filter works perfect!
    2) When I re-populatie this selectlist with new options, using AJAX, then the filter re-populates the selectlist with the options that were initially present. My AJAX-filled selectlist is gone !

    I found it: re-invoke the filterbyText() function directly after AJAX filled the options ! An initial invocation is not enough. Thank you, it’s working fine now!

  22. Hello

    Great plugin, I use it a lot. The only trouble was that the selects optgroups got removed by the plugin. I have rewritten it to preserve the optgroups (If it has any), and also added a minimum text length parameter to the functions options. I also removed the change event on the textbox because it was causing the filtering to fire twice.

    I’m only a PHP developer and not a JavaScript Dev so I probably rewritten some of it wrong.

    jQuery.fn.filterByText = function(textbox, selectSingleMatch, minimumTextValue) 
    {
      return this.each(function() 
      {
        var select = this;
        var optionsAndOptGroups = [];
        
        if(typeof selectSingleMatch === "undefined")  {   selectSingleMatch = false;   }
        if(typeof minimumTextValue === "undefined")  {   minimumTextValue = 0;  }
    
        //Find all options and option groups
        $(select).children('option, optgroup').each(function()
        {
          //Option group? Need to build options 
          if($(this).is("optgroup"))
          {
            var options = [];
            $(this).children('option').each(function() 
            {
              options.push({tagName: $(this).get(0).tagName, value: $(this).val(), text: $(this).text()});
            });   
            optionsAndOptGroups.push({tagName: $(this).get(0).tagName, label: $(this).attr('label'), options: options});
          }
          //Just an option with no group?
          else
          {
            optionsAndOptGroups.push({tagName: $(this).get(0).tagName, value: $(this).val(), text: $(this).text()});
          }
        });
    
        $(select).data('optionsAndOptGroups', optionsAndOptGroups);
    
        $(textbox).bind('keyup', function(e) 
        {
          //If del / backspace is pressed then we still want to filter the select to shown previously hidden elements
          if(textbox.val().length > minimumTextValue || e.which === 46 || e.which === 8)
          {
            var optionsAndOptGroups = $(select).empty().data('optionsAndOptGroups');
            var search = $.trim($(this).val());
            var regex = new RegExp(search,'gi');
    
            $.each(optionsAndOptGroups, function(i)
            {
              var jQueryObject = $('<' + optionsAndOptGroups[i].tagName + '>');
              if(jQueryObject.is('option'))
              {
                if(optionsAndOptGroups[i].text.match(regex) !== null) 
                {
                  $(select).append(jQueryObject.text(optionsAndOptGroups[i].text).val(optionsAndOptGroups[i].value));
                }   
                $(select).append(jQueryObject);
              }
              else if(jQueryObject.is('optgroup'))
              {
                var optionsAdded = false;
                jQueryObject.attr('label', optionsAndOptGroups[i].label);
                $.each(optionsAndOptGroups[i].options, function(index)
                {
                  if(optionsAndOptGroups[i].options[index].text.match(regex) !== null) 
                  {
                    optionsAdded = true;
                    $(jQueryObject).append($('<option>').text(optionsAndOptGroups[i].options[index].text).val(optionsAndOptGroups[i].options[index].value));
                  }
                });
                if(optionsAdded === true)  {   $(select).append(jQueryObject); }
              }
            });    
          }
        });
        if (selectSingleMatch === true && $(select).children().length === 1) 
        {
          $(select).children().get(0).selected = true;
        } 
      });
    };
    
  23. Ah it stripped a line of code because of the html tag…


    //Replace
    var jQueryObject = $('");
    //With
    var jQueryObject = $('<' + optionsAndOptGroups[i].tagName + '>');


  24. //Replace
    $(jQueryObject).append($('').text(optionsAndOptGroups[i].options[index].text).val(optionsAndOptGroups[i].options[index].value));
    //With
    $(jQueryObject).append($('<option>').text(optionsAndOptGroups[i].options[index].text).val(optionsAndOptGroups[i].options[index].value));

    Sorry for flooding your comments section i couldn't edit my original post

  25. No worries about the comments. Thank you for the contribution!

    I updated your comment with the edits you mentioned. I also used [js][/js] instead of <code> for the code formatting.

  26. This is exactly what I need …. but one small problem

    After I filter I want to use the selected item in an Ajax call to do a database insert. My problem is that I cannot seem to get the selected item. I run this function on a button press:

    function addSkill() {
    var selectVal = $(‘#ab_chooser :selected’).val();
    var selectText = $(‘#ab_chooser :selected’).text();
    alert(selectVal+” “+selectText);
    }

    This only causes an alert box is ‘undefined’ to show up. Any help?

    Thanks!

  27. Nevermind… I figured out my problem. I had forgot that I had renamed my select box to “ab_chooser1”

  28. Can I get some help please. I’m trying to create multiple select filters.
    There are 3 drop down boxes for Country, State and City.
    First I select “Country”. It shows the States.
    Then I select the “State” and it shows me the cities.

    How can I do this? thank you.

  29. Very nice script that saved a lot of time. I noticed one issue.
    1. If the value in the select has a ‘(‘ or ‘)’ char (and likely other special chars), the functionality breaks.

    It would also be nice if the regexp ignored whitespace. In my case the values have linebreaks and often more than a single whitespace. I would like to have it ignore whitespace in matching.
    Thanks

  30. To correct the special chars update your script from:

    //var search = $.trim($(this).val());

    To:

    var search = $.trim($(this).val().replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"));

  31. Here is the cleaned up version that handles special chars and totally ignores whitespace. Yoyu can easily modify the code to have flags besides selectSingleMatch that control this new functionality.

    var search = $.trim($(this).val());
    search = search.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
    //search = search.replace(/\s* /g, '\\s*'); //ignore extra spaces or linebreaks
    search = search.replace(/\s*/g, '\\s*'); //totally ignore whitespace
    
  32. So happy with this script and made a little modification to the call of it to make it work on every select box in a CMS of ours.

    	$(function() {
    		$('select').each( function(index) {
    			$(this).after('Filter dropdown: ');
    			$(this).filterByText($('#'+$(this).attr('id')+'search'), true);
    		});
    	});
    

    Loops through all select boxes and so long as they have an id with create a unique filter box for them.

    Thanks again!

  33. Pingback: html select list : chained and filter | BlogoSfera

  34. I was wondering – is it possible to only filter options that are NOT defined as selected? It is important for me to not filter options already selected.

  35. Man, you are a life saver. I have some code but it’s only working in mozilla. This one on the other working with every browser. Many kudos to you.

  36. Pingback: 2 Select boxes filter | BlogoSfera

  37. It’s required to trigger the change event when single match is selected.

    if (selectSingleMatch === true && $(select).children().length === 1) {
    $(select).children().get(0).selected = true;
    $(select).trigger('change');
    }

  38. I use this function a lot to filter selects because its so useful. I’ve rewritten again it to store the actual jQuery objects (Rather than the some of the selects attributes). using this will preserve any custom attributes that you may have on the options / optiongroups.


    jQuery.fn.filterByText = function(textbox, selectSingleMatch, minimumTextValue)
    {
    return this.each(function()
    {
    var select = this;
    var optionsAndOptGroups = [];

    if(typeof selectSingleMatch === "undefined") { selectSingleMatch = false; }
    if(typeof minimumTextValue === "undefined") { minimumTextValue = 0; }

    //Find all options and option groups
    $(select).children('option, optgroup').each(function()
    {
    optionsAndOptGroups.push($(this));
    });

    $(select).data('optionsAndOptGroups', optionsAndOptGroups);

    $(textbox).bind('keyup', function(e)
    {
    //If del / backspace is pressed then we still want to filter the select to shown previously hidden elements
    if(textbox.val().length > minimumTextValue || e.which === 46 || e.which === 8)
    {
    var optionsAndOptGroups = $(select).empty().data('optionsAndOptGroups');
    var search = $.trim($(this).val());
    var regex = new RegExp(search,'gi');

    $.each(optionsAndOptGroups, function()
    {
    if($(this).is('option'))
    {
    if($(this).text().match(regex) !== null)
    {
    $(select).append($(this));
    }
    }
    else
    {
    var optionGroupClone = $(this).clone(); //Clone the option instead of using the stored, so we can restore the group to its initial children
    $.each(optionGroupClone.children('option'), function()
    {
    if($(this).text().match(regex) === null)
    {
    $(this).remove();
    }
    });
    if(optionGroupClone.children().length) { $(select).append(optionGroupClone); }
    }
    });
    }
    });
    if (selectSingleMatch === true && $(select).children().length === 1)
    {
    $(select).children().get(0).selected = true;
    }
    });
    };

  39. Andrew,
    Thanks for that update. It allowed me to remove a hack I had that added this

    if(option.text.match(regex) !== null){//modified to set title
        jQuery(select).append(jQuery('<option>').text(option.text).val(option.value).attr('title', option.value));
    }

    My only other tweak was the addition of:

    var search = jQuery.trim(jQuery(this).val()); //existing
    search = search.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'); //NEW  escape special chars
    search = search.replace(/\s* /g, '\\s*'); //NEW ignore extra spaces or linebreaks
  40. Note that the code

    $(select)

    is used way too many times and causes a measurable performance degradation in cases where there are many options in the select. The only reason I need filtering is due to the large number of options in my case. This should be cached.
    Change

    var select = this;

    to

    var select = $(this);

    and all the other references of

    $(select)

    to

    select

    I would also suggest the use of jQuery as opposed to $ to avoid conflicts.

  41. This is amazing! How would I modify this to use a value (such as user id) instead? My use case requires a user select his own name from a group list. I was hoping to cut down on clicks by auto selecting the option by feeding your script the user id value.

    Currently, it only seems to work with an input event. (kind of noob at this sorry!)

  42. I made a small update to the code to slightly increase performance if you have a lot of options. Instead of appending each option one at a time, I prepare a list of options and append only once.

        var new_options_html = '';
    
        $.each(options, function(i, option) {
    
          if(option.text.match(regex) !== null) {
    
            new_options_html += '<option value="' + option.value + '">' + option.text + '</option>';
    
          }
    
        });
    
  43. Seems like the last comment got cut off? Trying again!


    var new_options_html = '';

    $.each(options, function(i, option) {

    if(option.text.match(regex) !== null) {

    new_options_html += '' + option.text + '';

    }

    });

  44. Ah, it’s filtering out my tags. So where it shows new_options_html += I’m creating html and using option tags. Such that: (option value=option.value) option.text (/option)

  45. Conner, is there an efficient way to do what you are doing using Andrew Berridge’s suggestion above of using JQuery objects?
    Thanks

  46. Hi Lessan,

    I’m facing an issue with your filter (that I find really really cool) !

    I have a select list with Countries – Towns – Places in towns and when I type the country it filters correctly and when I continue the fist item matching the first letter of the towns disapear. To be clear, this is what I have :

    Cameroon – Douala – Place 1
    Cameroon – Yaounde – Place 2
    Kenya – Nairobi – Place 1
    Kenya – Nyeri – Place 2

    When I type “Cameroon” I have

    Cameroon – Douala – Place 1
    Cameroon – Yaounde – Place 2

    And when I start typing the “-” I only have “Cameroon – Yaounde – Place 2” and “Cameroon – Douala – Place 1” dispear.

    I don’t know what’s wrong, may be it’s because it’s the first element in my select box or ??

    Can someone help me to fix this please ? Thanks