//-------------------------------------------------------------------------
// LookupTextField
//-------------------------------------------------------------------------

// This is a general purpose gui component that allows presenting  special
// values inside of a popup when the user fills a certain text field.
// The argument textFieldNodeOrId is the DOM node or the ID of a text input
// field, the second argument popupNodeOrId (optional) is the DOM node or ID
//  of a html container in which the popup should be viewed.
// The appearance of the popup can be influenced by both: using css classes
// or using explicit color arguments instead of css classes.
// The data source of the popup can be either static data or a function which
// returns data or the response of http request (XML or JSON format).
// Additionally you can filter the popup option set depending on the user input.
function LookupTextField(textFieldNodeOrId, popupNodeOrId) {
	//----- local variables -----

	// Reference to the current instance of LookupTextField - for closures
	var me = this;

	// DOM node of the text field
	var textField = null;

	// DOM node of the popup container element
	var popup = null;

	// Flag that indicates whether popup container has been passed in constructor
	// or whether is created dynamically
	var popupIsDynamic = false;

	// Old value of the textField, for undo when pressing esc
	var oldValue = '';

	// Index of the active row
	var activeRowIdx = -1;

	// Index of the hovered row
	var hoverRowIdx = -1;

	// Flag whether popup is open or not
	var popupOpened = false;

	// Has the popup closed by force (pressing esc, mouse button, etc.)
	var forcedClosedPopup = true;

	// If a search term is selected, should the form be submitted?
	var submitOnSelect = false;

	// IFrame for non-XMLHttpRequest communication - only used for lookup
	// mode 'get'
	var iFrame = null;

	// XMLHttpRequest for ajax communication - only used for lookup mode 'get'
	var xmlHttp = null;

	// Mode how to retrieve data
	// - data: static data array
	// - get: http 'get' request
	// - func: data retrieval function
	var lookupMode = 'data';

	// Data for popup - static data for lookup mode 'data' otherwise filled by
	// lookup
	var lookupData = null;

	// Url needed for http request - only used for lookup mode 'get'
	var lookupSourceUrl = '';

	// Lookup funcion and object - only used for lookup mode 'func'
	var lookupFunction = null;
	var lookupFunctionObject = null;

	// Minimal length to start lookup
	var lookupMinLength = 0;

	// Ajax communication is allowed - only used for lookup mode 'get'
	var allowHttpRequest = true;

	// IFrame communication is allowed - only used for lookup mode 'get'
	var allowIFrameRequest = true;

	// Type of filter for constraining presented rows.
	// Allows values are: null (no filter - default), 'startsWith',
	// 'contains' or a function who expects two arguments
	// (text field value and row value) and returns a boolean value
	// that indicates whether a row should be shown
	var filter = null;

	// Number of the bound column (1-based).
	// Text field value will be retrieved from that column.
	var boundColumn = 1;

	// Type of the returned lookup data,
	// currently allowed are 'native' (which is default),
	// 'JSON', 'XML' and 'trimmed XML'.
	// The latter type lookup returns XML node where all empty
	// string text nodes etc. have been removed.
	var lookupType = 'native';

	// Data processing function after lookup
	var dataProcessor = function(data) {
		return (lookupType == 'native'  || lookupType == 'JSON' ? data : null);
	}

	// Object to be used as 'this' when invoking data processing function
	var dataProcessorObj = this;

	// data after processing
	var processedData = null;

	// If useDefaultStyle is true then the actual appearance of the
	// popup is set by using style attributes not css classes.
	// If it is false then css class names are used.
	var useDefaultStyle = true;

	// If debugMode is true some alerts will show the current state of process.
	// This maked debugging much easier.
	var debugMode = false;

	// The default css class names to influence the style of the popup.
	// They will only be used when 'useDefaultStyle ' (see above) is false.
	// You can customize these class names by using
	// function 'setCssClasses' (see below).
	// cssClassesColumns is an array of all different class names
	// for the table colums in the popup.
	// cssAutoLevel determinates how strict only css classes are used:
	// 0: very strict, really only css classes are used for popup layout
	// 1: only very basic values (i.e. cursor) are set via style attributes
	// 2: same as 1 but also important position values are set
	//    automatically via style attributes
	// 3: same as 2 but also fonts are set automatically
	// 4: same as 3 but also a grey border is set by styles
	// 5: same as 4 but also text color is set to very dark gray
	var cssClassPopup = 'uiLtfPopup';
	var cssClassTable = 'uiLtfTable'
	var cssClassRow = 'uiLtfRow';
	var cssClassActiveRow = 'uiLtfActiveRow';
	var cssClassHoverRow = 'uiLtfHoverRow';
	var cssClassColumn = 'uiLtfColumn';
	var cssClassesColumns = [];
	var cssAutoLevelMax = 5;
	var cssAutoLevel = cssAutoLevelMax;

	// These are the default colors determinating the color appearance
	// of the component.
	// They will only be used when 'useDefaultStyle' (see above is) true.
	// You can customize these colors by using function 'setDefaultStyleColors'.
	var defaultColorText = '#444';
	var defaultColorPopup = '#f8f8f8';
	var defaultColorPopupBorder = '#444';
	var defaultColorHover = '#eaeaea';
	var defaultColorActiveRow = '#d8d8d8';


	//----- local functions	-----

	// Will show a message, but only if debugMode is true
	var debug = function(msg) {
		if (debugMode) {
			alert(msg);
		}
	}

	// Strips off whitespaces on left and right side of a string
	var trim = function(value) {
		var retVal = '';

		if (value !== undefined && value !== null
				&& typeof value !== 'object') {
			retVal = ('' + value).replace(/^\s+|\s+$/g, '');
		}

		return retVal;
	}

	// Trims a passed XML DOM node, which means the content of all text nodes
	// is trimmed (see function 'trim' above) and afterwards all empty
	// text nodes are removed.
	// Expects an XML DOM node to be passed and also returns a XML DOM node.
	var trimNode = function(node) {
		if (node && node.nodeType == 3) { // text-node
			node.nodeValue = trim(node.nodeValue);
		} else if (node && node.hasChildNodes()) {
			for (var i = 0; i < node.childNodes.length; ++i) {
				var child = node.childNodes[i];

				if (child.nodeType == 3 && trim(child.nodeValue) === '') {
					node.removeChild(child);
					--i;
				} else {
					trim(node);
				}
			}
		}
	}

	// Adds an event handler to an XML DOM node.
	var addEventHandler = function(obj, eventName, func) {
		if (document.addEventListener) {
			obj.addEventListener(eventName,
						func, false);
	    } else {
	    	var oldHandler = obj['on' + eventName];
	    	var funcToCall = func;

	    	if (oldHandler) {
	    		funcToCall = function() {
	    			oldHandler.apply(oldHandler, arguments);
	    			func.apply(oldHandler, arguments);
	    		}
	    	}

			obj['on' + eventName] = funcToCall;
		}
	}


	// Static function that calculates a position value of object
	// elem: The DOM element itself
	// whichPos: Determinates which position value is wanted
	//           (allowed values: left, right, top, bottom)
	var getElemPos = function(elem, whichPos) {
		var posVal = null;

		switch(('' + whichPos).toLowerCase(	)){
	    	case 'left':
				whichPos = 'Left';
	    		break;
	    	case 'right':
	    		whichPos = 'Right';
	    		break;

	    	case 'top':
				whichPos = 'Top';
	    		break;

	    	case 'bottom':
	    		whichPos = 'Bottom';
	    		break;

	    	default:
	    		whichPos = null;
	    }

		if (whichPos && elem) {
			var posVal = elem.offsetLeft;
			parOff = elem.offsetParent;

		    while (parOff != null) {
	    	    posVal += parOff['offset' + whichPos];
	    		parOff = parOff.offsetParent;
		    }
		}

	    return posVal;
	}

	// Closes the popup and sets all corresponding properties to neutral value.
	var clearPopup = function(forced) {
		popupOpened = false;
		forcedClosedPopup = !!forced;

		if (lookupMode != 'data') {
			lookupData = null;
		}

		processedData = null;
		activeRowIdx = -1;

		if (popup) {
			while (popup.hasChildNodes()) {
				popup.removeChild(popup.childNodes[0]);
			}
		}

		if (popupIsDynamic) {
			try {
				document.body.removeChild(popup);
			} catch(ignore) {
			}
		}
	}

	// Closes the hidden IFrame which was (perhaps) used for communication.
	// Dependent values will be set to default.
	var closeIFrame  = function() {
	   if (iFrame) {
	   iFrame.src = null;
			iFrame.onLoad = null;

			if (iFrame.parentNode) {
				iFrame.parentNode.removeChild(iFrame);
			}
		}

		iFrame = null;
	}

	// Aborts the xml http request which was (perhaps) used for communication.
	// Dependent values will be set to default.
	var closeXmlHttp = function() {
		if (xmlHttp) {
			xmlHttp.abort();
		}

		xmlHttp = null;
	}


	// filter recordsets if necessary
	var filterData = function(textFieldValue, recordsets) {
		retVal = null;
		var fieldLc = trim(textFieldValue).toLowerCase();

		if (!recordsets) {
			recordsets = [];
		}

		if (fieldLc === '' || !filter) {
			retVal = recordsets;
		} else {
			var newRecordsets = [];

			for (var i = 0; i < recordsets.length; ++i) {
				var recordset = recordsets[i];

				if (typeof recordset == 'string') {
					recordset = [recordset];
				}

				var rowLc = trim(recordset[boundColumn - 1])
						.toLowerCase();

				if (filter == 'startsWith'
						&& rowLc.substring(0, fieldLc.length) == fieldLc
						|| filter == 'contains'
						&& rowLc.indexOf(fieldLc) != -1
						|| typeof filter == 'function'
						&& filter(fieldLc, rowLc)) {
					newRecordsets.push(recordset);
				}
			}

			retVal = newRecordsets;
		}

		return retVal;
	}

	// This will process the responsed data.
	// This is called after  'requestDataAndProcess'.
	var processData = function() {
		debug("Entering function 'processData'...");

		if (dataProcessor && lookupData) {
			processedData = dataProcessor(lookupData);
		} else {
			processedData = lookupData;
		}

		processedData = filterData(textField.value, processedData);

		if (!processedData || processedData === []) {
			processedData = null;
			recordset = [];
		}

		var recordsets = processedData;
		var colCount = 0;

		if (recordsets && recordsets.length) {
			for (var i = 0; i < recordsets.length; ++i) {
				if (typeof recordsets[i] == 'string') {
					colCount = Math.max(colCount, 1);
				} else if (recordsets[i] && recordsets[i].length) {
					colCount = Math.max(colCount, recordsets[i].length);
				}
			}
		}

		if (recordsets && recordsets.length > 0) {
			var container = document.createElement('div');
			var table = document.createElement('table');
			var header = document.createElement('div');
			var thead = document.createElement('thead');
			var tbody = document.createElement('tbody');

			if (cssAutoLevel > 0) {
				with (container.style) {
					position = 'absolute';
					zIndex = '9999';
					marginTop = '1px';
				}
			}

			with (table.style) {
				if (cssAutoLevel > 0) {
					cssFloat = 'none';
					zIndex = '9999';
				}

				if (cssAutoLevel >= 3) {
					fontFamily = 'Helvetica, Arial, Sans-Serif';
					fontSize = '9pt';
				}

				if (cssAutoLevel >= 5) {
					color = '#444';
				}
			}

			if (useDefaultStyle) {
				with (container.style) {
					backgroundColor = defaultColorPopup;
					border = '1px solid ' + defaultColorPopupBorder;
				}
			} else {
				container.className = cssClassPopup;
				table.className = cssClassTable;

				if (cssAutoLevel >= 4) {
					container.style.border = '1px solid #666';
				}
			}

			table.setAttribute('cellPadding', '0');
			table.setAttribute('cellSpacing', '0');
			table.setAttribute('border', '0');
			table.appendChild(thead);
			table.appendChild(tbody);

			for (var i = 0; i < recordsets.length; ++i) {
				var recordset = recordsets[i];

				if (typeof recordset == 'string') {
					recordset = [recordset];
				}

				if (!recordset || recordset.constructor !== Array) {
					continue;
				}

				var row = document.createElement('tr');

				if (cssAutoLevel > 0) {
					try {
						row.style.cursor = 'pointer';
					} catch(ignore) {
					}
 				}

				if (!useDefaultStyle) {
					row.className = cssClassRow;
				}

				var select = function(value) {
					return function() {
						textField.value = value;
						clearPopup();

						if (textField.form && submitOnSelect) {
							textField.form.submit();
						}
					}
				}

				var mouseOut = function(row, idx) {
					return function() {
						hoverRowIdx = -1;

						if (useDefaultStyle) {
							with (row.style) {
								if (idx == activeRowIdx) {
									backgroundColor = defaultColorActiveRow;
								} else {
									backgroundColor = '';
								}
							}
						} else {
							row.className = cssClassRow;

							if (idx == activeRowIdx) {
								row.className += ' ' + cssClassActiveRow;
							}
						}
					}
				}

				var mouseOver = function(row, idx) {
					return function()  {
						hoverRowIdx = idx;

						if (useDefaultStyle) {
							with (row.style) {
								backgroundColor = defaultColorHover;
							}
						} else if  (cssClassHoverRow !== '') {
							row.className += ' ' + cssClassHoverRow;
						}
					}
				}

				addEventHandler(row, 'mousedown',
						select(recordset[boundColumn - 1]));
				addEventHandler(row, 'mouseout', mouseOut(row, i));
				addEventHandler(row, 'mouseover', mouseOver(row, i));

				for (var j = 0; j < colCount; ++j) {
					var cell = document.createElement('td');
					cell.noWrap = 'true';

					if (cssAutoLevel >= 2) {
						if (j > 0) {
							cell.style.padding = '1px 2px 1px 20px';
						} else {
							cell.style.padding = '1px 2px';
						}
					}

					if (useDefaultStyle) {
						cell.style.color = defaultColorText;
					} else {
						cell.className = cssClassColumn;

						if (typeof cssClassesColumns[j] == 'string') {
							cell.className = cssClassColumn
									+ ' ' + cssClassesColumns[j];
						} else if (cssClassColumn !== '') {
							cell.className += ' '
									+ cssClassColumn + '_' + (j + 1);
						}
					}

					cell.appendChild(
						document.createTextNode(recordset[j]));
					row.appendChild(cell);
				}

				tbody.appendChild(row);
			}

			container.appendChild(header);
			container.appendChild(table);

			popupOpened = true;

			if (popup) {
				popup.appendChild(container);
				var clearDiv = document.createElement('div');
				clearDiv.style.clear = 'both';
				popup.appendChild(clearDiv);

				// set popup position while popup is opened
				var setPopupPosition = function() {
					if (popupOpened) {
						var textFieldLeft = getElemPos(textField, 'left');
						var textFieldTop = getElemPos(textField, 'top');
						var textFieldHeight = textField.offsetHeight;
						popup.style.zIndex = '9999';
						popup.style.position = 'absolute';
						var newTop = (textFieldTop + textFieldHeight + 1);
						var newLeft = textFieldLeft;
						popup.style.left = newLeft + 'px';
						popup.style.top = newTop + 'px';
						setTimeout(setPopupPosition, 0);
					}
				}

				if (popupIsDynamic) {
					setPopupPosition();
					document.body.appendChild(popup);
				}
			}

			addEventHandler(textField, 'blur', function() {
				clearPopup(true)
				return false;
			});
		}
	}

	// This will retrive the lookup data and calls
	// function 'processData' afterwards.
	var requestDataAndProcess = function() {
		if (lookupMode == 'data' && popupOpened && !filter) {
			return;
		}

		// Just for debugging a little request counter
		var requestCounter = document.getElementById('ltfRequestCounter');

		if (requestCounter) {
			requestCounter.value =
					(isNaN(requestCounter.value) ? 1
					: Math.floor(requestCounter.value) + 1);
		}

		debug("Entering function 'requestDataAndProcess' (lookup mode is '"
				+ lookupMode + "')...");

		clearPopup();
		closeIFrame();
		closeXmlHttp();

		if (lookupMode == 'data') {
			processData();
		} else if (lookupMode == 'func'
				&& typeof lookupFunction == 'function') {
			lookupData = lookupFunction
					.call(lookupFunctionObject, textField.value);
			processData();
		} else if (lookupMode == 'get') {
			var url = lookupUrl;
			var pos = url.indexOf('[TEXT]');

			if (pos >= 0) {
				url = url.replace('[TEXT]', escape(textField.value));
			} else {
				url += escape(textField.value);
			}

			var useHttpRequest = allowHttpRequest &&
					(document.implementation
					&& document.implementation.createDocument
					|| window.ActiveXObject);

			var useIFrame = allowIFrameRequest;

			// This will  try to communicate via IFrame if allowed.
			var communicateViaIFrame = function() {
				closeIFrame();
				iFrame = document.createElement('iframe');

				if (iFrame.style && iFrame.style.setAttribute) {
					iFrame.style.setAttribute('width', '1px');
					iFrame.style.setAttribute('width', '1px');
					iFrame.style.setAttribute('position', 'absolute');
				} else {
					iFrame.setAttribute('style', 'width: 1px; height: 1px; '
							+ 'border: 0; position: absolute');
				}

				iFrame.src = url;
				textField.parentNode.appendChild(iFrame);

				var doOnIFrameLoad = function() {
					var s = '';
					clearPopup();

					if (lookupType == 'JSON') {
						var jsonValue = null;

						if (iFrame && iFrame.contentWindow.document.body) {
							var jsonText = iFrame.contentWindow
								.document.body.textContent;

							if (typeof jsonText != 'string' || jsonText === '') {
								jsonText = iFrame.contentWindow
									.document.body.innerText;
							}
							try {
								jsonValue = eval('(' + jsonText + ')');
							} catch(ignore) {
							}
						}

						lookupData = jsonValue;
					} else {
						var node = null;
						iFrameChildren = null;

						if (iFrame) {
						    if (document.all && iFrame && iFrame.contentWindow
						    		&& iFrame.contentWindow.document
						    		&& iFrame.contentWindow.document.XMLDocument) {
								iFrameChildren = iFrame.contentWindow.document
										.XMLDocument.childNodes;
							} else if (iFrame.contentDocument) {
								iFrameChildren = iFrame.contentDocument.childNodes;
							} else if (iFrame.document) {
								iFrameChildren = iFrame.document.body.childNodes;
							}

							node = null;

							if (iFrameChildren && iFrameChildren.length) {
								node = iFrameChildren[0];

								while (node && node.nodeType != 1) {
									node = node.nextSibling;
								}
							}

							if (lookupType == 'trimmed XML') {
								trimNode(node);
							}

							lookupData = node;
						}
					}

					closeIFrame();
					processData();
				}

				if (iFrame.attachEvent) {
					iFrame.attachEvent('onload', doOnIFrameLoad);
				} else {
					iFrame.onload = doOnIFrameLoad;
				}
			}

			// This will  try to communicate via XMLHttPRequest.
			// If the is not possible/not allowed it will call function
			// 'communicateViaXmlHttp' instead.
			function communicateViaXmlHttp() {
				xmlHttp = null;

				// Mozilla, Opera, Safari, IE7 etc.
				if (typeof XMLHttpRequest != 'undefined') {
				    xmlHttp = new XMLHttpRequest();
				}

				if (!xmlHttp) {
				    // Internet Explorer 6 and older
				    try {
				        xmlHttp  = new ActiveXObject("Msxml2.XMLHTTP");
				    } catch(e) {
				        try {
				            xmlHttp  = new ActiveXObject("Microsoft.XMLHTTP");
				        } catch(e) {
				            xmlHttp  = null;
				        }
				    }
				}

				if (!xmlHttp) {
					if (useIFrame) {
						communicateViaIFrame();
					}
				} else {
				    xmlHttp.open('GET', url, true);

				    xmlHttp.onreadystatechange = function () {
				    	clearPopup();
				        if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
				        	if (lookupType == 'JSON') {
				        		var jsonValue = null;
				        		jsonText = xmlHttp.responseText;

								try {
									jsonValue = eval('(' + jsonText + ')');
								} catch(ignore) {
								}

								lookupData = jsonValue;
				        	} else {

							var node = xmlHttp.responseXML;

				            if (node) {
								// find root node
					            node = node.firstChild;

								while (node && node.nodeType != 1 && !node.tagName) {
									node = node.nextSibling;
								}

					           	if (node && lookupType == 'trimmed XML') {
						           		trimNode(node);
								}
									lookupData = node;
								}
							}

							processData();
				        }
				    };

				    xmlHttp.send(null);
				}
			}

			if (!useHttpRequest) {
				if (useIFrame) {
					communicateViaIFrame();
				}
			} else {
				communicateViaXmlHttp();
			}
		}
	}

	// onKeyDown event handler for text field
	var doOnKeyDown = function(keyCode) {
		closeIFrame();

		if (keyCode == 13 || keyCode == 9) { // carriage return / tab
			clearPopup(true);
		} else if (keyCode == 38 && processedData) {
			// move up
			if (activeRowIdx > 0) {
				var rows = popup.firstChild.childNodes[1]
						.childNodes[1].childNodes;

				if (useDefaultStyle) {
					if (hoverRowIdx != activeRowIdx) {
						rows[activeRowIdx].style.backgroundColor = '';
					}

					--activeRowIdx;

					if (hoverRowIdx != activeRowIdx) {
						rows[activeRowIdx].style.backgroundColor =
								defaultColorActiveRow;
					}
				} else {
					rows[activeRowIdx].className = cssClassRow;

					if (hoverRowIdx == activeRowIdx) {
						rows[activeRowIdx].className += ' ' + cssClassHoverRow;
					}

					rows[--activeRowIdx].className = cssClassRow
							+ ' ' + cssClassActiveRow;
				}

				textField.value = processedData[activeRowIdx][0];
			}
		} else if (keyCode == 40 && !forcedClosedPopup && processedData) {
			// move down
			if (activeRowIdx < processedData.length -1) {
				var rows = popup.firstChild.childNodes[1].childNodes[1]
						.childNodes;

				if (activeRowIdx >= 0) {
					if (useDefaultStyle) {
						if (hoverRowIdx != activeRowIdx) {
							rows[activeRowIdx].style.backgroundColor = '';
						}

						++activeRowIdx;

						if (hoverRowIdx != activeRowIdx) {
							rows[activeRowIdx].style.backgroundColor =
									defaultColorActiveRow;
						}
					} else {
						rows[activeRowIdx].className = cssClassRow;

						if (hoverRowIdx == activeRowIdx) {
							rows[activeRowIdx].className +=
									' ' + cssClassHoverRow;
						}

						rows[++activeRowIdx].className = cssClassRow + ' '
								+ cssClassActiveRow;
					}
				} else {
					activeRowIdx = 0;

					if (useDefaultStyle) {
						if (hoverRowIdx == activeRowIdx) {
							rows[activeRowIdx].style.backgroundColor =
									defaultColorHover;
						} else {
							rows[0].style.backgroundColor =
									defaultColorActiveRow;
						}
					} else {
						rows[0].className = cssClassRow + ' '
								+ cssClassActiveRow;
					}
				}

				textField.value = processedData[activeRowIdx][0];
			}
		} else if (keyCode == 27 // esc
				|| textField.value.length < lookupMinLength) {
			clearPopup(true);

			if (keyCode == 27) {
				textField.value = oldValue;
			}
		} else if (textField.value.length >= lookupMinLength
				&& (textField.value !== oldValue
				|| keyCode == 40 && forcedClosedPopup)) {
			closeXmlHttp();
			closeIFrame();

			if (lookupMode != 'data') {
				clearPopup();
			}

			if (lookupType) {
				requestDataAndProcess();
			}
		}

		oldValue = textField.value;
	}


	//----- public functions -----

	// This function sets the names of the css classes used for the popup.
	// These css classes are only used when 'useDefaultStyle' is false.
	// Therefore 'useDefaultStyle' will be set to false automatically
	// when calling this function.
	// Argument valueObj
	// autoLevel determinates how strict only css classes are used:
	// 0: very strict, really only css classes are used for popup layout
	// 1: only very basic values (i.e. cursor) are set via style attributes
	// 2: same as 1 but also important position values are set
	//    automatically via style attributes
	// 3: same as 2 but also fonts are set automatically
	// 4: same as 3 but also a grey border is set by styles
	// 5: same as 4 but also text color is set to very dark gray
	this.setCssClasses = function(valueObj, autoLevel) {
		if (!valueObj) {
			valueObj = {};
		}

		useDefaultStyle = false;
		cssClassPopup = trim(valueObj.popup);
		cssClassTable = trim(valueObj.table);
		cssClassRow = trim(valueObj.row);
		cssClassActiveRow = trim(valueObj.rowActive);
		cssClassHoverRow = trim(valueObj.rowHover);
		cssClassColumn = trim(valueObj.column);
		cssClassesColumns = (valueObj.columns &&
				valueObj.columns.constructor === Array)
				? valueObj.columns : [];
		cssAutoLevel = (isNaN(autoLevel) ? 0
				: Math.max(0,
				Math.min(Math.floor(autoLevel), cssAutoLevelMax)));
	}


	// Determinates whether the appearance of the popup should be
	// in default style(without using css classes)
	// or whether it should be determinated by css classess
	this.setUseDefaultStyle = function(value) {
		if (value) {
			this.setCssClasses({}, cssAutoLevelMax);
		}

		useDefaultStyle = !!value;
	}


	// This function sets the colors used for the popup.
	// These colors are only used when 'useDefaultStyle' is true.
	// All arguments except for 'normal' are optional,
	// If you do not pass them they will be calculated automatically.
	// Automatically sets property useDefaultStyle to true
	this.setDefaultStyleColors = function(objValue) {
		if (!objValue) {
			objValue = {};
		}

		var normal = trim(objValue.bgRowActive);

		if (normal === '') {
			normal = 0xd8d8d8;
		}

		var bright = trim(objValue.bgRowHover);
		var background = trim(objValue.bgPopup);
		var textColor = trim(objValue.text);
		border = trim(objValue.border);

		if (textColor === '') {
			textColor == 0x040404;
		}

		// Pass a rgb integer value and a return a triple of the rgb values
		// will bee returned.
		var getRgb = function(val) {
			var r = Math.floor(val / 65536);
			var b = val % 256;
			var g = Math.floor((val - r * 65536) / 256);
			return [r, g, b];
		}

		// Converts rgb values a color to hsl values (both as array triples).
		// Ask wikipedia for a description of the algorithm.
		var rgbToHsl = function(rgb) {
			var red = rgb[0] / 255;
			var green = rgb[1] / 255;
			var blue = rgb[2] / 255;
			var max = Math.max(red, green, blue);
			var min = Math.min(red, green, blue);

			var diff = max - min;
			var hue = null;
			var sat = null;
			var lum = null;

			if (diff == 0) {
				hue = 0;
			} else {
				if (red == max) {
					hue = 60 * (green - blue) / diff ;
				} else if (green == max) {
					hue = 60 * (blue - red) / diff + 120;
				} else {
					hue = 60 * (red - green) / diff + 240;
				}

				if (hue < 0) {
					hue += 360;
				}
			}

			lue = (max + min) / 2;

			if (lue == 0 || diff == 0) {
				sat = 0;
			} else if (lue > 0 && lue  <= 0.5)  {
				sat = diff / 2 / lue;
			} else {
				sat = diff / (2 - 2 * lue);
			}

			return [hue, sat, lue];
		}

		// Converts hsl values a color to rgb values (both as array triples).
		// Ask wikipedia for a description of the algorithm.
		var hslToRgb = function(hsl) {
			var hue = hsl[0];
			var sat = hsl[1];
			var lue = hsl[2];
			var rgb = [0, 0, 0];

			if (sat == 0) {
				rgb = [lue, lue, lue];
			} else {
				var temp1 = null;
				var temp2 = null;
				var temp3 = null;

				if (lue < 0.5) {
					temp2 = lue * (1 + sat);
				} else {
					temp2 = lue + sat - (lue * sat);
				}

				temp1 = 2 * lue - temp2;
				var h = hue / 360;

				var temp3 = [h + 1 / 3, h, h - 1 /3];

				for (var i = 0; i < 3; ++i) {
					if (temp3[i] < 0) {
						temp3[i] += 1;
					} else if (temp3[i] > 1) {
						temp3[i] -= 1;
					}
				}

				for (var i = 0; i < 3; ++i) {
					if (temp3[i] < 1 / 6) {
						rgb[i] = temp1 + (temp2 - temp1) * 6 * temp3[i];
					} else if (temp3[i] >= (1 / 6) && temp3[i] < 0.5) {
						rgb[i] = temp2;
					} else if (temp3[i] >= 0.5 && temp3[i] < (2 / 3)) {
						rgb[i] = temp1 + (temp2 - temp1)
								* (2 / 3 - temp3[i]) * 6;
					} else {
						rgb[i] = temp1;
					}
				}
			}

			rgb[0] = Math.round(rgb[0] * 255);
			rgb[1] = Math.round(rgb[1] * 255);
			rgb[2] = Math.round(rgb[2] * 255);
			return rgb;
		}


		// returns a css compatible string of the passed integer value color.
		// If the passed color is null then the 'normal' color
		// and the arguments 'lueRatio' and 'satRatio'
		// are used to calculate it automatically.
		var adjustColor = function(color, lueRatio, satRatio) {
			var rgb = getRgb(normal);

			if (color) {
				colVal = rgb;
			} else {
				var hsl = rgbToHsl(rgb);

				if (!isNaN(lueRatio)) {
					hsl[2] = lueRatio;

					if (lueRatio == 1) {
						hsl[1] = 0;
					}
				}

				if (!isNaN(satRatio)) {
					hsl[1] = satRatio;

					if (satRatio == 1) {
						hsl[2] = 0;
					}
				}

				colVal =  hslToRgb(hsl);
			}

			var retVal = 'rgb(' + colVal + ')';
			return retVal;
		}

		var norm = rgbToHsl(getRgb(normal));
		var satNorm = norm[1];
		var lumNorm = norm[2];
		defaultColorText = textColor;
		defaultColorActiveRow = adjustColor(normal);
		defaultColorPopup = adjustColor(background,
				lumNorm + (1- lumNorm) * 0.9, 0.1);
		defaultColorPopupBorder = adjustColor(border, lumNorm / 5);
		defaultColorHover = adjustColor(bright,
				lumNorm + (1- lumNorm) * 0.5, satNorm * 0.9);
		this.setUseDefaultStyle(true);
	}

	// Sets the mode how to retrieve the data for the popup.
	// Allowed values for mode are:
 	// - data: static data array (option1 and option2 will be ignored)
	// - func: data retrieval function
	//        ref: function
	//        option1: subject for function if needed
	// - get: http 'get' request
	//        ref: URL to request (use [TEXT] as placeholder,
	//                             if not given text will be appended to URL)
	//        option1: allowIFrameRequest (default: true)
	//        option2: allowHttpRequest (default: true)
	this.setLookupMode = function(mode, ref, option1, option2) {
		lookupData = null;
		lookupUrl = '';
		lookupFunction = null;
		allowIFrameRequest = true;
		allowHttpRequest = true;

		switch (mode) {
			case 'data':
				lookupData = ref;
				break;

			case 'func':
				lookupFunction = ref;
				lookupFunctionObject = option1;
				break;

			case 'get':
				lookupUrl = ref;
				allowIFrameRequest = (option1 === undefined || !!option1);
				allowHttpRequest = (option2 === undefined || !!option2);
				break;

			default:
				throw '[LookupTextField.setLookupMode] '
						+ "Lookup mode '" + mode + "' is not allowed!";
		}

		lookupMode = mode;
		debug("Lookup mode set to '" + mode + "'.");
	}

	// Sets the type of the retrieved data.
	// Allowed values are 'native', 'JSON', 'XML' and 'trimmed XML'.
	// The latter one will remove all whitespace and empty text nodes.
	// And the content of all other text nodes is trimmed.
	this.setLookupType = function(type) {
		if (type !== 'native' && type != 'JSON' && type != 'XML'
				&& type != 'trimmed XML') {
			throw  "[LookupTextField.setLookupType] Type '"
					+ type + "' is not allowed!";
		}

		lookupType = type;
	}

	// Sets the minimum text length to start a lookup.
	this.setLookupMinLength = function(len) {
		lookupMinLength = len;
	}

	// Sets the filter for constraining the presented rows.
	// Allowed values are null, startsWith and contains.
	this.setFilter = function(value) {
		if (value !== null && value != 'startsWith' && value != 'contains'
				&& typeof value != 'function') {
			throw  "[LookupTextField.setFilter] Filter '"
					+ value + "' is not allowed!";
		}

		filter = value;
	}

	// Sets the number of that column which should be used for determinating
	// the actual value after the user selection (is 1-based).
	this.setBoundColumn = function(num) {
		boundColumn = num;
	}

	// Sets the value of 'submitOnSelect' which determinates wheter after
	// user selection the corresponding form of the lookup text field should
	// be submitted automatically.
	this.setSubmitOnSelect = function(value) {
		submitOnSelect = !!value;
	}


	// This activates all the lookup and popup functionality.
	this.launch = function() {
		if (textField) {
			// this is just a workaround because safari fires arrow key events twice
			var safariBugfixFlag = false;

			// The real key down event handler will be deferrred because
			// this is needed to make sure that the value property
			// of the text field
			// is already updated by system.
			var deferredKeyHandler = function(evt) {
				if (!safariBugfixFlag) {

					safariBugfixFlag = true;

					if (!evt) {
						evt = window.event;
					}

					var keyCode = (evt.which ? evt.which : evt.keyCode);
					closeIFrame();

					setTimeout(function() {
							safariBugfixFlag = false;
							doOnKeyDown(keyCode)
						}, 0);
				}
			}


			textField.setAttribute('autocomplete', 'off');
			addEventHandler(textField, 'keydown', deferredKeyHandler);
			var textFieldLen = 0;

			addEventHandler(textField, 'focus', function() {
				// deferred, because maybe there are otherwise interferences
				// with other registered event handlers
				var deferred = function() {
					if (typeof textField.value == 'string'
							&& textField.value.length >= lookupMinLength
							&& !popupOpened
							&& forcedClosedPopup) {
						requestDataAndProcess();
					}
				};

				setTimeout(deferred, 0);
			});

			debug('LookupTextField successfully launched!');
		} else {
			debug('Could not launch lookupTextField!');
		}
	}

	// Sets the data processing function which should be used
	// to handle the responsed lookup data.
	// That function should accepts one argument
	// which is the lookup response, at the moment
	// this is always a xml node.
	// And it should return an array of the popup table content.
	this.setDataProcessor = function(func, obj) {
		dataProcessor = func;
		dataProcessorObj = (obj ? obj : this);
	}

	// Sets the debug mode. If it is true then some alerts
	// inform you about the current state of processing.
	// Useful for debugging.
	this.setDebugMode = function(value) {
		debugMode = !!value;
	}

	//----- logic of constructor function LookupTextField -----
	if (typeof textFieldNodeOrId == 'string') {
		textField = document.getElementById(textFieldNodeOrId);
	} else {
		textField = textFieldNodeOrId;
	}

	if (typeof popupNodeOrId == 'string') {
		popup = document.getElementById(popupNodeOrId);
	} else {
		popup = popupNodeOrId;
	}

	if (!popup) {
		popup = document.createElement('div');
		popupIsDynamic = true;
	} else {
		popupIsDynamic = false;
	}
}

// ----- end: LookupTextField -----



var suggSrch_dataProcessor = function(node) {
	var recordsets = [];

	if (node && node.tagName
			&& node.tagName == 'searchTerms') {
		var children = node.childNodes;

		for (var i = 0; i < children.length; ++i) {
			var child = children[i];
			if (typeof child.getAttribute != 'undefined') {
				var hits = parseInt(child.getAttribute('hits'));

				if (hits > 0) {
					var grandChild = child.firstChild;

					if (grandChild && grandChild.nodeType == 3) { // text node
						var value = grandChild.nodeValue;
						//recordsets.push([value, '(' + hits + ')']);
						recordsets.push([value, '']);
					}
				}
			}
		}
	}

	return recordsets;
}

