function Input(e, prop)
{
	e = (typeof(e) == 'string') ? $(e) : (e ? e : $(prop.id));
	if(e == null)
	{
		printStackTrace();
		alert('NULL id: '+prop.id);
		return;
	}
	ce(e);

	for(var key in prop)
	{
		try { e[key] = prop[key]; } catch(error) { }
	}
	
	if(prop.titleStyle == 'INSET' || prop.titleStyle == 'BOTH')
	{
		e.onfocus = function()
		{
			e.className = e.className.replace(/\s*titleInset/, ''); 
			if(e.title == e.value)
				e.value = '';
				//e.select();
		};
		
		e.onblur = function()
		{
			if(e.value == '') 
				e.value = e.title; 
			if(e.title == e.value) 
				e.className += ' titleInset';
		};
	}
	e.onchange = function()
	{
		if(e.form && e.form.onchange)
			e.form.onchange();
	};
	
	e.check = function()
	{
		if(e.required && e.value == '')
		{
			e.error = e.message;
			e.parentNode.appendChild(e.getError());
			return e.error;
		}
		else return null;
	};
	
	e.getError = function()
	{
		if(e.errorObj)
			return e.errorObj;
		return e.errorObj = ce('div').at(e.error).sp('className', 'error');
	};
	
	e.getValue = function()
	{
		return e.value;
	};
	
	return e;
}

function CheckBox(e, prop)
{
	var e = Input(e, prop);

	e.check = function()
	{
		if(e.required && !e.checked)
		{
			e.error = e.message;
			e.parentNode.appendChild(e.getError());
			return e.error;
		}
		else return null;
	};
	
	var onfocus = e.onfocus;
	e.onfocus = function()
	{ 
		if(onfocus) onfocus();
		e.className = e.className+' focused';
		if(e.errorObj)
			e.errorObj = e.errorObj.rs();
	};
	var onblur = e.onblur;
	e.onblur = function()
	{
		if(onblur) onblur();
		e.className = e.className.replace(/\s*focused/, '');
		if(e.check())
			e.parentNode.appendChild(e.getError());
	}

	return e;
}

function Group(e, prop)
{
	e = Input(e, prop);
	
	var fields = {};
	for(var key in e.fields)
	{
		var obj = e.fields[key];
		if(obj != null)
		{
			if(window[obj.jsFunc])
				fields[key] = window[obj.jsFunc](null, obj);
			else ce(document.body).at('undefined javascript function: '+obj.jsFunc+' '+obj['class']+' '+key).ac(ce('br'));
		}
	}
	e.fields = fields;
	
	e.check = function()
	{
		var errors = [];
		for(var key in fields)
		{
			var error = fields[key].check();
			if(error)
				errors.push(error);
		}
		return errors.length ? errors : null;
	};
	
	e.getValue = function()
	{
		var obj = {};
		for(var key in e.fields)
			if(e.fields[key].getValue)
				obj[key] = e.fields[key].getValue();
		return obj;
	};
	return e;
}

function CheckMultiple(e, prop)
{
	e = Select(e, prop);
	e.inputs = e.getElementsByTagName('input');
	
	for(var n = 0; n < e.inputs.length; n++)
		e.inputs[n].onclick = function()
		{
			if(this.form && this.form.onchange)
				this.form.onchange();
		};
	e.getValue = function()
	{
//		printR(e.inputs);
		var arr = [];
		for(var n = 0; n < e.inputs.length; n++)
			if(e.inputs[n].checked)
				arr.push(e.inputs[n].value);
//		printR(arr);
		return arr;
	}	
	return e;
}

function Radio(e, prop)
{
	e = Select(e, prop);
	e.inputs = e.getElementsByTagName('input');

	for(var n = 0; n < e.inputs.length; n++)
	{
		e.inputs[n].onclick = function() { if(e.onchange instanceof Function) e.onchange(); };
		e.inputs[n].onchange = null;
	}

	e.getValue = function()
	{
		for(var n = 0; n < e.inputs.length; n++)
		{
			if(e.inputs[n].checked)
				return e.inputs[n].value;
		}
		return null;
	};
	
	return e;
}

