Last Updated: 05 January 2011
Current Version: 1.1.1

DHTML Combobox

Currently HTML forms are missing a "combobox". This control, familiar to traditional GUI programmers, combines the "text box" and "drop down select list". Users can either select a value from the list, or type in their own value. I spent a lot of time looking around the internet for a "combobox" that would do what I wanted it to do but could only find partial answers, eg. they would work great in IE but not in Mozilla browsers or the definition of a "combobox" wasn't quite the same as mine.

So what I have here then is a combination of some great ideas found around the net with a dash of my own thrown in for good measure. I hope you like it and find it useful.

Example Combobox

The combobox is a cross browser answer that combines the functionality of a textbox and a dropdown list. Have a go here - type in text or choose from the select list. I've put 2 comboSelect boxes here to illustrate the ability to have, firstly, more than one on any form and also to display that it will work with CSS width or with the standard 'size' instruction.

ComboSelect Box 1:   

ComboSelect Box 2:   

How it works

The HTML code is fairly straight forward. Place a block element with an ID of 'bcs_comboBox' where you want to display your comboSelect box and then place the input and select elements within the block, and assign an ID to each. You can, but do not have to, specifiy a style length to the input field so that it fits in with your form. Below is an example of the HTML code.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" >
  <head>
    <title>comboSelect</title>
    <meta http-equiv="content-type" content="text/html; charset=iso-8859-1" />
    <meta name="author" content="www.baytree-cs.com - Peter Barkway">
    <meta name="copyright" content="(C)2006 Baytree Computer Services, All right reserved.">
    <script src="comboselectbox.js" type="text/javascript"></script>

    <style type="text/css" title="currentStyle" media="screen">
      .comboInput {
        width: 142px;
      }
      .comboSel {
        visibility: hidden;
      }
    </style>
  </head>
  <body>
    <p><strong>Example Combobox</strong></p>
    <p>The combobox is a cross browser answer that combines the functionality of a textbox and a
    dropdown list.  Have a go here - type in text or choose from the select list. I've put 2
    comboSelect boxes here to illustrate the ability to have, firstly, more than one on any form
    and also to display that it will work with CSS width or with the standard 'size' instruction.</p>
    <p>
      <span id="bcs_comboBox">
        <strong><i>ComboSelect Box 1:</i></strong>
        <input type="text" name="comboInput" id="comboInput" class="comboInput" />
        <select name="comboSel" id="comboSel" tabindex="-1">
          <option value="1">Choice 1</option>
          <option value="2">Choice 2</option>
          <option value="3">Choice 3</option>
          <option value="4">Choice 4</option>
        </select>
      </span>
        <input type="button" name="showText1" value="Retrieve Value"
          onclick="alert('ComboSelect Box 1 value is \'' +
                          document.getElementById('comboInput').value + '\'');">
    </p>

    <p>
      <span id="bcs_comboBox">
        <strong><i>ComboSelect Box 2:</i></strong>
        <input type="text" name="comboInput2" id="comboInput2" maxlength="50" size="40" />
        <select name="comboSel2" id="comboSel2" tabindex="-1">
          <option value="1">Choice 1</option>
          <option value="2">Choice 2</option>
          <option value="3">Choice 3</option>
          <option value="4">Choice 4</option>
        </select>
      </span>
        <input type="button" name="showText2" value="Retrieve Value"
          onclick="alert('ComboSelect Box 2 value is \'' +
                          document.getElementById('comboInput2').value + '\'');">
    </p>
  </body>
</html>
	  

Now for the javascript stuff. Again, it's really not too complicated, but it is prudent to do some browser checking beforehand so that we can align the input and the select boxes. The select box, by the way, is cropped so that only the button is displayed. As each browser behaves differently we therefore need to crop using slightly different dimensions.

Here is the javascript code for the comboselectbox.js file

// ===================================================================
// Author: Peter Barkway
// WWW: http://www.baytree-cs.com/
// Version: 1.1.1
// Date: 05/01/2011
//
// NOTICE: You may use this code for any purpose, commercial or
// private, without any further permission from the author. You may
// remove this notice from your final code if you wish, however it is
// appreciated by the author if at least my web site address is kept.
//
// You may *NOT* re-distribute this code in any way except through its
// use. That means, you can include it in your product, or your web
// site, or any other form where the code is actually being used. You
// may not put the plain javascript up on your site for download or
// include it in your javascript libraries for download.
// If you wish to share this code with others, please just point them
// to the URL instead.
//
// Please DO NOT link directly to my .js files from your site. Copy
// the files to your server and use them there. Thank you.
// ===================================================================

