/*
(c) Intechnic Europe 2007 http://www.intechnic.lv
Author: Konstantin Tjuterev

The class adds a dynamic drop-down to the edit-box
Usage:

<input type="text" id="combo_input" style="width: 200px"><br/>
<input type="text" id="combo_input1" style="width: 200px"><br/>
<script type="text/javascript">
	new AJAXDropDown('combo_input', function(cur_value) {return 'items.xml?'}, function(item) {
			var val = item.getAttribute('a');
			return val ? val : item.innerHTML;
		});
	new AJAXDropDown('combo_input1', function(cur_value) {return 'items1.xml?cur='+escape(cur_value)});

	new AJAXDropDown('curr_search_keyword', function(cur_value) {
			var $url = '<inp2:m_Link template="dummy" pass="m,curr" curr_event="OnSuggestValues" field="Name" cur_value="#CUR_VALUE#" no_amp="1"/>';
			return $url.replace('#CUR_VALUE#', escape(cur_value));
		}
	);
</script>

The AJAXDropDown constructor takes the following arguments:
	input_id - id of textbox to attach to
	suggest_url_callback - function which should return URL which returns XML of suggested values,
		function takes one argument - it is current value of the text_box
	[get_value_callback] - optional argument - function which returns the value to be set in the textbox,
		based on currently selected item (out of suggested). If the argument is skipped - the node value of
		<item> element will be used as the value
		<item> element in the suggestions XML may have unlimited number of attribuets, all the attributes will be set
		to the corresponding DIV element and this DIV element will be passed as argument to the get_value_callback,
		so one may use the argument to alter the value set into textbox as in the example above

Response XML structure:
	The script will look for ALL elements with tagname = 'item', so basically the structure should look like this:

	<suggestions>
		<item>Suggestion 1</item>
		<item>Suggestion 2</item>
		<item>Suggestion 3</item>
	</suggestions>

Design & CSS
	The script will automatically display a div which width will match the width of the text-box
	You may control the div look by changing .suggest-box CSS selector
	The items inside the box will have .suggest-item and .suggest-item-over

	The default styles which may be used is:

	<style type="text/css">
		.suggest-box {
			border: 1px solid #999;
			background-color: #fff;
		}

		.suggest-item, .suggest-item-over {
			padding: 1px 2px 0px 2px;
			font-family: arial,verdana;
			font-size: 12px;
		}

		.suggest-item-over {
			background-color: #3366CC;
			color: #fff;
		}
	</style>

*/

function addLoadEvent(func, wnd) {
	if (!wnd) wnd = window
	var oldonload = wnd.onload;
	if (typeof wnd.onload != 'function') {
		wnd.onload = func;
	} else {
		wnd.onload = function() {
			if (oldonload) {
				oldonload();
			}
			func();
		}
	}
}

function addEvent(el, evname, func, traditional) {
	if (traditional) {
		eval('el.on'+evname+'='+func);
		return;
	}

	if (is.ie) {
		el.attachEvent("on" + evname, func);
	} else {
		el.addEventListener(evname, func, true);
	}
};

function addElement($dst_element, $tag_name) {
	var $new_element = document.createElement($tag_name.toUpperCase());
	$dst_element.appendChild($new_element);
	return $new_element;
}

