//DHTML COMBOBOX jamesgeraldgayoiloilophilippines2007
// cleaned up and annotated by Catskill Technology Services, LLC 2010
//      also add cbMaxItemsShow (length of suggested matches list to show)

//cb global vars
//defaults: cbAutoExpand=false;cbLimitToList=false;cbMatchOnAnyPart=true;
//          cbHighLightMatch=false;cbAddToList=false;cbMaxItemsShow=-1;
     var  jvArray,txtList; // jvArray is list of data, intermediate step
                           // txtList holds full list as one long string
     var  cbIgnoreKeyDown=false;  // flag: key should be ignored 
                                  // for various reasons (e.g., no input yet)
     var  cbOldTextValue='';  // hold old value of typed-in text
     var  cbTextControl;   // input text from user
     var  cbHighlightedItem;  // flag highlighting in typed-in text
     var  limitedText='';   // holds suggestion list
     var  cbListBox,cbListBoxBckgrnd,cbButton; // drop-down list and button
     var  justhide=false;  // flag for hiding drop-down list
// note the use of many fixed id's, restricting you to one combobox per page

// ===== gets onfocus event for all inputs, onfocus= for input box
function cbActivate(e) {
  var elem=(document.getElementById && !document.all) ? (e.target):(window.event.srcElement);
//jvArray=''; txtList='';
//cbIgnoreKeyDown=false;
//cbOldTextValue='';
//cbTextControl='';
//limitedText='';

  // cbData is data array or list structure
  if (!elem.getAttribute("cbData")) {
    alert("No data parameter passed.");
    return;
  }
  // if can't find id=cbBox (list box) div somewhere, create one just before 
  // </body> (!!). also a background div and selection list button
  if (document.getElementById("cbBox")==null)
  {
    document.body.insertAdjacentHTML ("beforeEnd",'<div class="cbListBoxbckgrnd" id="cbBoxbckgrnd">&nbsp;</div>');
    document.body.insertAdjacentHTML ("beforeEnd",'<div class="cbListBox" id="cbBox"></div>');
//    document.body.insertAdjacentHTML ("beforeEnd",'<img id="cbDrpDwnBttn" onclick="cbDrpDwnBttnonclick(event);" src="combobutton.bmp">');

    // play games with transparency of box background
    if (document.getElementById && !document.all) {
      document.getElementById("cbBoxbckgrnd").style.MozOpacity =.95;
    } else {
      document.getElementById("cbBoxbckgrnd").style.filter="Alpha(Opacity=80)";
    }
  }

  // create cbTextControl containing all the data
  if ((cbTextControl==undefined) || (cbTextControl!=elem)) {
    cbTextControl=elem;
    cbOldTextValue=elem.value;
    // create sorted list of values, joined into one long list string
    jvArray=eval(elem.getAttribute("cbData") );
    jvArray=jvArray.sort();
    txtList=jvArray.join("|");
    // get three-part existing added items and make invisible/no space
    cbListBox=document.getElementById("cbBox");
    cbListBoxBckgrnd=document.getElementById("cbBoxbckgrnd");
//    cbButton=document.getElementById("cbDrpDwnBttn");
    cbHide();

    // tweak where exactly to place new items, and move parent node up node
    // hierarchy until they are children of the <body>
    var bT,pT,pL,vparent;
    bT=0;
    pT=0;
    pL=0;
    vparent=elem.parentNode;
    while (vparent!=null && vparent.tagName!="BODY") {
      if (vparent.tagName!="FORM") {
        if (isNaN(parseInt(vparent.style.borderTopWidth))) {
          bT=0
        } else {
          bT=parseInt(vparent.style.borderTopWidth)-1;
        }
        pT=pT+vparent.offsetTop+bT;
        pL=pL+vparent.offsetLeft;
      }
      vparent=vparent.parentNode;
    }
    // final placement of list box
// temp
pT = 0;
    cbListBox.style.top=(elem.offsetTop+elem.offsetHeight+pT-0)+'px';
    cbListBox.style.left=(elem.offsetLeft+pL+1)+'px';
//    cbListBox.style.left=(elem.offsetLeft+pL)+'px';

    var w=(document.getElementById && !document.all) ? elem.offsetWidth-2 :elem.offsetWidth;
//    cbListBox.style.width=(w+16 )+'px';
    cbListBox.style.width=w+'px';
    // tune placement of background and select button
    cbListBoxBckgrnd.style.top=cbListBox.style.top;
    cbListBoxBckgrnd.style.left=cbListBox.style.left;
    cbListBoxBckgrnd.style.width=cbListBox.style.width;
//    cbButton.style.left=elem.offsetLeft+pL+elem.offsetWidth-0;
//    cbButton.style.top=parseInt(cbListBox.style.top)-18; //elem.offsetHeight;
    // mouse and keyboard events for new items
    cbListBox.onmouseover=cbonMouseOver;
    cbListBox.onmouseout=cbonMouseOut;
    cbListBox.onclick=cbonMouseClick;
    elem.onkeypress=cbonKeyPress;
    elem.onkeydown=cbonKeyDown;
    elem.onkeyup=cbonKeyUp;
    // update presented list with (reduced size) list
    cbUpdateListBox(e);
    // entire page onclick goes to combobox processing
    document.body.onclick=documentclick;
  } else {
    if (justhide==true) {
      justhide=false;
    } else {
      cbUpdateListBox(e);
    }
  }
} // end cbActivate()