function Alternate(e, prop)
{
	e = Group(e, prop);
	for(var key in e.fields)
	{
		e.field = e.fields[key];
		break;
	}
	var currentForm = e.field.getValue();
	
	e.field.onchange = function()
	{
			if(currentForm)
				e.fields[currentForm].style.display = 'none';
			currentForm = e.field.getValue();
			e.fields[currentForm].style.display = '';
	};
	
	e.check = function()
	{
		return e.fields[currentForm].check();
	};

	e.getValue = function()
	{
		var key = e.field.getValue();
		return key ? e.fields[key].getValue() : null;
	};
	
	return e;
}
function AlternateForm(e, prop)
{
	e = Form(e, prop);
	e.getValue = function()
	{
		return e.field.getValue();		
	};
	return e;
}

function CountForm(e, prop)
{
	e = Form(e, prop);
	var countField = ce($('count_'+prop.id));
	var data;
	
	e.onchange = function() 
	{
		var temp = e.getValue();
		if(data == null || compare(temp, data) == false)
			e.load(e.service, data = temp, display);
		return false;
	};
	
	function compare(x, y)
	{
		for(var key in x)
		{
			if((x[key] instanceof Object) && (y[key] instanceof Object))
			{
				if(compare(x[key], y[key]) == false)
					return false;
			}
			else if(x[key] != y[key])
				return false;
		}
		for(var key in y)
			if(y[key] != x[key])
				return false;
		return true;
	}
	
	function display(count)
	{
		countField.cl().at(e.countMessage.replace(/COUNT/, count));
	}

//	alert(countField);
//	e.onchange = function() { e.submit(); }
	return e;
}

function MultipleSingleForm(e, prop)
{
	e = Form(e, prop);
	return e;
}

function Form(e, prop)
{
	e = Group(e, prop);
	var div = ce('div'); e.ac(div);
	ce(e.saveForm);
//	e.saveForm = Submit(null, e.saveForm);
	
	e.load = load;
	e.onsubmit = onSubmit;
	return e;

	function onSubmit()
	{
//		alert(e.check() == null);
		return e.check() == null;
	}
	function display(text)
	{
		printR(text, div);
	}
	
	function load(url, data, handler)
	{
		if(data instanceof Object)
			data = toURL(data);

		var request = (window.XMLHttpRequest) ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");
		request.onreadystatechange = onStateChange;
		if(e.method == 'get')
		{
			url += (url.match(/\?/) ? '' : '?1=1')+data;
			request.open('GET', url, true);
			request.send('');
		}
		else
		{
			request.open('POST', url, true);
			request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
			request.send(data);
		}
		
		function onStateChange()
		{
			if(request.readyState == 4) // 4 means done loading
			{
				if(request.status == 200)
					handler(request.responseText);
				//else alert('Load Failed: status: '+request.status+": "+request.statusText);
			}
		}
		
		function toURL(data, pre)
		{
			if(typeof(data) == 'object')
			{
				var url = '';
				if(data instanceof Array)
				{
					for(var n = 0; n < data.length; n++)
						url += toURL(data[n], (pre || '&data')+'['+n+']');
				}
				else
				{
					for(var key in data)
						url += toURL(data[key], pre ? pre+'['+key+']' : '&'+key);
				}
				return url;
			}
			else return pre+'='+encodeURI(data).replace(/&/g, '$@$'); // decoded in Data::Value()
		}
	}
}

function MultipleForm(e, prop)
{
	e = Form(e, prop);
	e.field = e.fields[prop.field.baseName]; //window[prop.field.jsFunc](prop.field, prop.field);
	e.check = function()
	{
		return e.field.check();
	};

	return e;
}

function MultipleBlockForm(e, prop)
{
	e = MultipleForm(e, prop);
	return e;
}

function MultipleInline(e, prop)
{
	e = Multiple(e, prop);
	return e;
}

function MultipleBlock(e, prop)
{
	e = Multiple(e, prop);
	
	for(var n = 0; n < e.rows.length; n++)
	{
		var row = e.rows[n];
		row.removeRow.onclick = function()
		{
			var current = this.parentNode;
			while(current.nodeName.toUpperCase() != 'TBODY')
				current = current.parentNode;
			current.parentNode.removeChild(current);
			return false;
		}
	}

	return e;
}