function Is ()
{   // convert all characters to lowercase to simplify testing
    var agt=navigator.userAgent.toLowerCase();

    // *** BROWSER VERSION ***
    // Note: On IE5, these return 4, so use is.ie5up to detect IE5.

    this.major = parseInt(navigator.appVersion);
    this.minor = parseFloat(navigator.appVersion);

    // Note: Opera and WebTV spoof Navigator.  We do strict client detection.
    // If you want to allow spoofing, take out the tests for opera and webtv.
    this.nav  = ((agt.indexOf('mozilla')!=-1) && (agt.indexOf('spoofer')==-1)
                && (agt.indexOf('compatible') == -1) && (agt.indexOf('opera')==-1)
                && (agt.indexOf('webtv')==-1) && (agt.indexOf('hotjava')==-1));
    this.nav2 = (this.nav && (this.major == 2));
    this.nav3 = (this.nav && (this.major == 3));
    this.nav4 = (this.nav && (this.major == 4));
    this.nav4up = (this.nav && (this.major >= 4));
    this.navonly      = (this.nav && ((agt.indexOf(";nav") != -1) ||
                          (agt.indexOf("; nav") != -1)) );
    this.nav6 = (this.nav && (this.major == 5));
    this.nav6up = (this.nav && (this.major >= 5));
    this.gecko = (agt.indexOf('gecko') != -1);

    this.ie     = ((agt.indexOf("msie") != -1) && (agt.indexOf("opera") == -1));
    this.ie3    = (this.ie && (this.major < 4));
    this.ie4    = (this.ie && (this.major == 4) && (agt.indexOf("msie 4")!=-1) );
    this.ie4up  = (this.ie  && (this.major >= 4));
    this.ie5    = (this.ie && (this.major == 4) && (agt.indexOf("msie 5.0")!=-1) );
    this.ie5_5  = (this.ie && (this.major == 4) && (agt.indexOf("msie 5.5") !=-1));
    this.ie5up  = (this.ie  && !this.ie3 && !this.ie4);
    this.ie5_5up =(this.ie && !this.ie3 && !this.ie4 && !this.ie5);
    this.ie6    = (this.ie && (this.major == 4) && (agt.indexOf("msie 6.")!=-1) );
    this.ie6up  = (this.ie  && !this.ie3 && !this.ie4 && !this.ie5 && !this.ie5_5);

    // KNOWN BUG: On AOL4, returns false if IE3 is embedded browser
    // or if this is the first browser window opened.  Thus the
    // variables is.aol, is.aol3, and is.aol4 aren't 100% reliable.
    this.aol   = (agt.indexOf("aol") != -1);
    this.aol3  = (this.aol && this.ie3);
    this.aol4  = (this.aol && this.ie4);
    this.aol5  = (agt.indexOf("aol 5") != -1);
    this.aol6  = (agt.indexOf("aol 6") != -1);

    this.opera = (agt.indexOf("opera") != -1);
    this.opera2 = (agt.indexOf("opera 2") != -1 || agt.indexOf("opera/2") != -1);
    this.opera3 = (agt.indexOf("opera 3") != -1 || agt.indexOf("opera/3") != -1);
    this.opera4 = (agt.indexOf("opera 4") != -1 || agt.indexOf("opera/4") != -1);
    this.opera5 = (agt.indexOf("opera 5") != -1 || agt.indexOf("opera/5") != -1);
    this.opera5up = (this.opera && !this.opera2 && !this.opera3 && !this.opera4);

    this.webtv = (agt.indexOf("webtv") != -1);

    this.TVNavigator = ((agt.indexOf("navio") != -1) || (agt.indexOf("navio_aoltv") != -1));
    this.AOLTV = this.TVNavigator;

    this.hotjava = (agt.indexOf("hotjava") != -1);
    this.hotjava3 = (this.hotjava && (this.major == 3));
    this.hotjava3up = (this.hotjava && (this.major >= 3));

    // *** JAVASCRIPT VERSION CHECK ***
    if (this.nav2 || this.ie3) this.js = 1.0;
    else if (this.nav3) this.js = 1.1;
    else if (this.opera5up) this.js = 1.3;
    else if (this.opera) this.js = 1.1;
    else if ((this.nav4 && (this.minor <= 4.05)) || this.ie4) this.js = 1.2;
    else if ((this.nav4 && (this.minor > 4.05)) || this.ie5) this.js = 1.3;
    else if (this.hotjava3up) this.js = 1.4;
    else if (this.nav6 || this.gecko) this.js = 1.5;
    // NOTE: In the future, update this code when newer versions of JS
    // are released. For now, we try to provide some upward compatibility
    // so that future versions of Nav and IE will show they are at
    // *least* JS 1.x capable. Always check for JS version compatibility
    // with > or >=.
    else if (this.nav6up) this.js = 1.5;
    // note ie5up on mac is 1.4
    else if (this.ie5up) this.js = 1.3

    // HACK: no idea for other browsers; always check for JS version with > or >=
    else this.js = 0.0;

    // *** PLATFORM ***
    this.win   = ( (agt.indexOf("win")!=-1) || (agt.indexOf("16bit")!=-1) );
    // NOTE: On Opera 3.0, the userAgent string includes "Windows 95/NT4" on all
    //        Win32, so you can't distinguish between Win95 and WinNT.
    this.win95 = ((agt.indexOf("win95")!=-1) || (agt.indexOf("windows 95")!=-1));

    // is this a 16 bit compiled version?
    this.win16 = ((agt.indexOf("win16")!=-1) ||
               (agt.indexOf("16bit")!=-1) || (agt.indexOf("windows 3.1")!=-1) ||
               (agt.indexOf("windows 16-bit")!=-1) );

    this.win31 = ((agt.indexOf("windows 3.1")!=-1) || (agt.indexOf("win16")!=-1) ||
                    (agt.indexOf("windows 16-bit")!=-1));

    // NOTE: Reliable detection of Win98 may not be possible. It appears that:
    //       - On Nav 4.x and before you'll get plain "Windows" in userAgent.
    //       - On Mercury client, the 32-bit version will return "Win98", but
    //         the 16-bit version running on Win98 will still return "Win95".
    this.win98 = ((agt.indexOf("win98")!=-1) || (agt.indexOf("windows 98")!=-1));
    this.winnt = ((agt.indexOf("winnt")!=-1) || (agt.indexOf("windows nt")!=-1));
    this.win32 = (this.win95 || this.winnt || this.win98 ||
                    ((this.major >= 4) && (navigator.platform == "Win32")) ||
                    (agt.indexOf("win32")!=-1) || (agt.indexOf("32bit")!=-1));

    this.winme = ((agt.indexOf("win 9x 4.90")!=-1));
    this.win2k = ((agt.indexOf("windows nt 5.0")!=-1));

    this.os2   = ((agt.indexOf("os/2")!=-1) ||
                    (navigator.appVersion.indexOf("OS/2")!=-1) ||
                    (agt.indexOf("ibm-webexplorer")!=-1));

    this.mac    = (agt.indexOf("mac")!=-1);
    // hack ie5 js version for mac
    if (this.mac && this.ie5up) this.js = 1.4;
    this.mac68k = (this.mac && ((agt.indexOf("68k")!=-1) ||
                               (agt.indexOf("68000")!=-1)));
    this.macppc = (this.mac && ((agt.indexOf("ppc")!=-1) ||
                                (agt.indexOf("powerpc")!=-1)));

    this.sun   = (agt.indexOf("sunos")!=-1);
    this.sun4  = (agt.indexOf("sunos 4")!=-1);
    this.sun5  = (agt.indexOf("sunos 5")!=-1);
    this.suni86= (this.sun && (agt.indexOf("i86")!=-1));
    this.irix  = (agt.indexOf("irix") !=-1);    // SGI
    this.irix5 = (agt.indexOf("irix 5") !=-1);
    this.irix6 = ((agt.indexOf("irix 6") !=-1) || (agt.indexOf("irix6") !=-1));
    this.hpux  = (agt.indexOf("hp-ux")!=-1);
    this.hpux9 = (this.hpux && (agt.indexOf("09.")!=-1));
    this.hpux10= (this.hpux && (agt.indexOf("10.")!=-1));
    this.aix   = (agt.indexOf("aix") !=-1);      // IBM
    this.aix1  = (agt.indexOf("aix 1") !=-1);
    this.aix2  = (agt.indexOf("aix 2") !=-1);
    this.aix3  = (agt.indexOf("aix 3") !=-1);
    this.aix4  = (agt.indexOf("aix 4") !=-1);
    this.linux = (agt.indexOf("inux")!=-1);
    this.sco   = (agt.indexOf("sco")!=-1) || (agt.indexOf("unix_sv")!=-1);
    this.unixware = (agt.indexOf("unix_system_v")!=-1);
    this.mpras    = (agt.indexOf("ncr")!=-1);
    this.reliant  = (agt.indexOf("reliantunix")!=-1);
    this.dec   = ((agt.indexOf("dec")!=-1) || (agt.indexOf("osf1")!=-1) ||
                  (agt.indexOf("dec_alpha")!=-1) || (agt.indexOf("alphaserver")!=-1) ||
                  (agt.indexOf("ultrix")!=-1) || (agt.indexOf("alphastation")!=-1));
    this.sinix = (agt.indexOf("sinix")!=-1);
    this.freebsd = (agt.indexOf("freebsd")!=-1);
    this.bsd = (agt.indexOf("bsd")!=-1);
    this.unix  = ((agt.indexOf("x11")!=-1) || this.sun || this.irix || this.hpux ||
                 this.sco ||this.unixware || this.mpras || this.reliant ||
                 this.dec || this.sinix || this.aix || this.linux || this.bsd || this.freebsd);

    this.vms   = ((agt.indexOf("vax")!=-1) || (agt.indexOf("openvms")!=-1));
}

