DHTML Combobox

HTML forms are missing a crucial control, the so-called "combobox". This control, familiar to traditional GUI programmers, combines the "textbox" and "dropdownlist". Users can either select a value from the list, or type in their own value.

This article will show how to simulate the combobox in HTML forms, using JavaScript and CSS.

An Example Page

Click the online example. While this code will work in both Internet Explorer and FireFox, it works much better in FireFox. This is because Internet Explorer has a well-known (though never fixed) problem with HTML select tags. Nothing can be placed on top of them, so we have to move the textbox above the select list. Unfortunate, but that's life.

A Tale of Two Controls

A combobox combines the functionality of a textbox and a dropdown list ("select list" in HTML parlance). So that's just what we're going to do: place two controls on our form, and make them interact with each other to simulate combobox functionality.

<form>
  <input id="txtCombo"/><br/>
  <select id="selCombo"/>
    <option value="Volvo"/>Volvo
    <option value="Saab"/>Saab
    <option value="Fiat"/>Fiat
    <option value="Audi"/>Audi
  </select>
</form>

Putting on the Ritz

Yeah, well, I couldn't think of a snazzy quote for "style". Sorry. Our next step is to apply styles to the elements to make them visually appear to be a single control, at least in FireFox. Since it is our dearest hope that Microsoft will eventually fix Internet Explorer, we'll make these our default styles.

<style type="text/css">
.txtBox
{
  position: absolute;
  top: 20px;
  left: 20px;
  width: 100px;
  z-index: 5;
}

.dropDown
{
  position: absolute;
  top: 20px;
  left: 20px;
  width: 120px;
  border: 0;
}
</style>

Of course, you need to add the "class" attribute to our form controls, to make them apply our style definitions.

<form>
  <input id="txtCombo" class="txtBox"/><br/>
  <select id="selCombo" class="dropDown"/>
    <option value="Volvo"/>Volvo
    <option value="Saab"/>Saab
    <option value="Fiat"/>Fiat
    <option value="Audi"/>Audi
  </select>
</form>

Now, in FireFox, we have the visual appearance of a single control. The textbox sits on top of the select list, but the width of the two controls allows the select list to peek it's "arrow widget" out from behind. The user can type into the textbox, or, they can click the arrow widget and select a value from the select box. In Explorer, though, you'll never see the textbox...

Internet Exploder

It's the best I could do. I thought of "Insipid Explorer", but... nevermind. We're going to add a script to our page that will, if the user is browsing with IE, move the textbox up out of the way. We'll also adjust its width to make it match the select list.

<script type="text/javascript">

function ieStinks()
{
  if (navigator.appName == "Microsoft Internet Explorer")
  {
    document.getElementById("txtCombo").style.width = "120px";
    document.getElementById("selCombo").style.top = "44px";
  }
}

</script>

Be sure to add the onload="ieStinks()" attribute to your <body> tag.

We have two tasks now. [1] If the user picks an item from the select list, we need to make the textbox value match the selected item. [2] If, though, the user types a value in the textbox, ideally that value should be added to the select list. The first task is easy. The second task is a little tricky, and once again, there is a difference between FireFox and Internet Explorer.

Putting on the Scripts

Note to self: never, ever write "cute" subheadings again. First, we'll post the code, then we'll walk through an explanation.

function fakeCombo(x,e)
{
  var S = document.getElementById("selCombo");
  var L = S.options.length;
  var found = false;
  var myIndex = 0;

  var keycode;
  if (navigator.appName == "Microsoft Internet Explorer")
  { keycode = e.keyCode; }
  else
  { keycode = e.which }

  if (keycode == 13)
  {
    for (var i=0; i <= L-1; i++)
    {
      if (x.value == S.options[i].value) {found = true; myIndex = i};

    }

    if (found)
    {
      S.options.selectedIndex = myIndex;
    }
    else
    {
      S.options[S.options.length] = new Option(x.value,x.value);
      S.options.selectedIndex = (S.options.length - 1);
    }

    return false;
  }
}

function mySelect(x)
{
 document.getElementById("txtCombo").value = x.options[x.selectedIndex].value;
}

We have to "wire" these functions up to JavaScript events in our form. For the select list, we want to perform the mySelect(x) function when the user changes the selection in the list. The event handler is onChange.