function Multiple(e, prop)
{
	e = Input(e, prop); // should extend Group, but initializing fields array is causing trouble
//	alert(e.addRow+' '+prop.addRow);
//	e.addRow = Submit(null, prop.addRow);
//	alert(e.addRow);
//for(var key in e.fields)
//	if(e.fields[key] == e.addRow)
//		alert(key+' '+e.fields[key]+' '+e.addRow);

	for(var n = 0; n < e.rows.length; n++)
	{
		var row = e.rows[n];
		for(var key in row)
		{
			var obj = row[key];
//			alert(obj);
			if(obj != null)
			{
				if(window[obj.jsFunc])
					row[key] = eval(obj.jsFunc)(null, obj);
				else ce(document.body).at('undefined javascript function: '+obj.jsFunc+' '+obj['class']+' '+key).ac(ce('br'));
			}

		}

		row.removeRow.onclick = function()
		{
			var current = this.parentNode;
			while(current.nodeName.toUpperCase() != 'TR')
				current = current.parentNode;
			ce(current).rs();
			return false;
		}
	}

	e.check = function()
	{
		var errors = [];
		for(var n = 0; n < e.rows.length; n++)
		{
			var row = e.rows[n];
			var rowErrors = [];
			for(var key in row)
			{
				if(row[key]) // hidden fields are null
				{
					var error = row[key].check();
					if(error)
						rowErrors.push(error);
				}
			}
			if(rowErrors.length)
				errors.push(rowErrors);
		}
		return errors.length ? errors : null;
	};
	
	
	
/*
	e.addRow.onclick = function()
	{
		var table = e.getElementsByTagName('table')[1];
		var tr = ce('tr'); // var temp = tr.innerHTML = e.rowTemplate.replace(/<tr[^>]*>|<\/tr>/g, '');
		var matches = e.rowTemplate.match(/<td[^>]*>(.*?)<\/td>/g);
		for(var n = 0; n < matches.length; n++)
		{
			var td = ce('td'); 
			td.innerHTML = matches[n].replace(/<td[^>]*>|<\/td>/g, ''); 
			ac(td, tr);
		}
		ac(tr, table.tBodies[0]);
		alert(tr+' '+table.tBodies[0]+'\n\n'+e.rowTemplate+'\n\n'+"\n\n"+tr.innerHTML);
		return false;
		
		var row = {};
		for(var key in e.fields)
		{
			var obj = e.fields[key];
			if(obj.jsFunc && typeof(window[obj.jsFunc]) == 'function')
			{
			alert(obj.id+'\n'+tr.innerHTML);
				var field = row[key] = eval(obj.jsFunc)(null, obj);
				
				field.id += '_'+(e.idCounter++);
				field.name = (e.name ? e.name+'[rows]' : 'rows')+'['+e.rows.length+']['+field.name+']';
				alert(field.id+' '+field.name+' '+field);
			}
			else alert(obj.jsFunc);
		}
		rows.push(row);
		return false;
	};
//	*/
	return e;
}

function PrimaryKey(e, prop)
{
	e = Hidden(e, prop);
	return e;
}
function Hidden(e, prop)
{
	e = Input(e, prop);
	return e;
}
function HiddenURL(e, prop)
{
	e = Hidden(e, prop);
	return e;
}

function CompactRange(e, prop)
{
	e = Text(e, prop);
	return e;
}

function CompactRangeNumeric(e, prop)
{
	e = CompactRange(e, prop);
	return e;
}

function DateCompactRange(e, prop)
{
	return Range(e, prop);
}

function Range(e, prop)
{
	e = Input(e, prop);
	if(window[e.fieldType])
	{
		e.minField = window[e.fieldType](null, prop.minField);
		e.maxField = window[e.fieldType](null, prop.maxField);
	}
	
	e.getValue = function()
	{
		return {min:e.minField.value, max:e.maxField.value};
	};
	return e;
}

function NumericRange(e, prop)
{
	e = Range(e, prop);
	return e;
}

function NumericMoneyRange(e, prop)
{
	e = NumericRange(e, prop);
	return e;
}

function SelectInteger(e, prop)
{
	e = Select(e, prop);
	return e;
}