var is;
var isIE3Mac = false;
// this section is designed specifically for IE3 for the Mac

if ((navigator.appVersion.indexOf("Mac")!=-1) && (navigator.userAgent.indexOf("MSIE")!=-1) &&
(parseInt(navigator.appVersion)==3))
       isIE3Mac = true;
else   is = new Is();

function AJAXDropDown(input_id, suggest_url_callback, get_value_callback)
{
	this.Input = document.getElementById(input_id);

	this.KeyUpWaiting = false;
	this.KeyUpTimer = false;
	this.Box = '';
	this.SuggestURLCallback = suggest_url_callback;
	if (!get_value_callback) get_value_callback = this.GetValue;
	this.GetValueCallback = get_value_callback;
	this.BoxOpen = false;
	this.SelectedItem = false;
	var obj = this;
	addLoadEvent(function() {obj.Init()});
}

AJAXDropDown.prototype.Init = function()
{

	// draw box
	this.Box = document.createElement('DIV');
	document.body.appendChild(this.Box);

//	this.Box = addElement(this.Input.parentNode, 'div');
	this.Box.style.display = 'none';
	this.Box.style.zIndex = 99;
	this.Box.style.position = 'absolute';
	this.Box.style.overflow = 'auto';
	this.Box.className = 'suggest-box'

	var obj = this;

	this.Input.setAttribute('autocomplete', 'off');
	// add onkeyup

	addEvent(this.Input, 'keyup', function(ev) {obj.KeyUp(ev)});
	addEvent(this.Input, 'blur', function(ev) {obj.Blur(ev)});
	addEvent(this.Box, 'scroll', function(ev) {if (obj.BlurWaiting) {window.clearTimeout(obj.BlurTimer);}});
	addEvent(this.Box, 'mouseup', function(ev) {obj.BlurWaiting = false});

}

