/*************************************************************************
Author: Todd Kingham
File: JSMXsuggest.js
Date: 2/13/2006
Revised: 9/7/2006 (forgot to add +"px" to line 65.)
Purpose: allows you to "bind" an input field with an Array() on the server
		to create a "Suggest" field.
DOWNLAOD THE ENTIER EXAMPLE AT: http://www.lalabird.com/?fa=JSMX.downloads		
*************************************************************************/

var URL = 'JSMXsuggest.cfc?method=';

addLoadListener($constructor);
function $constructor(){
	var elms = getSuggestFields();
	for(var i=0;i<elms.length;i++){
		elms[i].suggestObject = new SuggestObject(elms[i],URL);
	}
}

/*-------------------------BEGIN: SUGGEST OBJECT -----------------------------*/
function SuggestObject(elm,url){
	//set AJAX properties
	var sObj = this;	
	this.url = url;
	this.method = elm.title;
	this.request = suggest_request;
	this.callback = function(result){
		sObj.comboBox.ul.innerHTML = '';
		for(var i=0; i < result.length; i++){
			sObj.addItem(result[i],i);
		}
		var LIs = sObj.comboBox.ul.getElementsByTagName('LI');
			sObj.itemCount = LIs.length;
		if(sObj.itemCount){
			LIs[0].className = 'active';	
			sObj.selIndex = 0;
		}
	}
	
	//set INPUT field properties
	this.txtInput = elm;
	this.txtInput.onkeyup = _OnKeyUp;
	this.txtInput.onkeydown = _OnKeyDown;
	this.txtInput.onblur = _OnBlur;
	this.txtInput.oldValue = elm.value;
	this.txtInput.autocomplete = 'off';
	this.txtInput.title = '';
	
	//set SUGGEST BOX properties
	this.itemCount = 0;
	this.selIndex = -1;
	this.comboBox = new suggestBox(this.txtInput);
	this.selectItem = _selectItem;
	this.addItem = _addItem;
	
return this;	
}

//build the actual suggest box "div"
function suggestBox(fld){
	var xy = getCoords(fld);
	this.ul = document.createElement('UL');
	this.div = document.createElement('DIV');
	this.div.className = 'suggestBox';
	this.div.style.top = xy.Y+fld.offsetHeight+'px';
	this.div.style.left = xy.X;
	this.div.style.width = fld.offsetWidth+'px';
	this.div.appendChild(this.ul);
	document.body.appendChild(this.div);
}

//add items into the suggest box
function _addItem(ct,idx){
	var obj = this;
	var A = document.createElement('A');
		A.onclick = function(){ obj.selIndex = idx;	_selectItem(obj); }
		A.style.cursor = 'pointer';
		A.innerHTML = ct;		
	
	var LI = document.createElement('LI');
		LI.appendChild(A);	
		
		this.comboBox.ul.appendChild(LI);
}

//choose an item from the suggest box
function _selectItem(obj) {
	var li = obj.comboBox.ul.getElementsByTagName('LI');
	var value = '';
	if(li.length){
		value = li[obj.selIndex].getElementsByTagName('A')[0].innerHTML;
	}else{
		value = obj.txtInput.value;
	}
	
	obj.txtInput.value = value;
	obj.txtInput.oldValue = value;
	obj.comboBox.ul.innerHTML = '';
	obj.txtInput.focus();
}
/*-------------------------END: SUGGEST OBJECT -----------------------------*/




/*-------------------------BEGIN: LISTENER EVENTS (to handle key up/down, etc..) -----------------------------*/
function _OnKeyUp(e){
	obj = this.suggestObject;
	if(obj.txtInput.oldValue != obj.txtInput.value){
	// MAKE THE AJAX CALL: slow it down. save some bandwidth
		if(typeof timeout != 'undefined'){clearTimeout(timeout)};
		timeout = setTimeout('obj.request();',400);
	}
}

function _OnKeyDown(e){
	var obj = this.suggestObject;
	var LIs = obj.comboBox.ul.getElementsByTagName('LI');
	obj.txtInput.oldValue = obj.txtInput.value;
	
	if (!e) e = window.event;
	switch (e.keyCode) {
		case 13: // enter
			_selectItem(obj);
			return false;
		case 9: // tab
			_selectItem(obj);
			break;
		case 27: // escape
			obj.comboBox.ul.innerHTML = '';
			return false;
		case 38: // up arrow
			if(obj.selIndex > 0 ){
				LIs[obj.selIndex].className = '';
				obj.selIndex --;
				LIs[obj.selIndex].className = 'active';
			}
			return false;
		case 40: // down arrow
			if(obj.selIndex < (obj.itemCount-1) ){
				LIs[obj.selIndex].className = '';
				obj.selIndex ++;
				LIs[obj.selIndex].className = 'active';
			}
			return false;
		}	
}

function _OnBlur(e){
	var e = ( typeof e == "undefined" ) ? window.event : e ;
		tmpObj = getEventTarget(e).suggestObject;
	var timer = setTimeout("tmpObj.comboBox.ul.innerHTML = ''",500);		
}
/*-------------------------END: LISTENER EVENTS -----------------------------*/



/*-------------------------BEGIN: AJAX CALL -----------------------------*/
//THIS DOES NOT GET CALLED DIRECTLY FROM THE UI. INSTEAD IT IS SET AS A
//PROPERTY OF A SuggestObject() INSTANCE. THIS MAKES IT EASIER TO 
//MANIPULATE THE DHTML ASPECTS OF THE DYNAMICALLY GENERATED "SUGGEST BOX"
//AND ALLOWS US TO EASILY APPLY MULTIPLE SUGGEST FIELDS WITHIN ONE PAGE.

// Send JSMX request
function suggest_request() {
	var param = 'value='+this.txtInput.value;
		http( 'POST' , this.url+this.method , this.callback , param ); 
}

/*-------------------------END: AJAX CALL -----------------------------*/


/*-------------------------BEGIN: UTILITY FUNCTIONS -----------------------------*/
function getSuggestFields(){
	var ret = [];
	var x = document.getElementsByTagName('INPUT');
	var rex = new RegExp("(^| )jsmxsuggest( |$)");
	for(var i=0;i<x.length;i++){
		if(rex.test(x[i].className.toLowerCase())){
			ret[ret.length]=x[i];
		}
	}
return ret;
}

//these utility functions are based on code found in the most excellent
//sitepoint book: The JavaScript Anthology by James Edwards and Cameron Adams
function getCoords(elm){
  var X = 0;
  var Y = 0;
  while (elm != null){
    X += elm.offsetLeft;
    Y += elm.offsetTop;
    elm = elm.offsetParent;
  }
  return {"X":X,"Y":Y};
}

function addLoadListener(fn)
{
  if (typeof window.addEventListener != 'undefined')
  {
    window.addEventListener('load', fn, false);
  }
  else if (typeof document.addEventListener != 'undefined')
  {
    document.addEventListener('load', fn, false);
  }
  else if (typeof window.attachEvent != 'undefined')
  {
    window.attachEvent('onload', fn);
  }
  else
  {
    var oldfn = window.onload;
    if (typeof window.onload != 'function')
    {
      window.onload = fn;
    }
    else
    {
      window.onload = function()
      {
        oldfn();
        fn();
      };
    }
  }
}

function getEventTarget(event){
  var targetElement = (typeof event.target != "undefined")? event.target :event.srcElement ;
  while (targetElement.nodeType == 3 && targetElement.parentNode != null){
    targetElement = targetElement.parentNode;
  }
 return targetElement;
}
/*-------------------------END: UTILITY FUNCTIONS -----------------------------*/ 