var _objSelActive;
var _offsetTop;
var _offsetLeft;
var _offsetLeftClip;
var _comboBoxArray;
var _blkName = 'bcs_comboBox';
var _currentBrowser = '';

window.onload = function () {
  var _currentBrowser = checkBrowser();
  if(_currentBrowser[0] == "Explorer") {
    _offsetTop = 0;
    _offsetLeft = -3;
    _offsetLeftClip = 18;
  } else {
    _offsetTop = 0;
    _offsetLeft = -1;
    _offsetLeftClip = 18;
  }
  _objSelActive = false;

  if(document.getElementById){
    _comboBoxArray = createComboList();
    repositionComboBox();
    repositionComboBox(); // this second request appears to take care of spans that are in embedded in tables
  }

  window.onresize = repositionComboBox;
}

function createComboList() {
  // get all "bcs_comboBox" block elements in the document
  var elements = null;
  var found = new Array();
  var re = new RegExp('\\b'+_blkName+'\\b');
  if (document.getElementsByTagName) {elements = document.getElementsByTagName('*');}
  else if (document.all) {elements = document.all.tags('*');}
  if (elements) {
    for (var i = 0; i < elements.length; ++i) {
      if (elements[i].id.search(re) != -1) {
        // Now we have a valid block element get the input and select id
        inpObj = elements[i].getElementsByTagName("input")[0];
        selObj = elements[i].getElementsByTagName("select")[0]
        found[found.length] = [inpObj.id,selObj.id];
        selObj.selectedIndex = -1;
      }
    }
  }
  return found;
}

function checkEvent(evt){
  var ie_var = "srcElement";
  var moz_var = "target";
  // "target" for Mozilla, Netscape, Firefox et al. ; "srcElement" for IE
  if(evt[moz_var]) {
    return [ evt[moz_var]['inputEl'],evt[moz_var]['selectEl'] ];
  } else {
    return [ evt[ie_var]['inputEl'],evt[ie_var]['selectEl'] ];
  }
}

function comboFocus(cId) {
  document.getElementById(cId).focus();
  return false;
}

function evtSelect(evt) {
  objs = checkEvent(evt);
  idEdit = objs[0];
  idSel = objs[1];
  if(idSel.selectedIndex > -1) {
    document.getElementById(idEdit).value = idSel.options[idSel.options.selectedIndex].text;
    idSel.selectedIndex = -1;
  }
  comboFocus(idEdit);
}

function evtKey(evt) {
  objs = checkEvent(evt);
  idEdit = objs[0];
  idSel = objs[1];

  if(window.event)
    keyCode = window.event.keyCode;  //IE
  else
    keyCode = evt.keyCode;           //firefox

  if (keyCode == 27) {
    idSel.selectedIndex = -1;
    comboFocus(idEdit);
    _objSelActive = false;
  }
}

function findPos(obj) {
  // Credit for this function: http://www.quirksmode.org/js/findpos.html
  // Visit the URL for a complete tutorial on this function
  var curleft = curtop = parent_offSetLeft = parent_offSetTop = 0;
  if (obj.offsetParent) {
     curleft = obj.offsetLeft
     curtop = obj.offsetTop
     curwidth = obj.offsetWidth;
     while (obj = obj.offsetParent) {
        curleft += obj.offsetLeft
        curtop += obj.offsetTop
        if(obj.id) {
           parent_offSetLeft = obj.offsetLeft;
           parent_offSetTop = obj.offsetTop;
         }
     }
  }
  return [curleft,curtop,curwidth,parent_offSetLeft,parent_offSetTop];
}