AJAXDropDown.prototype.Blur = function(ev)
{
	if (this.BlurWaiting) return;
	this.BlurWaiting = true;
	var obj = this;
	this.BlurTimer = window.setTimeout(function() {
			obj.CloseBox();
			this.BlurWaiting = false;
		}, 300)
}

AJAXDropDown.prototype.KeyUp = function(ev)
{
	var e = !is.ie ? ev : window.event;
	switch (e.keyCode) {
		case 38: //arrow up
			if (!this.BoxOpen) break;
			this.SelectPrev();
			break;
		case 40: //arrow down
			if (!this.BoxOpen) break;
			this.SelectNext();
			break;
		case 27: //Enter
			this.Input.value = this.OriginalValue;
			this.CloseBox();
			break;
		case 13: //Escape
			this.CloseBox();
			break;
		default:
			if (this.Input.value == '') return;
			var obj = this;
			if (this.KeyUpWaiting && this.KeyUpTimer) {
				window.clearTimeout(this.KeyUpTimer);
				this.KeyUpTimer = false;
			}
			this.KeyUpWaiting = true;
			this.KeyUpTimer = window.setTimeout(function() {
					obj.RequestSuggestions();
				}, 300)
	}
}

AJAXDropDown.prototype.RequestSuggestions = function()
{
	Request.makeRequest(this.SuggestURLCallback(this.Input.value), false, '', this.successCallback, this.errorCallback, 'reload', this);
}

