A jQuery UI Combobox: Under the hood

read 67 comments
Update on 2010-08-17: This article was updated to reflect the changes to the combobox code in jQuery UI 1.8.4

The jQuery UI 1.8 release brings along the new autocomplete widget. An autocomplete adds a list of suggestions to an input field, displayed and filtered while the user is typing. This could be attached to a search field, suggesting either search terms or just matching results for faster navigation. But what if there is a fixed list of options, usually implemented as a standard HTML select element, where the ability to filter would help users find the right value way faster?

That's a "combobox." A combobox works like a select, but also has an input field to filter the options by typing. jQuery UI 1.8 actually provides a sample implementation of a combobox as a demo. In this article, we'll look under the hood of the combobox demo, to explore both the combobox widget and the autocomplete widget that it uses.

Let's starts with the initial markup:

HTML:
  1. <label>Your preferred programming language: </label>
  2. <select>
  3.   <option value="a">asp</option>
  4.   <option value="c">c</option>
  5.   <option value="cpp">c++</option>
  6.   <option value="cf">coldfusion</option>
  7.   <option value="g">groovy</option>
  8.   <option value="h">haskell</option>
  9.   <option value="j">java</option>
  10.   <option value="js">javascript</option>
  11.   <option value="p1">perl</option>
  12.   <option value="p2">php</option>
  13.   <option value="p3">python</option>
  14.   <option value="r">ruby</option>
  15.   <option value="s">scala</option>
  16. </select>

Nothing special there, just a label and a select element with a few options.

The code to apply the combobox widget to the select is quite simple, too:

JavaScript:
  1. $("select").combobox();

Let's look at the code for this combobox widget. First, the full code, to give you an overview. We'll dig into the details step-by-step afterwards.

JavaScript:
  1. $.widget( "ui.combobox", {
  2.   _create: function() {
  3.     var self = this;
  4.     var select = this.element.hide(),
  5.       selected = select.children( ":selected" ),
  6.       value = selected.val() ? selected.text() : "";
  7.     var input = $( "<input />" )
  8.       .insertAfter(select)
  9.       .val( value )
  10.       .autocomplete({
  11.         delay: 0,
  12.         minLength: 0,
  13.         source: function(request, response) {
  14.           var matcher = new RegExp( $.ui.autocomplete.escapeRegex(request.term), "i" );
  15.           response( select.children("option" ).map(function() {
  16.             var text = $( this ).text();
  17.             if ( this.value && ( !request.term || matcher.test(text) ) )
  18.               return {
  19.                 label: text.replace(
  20.                   new RegExp(
  21.                     "(?![^&;]+;)(?!<[^<>]*)(" +
  22.                     $.ui.autocomplete.escapeRegex(request.term) +
  23.                     ")(?![^<>]*>)(?![^&;]+;)", "gi"),
  24.                   "<strong>$1</strong>"),
  25.                 value: text,
  26.                 option: this
  27.               };
  28.           }) );
  29.         },
  30.         select: function( event, ui ) {
  31.           ui.item.option.selected = true;
  32.           self._trigger( "selected", event, {
  33.             item: ui.item.option
  34.           });
  35.         },
  36.         change: function(event, ui) {
  37.           if ( !ui.item ) {
  38.             var matcher = new RegExp( "^" + $.ui.autocomplete.escapeRegex( $(this).val() ) + "$", "i" ),
  39.             valid = false;
  40.             select.children( "option" ).each(function() {
  41.               if ( this.value.match( matcher ) ) {
  42.                 this.selected = valid = true;
  43.                 return false;
  44.               }
  45.             });
  46.             if ( !valid ) {
  47.               // remove invalid value, as it didn't match anything
  48.               $( this ).val( "" );
  49.               select.val( "" );
  50.               return false;
  51.             }
  52.           }
  53.         }
  54.       })
  55.       .addClass("ui-widget ui-widget-content ui-corner-left");
  56.    
  57.     input.data( "autocomplete" )._renderItem = function( ul, item ) {
  58.       return $( "<li></li>" )
  59.         .data( "item.autocomplete", item )
  60.         .append( "<a>" + item.label + "</a>" )
  61.         .appendTo( ul );
  62.     };
  63.    
  64.     $( "<button> </button>" )
  65.     .attr( "tabIndex", -1 )
  66.     .attr( "title", "Show All Items" )
  67.     .insertAfter( input )
  68.     .button({
  69.       icons: {
  70.         primary: "ui-icon-triangle-1-s"
  71.       },
  72.       text: false
  73.     })
  74.     .removeClass( "ui-corner-all" )
  75.     .addClass( "ui-corner-right ui-button-icon" )
  76.     .click(function() {
  77.       // close if already visible
  78.       if (input.autocomplete("widget").is(":visible")) {
  79.         input.autocomplete("close");
  80.         return;
  81.       }
  82.       // pass empty string as value to search for, displaying all results
  83.       input.autocomplete("search", "");
  84.       input.focus();
  85.     });
  86.   }
  87. });

Let's break this down, piece by piece:

JavaScript:
  1. $.widget( "ui.combobox", {
  2.   _create: function() {
  3.     // all the code
  4.   }
  5. });

This defines a new widget, in the ui namespace (don't use this for your own widgets, it's reserved for jQuery UI widgets) and adds the only method, _create. This is the constructor method for jQuery UI widgets, and will be called only once. In versions prior to 1.8 it was called _init. The _init method still exists, but it is called each time you call .combobox() (with or without options). Keep in mind that our widget implementation is not complete, as it lacks the destroy method. It's just a demo.

Coming up next is the creation of an input element and applying the autocomplete to it, with data provided by the select element.

JavaScript:
  1. var self = this;
  2. var select = this.element.hide(),
  3.   selected = select.children( ":selected" ),
  4.   value = selected.val() ? selected.text() : "";
  5. var input = $("<input />")
  6.   .val( value )
  7.   .autocomplete({
  8.     delay: 0,
  9.     minLength: 0
  10.     source: function(request, response) {
  11.       // implements retrieving and filtering data from the select
  12.     },
  13.     select: function(request, response) {
  14.       // implements first part of updating the select with the selection
  15.     },
  16.     change: function(event, ui) {
  17.       // implements second part of updating the select with the selection
  18.     },
  19.   })
  20.   .addClass("ui-widget ui-widget-content ui-corner-left");

It starts with a few variable declarations: var self = this will be used inside callbacks below, where this will refer to something else. The var select references the select element on which the combobox gets applied. To replace the select with the text input, the select is hidden.

The selected and value variables are used to initialized the autocomplete with the current value of the select.

An input element is created from scratch, inserted after the select element into the DOM, and transformed into an autocomplete widget. All three autocomplete options are customized:

  • delay specifies the amount of time to wait for displaying data between each key press, here set to zero as the data is local
  • minLength is set to 0, too, so that a cursor-down or -up key press will display the autocomplete menu, even when nothing was entered.
  • source provides the filtered data to display

Let's break down the source implementation:

JavaScript:
  1. source: function(request, response) {
  2.   var matcher = new RegExp( $.ui.autocomplete.escapeRegex(request.term), "i" );
  3.   response( select.children("option" ).map(function() {
  4.     var text = $( this ).text();
  5.     if ( this.value && ( !request.term || matcher.test(text) ) )
  6.       return {
  7.         label: text.replace(
  8.           new RegExp(
  9.             "(?![^&;]+;)(?!<[^<>]*)(" +
  10.             $.ui.autocomplete.escapeRegex(request.term) +
  11.             ")(?![^<>]*>)(?![^&;]+;)", "gi"),
  12.           "<strong>$1</strong>"),
  13.         value: text,
  14.         option: this
  15.       };
  16.   }) );
  17. },

There is a bit of matching and mapping involved here: At first, a regular expression object is defined, based on the entered term, escaped with the help of the $.ui.autocomplte.escapeRegex utility method. This regex gets reused in the function below. The response argument, a callback, gets called, to provide the data to display. The argument passed is the result of the call to select.find("option").map(callback). That finds all option elements within our original select, then maps each option to a different object, implemented in another callback passed to the map method.

This callback will return undefined, thereby removing an item, when a search term is present and the text of the option doesn't match the entered value. Otherwise (no term, or it matches), it'll return an object with three properties:

  • label: based on the text of the option, with the matched term highlighted with some regexing (another example of a write-only regular expression)
  • value: the unmodified text of the option, to be inserted into the text input field
  • option: the option element itself, to update the select (via the selected-property) or to pass on in custom events

The label and value properties are expected by the autocomplete widget, the option property has an arbitrary name, used here only by the combobox widget.

Before, I mentioned that the combobox widget customizes all three autocomplete options, but there were actually five options specified. The fourth and fifth properties, select and change, are event. This is the select implementation:

JavaScript:
  1. select: function(event, ui) {
  2.   ui.item.option.selected = true;
  3.   self._trigger( "selected", event, {
  4.     item: ui.item.option
  5.   });
  6. },

The ui.item argument refers to the data we provided in the source option. Via the ui.item.option property we can update the underlying select to the selected item, as well as triggering a selected events for further customization of the combobox widget.

To cover the case where no selection is made, the change event is used:

JavaScript:
  1. change: function(event, ui) {
  2.   if ( !ui.item ) {
  3.     var matcher = new RegExp( "^" + $.ui.autocomplete.escapeRegex( $(this).val() ) + "$", "i" ),
  4.     valid = false;
  5.     select.children( "option" ).each(function() {
  6.       if ( this.value.match( matcher ) ) {
  7.         this.selected = valid = true;
  8.         return false;
  9.       }
  10.     });
  11.     if ( !valid ) {
  12.       // remove invalid value, as it didn't match anything
  13.       $( this ).val( "" );
  14.       select.val( "" );
  15.       return false;
  16.     }
  17.   }
  18. },

Here the entered value is used to try and match a selection. If the value doesn't match anything, it'll get removed, and the underlying select is set to an empty value, too.

The next block customizes the _renderItem method of the underlying autocomplete. This is necessary to output the highlighting on each row, as autocomplete by default will escape any html.