// ===== on blur (out of focus), make sure drop-down cleared
function cbDeactivate(e) {
/*
  var elem=(document.getElementById && !document.all) ? (e.target):(window.event.srcElement);

  if (document.getElementById("cbBox")!=null)
  {
    document.getElementById("cbBox").outerHTML = '';
    document.getElementById("cbBoxbckgrnd").outerHTML = '';
//  document.getElementById("cbDrpDwnBttn").outerHTML = '';
  }
*/
} // end cbDeactivate()

// ===== if not Text Control element, hide and check for value
// ===== entire body click handled here while combobox active (has focus)
function documentclick(e) {
  var elem=(document.getElementById && !document.all) ? (e.target):(window.event.srcElement);

  if (elem==cbTextControl) {
//alert("cbTextControl got mouse click");
    return
  }
//alert("click outside of combobox");
  cbHide();
  cbDoesValueExist();
} // end documentclick() 

// ===== key-down handling of certain keys for activated element
function cbonKeyDown(e) {
  var elem=(document.getElementById && !document.all) ? (e.target):(window.event.srcElement);
  var keyc=(document.getElementById && !document.all) ? (e.keyCode):(window.event.keyCode);

  // Backspace key ignored
  if (keyc==8) {
    if (elem.value.length>0) {
      cbIgnoreKeyDown=true;
    }
    return;
  }
  // Enter key call its processor
  if (keyc==13) {
    cbonEnter(e);
    return;
  }
  // Period . key nothing special
  if (keyc==46) {
    return;
  }//DELETE
  // 
  // Tab key handling as Enter
  if (keyc==9) {
    if (document.getElementById && !document.all) {
      e.preventDefault();
    }
    cbonEnter(e);
    return;
  }
  // & key special meaning
  if (keyc==38 && cbListBox.childNodes.length>0 ) {
    if (cbHighlightedItem==null ) {
      cbHighlightItem(e,cbListBox.childNodes[0])
    } else {
      if (cbHighlightedItem==cbListBox.firstChild) {
        return;
      }
      cbHighlightItem(e,cbHighlightedItem.previousSibling);
    }
    return;
  }
  // ( key special meaning
  if (keyc==40 && cbListBox.childNodes.length>0 ) {
    if (cbHighlightedItem==null ) {
      cbHighlightItem(e,cbListBox.childNodes[0])
    } else {
      if (cbHighlightedItem==cbListBox.lastChild) {
        return;
      }
      cbHighlightItem(e,cbHighlightedItem.nextSibling);
    }
    return;
  }
} // end cbonKeyDown()