AJAXDropDown.prototype.successCallback = function (request, params, object) {
	object.OriginalValue = object.Input.value;
	object.OpenBox();
	object.KeyUpWaiting = false;
	var items = request.responseXML.getElementsByTagName('item');
	object.ClearItems();
	for (var i=0; i<items.length; i++)
	{
		object.AddItem(items[i].firstChild.nodeValue, items[i].attributes);
	}
}

AJAXDropDown.prototype.errorCallback = function (request, params, object) {
	this.KeyUpWaiting = false;
}

AJAXDropDown.prototype.ClearItems = function()
{
	this.Box.scrollTop = 0;
	this.UnselectItem();
	for (var i=this.Box.childNodes.length-1; i>=0; i--)
	{
		this.Box.removeChild(this.Box.childNodes[i]);
	}
}

AJAXDropDown.prototype.OpenBox = function()
{
	var pos = findPos(this.Input);
	var dim = getDimensions(this.Input);

	this.Box.style.left = pos[0] + 'px';
	this.Box.style.top = (pos[1] + dim.innerHeight + dim.borders[0] + dim.borders[2] +5) + 'px';
	this.Box.style.width = (dim.innerWidth + dim.borders[1] + (is.ie ? dim.borders[3] : 0 ) -3) + 'px';
	this.Box.style.display = 'block';
//	alert('box opened at '+this.Box.style.left+','+this.Box.style.top+' pos x:'+pos[0]);
	this.BoxOpen = true;
}

AJAXDropDown.prototype.CloseBox = function()
{
	if (!this.BoxOpen) {
		return;
	}
	this.Box.style.display = 'none';
	this.BoxOpen = false;
}

AJAXDropDown.prototype.AddItem = function(value, attributes)
{
	var item = addElement(this.Box, 'div');
	for (var i=0; i<attributes.length; i++) {
		item.setAttribute(attributes[i].nodeName, attributes[i].nodeValue)
	}
	item.className = 'suggest-item';
	item.innerHTML = value;
	var obj = this;
	addEvent(item, 'mousemove', function() {obj.SelectItem(item)});
	addEvent(item, 'mousedown', function() {obj.ClickItem(item)});
}

AJAXDropDown.prototype.ClickItem = function(item)
{
	this.Input.value = this.GetValueCallback(item);
	this.CloseBox();
}

AJAXDropDown.prototype.SelectNext = function()
{
	if (!this.SelectedItem) {
		this.SelectItem(this.Box.firstChild, true);
		return;
	}
	if (isset(this.SelectedItem.nextSibling)) {
		this.SelectItem(this.SelectedItem.nextSibling, true)
	}
	else { // down from last
		this.UnselectItem();
		this.Input.value = this.OriginalValue;
	}
}

AJAXDropDown.prototype.SelectPrev = function()
{
	if (!this.SelectedItem) {
		this.SelectItem(this.Box.lastChild, true);
		return;
	}
	if (isset(this.SelectedItem.previousSibling)) {
		this.SelectItem(this.SelectedItem.previousSibling, true)
	}
	else { // up from first
		this.UnselectItem();
		this.Input.value = this.OriginalValue;
	}
}

AJAXDropDown.prototype.UnselectItem = function(item)
{
	if (!item) item = this.SelectedItem;
	if (!item) return;
	item.className = 'suggest-item';
	this.SelectedItem = false;
}


AJAXDropDown.prototype.SelectItem = function(item, setvalue)
{
	if (this.SelectedItem) {
		this.UnselectItem(this.SelectedItem);
	}
	item.className = 'suggest-item-over';
	this.SelectedItem = item;
	item.scrollIntoView(false);

	if (setvalue) {
		this.Input.value = this.GetValueCallback(item);
	}
}

AJAXDropDown.prototype.GetValue = function(item)
{
	return item.innerHTML;
}