JavaScript:
  1. input.data( "autocomplete" )._renderItem = function( ul, item ) {
  2.   return $( "<li></li>" )
  3.     .data( "item.autocomplete", item )
  4.     .append( "<a>" + item.label + "</a>" )
  5.     .appendTo( ul );
  6. };

And finally, the last block creates the button that opens the full list of options:

JavaScript:
  1. $( "<button> </button>" )
  2. .attr( "tabIndex", -1 )
  3. .attr( "title", "Show All Items" )
  4. .insertAfter( input )
  5. .button({
  6.   icons: {
  7.     primary: "ui-icon-triangle-1-s"
  8.   },
  9.   text: false
  10. })
  11. .removeClass( "ui-corner-all" )
  12. .addClass( "ui-corner-right ui-button-icon" )
  13. .click(function() {
  14.   // close if already visible
  15.   if (input.autocomplete("widget").is(":visible")) {
  16.     input.autocomplete("close");
  17.     return;
  18.   }
  19.   // pass empty string as value to search for, displaying all results
  20.   input.autocomplete("search", "");
  21.   input.focus();
  22. });

Another element is created on-the-fly. It gets tabIndex="-1" to take it out of the tab order, as it's mostly useful for mouse interactions. Keyboard interaction is already covered by the input element. It gets a title attribute to provide a tooltip and is inserted after the input element into the DOM. A call to .button() with some options together with a bit of class-mangling transforms the button into a Button widget that displays a down-arrow icon with rounded corners on the right (the input has rounded-corners on the left).

Finally a click event is bound to the button: If the autocomplete menu is already visible, it gets closed, otherwise the autocomplete's search method is called with an empty string as the argument, to search for all elements, independent of the current value within the input. As the input handles keyboard input, it gets focused. Having focus on the button would be useless or would require duplicate keyboard interaction that the input already supports.

And that's it! We can see that the autocomplete widget is flexible enough to allow all this with option customization, events, and calling a few methods. We don't have to "subclass" autocomplete (creating a new widget with the autocomplete as the parent prototype instead of $.widget). Instead, we can make the combobox mostly independent of any internal or private autocomplete methods. For the full experience and latest version, check out the combobox demo on the jQuery UI site.

comment feed