function positionComboBox(inpId, selId) {
  inpObj = document.getElementById(inpId);
  selObj = document.getElementById(selId);
  // Positioning of the combotext boxes
  inpObj.style.marginRight = _offsetLeftClip+'px';
  inpObj.style.position = "relative";
  selObj.style.position = "absolute";
  selObj.style.height = inpObj.offsetHeight+'px';

  ofs=findPos(inpObj);                                        // Find the left,top & width of span

//alert('Left:'+ofs[0]+', Top:'+ofs[1]+', Width:'+ofs[2]+', Parent Left:'+ofs[3]+', Parent Top:'+ofs[4]);
//alert('curtop:'+ofs[1]+' parent offset top:'+ofs[4]+' parent top'+inpObj.offsetParent.offsetTop);
//alert('position top:'+(ofs[1]-inpObj.offsetParent.offsetTop));
//alert('curleft:'+ofs[0]+' parent offset left:'+ofs[3]+' parent left'+inpObj.offsetParent.offsetLeft);
//alert('position left:'+(ofs[0]-inpObj.offsetParent.offsetLeft));
  selObj.style.top=(ofs[1]-inpObj.offsetParent.offsetTop-ofs[4]+_offsetTop)+'px';      // Set select box top location
  selObj.style.left=(ofs[0]-ofs[3]+_offsetLeft)+'px';    // Set select box left location = curleft+_offsetLeft+parent_offSetLeft
  selObj.style.width=(inpObj.offsetWidth+_offsetLeftClip)+'px';
  // The next line crops the select box and shows only the button
  selObj.style.clip = 'rect(0px, '+selObj.offsetWidth+'px, auto, '+
                        (selObj.offsetWidth-_offsetLeftClip)+'px)';

  if(window.addEventListener){ // Mozilla, Netscape, Firefox
    selObj.addEventListener('change', evtSelect, false);
    selObj.addEventListener('keyup', evtKey, false);
    selObj.inputEl = inpObj.id;
    selObj.selectEl = selObj;
  } else { // IE
    selObj.attachEvent('onchange', evtSelect);
    selObj.attachEvent('onkeyup', evtKey);
    selObj.inputEl = inpObj.id;
    selObj.selectEl = selObj;
  }

  selObj.style.visibility = 'visible';
}

function checkBrowser() {
  // Credit for this function: http://www.quirksmode.org/js/detect.html
  // Visit the URL for a complete tutorial on this function
  var BrowserDetect = {
    init: function () {
      this.browser = this.searchString(this.dataBrowser) || "An unknown browser";
      this.version = this.searchVersion(navigator.userAgent)
        || this.searchVersion(navigator.appVersion)
        || "an unknown version";
      this.OS = this.searchString(this.dataOS) || "an unknown OS";
    },
    searchString: function (data) {
      for (var i=0;i<data.length;i++) {
        var dataString = data[i].string;
        var dataProp = data[i].prop;
        this.versionSearchString = data[i].versionSearch || data[i].identity;
        if (dataString) {
          if (dataString.indexOf(data[i].subString) != -1)
            return data[i].identity;
        }
        else if (dataProp)
          return data[i].identity;
      }
    },
    searchVersion: function (dataString) {
      var index = dataString.indexOf(this.versionSearchString);
      if (index == -1) return;
      return parseFloat(dataString.substring(index+this.versionSearchString.length+1));
    },
    dataBrowser: [
      { string: navigator.userAgent,
        subString: "OmniWeb",
        versionSearch: "OmniWeb/",
        identity: "OmniWeb"
      },
      {
        string: navigator.vendor,
        subString: "Apple",
        identity: "Safari"
      },
      {
        prop: window.opera,
        identity: "Opera"
      },
      {
        string: navigator.vendor,
        subString: "iCab",
        identity: "iCab"
      },
      {
        string: navigator.vendor,
        subString: "KDE",
        identity: "Konqueror"
      },
      {
        string: navigator.userAgent,
        subString: "Firefox",
        identity: "Firefox"
      },
      {
        string: navigator.vendor,
        subString: "Camino",
        identity: "Camino"
      },
      {   // for newer Netscapes (6+)
        string: navigator.userAgent,
        subString: "Netscape",
        identity: "Netscape"
      },
      {
        string: navigator.userAgent,
        subString: "MSIE",
        identity: "Explorer",
        versionSearch: "MSIE"
      },
      {
        string: navigator.userAgent,
        subString: "Gecko",
        identity: "Mozilla",
        versionSearch: "rv"
      },
      {     // for older Netscapes (4-)
        string: navigator.userAgent,
        subString: "Mozilla",
        identity: "Netscape",
        versionSearch: "Mozilla"
      }
    ],
    dataOS : [
      {
        string: navigator.platform,
        subString: "Win",
        identity: "Windows"
      },
      {
        string: navigator.platform,
        subString: "Mac",
        identity: "Mac"
      },
      {
        string: navigator.platform,
        subString: "Linux",
        identity: "Linux"
      }
    ]

  };
  BrowserDetect.init();

//  document.write('Browser name:'+BrowserDetect.browser);
//  document.write("<br />");
//  document.write('Browser version: '+BrowserDetect.version);
//  document.write("<br />");
//  document.write('OS name: '+BrowserDetect.OS);
//  document.write("<br />");

  return [BrowserDetect.browser,BrowserDetect.version,BrowserDetect.OS];
}

function repositionComboBox() {
  for(j=0; j<_comboBoxArray.length;j++) {
    positionComboBox(_comboBoxArray[j][0],_comboBoxArray[j][1])
  }
}