var keyTime, keyStr = '', allOpts, lastElement;
var agt = navigator.userAgent.toLowerCase();
//var is_ie = ((agt.indexOf("msie") != -1) && (agt.indexOf("opera") == -1));
var is_gecko = (agt.indexOf("gecko") != -1);
function populate(srcEvent)
{
  var element = (srcEvent)? ((srcEvent.target)? srcEvent.target : srcEvent.srcElement) : window.event.srcElement;
  if(lastElement != element)
  {
    allOpts = new Array();
    for(var i = 0; i < element.options.length; i++)
      allOpts[i] = element.options[i].text.toLowerCase();
    lastElement = element;
  }
}
function setSelection(srcEvent)
{
  var myEvent = (srcEvent)? srcEvent : window.event;
  var element = (myEvent.target)? myEvent.target : myEvent.srcElement;
  var keyCode = myEvent.keyCode;
  // messy JS keycodes force me to preprocess. Note: I use a US keyboard, other keyboards may vary?
  if((keyCode > 47 && keyCode < 58) || (keyCode > 64 && keyCode < 91 || keyCode == 32)) ; // space or alphanumerical characters, leave them alone
  else if(keyCode > 95 && keyCode < 106) keyCode -= 48; // keypad numbers
  else if(keyCode > 105 && keyCode < 112) keyCode -= 64; // keypad '+', '-', '/', '*', '.'
  else if(keyCode > 187 && keyCode < 192) keyCode -= 144; // '/', '.', ',', '-'
  else if(keyCode > 218 && keyCode < 222) keyCode -= 128; // '\', '[', ']'
  else
  {
    switch(keyCode)
    {
      case 187: keyCode = 61; break; // '='
      case 222: keyCode = 39; break; // '''
      case 192: keyCode = 96; break; // '`'
      case 186: keyCode = 59; break; // ';'
      default: return; // do not process non printable characters (unfortunately backspace cannot be supported because browsers like IE interpret backspace as go back a page in history)
    }
  }
  var currentKey = String.fromCharCode(keyCode).toLowerCase();
  var idx, currentSIdx = element.selectedIndex, useOld = false;
  var newTime = new Date().getTime();
  if(keyTime != null && newTime - keyTime < 500) // do type-ahead if two keys were pressed within 500 milliseconds (0.5 second, one can change this value for customization)
  {
    keyStr += currentKey;
    idx = findIdx();
    if(idx == -1) return; // not found, keep current selection then (leave the incorrect keyStr alone)
  }
  else // unfortunately we seem to have to handle default browser behavior too
  {
    keyStr = currentKey;
    // behavior should be: if next option is available and begins with the same character, select the next option
    // when there is either no more option, or no more option that begins with the same character as the current option,
    // then select the first option that starts with the currentKey
    idx = currentSIdx + 1;
    if(idx >= allOpts.length || allOpts[idx].length == 0 || allOpts[idx].charAt(0) != keyStr)
      idx = findIdx();
  }
  if(idx >= 0) // if keyStr is found in an option, select the option
  {
    element.options[currentSIdx].selected = false;
    // gecko-based browsers have a very strange bug that strikes when user presses
    // the same character multiple times (like 'AAA', 'BBBB'), which could be "fixed"
    // in a strange way too (actually the idx > 0 test is not even necessary!)
    // first make a pattern to check if it's same character multiple times
    var pattern = new RegExp('^' + keyStr.charAt(0) + '+$', "i");
    if(is_gecko && pattern.test(keyStr) && idx > 0) element.options[idx-1].selected = true;
    else element.options[idx].selected = true;
  }
  keyTime = newTime;
}
function findIdx()
{
  // full scan to find the smallest idx that match string keyStr (case-insensitive)
  var len = keyStr.length;
  for(var i = 0; i < allOpts.length; i++)
    if(allOpts[i].length >= len && allOpts[i].substring(0, len) == keyStr)
      return i;
  return -1;
}