For the textbox control, we want to wire the fakeCombo(x,e) function up to the textbox's onkeypress event handler. The idea is, when the user taps "Enter", they have finished entering a value. This may not be ideal, as users are becoming more accustomed to moving through forms using the "tab" key. In that case, you could wire the function to the onblur event handler. In this example, I'm coding to the "enter key".

Below is a complete code listing for our entire page, with all scripts, styles, and attributes applied.

<html>
<head>
<style type="text/css">
.txtBox
{
  position: absolute;
  top: 20px;
  left: 20px;
  width: 100px;
  z-index: 5;
}

.dropDown
{
  position: absolute;
  top: 20px;
  left: 20px;
  width: 120px;
  border: 0;
}
</style>

<script type="text/javascript">

function ieStinks()
{
  if (navigator.appName == "Microsoft Internet Explorer")
  {
    document.getElementById("txtCombo").style.width = "120px";
    document.getElementById("selCombo").style.top = "44px";
  }
}

function fakeCombo(x,e)
{
  var S = document.getElementById("selCombo");
  var L = S.options.length;
  var found = false;
  var myIndex = 0;

  var keycode;
  if (navigator.appName == "Microsoft Internet Explorer")
  { keycode = e.keyCode; }
  else
  { keycode = e.which }

  if (keycode == 13)
  {
    for (var i=0; i <= L-1; i++)
    {
      if (x.value == S.options[i].value) {found = true; myIndex = i};

    }

    if (found)
    {
      S.options.selectedIndex = myIndex;
    }
    else
    {
      S.options[S.options.length] = new Option(x.value,x.value);
      S.options.selectedIndex = (S.options.length - 1);
    }

    return false;
  }
}

function mySelect(x)
{
 document.getElementById("txtCombo").value = x.options[x.selectedIndex].value;
}

</script>
</head>
<body onload="ieStinks();">
  <form>
    <input onkeypress="JavaScript:return fakeCombo(this,event);"
      id="txtCombo" class="txtBox" /><br/>
    <select id="selCombo" class="dropDown"
      onChange="JavaScript:mySelect(this);" />
    <option value="Volvo"/>Volvo
    <option value="Saab"/>Saab
    <option value="Fiat"/>Fiat
    <option value="Audi"/>Audi
    </select>
  </form>
</body>
</html>

If the user selects a value in the select list, the mySelect(x) function fires. The keyword "this" in the function call refers to the selection list itself. It is passed into the function's "x" variable. The selection list contains an array, named "options", containing all of the items in the list. The selection list also contains a property, selectedIndex, which is the numerical value of the currently selected item. What we want is the textual value of that item, and we want to place that value inside the textbox:

document.getElementById("txtCombo").value = x.options[x.selectedIndex].value;

On the other hand, if the user enters a value in the textbox (and taps "enter"), we want to add that value to the select list. Notice the signature of the function call. We use "this", to pass in the textbox object, but we also have "event" passed to the function. This is the JavaScript Event object, which contains information about the current event. The "keypress" event happened, but we'll ask the Event Object which key was pressed to cause the event.

The fakeCombo(x,e) function fires. We setup some variables to contain the select list, the length of (number of options in) the list, and variables to hold a boolean, an index value, and the numerical value of the key that was pressed.

In Internet Explorer, the numerical value of the key that was pressed is in the Event Object's keyCode property. In Firefox, the property is which. We branch through an "if" statement to place this value into our variable, keycode.

If the value is "13", the enter key, we then loop through the options array, comparing the value of the textbox with the values in the option list. If we find a match, we set our boolean to "true", and store the current index into our myIndex variable.

When we fall out of the loop, we test the boolean. If it's "true", the user typed in a value that was already in the select list. We should make sure that value is the currently selected value. If the boolean is false, we add the value into the select list, and make it the currently selected item. The last statement, return false;, makes sure that the Enter key doesn't submit the form.

About the Author

Thomas D. Greer has years of experience in the printing business. He held the position of Director of Development for Consolidated Graphics, where he wrote the COIN eCommerce platform. Prior to that he was Vice-President of Technology of a large printing company acquired by Consolidated Graphics, where he was responsible for the development of a completely custom-written plant management system still in use.

Today Thomas provides consulting, development, implementation, and training services to commercial printers. He can be reached on the web at www.tgreer.com.

Now What?

Perhaps you'd like to read some other technical articles I've written?

If you'd like to discuss this article, or make suggestions for future articles, join my free discussion forum.