function Select(e, prop)
{
	e = Input(e, prop);
	
/*	var check = e.check;
	e.check = function()
	{
		alert(prop.required+' '+prop.message+' '+prop.defaultValue+' '+e.selectedIndex+' '+check());
	};
*/
	return e;
}

function Submit(e, prop)
{
	e = Input(e, prop);
	return e;
}

function Upload(e, prop)
{
	e = Input(e, prop);
	e.change = Submit(null, prop.change);
	e.hiddenF = Hidden(null, prop.hidden);
	e.iframe = window[e.name] = ce('iframe').sp('className', e.className).sp('src', e.frameSrc);
	e.change.onclick = function() { showIframe(); return false; };

	var links = e.parentNode.getElementsByTagName('a');
	var link = links.length ? ce(links[0]) : null;

	e.iframe.preview = showPreview;
	if(e.style.display == '')
		showIframe();

	e.check = function()
	{
		if(prop.required && e.iframe.parentNode == e.parentNode)
			return e.error = 'Please wait for the file to finish uploading';
		return null;
	};
	
	return e;

	function showPreview(href)
	{
		e.change.st('display', '');
		e.hiddenF.value = href; 
		e.iframe.rs();
		ce(e.parentNode).ib(link = ce('a').sp('href', href).at(href), e.change);
	}
	
	function showIframe()
	{
		e.st('display', 'none');
		e.change.st('display', 'none');
		e.hiddenF.value = '';
		ce(e.parentNode).ac(e.iframe);
		if(link)
			link = link.rs();
	}
}
/*
function Upload(e, prop)
{
	e = Input(e, prop);
	e.change = Submit(null, e.change);
	e.hidden = Hidden(null, e.hidden);
	e.iframe = window[e.name] = ce('iframe').sp('className', e.className).sp('src', e.frameSrc);
	e.change.onclick = function() { showIframe(); return false; };

	var links = e.parentNode.getElementsByTagName('a');
	var link = links.length ? ce(links[0]) : null;

//	return e;
	
	e.iframe.preview = showPreview;
	if(e.style.display == '')
		showIframe();

	e.check = function()
	{
		if(prop.required && e.iframe.parentNode == e.parentNode)
			return e.error = 'Please wait for the file to finish uploading';
		return null;
	};
	
	return e;

	function showPreview(href)
	{
		e.change.st('display', '');
		e.hidden.value = href; 
		e.iframe.rs();
		ce(e.parentNode).ib(link = ce('a').sp('href', href).at(href), e.change);
	}
	
	function showIframe()
	{
		e.st('display', 'none');
		e.change.st('display', 'none');
		e.hidden.value = '';
		ce(e.parentNode).ac(e.iframe);
		if(link)
			link = link.rs();
	}
}
*/