// ===== key-release handling for activated element
function cbonKeyUp(e) {
  var keyc=(document.getElementById && !document.all) ? (e.keyCode):(window.event.keyCode);
  var elem=(document.getElementById && !document.all) ? (e.target):(window.event.srcElement);

  if (cbIgnoreKeyDown==false) {
    return;
  }

  var elem=(document.getElementById && !document.all) ? (e.target):(window.event.srcElement);

  cbUpdateListBox(e);
  if ( cbListBox.childNodes.length==0) {
    return;
  } 
  if (document.getElementById && !document.all) {
    var afText=cbListBox.childNodes[0].innerHTML;
  } else {
    var afText=cbListBox.childNodes(0).innerText;
  }  
  if (afText.toLowerCase().indexOf(elem.value.toLowerCase())!=0) {
    cbHighlightItem(e);
    return;
  } else {
    cbIgnoreKeyDown=false ;
  }  
} // end cbonKeyUp()

// ===== key-stroke (press+release) handling of certain keys for activated element
function cbonKeyPress(e) {
  var elem=(document.getElementById && !document.all) ? (e.target):(window.event.srcElement);
  var keyc=(document.getElementById && !document.all) ? (e.charCode):(window.event.keyCode);

  // null or Enter key
  if (keyc==0 || keyc==13) {
    return;
  } //darn Mozilla; i never knew this;gave me a hard time why updn arrow routine won't work
  // ESCape key -- undo?
  if (keyc==27) { 
    elem.value=cbOldTextValue;
    cbIgnoreKeyDown=true;
    return;
  }
  if (cbIgnoreKeyDown==true) {
    return;
  }
  if (typeof elem.createTextRange!="undefined") {
    var txt1=elem.createTextRange();
    var txt2=document.selection.createRange();
    var txt2txt=txt2.text;

    if (txt1.text.toLowerCase().substr(txt1.text.length-txt2txt.length)==txt2txt.toLowerCase() && txt2txt.length>0) {
    } else {
      txt2.moveToPoint(window.event.x, window.event.y);
      txt2.expand("character");
      if (txt2.text.length>0) {
        return;
      }
    }
    window.event.returnValue=false;
    elem.value=elem.value.substr(0,(elem.value.length-txt2txt.length))+String.fromCharCode(keyc);
    cbUpdateListBox(e);

    var xtxtrange=elem.createTextRange();

    xtxtrange.moveStart("character",elem.value.length);
    xtxtrange.select();
    if (cbListBox.childNodes.length==0) {
      return;
    }
    cbHighlightItem();

    var afText=cbListBox.childNodes[0].innerText;

    if (afText.toLowerCase().indexOf(elem.value.toLowerCase())!=0) {
      return;
    }
    if (!elem.getAttribute("cbAutoExpand")) {
      return;
    } else {
      if (eval(elem.getAttribute("cbAutoExpand"))==false) {
        return;
      }
    }

    var subtext=elem.value;

    elem.value=afText;
    var txtrange=elem.createTextRange();
    txtrange.moveStart("character",subtext.length);
    txtrange.select();
  } else { //darn mozilla;never thought it to be simpler(just this)
    var txt2=window.getSelection();
    var txt2txt=txt2.toString();

    if (elem.selectionStart!=elem.value.length) {
      if (((elem.selectionEnd-elem.selectionStart)+elem.selectionStart)!=elem.value.length) {
        return;
      }
    }

    var thechar=String.fromCharCode(keyc);
    var re=thechar.match(/[\s\u00C0-\u00FFA-Za-z0-9,'\-]/i);

    if (re==null) {
    } else {
      e.preventDefault();
      elem.value=elem.value.substr(0,(elem.value.length-(elem.selectionEnd-elem.selectionStart)))+String.fromCharCode(keyc);
    }
    cbUpdateListBox(e);
    if (cbListBox.childNodes.length==0) {
      return;
    }
    cbHighlightItem();

    var afText=cbListBox.childNodes[0].innerHTML;

    if (afText.toLowerCase().indexOf(elem.value.toLowerCase())!=0) {
      return;
    }
    if (!elem.getAttribute("cbAutoExpand")) {
      return;
    } else {
      if (elem.getAttribute("cbAutoExpand")==false) {
        return;
      }
    }

    var subtext=elem.value;

    elem.value=afText;
    elem.Select;
    elem.selectionStart=subtext.length;
  }  
} // end cbonKeyPress()

// ===== handle mouseover on cbListBox
function cbonMouseOver(e) {
  if (cbHighlightedItem!=null) {
    cbHighlightedItem.className='cbListBoxItem';
  }
  if (cbonMouseOver.arguments.length==2) {
    var elem=cbonMouseOver.arguments[1]
  } else {
    var elem=(document.getElementById && !document.all) ? (e.target):(window.event.srcElement);
  }
  if (elem.className=="cbListBoxItem") {
    elem.className="cbListBoxItemonmouse";
  }
  cbHighlightedItem=elem;
} // end cbonMouseOver()

// ===== handle mouseout for cbListBox
function cbonMouseOut(e) {
  if (cbHighlightedItem!=null && cbHighlightedItem.tagName.toLowerCase()=="span") {
    cbHighlightedItem.className='cbListBoxItem';
  }
  if (cbonMouseOut.arguments.length==2) {
    var elem=cbonMouseOut.arguments[1];
  } else {
    var elem=(document.getElementById && !document.all) ? (e.target):(window.event.srcElement);
  }
  if (elem.className=="cbListBoxItemonmouse") {
    elem.className="cbListBoxItem";
  }
  cbHighlightedItem=null;
} // end cbonMouseOut()

// ===== handle mouse click for cbListBox
function cbonMouseClick(e) {
  var elem=(document.getElementById && !document.all) ? (e.target):(window.event.srcElement);

//alert("cbonMouseClick handles");
  if (elem.className=="cbListBoxItemonmouse" ||elem.className=="cbListBoxItem" ) {
//alert("cbListBoxItemonmouse or cbListBoxItem class");
    cbTextControl.value=elem.innerHTML;
    cbHide();
  }
} // end cbonMouseClick() 

// ===== hide box and suggestion list on certain keys/clicks
function cbHide() {
  cbListBox.style.display="none";
  cbListBoxBckgrnd.style.display="none";
//  cbButton.style.display="none";
} // end cbHide()

// ===== make box and suggestion list visible
function cbUnhide() {
  cbListBox.style.display="block";
  cbListBoxBckgrnd.style.display="block";
//  cbButton.style.display="block";
} // end cbUnhide()

// ===== cbonKeyDown() calls for Enter and Tab
function cbonEnter(e) {
  cbTextControl.value=cbTextControl.value.replace(/(^ +)|( +$)/g,'');
  cbUpdateListBox(e);

  var elem=(document.getElementById && !document.all) ? (e.target):(window.event.srcElement);

  if (cbHighlightedItem!=null) {
    elem.value=cbHighlightedItem.innerHTML;
  }
  cbHide();
  cbDoesValueExist();
} // end cbonEnter() 

// ===== called many places to trim down the displayed list of choices
// ===== 'e' is the user-entered string search pattern
function cbUpdateListBox(e) {
  var elem=(document.getElementById && !document.all) ? (e.target):(window.event.srcElement);
//alert("cbUpdateListBox, elem.value="+elem.value);
  var txt=cbLimitChoice(txtList,elem.value);
//alert("cbUpdateListBox txt="+txt);
  // no suggestions to show?
  if (txt.length == 0) {
    cbHide();
    return;
  }

  // restrict choices to those matching pattern
  cbListBox.innerHTML=txt;
  // ensure that new items are block type displays
  if (cbListBox.style.display!="block") {
    cbUnhide();
  }
  // 10 or more items (child nodes) style one way; else another for small list
  var xchildNodes=(cbListBox.childNodes.length);
  if (xchildNodes>=10) {
    cbListBox.style.height=((((10)*16)+2)+'px');
    cbListBoxBckgrnd.style.height=cbListBox.style.height;
  } else {
    if (xchildNodes<1) {
      xchildNodes=1.175;
    }
    cbListBox.style.height=((((xchildNodes)*16)+2)+'px');
    cbListBoxBckgrnd.style.height=cbListBox.style.height;
  }
  // no child nodes (list items)? tweak height
  if (document.getElementById && document.all) {
    if (cbListBox.childNodes.length==0) {
      cbListBox.style.height=parseInt(cbListBox.style.height)+1;
    }
  }    
} // end cbUpdateListBox() 

// ===== not used?
function getelem(e) {
  var elem=(document.getElementById && !document.all) ? (e.target):(window.event.srcElement);
  return elem;
} // end getelem()

// ===== not used?
function getkeyc() {
  var keyc=(document.getElementById && !document.all) ? (e.charCode):(window.event.keyCode);
  return keyc;
} // end getkeyc()

// ===== called on various key and mouse events, dealing with adding new value
function cbDoesValueExist() { // simply check substring in txtList
  if (cbTextControl.value.length==0) {
    return;
  }
  if (!cbTextControl.getAttribute("cbLimitToList")) {
    if (!cbTextControl.getAttribute("cbAddToList")) {
      return;
    }
  } else {
    if (cbTextControl.getAttribute("cbLimitToList")==false) {
      return;
    }
  }

  var xlist=txtList+"|";
  var whereInList=xlist.toLowerCase().indexOf(cbTextControl.value.toLowerCase()+"|",0);
  if (whereInList==-1 || txtList.length==0) {
    if (!cbTextControl.getAttribute("cbAddToList")) {
      cbTextControl.value=cbOldTextValue;
      alert("Value does not exist! (1)");
    } else {
      if (eval(cbTextControl.getAttribute("cbAddToList"))==false) {
        cbTextControl.value=cbOldTextValue;
        alert("Value does not exist! (2)");
      } else {
        cbAdd(cbTextControl.value);
      }
    }
  }
} // end cbDoesValueExist() 

// ===== helper function for cbDoesValueExist(), to add new choice to list, 
// ===== if permitted to add
function cbAdd(newChoice) {
  if (!newChoice) {
    return;
  }
  if (newChoice.length==0) {
    return;
  }//not necessary

  var cbList=cbTextControl.getAttribute("cbData");

  eval(cbList+'['+cbList+'.length]='+'"'+newChoice.replace(/([\*\^\\])/g,"\\$1")+'"');
  jvArray=eval(cbTextControl.getAttribute("cbData") );
  jvArray=jvArray.sort();
  txtList=jvArray.join("|");
} // end cbAdd() 

// ===== called by cbHighlightItem() to get index of cbListBox item (child)
function getIndex() {
  if (cbListBox.childNodes.length==0) {
    return;
  }

  var theValue=","+cbTextControl.value;
  var afTextList=limitedText;

  afTextList=","+afTextList.replace(/(\s\s)/g,",");

  var newlist=afTextList.substr(0,afTextList.toLowerCase().indexOf(theValue.toLowerCase()));

  newlist=newlist.match(/(,)/g);
  if (newlist==null) {
    return  0;
  }
  return newlist.length;
} // end getIndex() 

// ===== called by various "onKey" functions for certain keys &, (, etc.
function cbHighlightItem(e) {
  if (cbHighlightItem.arguments.length>1) { 
    var elem=cbHighlightItem.arguments[1];
  } else{    
    var xindex=getIndex()+0;
    var elem=cbListBox.childNodes[xindex];

    if (!cbTextControl.getAttribute("cbHighLightMatch")) {
      return;
    } else {
      if (eval(cbTextControl.getAttribute("cbHighLightMatch"))==false) {
        return;
      }
    }
  }
  if (elem==undefined) {
    return;
  }
  if (elem.offsetHeight+elem.offsetTop+16>cbListBox.offsetHeight+cbListBox.scrollTop) {
    cbListBox.scrollTop=elem.offsetTop;
  }
  if (elem.offsetTop-cbListBox.scrollTop<0) {
    cbListBox.scrollTop=elem.offsetTop;
  }
  if (cbHighlightedItem!=null) {
    cbHighlightedItem.className='cbListBoxItem';
  }
  cbHighlightedItem=elem;
  elem.className="cbListBoxItemonmouse";
} // end cbHighlightItem() 

// ===== handle button click (invoked by button div's onclick= )
function cbDrpDwnBttnonclick(e) {
  if (document.getElementById && !document.all) {
    e.cancelBubble=true;
  } else { 
    window.event.cancelBubble=true;
  }
  if (cbListBox.style.display=="none") {
    cbTextControl.focus()
  } else {
    cbHide();
    justhide=true;
    cbTextControl.focus();
  }
} // cbDrpDwnBttnonclick() 

// ===== called by cbUpdateListBox() to restrict suggestions to those in full
// ===== list 'theList' matching the pattern ("thefilter" user typed-in)
function cbLimitChoice(theList,thefilter) {
  cbHighlightedItem=null;

var theListX=theList.slice(0,150);
if (theListX.slice(0,16)!='   ()|A.T. Still')alert("cbLimitChoice against theList="+theListX+"...");
  var xtheList=theList.replace(/\,/g,"==="); //so that a comma stays a comma

  // if not a minimum number of characters, don't show suggestions
  if (thefilter.length < 1) {
    limitedText='';
    return '';
  }
   
  xjarray=jsfilter(xtheList,thefilter);
  if (xjarray==null) {
    limitedText='';
    return '';
  }
  // limit to 300 rows displayed
  if (xjarray.length>300) {
    xjarray=xjarray.slice(0,300);
  }
  // requested smaller number of items to show? show first Max items
  if (cbTextControl.getAttribute("cbMaxItemsShow")) {
    var Max = eval(cbTextControl.getAttribute("cbMaxItemsShow"));
    if (xjarray.length > Max) {
      xjarray=xjarray.slice(0,Max);
    }
  }

  xjarray=xjarray.toString();
  xjarray=xjarray.replace(/\|/ig,"");
  limitedText=xjarray;
  xjarray=xjarray.replace(/,/g,'</span><span class="cbListBoxItem">');
  xjarray=xjarray.replace(/===/g,','); //back to a comma
  if (xjarray.length>0) {
    xjarray='<span class="cbListBoxItem">'+xjarray+'</span>';
  }
  return xjarray; //return as string to be used as innerHTML; cool
} // end cbLimitChoice() 

// ===== helper function to cbLimitChoice() to actually apply filter (pattern)
// ===== to full list
function jsfilter(str,xfilter) {
  if (xfilter.length==0) {
    return str.split("|");
  }

  var regexp;
  var newstr=str;
  var xxfilter=xfilter.replace(/([\/\'\+\^\$\*\.\(\)\[\]\?\\])/g,"\\$1");

  xxfilter=xxfilter.replace(/\,/g,"===");
  if (!cbTextControl.getAttribute("cbMatchOnAnyPart")) {
    regexp="/[^|]*"+xxfilter+"[^|]*/ig";
  } else {
    if (eval(cbTextControl.getAttribute("cbMatchOnAnyPart"))==true) {
      regexp="/[^|]*"+xxfilter+"[^|]*/ig";
    } else {
      regexp="/[|]"+xxfilter+"[^|]*/ig";
      newstr="|"+str;
    }
  }    
  return eval("newstr.match("+regexp+")");
} // end jsfilter() 

// ===== start-up code
if (typeof HTMLElement != "undefined") {
  HTMLElement.prototype.insertAdjacentHTML = function (sWhere, sHTML) {
    var df;
    var r = this.ownerDocument.createRange();

    switch (String(sWhere).toLowerCase()) {
      case "beforebegin": r.setStartBefore(this);
                          df = r.createContextualFragment(sHTML);
                          this.parentNode.insertBefore(df, this);
                          break;
      case "afterbegin": r.selectNodeContents(this);
                         r.collapse(true);
                         df = r.createContextualFragment(sHTML);
                         this.insertBefore(df, this.firstChild);
                         break;
      case "beforeend": r.selectNodeContents(this);
                        r.collapse(false);
                        df = r.createContextualFragment(sHTML);
                        this.appendChild(df);
                        break;
      case "afterend": r.setStartAfter(this);
                       df = r.createContextualFragment(sHTML);
                       this.parentNode.insertBefore(df, this.nextSibling);
                       break;
    }
  }
  HTMLElement.prototype.__defineGetter__("innerText", function () {
    var r = this.ownerDocument.createRange();
    r.selectNodeContents(this);
    return r.toString();
  });
} // end of define HTMLElement