67 comments

  1. Great info Jörn.

    I still don't like that jQuery implements the classnames hardcoded in the code. I would have liked the classnames to be options. That way it would have been easier to for example have 2 of the same kind of widgets with different looks on a page. You would just send in different classnames.

    I tried one time to extend a UI widget with my own code (I wanted to change the dialog to a version with nice looking shadows and such). My biggest problems was the classes (even if I had to update some HTML generating too), I got a nicve result in the end but had to overide most methods with some changes.

    But anyhow nice post with good info =)

  2. @Johan: Stuff like that should go to forum.jquery.com, but while we're here: Dialog has a dialogClass option that you can set to style individual dialogs. Tooltip will have that, too. Other widgets, like Accordion, don't need it as you can just set the extra class in your markup.

  3. @Johan:

    You can still style differently even with the same class names:

    <div class="style1">
      <strong>Hello World</strong>
    </div>
    <div class="style2">
      <strong>Hello World</strong>
    </div>

    Now in your CSS, use the parent "style1" or "style2" class to change up the styles:

    .style1 strong {
      font-weight: bold;
    }
    .style2 strong {
      font-weight: normal !important;
      text-decoration: underline;
    }
  4. I'm only just recently getting really into javascript/AJAX/WebService type web development. Did I just catch that you can return a structure in javascript as one would return a structure in a C program? If so that could be extremely good information. I think its time I pick up a book on javascript basics because all of my javascript, so far, is based on picking up techniques from jQuery examples and what not, while trying to design an internal company website.

  5. There is a question of combobox cause me lost a lot of brain cells.
    It's about option list.
    If there are a lot of options, menu of list is too long to display in the current screen viewport, I hope there is a overflow.
    I did that work, but ...
    When I want to click to scroll the slider, the focus of input is lost, the list of menu will be close...
    I collapse. .

    • Rob

      I had the same problem. I solved it by adding this to my style sheet.
      ul { height: 500px; overflow: auto; } (My container is larger than 500 px high)

      This gives a scroll bar in the drop down list that does not cause it to lose focus.

  6. I might be missing something here, but don't <select> elements already have a form of auto-complete? That is, when the element has the focus and you start typing, the browser finds the first match to your typed string. What does the combobox autocomplete do extra? I'm not being rude, and it is a neat gadget, but I'm not sure it's worth adding yet more code to a page for a feature already present in browsers.

    • me

      select elements only match to the beginning of the string you typed. The combobox also matches to internal strings,, and presents all matches together.

      Select example: Type app, go the first item "apple," type it again, go to the second item "applications," type it again, go to the third item "applied science." If these were in alphabetical order, you could use the down arrow to scroll through them, but if they were not contiguous, you would not see all items that start with app all together.

      Combobox example: Type app, pulldown presents apple, applications, applied science, bad apple, snapple, other apps....

  7. The browser knows only the data you already entered before into that same field. Thats useful for a login form, where you have only a few values, or just one anyway.

    This is for forms with data that the user knows about, but hasn't entered already. For example, a field that allows you to tag content - you'd offer existing tags, or let them enter their own.

    Or for a registration form, where you could use this combobox widget for the country select.

  8. jhtml

    The problem with the demo is the resizing. If you open up hte select box list and resize your browser window the input box moves to a new position but the opened list stays in a fixed position on screen. This makes it very odd looking.

    Do you know how to make the opened list on resize to autoclose.

    The $(window).resize(function() is probably the easiest to use but am not able to access the object methods to hide the list on the event.

    Could you assist otherwise than this it is a great control.

  9. Gabriel Tadeu

    Hi Jörn,

    is possible uses the regex synthax with other source, like a array (ex: var elms = ["c++", "java", "php", "coldfusion", "javascript", "asp", "ruby"]; )

    The hilite in autocomplete make all the difference, but i can use only with combobox widget.

  10. @Gabriel: Yeah, you can do that, as part of a custom source-option, similar to what happens here for the combobox.

  11. Gabriel Tadeu

    Anyone have idea how make a regex for latin chars? Like São Paulo or Perú, When i typing Sao obviously not work cuz my city call São.
    A mini list for chars with diacritic:

    a = ['a','á,'â','ä','à','ã']
    e = ['e','é,'ê','ë','è']
    i = ['i','í,'î','ï','ì']
    o = ['o','ó,'ô','ö','ò','õ']
    u = ['u','ú,'û','ü','ù']
    c = ['c','ç']
  12. Alan

    I noticed the change event is not being fired when you select an item from the autocomplete dropdown with the mouse. It is only fired if you select by typing and then move focus to a different control. Any way to get the change event to fire from the mouse click?

    • Tom

      I had the same problem as Alan. Adding the following code instad of the change function helped me out:

      
      select: function(event, ui) {
      	if (!ui.item) {
      		// remove invalid value, as it didn't match anything
      		$(this).val("");
      		return false;
      	}
      	select.val(ui.item.id);
      	select.change(); // Added: trigger change event
      	self._trigger("selected", event, {
      		item: select.find("[value='" + ui.item.id + "']")
      	});
      },
      
      • Combobox got various updates recently: http://github.com/jquery/jquery-ui/blob/master/demos/autocomplete/combobox.html

        It'll be in the 1.8.3 release, once that is out, I'll update this article.

      • Alan

        Thank you for your response. The select option works great. This control is --insert superlative here --

      • I also had the same problem, to use the 'ui.item.id' of the option in the select function, I had made a little chenge in the 'var input' source function:

        var input =....
        source: function( request, response ) {
        var matcher = new RegExp( $.ui.autocomplete.escapeRegex(request.term), "i" );
        response( select.children( "option" ).map(function() {
        var text = $( this ).text();
        var idOpt = $( this ).val();
        if ( this.value && ( !request.term || matcher.test(text) ) )
        return {
        label: text.replace(
        new RegExp(
        "(?![^&;]+;)(?!<[^]*)(" +
        $.ui.autocomplete.escapeRegex(request.term) +
        ")(?![^]*>)(?![^&;]+;)", "gi"
        ), "$1" ),
        value: text,
        id: idOpt,
        option: this
        };
        }) );
        },

        • Hector V.

          Had the same problem, my solution:

          
          change: function( event, ui ){
            if ( !ui.item )
            {
              var matcher = new RegExp( "^" + $.ui.autocomplete.escapeRegex( $(this).val() ) + "$", "i" ),valid = false;
              select.children( "option" ).each(function(){
                if ( this.text.match( matcher ) && select.val() ==this.value ) {
                  this.selected = valid = true;
                  return false;
                }
              });
              if ( !valid ) {
                // remove invalid value, as it didn't match anything
                $( this ).val( "" );
                select.val( "" );
                return false;
              }
            }
          }
          

          Regards!!
          XD

  13. Carl

    No offense to any developer, but I wanted to point out that without an ability to set the overall height of the open menu/list of items, this plugin has the potential to annoy a user.

    A user that uses this combobox as they would a traditional select tag, would be pretty annoyed with having to use their browser scroll bars to find an item on a longer list than the list in this current demo. The select example used above is a very limited list.

    To see what I mean: Try using combobox for a Country selection, and you will see what I mean.

    Until this particular "screen real estate" issue is resolved, I as a jquery form developer would avoid using the combobox plugin as a select menu replacement.

  14. Just updated the article to reflect the latest changes to the combobox demo in jQuery UI 1.8.4.

  15. Having an issue if someone could be kind enough to help, I got the autocomplete combobox functioning but not properly. When I click the button to toggle the list, it causes a post and refreshes the page. Anything in particular I could be doing wrong to make this do this?

    • Keith, I had the same problem when building off this demo code. This is happening because the demo does not wrap the form elements on a <form> tag, so the button press does nothing. Inside a form, it does. This is easily fixed by returning false from the button click behavior (you may also wish to stop the event from propogating):

      
          $( "<button> </button>" )
            .click(function() {
               // ... snip ...
              // Don't submit form
              return false;
            });
      
  16. Hi Jörn Zaefferer,

    Can you please explain how we set the event handlers for this? I have been trying out code from the jquery doc

    I see unanswered questions on this in the jquery forum too, so it must either be way too obvious or way too obscure!

    As an example:

    
    $(document).ready(function() {
          $("#categorySelector").combobox();
          $( "#categorySelector" ).autocomplete({
             select: function(event, ui) {
                alert("Value selected.");
             }
          });
    });
    

    What is wrong with this? Am I attaching the event handler to the wrong thing? Am I doing it too late or too early?

    Thanks for any assistance!

    Rob
    :)

    • Hi All,

      In short, this is what is needed to create a combobox with an event handler for the "selected" event (should be "select", but combobox is only a prototype):

      
      <script language="javascript" type="text/javascript">
         $(document).ready(function() {
            // For some SELECT with ID combobox
            $("#combobox").combobox({
               selected: function(event, ui) {
                  // Event handling code goes here.
               } // selected
            }); // combo
         }); // ready
      </script>
      

      For more detail on this, see my blog post: Event Handling with the jQuery Autocomplete Combobox.

      Rob
      :)

  17. Adam

    The "match a selection" section that somewhat gives an example for doing a "mustmatch", doesn't translate over to using the autocomplete plug-in with multiple choices.

    I understand the point of making a plug-in as simple as possible, keeping to its fundamentals, but this implementation seems a bit too bare bones. Even if people say it's really powerful... it doesn't seem to be an adequate replacement of Jörn's original one.

  18. Can you tell me how to get the autocomplete menu widget to line up under the input field? (See this screenshot, or a working example of my problem here.) Maybe there is something wrong in the CSS positioning or the placement of the UL in the DOM. Or maybe the problem is that jquery.bgiframe.js (or another .js file) is from an earlier version of jQuery/jQueryUI. Thanks for any help!

  19. Roman

    So it doesn't have highlighting implemented at all? I love how that used to work in your old plug-in. I just wish it still worked with jQiery 1.4.2, but it doesn't.

  20. Márcio

    Hi man!
    This function have a bug.
    On line 74 $( " " ) is very important setting button type = button, because all the input on the form when keypress try event submit.

    Sorry my writing english.

  21. Jacek

    There is one quite big issue with this plugin. When there is no option selected, everything is fine. But, when you have an option selected, it opens autocomplete list below input on load of the plugin. To fix this just add
    input.autocomplete( "close");
    after line 16 of source function.
    It took me some time to figure this out, hope it helps somebody.

  22. When I try to load the widget like this:

    $(function() {
    $("select").combobox();
    });

    I get the following:

    $("<input>").insertAfter(select).val(value).autocomplete({delay: 0, minLength: 0,
     source: function (request, response) {var matcher = new RegExp($.ui.autocomplete.escapeRegex(request.term),
    "i");response(select.children("option").map(function () {var text = $(this).text();if (this.value
    && (!request.term || matcher.test(text))) {return {label: text.replace(new RegExp("
    (?![^&;]+;)(?!<[^<>]*)(" + $.ui.autocomplete.escapeRegex(request.term) +
    ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<strong>$1</strong>"), value: text,
    option: this};}}));}, select: function (event, ui) {ui.item.option.selected =
    true;self._trigger("selected", event, {item: ui.item.option});}, change: function (event, ui) {if
    (!ui.item) {var matcher = new RegExp("^" + $.ui.autocomplete.escapeRegex($(this).val()) +
    "$", "i"), valid = false;select.children("option").each(function () {if
    ($(this).text().match(matcher)) {this.selected = valid = true;return false;}});if (!valid)
    {$(this).val("");select.val("");return false;}}}}).addClass is not a function
    

    What could be wrong?
    PS: (I set the widget code in a separated file, and loading after the dependencies);

  23. Excellent combo box, but what if the user wants to input an option that isn't in the select list?
    No Problem!

    1) Insert below code after ~Line 8: ((( value = selected.val() ? selected.text() : ""; )))
    myname=this.element[0].getAttribute('id');

    2) Insert below code after ~Line 11: ((( .val( value ) )))
    .attr('name', myname)

    3) Remark Line ~50 ((( //$( this ).val( "" ); )))

    Now just make sure your select box has an id and the newly created input box will now have the name of the id of the select box. The text in it will stay put so you can grab it on submission.

    just my 2 cents

  24. Christophe Maggi

    Hello,
    Nice code... but i'm pulling my hairs from days with that f**** combobox.
    All I need is a combobox who works like a select with the jquery ui css.
    I explain : I use the autocomplete only in my input. I dont need a select with input with autocomplete like explain in your example. It must be readonly.
    All I need is SELECT which has the same comportment than an HTML SELECT + jquery ui css.
    Is it possible and how to do that ?
    I dont see any documentation on how to stylize a stupid select with jquery ui.
    Hope somebody can help me.

  25. gray

    I made a fork of this that has a relevant improvement.

    In the original version, when your select element has an "onchange" defined, the original version of the autocomplete combobox ignores it. I wrote my own fork that doesn't do that. It does however require two external functions: Left and Right, which I know isn't quite what we're after, but I've also included so you can mess with it:

    
    (function( $ ) {
    	$.widget( "ui.combobox", {
    		_create: function() {
    			var self = this,
    				select = this.element.hide(),
    				selected = select.children( ":selected" ),
    				value = selected.val() ? selected.text() : "";
    			var input = this.input = $( "" )
    				.insertAfter( select )
    				.val( value )
    				.autocomplete({
    					delay: 0,
    					minLength: 0,
    					source: function( request, response ) {
    						var matcher = new RegExp( $.ui.autocomplete.escapeRegex(request.term), "i" );
    						response( select.children( "option" ).map(function() {
    							var text = $( this ).text();
    							var optionVal = $( this ).val();
    							var elementId = $( this ).parent().attr("id");
    							var elementOnChange = $( this ).parent().attr("onchange");
    							if ( this.value && ( !request.term || matcher.test(text) ) )
    								return {
    									label: text.replace(
    										new RegExp(
    											"(?![^&;]+;)(?!<[^]*)(" +
    											$.ui.autocomplete.escapeRegex(request.term) +
    											")(?![^]*>)(?![^&;]+;)", "gi"
    										), "$1" ),
    									value: text,
    									optionVal: optionVal,
    									elementId: elementId,
    									elementOnChange: elementOnChange,
    									option: this
    								};
    						}) );
    					},
    					select: function( event, ui ) {
    						ui.item.option.selected = true;
    						//this doesn't work, so i made a workaround below
    						//$('#'+ui.item.elementId).trigger(ui.item.elementOnChange.toString().replace(/this.value/g,"ui.item.optionVal"));
    						////////////////////////////////////////////////////////////////////////////
    						//Check browser
    						var isNav = (navigator.appName == "Netscape")?1:0;
    						var isIE = (navigator.appName.indexOf("Microsoft")!= -1)?1:0;
    						var NewElementOnChange = ui.item.elementOnChange.toString();
    						NewElementOnChange = NewElementOnChange.replace(/function onchange\(event\) {/g,"");
    						NewElementOnChange = NewElementOnChange.replace(/ion onchange\(\)/g,"");
    						NewElementOnChange = Left(NewElementOnChange, NewElementOnChange.length - 2);
    						//special handling, since firefox and IE will have different number of leftover characters to chop off
    						if (isNav)
    						{
    							NewElementOnChange = Right(NewElementOnChange,NewElementOnChange.length -5);
    						}
    						if (isIE)
    						{
    							NewElementOnChange = Right(NewElementOnChange,NewElementOnChange.length -8);
    						}
    						NewElementOnChange = NewElementOnChange.replace(/this.value/g,"ui.item.optionVal");
    						eval(NewElementOnChange);
    						////////////////////////////////////////////////////////////////////////////
    						self._trigger( "selected", event, {
    							item: ui.item.option
    						});
    					},
    					change: function( event, ui ) {
    						if ( !ui.item ) {
    							var matcher = new RegExp( "^" + $.ui.autocomplete.escapeRegex( $(this).val() ) + "$", "i" ),
    								valid = false;
    							select.children( "option" ).each(function() {
    								if ( $( this ).text().match( matcher ) ) {
    									this.selected = valid = true;
    									return false;
    								}
    							});
    							if ( !valid ) {
    								// remove invalid value, as it didn't match anything
    								$( this ).val( "" );
    								select.val( "" );
    								input.data( "autocomplete" ).term = "";
    								return false;
    							}
    						}
    					}
    				})
    				.addClass( "ui-widget ui-widget-content ui-corner-left" );
    			input.data( "autocomplete" )._renderItem = function( ul, item ) {
    				return $( "" )
    					.data( "item.autocomplete", item )
    					.append( "" + item.label + "" )
    					.appendTo( ul );
    			};
    			this.button = $( " " )
    				.attr( "tabIndex", -1 )
    				.attr( "title", "Show All Items" )
    				.insertAfter( input )
    				.button({
    					icons: {
    						primary: "ui-icon-triangle-1-s"
    					},
    					text: false
    				})
    				.removeClass( "ui-corner-all" )
    				.addClass( "ui-corner-right ui-button-icon" )
    				.click(function() {
    					// close if already visible
    					if ( input.autocomplete( "widget" ).is( ":visible" ) ) {
    						input.autocomplete( "close" );
    						return;
    					}
    					// pass empty string as value to search for, displaying all results
    					input.autocomplete( "search", "" );
    					input.focus();
    				});
    		},
    		destroy: function() {
    			this.input.remove();
    			this.button.remove();
    			this.element.show();
    			$.Widget.prototype.destroy.call( this );
    		}
    	});
    })( jQuery );
    //Purpose: to provide the same "Left" functionality that VB based ASP uses
    function Left(str, n)
    {
    	if (n  String(str).length)
    	    return str;
    	else
    	    return String(str).substring(0,n);
    }
    //Purpose: to provide the same "Right" functionality that VB based ASP uses
    function Right(str, n)
    {
        if (n  String(str).length)
           return str;
        else {
           var iLen = String(str).length;
           return String(str).substring(iLen, iLen - n);
        }
    }
    
  26. Would you consider adding a trigger for the original select box's change event?

    It's described here:
    http://stackoverflow.com/questions/4760285/jquery-ui-combobox-onchange#answer-4761830

    Having that added I was able to add this combobox to existing select elements with only minimal changes to my app--which is always nice. :)

    Thanks,
    Benjamin

  27. Rohit

    Hi,

    I have a requirement in which the client requires, the options returned should match the type words...rather than finding the charater in the whole string.

    For example if the user types 'A' in the combo box. Only option with word A should be given as options. so what do I have to change

    • Slammer

      Hey I figured it out. I'm sure you have too, but for future visitors.

      Under the source:
      create a variable for EXAMPLE: var re = $.ui.autocomplete.escapeRegex(request.term);
      then set the (var matcher = new RegExp( "^" + re, "i" );

      Run it and it should work.

  28. tahina

    a have a problem to use my code inside the jquery ui - autocomplete combobox. please help.

    i want to use the following code:

    
    
  29. David

    Hello. I love this widget but I have been stuggling for hours to try and figure out how to get it to autofill the input box, or do a SelectFirst or mouseenter on the first item. Any idea? I've seen bits and pieces of code referring to how to tweak the autosuggest to do autofill but nothing relating to the combobox application. Thanks!

  30. Paul

    Hi all,
    is there a way to load data from an array instead of a list of options?
    Could you please teach me how to do that?
    Thank you!

  31. Slammer

    Can anyone help with getting the regex to only display what is being typed if matched. Example: if you press c it will bring up everything that starts with c and not words with c in it? Any help would be great.

  32. George

    Can this combobox be added to the official jquery ui release?

  33. Ahmed

    How can I pass the value of minLength to the function

  34. Anil Woona

    I have an existing SELECT and would like to implement autocomplete widget on it. But my SELECT element uses OPTGROUP to group the options. I tried to use this widget, but coming up with empty results in the combobox. Any help is appreciated! Thank You!

  35. Sindhu

    Can we use a similar code to populate two consecutive combo boxes ?

    Selection of value in the first combo box should display a set of values in the second combo box , with selected value shown in the text content of the second combo box.

    Right now, on change of first combo box value, the second combo box value is changing with the input text content being same as the previous selection.

    Please help!

    • Kaine

      Hi Sindu,

      I was looking for an answer to the very question myself. Until there is a better method, I mocked this up:

      
      <div>
        <table>
        <tr>
          <td>Select a value:</td>
          <td>
            <select id="select">
              <option value="red">Red</option>
              <option value="blue">Blue</option>
              <option value="green">Green</option>
              <option value="orange">Orange</option>
              <option value="yellow">Yellow</option>
              <option value="purple">Purple</option>
            </select>
          </td>
        </tr>
        </table>
        <br />
        <button onclick="UpdateList()">Add New Items</button>
      </div>
      <script>
        var newOptions = [
          { "key": "steven", "value": "Steven" },
          { "key": "kaine", "value": "Kaine" },
          { "key": "graham", "value": "Graham" },
          { "key": "dalip", "value": "Dalip" },
          { "key": "adam", "value": "Adam" }
        ];
        $(document).ready(function () {
          $("#select").combobox();
        });
        function UpdateList() {
          var $select = $("#select").parent().html("<select id=\"select\" />").find("#select");
          $.each(newOptions, function () {
            $select.append($("<option />").attr("value", this.key).text(this.value));
          });
          $select.combobox();
        }
      </script>
      
      

      I'm sure that there must be a better way of doing it. It would be great if there was simply a method that could accept the new set of values.

      Hope it helps.

      Cheers,

      Kaine

  36. Wizzyboom

    How come when I write a click() event where the input field (created by the combobox widget) is in scope of the selector the event never fires?

    I have a form that has a validation which highlights the entire parent <div> of that form field using the css class 'reqFieldGroupEmpty' when the end-user doesn't fill it out correctly.

    When a user clicks inside the missed required field I have a click() event remove that class of that form field's parent <div>

    $('input, textarea, select').click(function () {
      $('div').removeClass('reqFieldGroupEmpty')
    });

    However, as I mentioned at the start, the combobox-created <input> isn't triggering the click(), thus not removing the class.

    What am I missing?

    Thank you for helping!

    WizzyBoom

  37. valesk

    is this compatible with firefox 3.6 ?

  38. Maria

    Hey everyone, I realize this is an old post so I probably wont get an answer, but worth a try.

    The combo box works wonderfully for me with one problem: I can only get the top 9 values from the select drop down to be visible in the search suggest. Where do you set the number of results that should be displayed by the search suggest? The select drop down is generated dynamically from a separate data source, but without the jquery on top, it displays the correct amount of records. Please help!!

    I'm fairly new to this, so I'm a little lost :)

    Thanks everyone!

  39. roshan

    i have two comboboxes with autocomplete widgets. both the combo having different values, at the time of change event it fires with only second combobox and not triggering even i change value from first combo. i have following code where this $("#"+id).trigger('change'); code fires event on basis of id, but still i am not getting first combo id.please help me...

    
    select: function( event, ui )
    {
    ui.item.option.selected = true;
    that._trigger( "selected", event, {
    item: ui.item.option
    });
    $("#"+id).trigger('change');
    },
    change: function( event, ui ) {
    		$("#"+id).trigger('change');
    		if ( !ui.item )
    	return removeIfInvalid( this );
    				}
    
  40. Kaine

    Hi Jörn,

    Thanks for posting your article. It's a great resource and very helpful for the community at large.

    I guess you're open to receive bug reports and suggestions, so after having a minor issue with your combo, I found the following thread with some solutions:

    https://forum.jquery.com/topic/autocomplete-combobox-problem-when-it-is-placed-inside-a-form-tag

    Hopefully the information is helpful.

    Thanks again,

    Kaine

  41. CList

    I hate to be pedantic but the functionality of the demo is not that of a "Combobox". A combobox gets it's name because it combines the functionality of a drop-down list with that of an Editbox, there-by allowing you to select something in the list, or type in a value that's not in the list.

    http://en.wikipedia.org/wiki/Combobox

    I only point this out because I found this article while looking for a JQuery combobox, and this in fact, is a list-box with auto-complete. While someone above mentioned a way to post the value of non-list items back using the edit-box field, another option might be to post them back by adding the typed-in new list item to the select list's option list, perhaps by adding a special attribute or value to the newly added "option" to flag it as the "item not in list" option. Then you could post that back when the form posts...

    I realize that more new-school programmers may call this a combobox as the definition of the term is morphing over time, but there needs to be a distinction for a control that is a true editbox/listbox combination....

    Hopefully we can all be more specific when describing these things in the future, especially since this is such a frequently referenced (and comprehensive) article!

    Cheers,
    Chris

  42. Chris

    I run out to a database and recreate my list of options using jquery as the user enters text. However, I have found that when I clear the list and recreate it (within the source: method that the .val() of the combo box is always that of the first item in the refreshed options list. It is as if the value is no longer getting updated from the mouse selection from the list.

  43. Ketan

    This is good tool but not compatible with Opera browser. Am I right? I tried the same on opera. It doesn't work for me. Is there any way to be work with Opera???

  44. Rodrigo

    Hi!

    Why OPTGROUP tags inside the select is not working?????

    It is very used by the sites.

    Thanks!

  45. Sangram

    How do I make alternate styles for rows displays in autocomplete ?.

  46. Itians

    Hi,

    I am using this combobox but when I select one of the option,and then make it blank
    by backspace and directly click submit button,then previousaly selected value set to form
    instead of blank value.....what to do

  47. Sofia Sanchez

    Hi,
    anyone know how you can call from the same autocomplete combobox ie autocomplete return a JavaScript function that can be called from any combobox item.

  48. Itians

    input.data( "autocomplete" )._renderItem = function( ul, item ) {
    return $( "" )
    .data( "item.autocomplete", item )
    .append( "a" + item.label + "/a" )
    .appendTo( ul );
    };


    In this code a tag has given to only list name(only word), so only when i click on word then only it get selected ...now i want when i go to item list li and click it should select value

  49. Shan

    Hi,
    How can i put a custom image on the right side of the dropdown .

  50. anant

    Is there any attribute which arrange the list which display upon typing characters in ascending order!

7 Pings

  1. [...] A jQuery UI Combobox: Under the hood Jörn Zaefferer walks us through the code behind widgets like autocomplete and combobox. A great look at the widget factory. [...]

  2. [...] formed. A jQuery UI Combo Box, Under the Hood! (via Jörn [...]

  3. [...] A jQuery UI Combobox: Under the hood Jörn Zaefferer walks us through the code behind widgets like autocomplete and combobox. A great look at the widget factory. [...]

  4. [...] Finally, there’s the combobox demo that brings Autocomplete functionality to select elements. The combobox hides the select element and instead displays a text field for the user to type in. The possible values are determined by parsing the options from the select, and the user can view all options by clicking a button next to the text field. For a detailed explanation of how the combobox works, check out Jörn’s article on Learning jQuery. [...]

  5. [...] Finally, there’s the com­bobox demo that brings Auto­com­plete func­tion­al­ity to select ele­ments. The com­bobox hides the select ele­ment and instead dis­plays a text field for the user to type in. The pos­si­ble val­ues are deter­mined by pars­ing the options from the select, and the user can view all options by click­ing a but­ton next to the text field. For a detailed expla­na­tion of how the com­bobox works, check out Jörn’s arti­cle on Learn­ing jQuery. [...]

  6. [...] A jQuery UI Combobox: Under the hood Jörn Zaefferer walks us through the code behind widgets like autocomplete and combobox. A great look at the widget factory. [...]

Sorry, but comments for this entry are now closed.