function Email(e, prop)
{
	e = Text(e, prop);

	var check = e.check;
	e.check = function()
	{
		if(e.required)
		{
			if(e.value.match(/^[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\_\`\{\|\}\~]+(\.[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\_\`\{\|\}\~]+)*@[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\_\`\{\|\}\~]+(\.[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\_\`\{\|\}\~]+)+$/) == null)
			{
				e.error = 'This is not a valid email address';
				e.parentNode.appendChild(e.getError());
				return e.error;
			}
			return check();
		}
	};

	return e;
}

function Composite(e, prop)
{
	e = Group(e, prop);
	if(prop.required != true)
		for(var key in prop.fields)
			prop.fields[key].required = false;// = function() { return NULL; }
			
	return e;
}

function Address(e, prop)
{
	e = Composite(e, prop);
	return e;
}

function Phone(e, prop)
{
	e = Composite(e, prop);
	if(e.required == false)
		prop.num1.required = prop.num2.required = prop.num3.required = false;

	var input1 = Text(null, prop.fields.num1);
	var input2 = Text(null, prop.fields.num2);
	var input3 = Text(null, prop.fields.num3);

//	element.focus    = function() { input1.focus(); };
//	element.getValue = function() { return input1.value+(prop.sep || '-')+input2.value+(prop.sep || '-')+input3.value; };
//	element.setValue = setValue;
//	element.check    = function() { return (prop.e && (input1.value.length != 3 || input2.value.length != 3 || input3.value.length != 4)) ? prop.e : false; };
	
	function setValue(values)
	{
		if(values instanceof String)
		{
			var match = values.match(/([\d]{3})[^\d]*([\d]{3})[^\d]*([\d]{4})/);
			values = (match) ? [match[1], match[2], match[3]] : [];
		}
		else if((values instanceof Array) == false)
			values = [];
		input1.value = (values.length > 0) ? values[0] : '';
		input2.value = (values.length > 1) ? values[1] : '';
		input3.value = (values.length > 2) ? values[2] : '';
	}

	var keyCode, tab = false, shift = false, stop = false;
	
	input1.onkeydown = input2.onkeydown = input3.onkeydown = function(ev)
	{
		stop = false;
		keyCode = (ev) ? ev.which : event.keyCode;
		if(keyCode == 9)
		{
			tab = true;
			shift = (ev) ? ev.shiftKey : event.shiftKey;
			return ((this == input3 && shift == false && (input3.value.length > 0 || input1.value.length == 0)) || (this == input1 && shift));
		}
		else tab = false;

		if(keyCode == 8 && this.value.length == 0)
		{
			if(this == input2)
				input1.focus();
			else if(this == input3)
				input2.focus();
		}
	};
	
	input1.onkeyup = function() 
	{
		if(stop)
			return false;
		if(tab)
		{
			stop = true;
			if(shift == false)
			{
				input2.focus();
				input2.select();
			}
		}
		else if(input1.value.length == 3)
		{
			input2.focus();
			input2.select();
		}
	};
	input2.onkeyup = function() 
	{
		if(stop)
			return false;
		if(tab)
		{
			stop = true;
			if(shift)
			{
				input1.focus();
				input1.select();
			}
			else if(input2.value.length > 0 || input1.value.length == 0)
			{
				input3.focus();
				input3.select();
			}
			return false;
		}
		else if(input2.value.length == 3)
		{
			input3.focus();
			input3.select();
		}
	};
	input3.onkeyup = function() 
	{
		if(stop)
			return false;
		if(tab)
		{
			stop = true;
			if(shift)
			{
				input2.focus();
				input2.select();
			}
		}
	};
	
	return e;
}

function PhoneE(e, prop)
{
	e = Phone(e, prop);
	var input1 = Text(null, prop.fields.num1);
	var input2 = Text(null, prop.fields.num2);
	var input3 = Text(null, prop.fields.num3);
	var input4 = Text(null, prop.fields.ext);
	
	var keyCode, tab = false, shift = false, stop = false;
	input4.onkeydown = input3.onkeydown = function(ev)
	{
		stop = false;
		keyCode = (ev) ? ev.which : event.keyCode;
		if(keyCode == 9)
		{
			tab = true;
			shift = (ev) ? ev.shiftKey : event.shiftKey;
			return ((this == input4 && shift == false && (input4.value.length > 0 || input1.value.length == 0)) || (this == input1 && shift));
		}
		else tab = false;

		if(keyCode == 8 && this.value.length == 0)
		{
			if(this == input2)
				input1.focus();
			else if(this == input3)
				input2.focus();
			else if(this == input4)
				input3.focus();
		}
	};
;
	
	input3.onkeyup = function() 
	{
		if(stop)
			return false;
		if(tab)
		{
			stop = true;
			if(shift)
			{
				input2.focus();
				input2.select();
			}
			else if(input3.value.length > 0 || input2.value.length == 0)
			{
				input4.focus();
				input4.select();
			}
			return false;
		}
		else if(input3.value.length == 4)
		{
			input4.focus();
			input4.select();
		}
	};

	input4.onkeyup = function() 
	{
		if(stop)
			return false;
		if(tab)
		{
			stop = true;
			if(shift)
			{
				input3.focus();
				input3.select();
			}
		}
	};

	return e;
}

function TextArea(e, prop)
{
	if(prop.value)
		prop.value = prop.value.replace(/<br c="nl" \/>/g, "\n");
	e = Text(e, prop);
	
	return e;
}

function Password(e, prop)
{
	e = Text(e, prop);
	return e;
}

function Href(e, prop)
{
	return Text(e, prop);
}

function Text(e, prop)
{
	e = Input(e, prop);
	var className = e.className;

	var temp = (e.parentNode.getElementsByClassName) ? e.parentNode.getElementsByClassName('error') : getElementsByClassName(e.parentNode, 'div', 'error');
	if(temp.length)
		e.errorObj = ce(temp[0]);
	
	var onfocus = e.onfocus;
	e.onfocus = function()
	{ 
		if(onfocus) onfocus();
		e.className = e.className+' focused';
		if(e.errorObj)
			e.errorObj = e.errorObj.rs();
	};
	var onblur = e.onblur;
	e.onblur = function()
	{
		if(onblur) onblur();
		e.className = e.className.replace(/\s*focused/, '');
		if(e.check())
			e.parentNode.appendChild(e.getError());
	}
/*		
	try
	{
		e.addEventListener('focus', function() 
		{ 
			e.className = e.className+' focused';
			if(e.errorObj)
				e.errorObj = e.errorObj.rs();
		}, true);
		e.addEventListener('blur', function() 
		{
			e.className = e.className.replace(/\s*focused/, '');
			if(e.check())
				e.parentNode.appendChild(e.getError());
		}, true);
	}
	catch(error) { }
	*/
	var check = e.check;
	e.check = function()
	{
		if(e.required)
		{
			if(e.length && e.length != e.value.length)
				return e.error = 'must be '+e.length+' characters';
			if(e.minlength && e.minlength > e.value.length)
				return e.error = 'must be at least '+e.minlength+' characters';
			return check();
		}
	};
	
	return e;
}

function CompactRangeDate(e, prop)
{
	e = CompactRange(e, prop);
	return e;
}

function DateCompact(e, prop)
{
	e = Text(e, prop);
	return e;
}

function Numeric(e, prop)
{
	e = Text(e, prop);
	var check = e.check;
	e.check = function()
	{
		if(e.required)
		{
			if(e.value.match(/^\-?(\.\d+|\d+\.?\d*?)$/) == null)
				return e.error = e.message;
			return check();
		}
	};
	
	return e;
}

function NumericMoney(e, prop)
{
	e = Numeric(e, prop);
	var value = e.value;
	
	var onkeyup = e.onkeyup;
	e.onkeyup = function(ev)
	{
		if(onkeyup) 
			onkeyup();
		var keyCode = ev ? ev.keyCode : event.keyCode;
//		printR(keyCode+' ', e.parentNode);
//printR('['+String.fromCharCode(ev.keyCode)+' '+ev.keyCode+'] ', e.parentNode);
		if(document.selection)
		{
			var bm = document.selection.createRange().getBookmark();
			var sel = e.createTextRange();
			sel.moveToBookmark(bm);
			var sleft = e.createTextRange();
			sleft.collapse(true);
			sleft.setEndPoint("EndToStart", sel);
			e.selectionStart = sleft.text.length
			e.selectionEnd = sleft.text.length + sel.text.length;
			e.selectedText = sel.text;
		}
		if(e.value && e.selectionEnd >= e.value.length && keyCode >= 48 || keyCode == 8)// && e.value.match(/\d$/))
			e.value = format(parseFloat(e.value.replace(/[^\d\.]/g, '')), prop.decimalPlaces);
	};
	
	var onblur = e.onblur;
	e.onblur = function() 
	{ 
		if(onblur) onblur(); 
		e.value = format(parseFloat(e.value.replace(/[^\d\.]/g, '')), prop.decimalPlaces);
		if(value != e.value && e.form.onchange)
			e.form.onchange();
	};
	
	return e;
	
	function format(num, places)
	{
		if(isNaN(num))
			return '';

		var out = '';
		var match = (num.toFixed(places)+'').match(/(\d+)\.?(\d*)/);
		num = match[1];
		dec = match[2];
		var len = num.length;
		for(var n = 1; n <= len; n++)
			out = ((n % 3) == 0 && len > 4 && n < len ? ',' : '')+num.charAt(len - n) + out;
		return (out != 0 ? '$' : '')+out+(parseInt(dec) ? '.'+dec : '');
	}
}

function Integer(e, prop)
{
	e = Text(e, prop);
	var check = e.check;
	e.check = function()
	{
		if(e.required)
		{
			if(e.value.match(/^\-?\s*\d+\s*$/) == null)
				return e.error = e.message;
			return check();
		}
	};
	
	return e;
}

function TextAutoKV(e, prop)
{
	e = TextAuto(e, prop);
	return e;
}
function TextAutoMultipleKV(e, prop)
{
	e = TextAutoKV(e, prop);
	return e;
}
function TextAutoMultiple(e, prop)
{
	e = TextAuto(e, prop);
	return e;
}

TextAuto.cache = {};
function TextAuto(e, prop)
{
	e = Text(e, prop);
	var name = e.name;
	e.parentNode.appendChild(ce('br')); // necessary for IE6 craziness
	var ul = ce('ul').sp('className', 'textAuto'); e.parentNode.appendChild(ul);
	
	var limit = e.displayLimit || 15;
	var offset = 0;
	var index = 0, dataSet;
	var last  = e.value;
	var lastSelection = e.value;
	var timerID, lock = false;
	var onLoadHandler = null;
	var valueAtFocus;

	var cacheKey = name;
	if(TextAuto.cache[cacheKey] == undefined)
		TextAuto.cache[cacheKey] = {};
	var cache = TextAuto.cache[cacheKey];

	var onblur = e.onblur;
	e.onblur = function()
	{
		if(e.onblur)
			onblur();
		e.name = name;
		window.setTimeout(function() { ul.st('display', 'none'); }, 500);
		if(e.value != valueAtFocus && e.form.onchange)
			e.form.onchange();
	};
//	var onclick = e.onclick;
//	e.onclick = function() { if(onclick) onclick(); lookup(); };
	var onfocus = e.onfocus;
	e.onfocus = function() { if(onfocus) onfocus(); valueAtFocus = e.value; ul.cl(); ul.st('display', ''); lookup(); e.name = Math.random(); };
	
	try
	{
//		e.addEventListener("click", function preventDef(event) { event.preventDefault(); }, false);
/*		
		e.addEventListener('blur', function()
		{
			e.name = name;
//			if(document.all == null) // for FireFox
//				e.focus();
			window.setTimeout(function() { ul.st('display', 'none'); }, 500);
			
			if(e.value != valueAtFocus && e.form.onchange)
				e.form.onchange();
		}, true);
*/
//		e.addEventListener('click', function() { lookup(); }, true);
//		e.addEventListener('focus', function() { valueAtFocus = e.value; ul.cl(); ul.st('display', ''); e.name = Math.random(); }, true);
	}
	catch(exception) { }
	
	e.onkeydown = function(ev)
	{
		var keyCode = ev ? ev.which : event.keyCode;
		if(keyCode == 38) // up
		{
			killEvent(ev);
			moveToIndex(index - 1);
			
			return false;
		}
		else if(keyCode == 40) // down
		{
			killEvent(ev);
			moveToIndex(index + 1);
			return false;
		}
		else if(keyCode == 13 && index > 0) // enter when an item is selected
		{
			killEvent(ev);
			ul.childNodes[index - 1].onclick();
			return false;
		}
		else if(keyCode == 13)
		{
			e.name = name;
			return true;
		}
		else if(keyCode == 9) // tab
		{
//			alert(prop.tabComplete+' '+last+' '+ul.childNodes.length);
//			if(prop.tabComplete && index)
			if(index)
				ul.childNodes[index - 1].onclick();
			ul.st('display', 'none');
			return true;
		}
		else return true;
	};
	
	e.onkeyup = function(ev)
	{
		var keyCode = ev ? ev.which : event.keyCode;
		if(keyCode == 38 || keyCode == 40 || keyCode == 13 && index > 0)
		{
			killEvent(ev);
			return false;
		}
			
		var key = e.value.toLowerCase();
		if(prop.delimiter)
		{
			key = key.replace(new RegExp('^.*'+prop.delimiter+'\\s*'), '');
//			var elem = ce(e.parentNode); elem.at('_'+key+'_ ');
		}
		var data = cache[key];

		if(timerID)
			window.clearTimeout(timerID);
		
		if(data)
		{
			last = key;
			show(data);
		}
		else if(lock == false)
		{
			var temp = key;
			while(temp.length > 1 && prop.jsFilter != 'JS_MATCH') // looks back through the cache to see if there is anything it can display and filter
			{
				if((data = cache[temp = temp.substr(0, temp.length - 1)]) && data.length < limit)
				{
					last = key;
					show(data);
					filter(key);
					return;
				}
			}
			
			if(key.length < last.length || filter(key)) // if there are less than limit items displayed
				timerID = window.setTimeout(lookup, 100);

			last = key;
		}
		else onLoadHandler = function() { e.onkeyup(ev); };
	};
	
	return e;
	

	function lookup()
	{
		var key = e.value.toLowerCase();
		if(prop.delimiter)
		{
			key = key.replace(new RegExp('^.*'+prop.delimiter+'\\s*'), '');
//			var elem = ce(e.parentNode); elem.at('_'+key+'_ ');
		}
		
		if(e.isStatic)
		{
			var len = key.length;
			var options = [];
			for(var n = 0; n < e.options.length; n++)
				if(e.options[n].substring(0, len).toLowerCase() == key)
					options.push(e.options[n]);
			show(cache[key] = options);
		}
		else // must load from server, e.options is the service path
		{
			lock = true;
			var request = (window.XMLHttpRequest) ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");
			request.onreadystatechange = onStateChange;
			request.open('GET', e.options+'?key='+key, true);
			request.send('');
		}
		
		function onStateChange()
		{
			if(request.readyState == 4) // 4 means done loading
			{
				if(request.status == 200)
				{	
					var data = eval(request.responseText);
//					alert(data);
//					alert(request.responseText);
					show(cache[key] = data);
				}
				else alert('Load Failed: status: '+request.status+": "+request.statusText);
			}
			lock = false;
		}
	}

	function selectIndex(index2)
	{
		if(prop.delimiter && e.value.indexOf(prop.delimiter) > -1)
		{
			last = dataSet[index2];
			e.value = e.value.replace(new RegExp('('+prop.delimiter+'\\s*)[^'+prop.delimiter+']*$'), '$1'+last+prop.delimiter);
		}
		else
		{
			e.value = last = dataSet[index2];
			e.value += prop.delimiter || '';
		}
		
		lastSelection = last;
		e.focus();
		ul.st('display', 'none');
		index = 0;
		//last = null;
		e.form.onchange();
	}
	
	function moveToIndex(newIndex) // index starts at 1, 0 for no selection
	{
		var lis = ul.childNodes;
		if(lis.length)
		{
			if(index > 0 && index <= lis.length)
				lis[index - 1].className = '';
			
			if(newIndex < 0)
				index = lis.length;
			else if(newIndex > lis.length)
				index = 1;
			else index = newIndex;
			
			if(index)
				lis[index - 1].className = 'current';
		}
	}
	
	function filter(key)
	{
		var key = key.toLowerCase();
		var length = key.length;
		var lis = ul.childNodes;

		for(var n = lis.length - 1; n >= 0; n--)
		{
			if(prop.jsFilter == 'JS_MATCH')
			{
				if(lis[n].innerHTML.match(new RegExp('(^| )'+key, 'i')) == null)
					lis[n].rs();
			}
			else if(lis[n].innerHTML.substr(0, length).toLowerCase() != key)
				lis[n].rs();
		}
		return lis.length < limit;
	}
	
	function show(data)
	{
		dataSet = data;
		index = 0;
		ul.cl();
		ul.st('display', '');
		for(var n = 0; n < data.length && n < limit; n++)
			ul.ac(getLi(n));
		function getLi(index) { return ce('li').at(data[n]).sp('onclick', function() { selectIndex(index); }); }

		if(prop.tabComplete && dataSet.length && e.value && (prop.delimiter == null && e.value != lastSelection || prop.delimiter && e.value.match(new RegExp(prop.delimiter+'$')) == null))
			moveToIndex(1);
	}
}




function killEvent(ev)
{
	if(ev)
	{
		ev.stopPropagation();
		ev.preventDefault();
	}
	else
	{
		window.event.cancelBubble = true;
		window.event.returnValue = false;
	}
}

function clone(obj)
{
	if(typeof(obj) == 'object')
	{
		if(obj instanceof Array)
		{
			var array = [];
			for(var n = 0; n < obj.length; n++)
				array.push(clone(obj[n]));
			return array;
		}
		else
		{
			var newObj = new Object();
			for(var key in obj)
				newObj[key] = clone(obj[key]);
			return newObj;
		}
	}
	else return obj;
}
