// mmsuggest.js: Suggestion dropdown box for HTML.
// Copyright exorbyte GmbH, 2005, 2006. All rights reserved.
// Author: Leo Meyer, leo.meyer_at_exorbyte.com
// Version: 4.4, 11.09.06
//
// Usage: see demo.
//
// Veraenderungen an diesem Code sind nur mit ausdruecklicher Zustimmung der 
// exorbyte GmbH gestattet.
// Modification of this code only with explicit permission by exorbyte GmbH.

var exorbyteLogo = "/html/templates/exorbyte/exlogo_tiny.gif";		// path to exorbyte logo, not affected by mmIconPath
var mmIconPath = "/html/templates/exorbyte/images/";		// relative or absolute path to icons, must end with "/" if not empty
var mm_refcnt = 0;
var mm_inputs = new Array();
var mmUA = navigator.userAgent;
var firefox = mmUA.search(/firefox/i) > 0;
var opera = mmUA.search(/opera/i) > 0;
var ie = mmUA.search(/MSIE/i);
if (ie > 0) {
	var ieVersion = mmUA.substr(ie + 4, 3);
	ie = true;
} else ie = false;
var ieZIndexBug = ie & parseInt(ieVersion) < 7.0;
var mm_flashtime = 50; 		// ms
var mm_qtime = 0;
var mmIgnoreFirstMouseEnter = false;	// firefox behaviour workaround: ignore first mouse enter when displaying

// bit flags
var AS_NOLOGO = 1;			// don't display exorbyte logo - only for licensed users
var AS_NOCOMMIT = 2;		// don't do a form commit on return while the suggest box is open
var AS_TABSELECTS = 4;		// pressing TAB activates the selected entry
var AS_DISPLAY_INPUT = 8;	// adds a header row containing the user's input
var AS_HIERARCHICAL = 16;	// display suggestions in hierarchical mode
var AS_GROUPED = 32;		// display suggestions in grouped mode (category grouping), useful for one-level categories
var AS_GROUPED_DISPLAYCAT = 64;		// display suggestions in grouped mode (category grouping) plus category labels, useful for |-separated subcategory labels
var AS_GENERATED_CATEGORIES_NOT_SELECTABLE = 128;	// HIERARCHICAL and GROUPED categories are not selectable if this flag is set

/** Parameter container for flexible parameter handling */
function mmSuggestParams() {
	this.requestURL = "";				// URL for the server side part
	this.headerFunction = null;			// custom function(parent, rowArray) returns header HTML for a set of rows. Parent is the parent DIV element to be filled
	this.rowFunction = null;			// custom function(target, rows, field_index, i, iDiv) sets iDiv's inner HTML. field_index is an array that contains the indexes of field names. Returns (boolean)addEvents.
	this.onActivate = null;				// custom function(input, rowArray, i) that is activated onClick or on enter key; return false to suppress commit
	this.searchDelay = 250;				// ms until search is started
	this.searchField = null;			// alternative search field, leave input value
	this.width = -1;				// if -1, width is calculated automatically; can be set in the setup function
	this.normalfg = "black";			// default normal foreground
	this.normalbg = "white";			// default normal background
	this.highlightfg = "white";			// default highlighted foreground
	this.highlightbg = "navy";			// default highlighted background
	this.flags = AS_NOLOGO;					// a combination of the AS_ flags
	this.debug = false;				// enables component-specific debugging
	this.overlappedObjects = null;			// Array of SELECT combobox objects that may be hidden by the dropdown (IE display bug workaround)
	this.align = "left";				// default: left alignment
	this.document = window.document;		// target document object, default: current document
	this.top = -1;					// fixed absolute top position (-1: dynamic)
	this.left = -1;					// fixed absolute left position (-1: dynamic)
	this.inputTitle	= "";		// text that is displayed with the input if AS_DISPLAY_INPUT is set
	this.clickout = false;				// set to true for clickout logging
	this.preFunction = null;			// preprocessing function(input, rowArray) returns rowArray
	this.hierarchicalSearchTermIndicator = "Suchbegriff";		// abstract search term denominator
	this.oneColumn = true;				// force one column display
}

var MM_DOWNDIR = false;
var MM_UPDIR = true;

////////// helper functions ///////////

// String extension
String.prototype.startsWith = function(s) {
	if (s.length > this.length) return false;
	return this.substring(0, s.length) == s;
}
	
function sortFirst(a, b) {
	// expects a, b to be arrays
	if (a[0] > b[0]) 
		return 1;
	else if (a[0] < b[0])
		return -1;
	else return 0;
}

function sortLengthLonger(a, b) {
	// expects a, b to be strings
	// sort longer ones first
	return b.length - a.length;
}

function sortLengthShorter(a, b) {
	// expects a, b to be strings
	// sort shorter ones first
	return a.length - b.length;
}

function Node(value, cat, label) {
	
	this.value = value;
	this.label = label;
	this.cat = cat;
	this.level = 0;
	this.children = new Array();
	this.parent = null;
	this.row = null;
	
	this.sink = function(node) {
		// disallow duplicates
		if (node.value == this.value)
			return true;
		// node is a child of this node if node's value starts with this node's value
		// and there should remain more than three characters
		if (node.value.startsWith(this.value) && (node.value.length - this.value.length > 3)) {
			// test children
			for (var i = 0; i < this.children.length; i++) {
				if (this.children[i].sink(node))
					return true;
			}
			// no child sinks the node
			// sink it here
			this.children.push(node);
			node.label = node.value.substr(this.value.length);
			node.level = this.level + 1;
			node.parent = this;
			return true;
		}
	}
	
	this.group = function(node, catRest) {
		if (typeof catRest == 'undefined') catRest = node.cat;
//		alert("group: node = " + node.value + ", catRest = " + catRest);
		// last category level?
		if (catRest == "") {
			this.addChild(node);
			node.cat = catRest;
			return true;
		}
		var cats = catRest.split("|");
		var cat = cats[0];
		// go through children
		for (var i = 0; i < this.children.length; i++) {
			// category equal?
			if (this.children[i].label == cat) {
				cats.shift();
				this.children[i].group(node, cats.join("|"));
				return true;
			}
		}
		// no child found, add category node
		var cnode = new Node((this.value != "" ? this.value + "|" : "") + cat, "", cat);
		this.addChild(cnode);
		cats.shift();
		return cnode.group(node, cats.join("|"));
	}	

	this.groupDisplayCat = function(node, catRest) {
		if (typeof catRest == 'undefined') catRest = node.cat;
//		alert("group: node = " + node.value + ", catRest = " + catRest);
		var cats = catRest.split("|");
		// last category level?
		if (cats.length == 1) {
			this.addChild(node);
			node.cat = catRest;
			return true;
		}
		var cat = cats[0];
		// go through children
		for (var i = 0; i < this.children.length; i++) {
			// category equal?
			if (this.children[i].label == cat) {
				cats.shift();
				this.children[i].groupDisplayCat(node, cats.join("|"));
				return true;
			}
		}
		// no child found, add category node
		var cnode = new Node((this.value != "" ? this.value + "|" : "") + cat, "", cat);
		this.addChild(cnode);
		cats.shift();
		return cnode.groupDisplayCat(node, cats.join("|"));
	}	
	
	this.dump = function(indent) {
		if (typeof indent == 'undefined') indent = "";
		var result = indent + this.label + " (" + this.value + ", " + this.cat + ")\n";
		for (var i = 0; i < this.children.length; i++) {
			result += this.children[i].dump(indent + "+-") + "\n";
		}
		return result;
	}
	
	this.getAsArray = function(arr, cutStr) {
		if (this.value == "") this.value = this.label;
		if (typeof this.label == 'undefined') this.label = this.value;
		if ((typeof cutStr != 'undefined') && (this.label != cutStr) && this.label.startsWith(cutStr)) 
			this.label = this.label.substr(cutStr.length);
		arr.push(new Array(label, this.cat, this));
		for (var i = 0; i < this.children.length; i++) {
			this.children[i].getAsArray(arr, this.label);
		}	
	}
	
	this.addChild = function(node) {
		this.children.push(node);
		node.parent = this;
		node.level = this.level + 1;
	}
	
	
	this.isLastChild = function() {
		if (this.parent == null) return true;
		return (this == this.parent.children[this.parent.children.length - 1]);
	}
	
	this.levelUp = function() {
		this.level--;
		// correct levels of children
		for (var i = 0; i < this.children.length; i++) {
			this.children[i].levelUp();
		}
	}
	
	this.contract = function() {
		
		// contract children
		for (var i = 0; i < this.children.length; i++) {
			this.children[i].contract();
		}
		if (this.parent == null) return;
		// does this node have only one child?
		if (this.children.length == 1) {
			// yes - contract the child
			var node = this.children[0];
			this.value = node.value;
			this.label = this.label + " " + node.label;
			this.cat = node.cat;
			this.children = node.children;
			// correct levels recursively
			for (var i = 0; i < this.children.length; i++) {
				this.children[i].parent = this;
				this.children[i].levelUp();
			}
		}
	}
	
	this.sortChildrenFirst = function(a, b) {
		var isPa = (a.cat != target.parameters.hierarchicalSearchTermIndicator);
		var isPb = (b.cat != target.parameters.hierarchicalSearchTermIndicator);
		if (isPa == isPb) 
			return 0;
		if (isPa) return -1;
		if (isPb) return 1;
	}
	
	this.sortChildren = function() {
		// sort children - products first
		// only on lower levels
		if (this.level >= 1)
			this.children.sort(this.sortChildrenFirst);
		// sort recursively
		for (var i = 0; i < this.children.length; i++) {
			this.children[i].sortChildren();
		}
	}
}

//////// helper functions end //////////

function mmDoBlur(event) {
	if (!event && window.event) {
		event = window.event;
	}
	// hide suggestion boxes
	for (var i = 0; i < mm_inputs.length; i++) {
		mmHideSuggBox(mm_inputs[i]);
	}
	target = (typeof(event.srcElement) == "undefined" ? event.target : event.srcElement);
	// target lost focus
	target.lostFocus = true;
	if ((typeof target.oldBlur != "undefined") && (target.oldBlur != null))
		target.oldBlur(event);
}
function mmDoFocus(event) {
	if (!event && window.event) {
		event = window.event;
	}
	target = (typeof(event.srcElement) == "undefined" ? event.target : event.srcElement);
	// target has focus
	target.lostFocus = false;
	if ((typeof target.oldFocus != "undefined") && (target.oldFocus != null))
		target.oldFocus(event);
}

function mmCheckKey(event, target) {
	if (event.ctrlKey && (event.altKey || event.shiftKey) && (event.keyCode == 120)) {
		target.dynamicNotification = !target.dynamicNotification;
		return true;
	}
	if (event.ctrlKey && (event.altKey || event.shiftKey) && (event.keyCode == 119)) {
		mmDoSearch(target.targetIndex, true);
		return true;
	}
	if (event.ctrlKey && (event.altKey || event.shiftKey) && (event.keyCode == 118)) {
		target.parameters.debug = !target.parameters.debug;
		return true;
	}	
	return false;
}

function mmCancelEvent(event) {
	event.cancelBubble = true;
	event.returnValue = false;
	event.cancel = true;
	return false;
}

function mmDoFieldKeyDown(event) {
	if (!event && window.event) {
		event = window.event;
	}
	target = (typeof(event.srcElement) == "undefined" ? event.target : event.srcElement);
//	alert(event.keyCode);
	if (!target.xmlhttp) return;
	switch (event.keyCode) {
		case 40: {
			// down
			if (!target.suggVisible) {
				mmCallSearch(target, 10);
				return false;
			} else {
				if (target.suggCount > 0) {
					if (target.lastHighlightedId < target.suggCount - 1) {
						mmSelectRow(target, target.lastHighlightedId + 1, MM_DOWNDIR);
					}
					return mmCancelEvent(event);
				}
			}
			break;
		}
		case 38: {
			// up
			if (target.suggCount > 0) {
				if (target.lastHighlightedId > 0) {
					mmSelectRow(target, target.lastHighlightedId - 1, MM_UPDIR);
					mmShowSuggBox(target);
				}
				return mmCancelEvent(event);
			}
			break;
		}
		case 33: {
			// PgUp
			if (target.suggVisible && (target.suggCount > 0)) {
				mmSelectRow(target, 0, MM_UPDIR);
				return mmCancelEvent(event);

			}
			break;
		}
		case 34: {
			// PgDn
			if (target.suggVisible && (target.suggCount > 0)) {
				mmSelectRow(target, target.suggCount - 1, MM_DOWNDIR);
				return mmCancelEvent(event);
			}
			break;
		}
		case 35: {
			// End
			if (target.suggVisible && (target.suggCount > 0)) {
				mmSelectRow(target, target.suggCount - 1, MM_DOWNDIR);
				// don't cancel event				
				return true;
			}
			break;
		}
		case 36: {
			// Home
			if (target.suggVisible && (target.suggCount > 0)) {
				mmSelectRow(target, 0, MM_UPDIR);
				// don't cancel event				
				return true;
			}
			break;
		}
		case 13: {
			// Enter
			if (target.suggVisible && (target.lastHighlightedId >= 0)) {
				mmHideSuggBox(target);
				var row = target.parameters.document.getElementById("suggRow" + target.mm_refcnt + "_" + target.lastHighlightedId);
				// row selected?
				if (row) {
					var fx = row.onmousedown;
					fx();
				}
				return mmCancelEvent(event);
			} else {
				// no box visible or nothing selected, submit input
				mmSubmitString(target, target.value);
				return mmCancelEvent(event);
			}
			break;
		}
		case 9: {
			// TAB
//			alert(target.suggVisible + " " + target.lastHighlightedId + " " + target.parameters.flags);
			if (target.suggVisible && (target.lastHighlightedId >= 0) && ((target.parameters.flags & AS_TABSELECTS) == AS_TABSELECTS)) {
//				alert("Tab");
				mmHideSuggBox(target);
				var row = target.parameters.document.getElementById("suggRow" + target.mm_refcnt + "_" + target.lastHighlightedId);
				if (row) {
					var fx = row.onmousedown;
					if (!fx()) return mmCancelEvent(event);
				}
				return true;
			}
			break;
		}
		case 27: {
			// Escape
			if (target.suggVisible) {
				mmSelectRow(target, -1);
				mmHideSuggBox(target);
				return mmCancelEvent(event);
			}
			break;
		}
	}
	if (mmCheckKey(event, target)) return false;
	if ((event.keyCode == 8) || (event.keyCode == 32) || (event.keyCode >= 46)) {
		mmCallSearch(target, target.parameters.searchDelay);
	}
}

function mmGetXMLHTTP() {
  var result = false;
  if(typeof XMLHttpRequest != "undefined") {
    result = new XMLHttpRequest();
  } else {
	try {
		result = new ActiveXObject("Msxml2.XMLHTTP");
	} catch (e) {
		try {
			result = new ActiveXObject("Microsoft.XMLHTTP");
		} catch (ie) {}
	}
  }
  return result;
}

function mmGetParentProps(elem, prop) {
	// returns the sum of the property "prop" along the offsetParent row of elem
	var result = 0;
	while (elem != null) {
		result += elem[prop];
		elem = elem.offsetParent;
	}
	return result;
}

function mmSelectRow(target, row, direction) {
	var rowDiv;
	if (target.lastHighlightedId > -1) {
		rowDiv = target.parameters.document.getElementById("suggRow" + target.mm_refcnt + "_" + target.lastHighlightedId);
		if (rowDiv) {
			rowDiv.style.backgroundColor = rowDiv.oldBackgroundColor;
			rowDiv.style.color = rowDiv.oldColor;
			var children = rowDiv.childNodes;
			for (i = 0; i < children.length; i++) {
				children[i].style.backgroundColor = children[i].oldBackgroundColor;
				children[i].style.color = children[i].oldColor;
			}
		}
	}		
	var selectable = false;
	var safeCount = 0;
	// find next selectable element for the given direction, if possible
	while (!selectable && (safeCount < 2 * target.rows.length)) {
		rowDiv = target.parameters.document.getElementById("suggRow" + target.mm_refcnt + "_" + row);
		if (!rowDiv) break;
		selectable = typeof (rowDiv.selectable) != 'undefined';
		if (!selectable) {
			if (direction == MM_UPDIR) {
				row--;
				safeCount++;
			} else {
				row++;
				safeCount++;
			}
			if (row <= 0) {
				direction = !direction;
				row = 0;
			}
 			if (row >= target.rows.length) {
				direction = !direction;
				row = target.rows.length - 1;
			}
		}
	}
	if (rowDiv) {
		target.lastHighlightedId = row;
		if (rowDiv.oldBackgroundColor != rowDiv.style.backgroundColor)
			rowDiv.oldBackgroundColor = rowDiv.style.backgroundColor;
		if (rowDiv.oldColor != rowDiv.style.color)
			rowDiv.oldColor = rowDiv.style.color;
		if (target.parameters.highlightbg != '')
			rowDiv.style.backgroundColor = target.parameters.highlightbg;
		if (target.parameters.highlightfg != '')
			rowDiv.style.color = target.parameters.highlightfg;
		var children = rowDiv.childNodes;
		for (i = 0; i < children.length; i++) {
			if (children[i].oldBackgroundColor != children[i].style.backgroundColor)
				children[i].oldBackgroundColor = children[i].style.backgroundColor;
			if (children[i].oldColor != children[i].style.color)
				children[i].oldColor = children[i].style.color;
			if (target.parameters.highlightbg != '')
				children[i].style.backgroundColor = target.parameters.highlightbg;
			if (target.parameters.highlightfg != '')
				children[i].style.color = target.parameters.highlightfg;
		}
	}
}

function mmMouseEnter(target_id, id) {
	// firefox selection workaround
	if (mmIgnoreFirstMouseEnter) {
		mmIgnoreFirstMouseEnter = false;
		return;
	}
	var target = mm_inputs[target_id];
	mmSelectRow(target, id);
}

function mmSubmitString(target, string) {
	// if an alternative search field has been specified, use it
	if (target.parameters.searchField != null) 
		target.parameters.searchField.value = string;
	else
		target.value = string;
	if ((target.form.action != "") && ((target.parameters.flags & AS_NOCOMMIT) != AS_NOCOMMIT)) {
		target.form.submit();
	}
}

function mmSetDivSize(target) {
	if (target.suggBox) {
		var x,y;
		if (self.innerHeight) {
			x = self.innerWidth;
			y = self.innerHeight;
		} else if (target.parameters.document.documentElement && target.parameters.document.documentElement.clientHeight) {
			x = target.parameters.document.documentElement.clientWidth;
			y = target.parameters.document.documentElement.clientHeight;
		} else if (target.parameters.document.body) {
			x = target.parameters.document.body.clientWidth;
			y = target.parameters.document.body.clientHeight;
		}
		if (target.parameters.top < 0)
			target.suggBox.style.top = 
				mmGetParentProps(target, "offsetTop") + target.offsetHeight + "px";
		else 
			target.suggBox.style.top = target.parameters.top + "px";
		var w = (target.parameters.width < 0 ? target.offsetWidth : target.parameters.width);
		target.suggBox.style.width = w + "px";
		var l = 0;
		if (target.parameters.align == "right")
			l = mmGetParentProps(target, "offsetLeft") - w + target.offsetWidth;
		else 
			l = mmGetParentProps(target, "offsetLeft");
		if (l + w > x) l = x - w;
		if (l < 0) l = 0;
		if (target.parameters.top < 0)
			target.suggBox.style.left = l + "px";
		else 
			target.suggBox.style.left = target.parameters.left + "px";			
	}
}

function mmUnflash(target_id, oCol, nCol, count) {
	var target = mm_inputs[target_id];
	target.style.backgroundColor = oCol;
	count--;
	if (count > 0) {
		setTimeout("mmFlash(" + target_id + ", \"" + nCol + "\", " + count + ");", mm_flashtime);
	}
}
function mmFlash(target_id, nCol, count) {
	var target = mm_inputs[target_id];
	var oCol = target.style.backgroundColor;
	target.style.backgroundColor = nCol;
	var cmd = "mmUnflash(" + target_id + ", \"" + oCol + "\", \"" + nCol + "\", " + count + ");";
	setTimeout(cmd, mm_flashtime);
}

function replaceHTMLEntities(str) {
	var result = str.replace(/&/g, "&amp;");	
	result = result.replace(/</g, "&lt;");	
	result = result.replace(/>/g, "&gt;");	
	return result;
}

function mmRedirectClick(target_id, row, coBypass) {
	var target = mm_inputs[target_id];
	if (target.parameters.clickout && (typeof coBypass == "undefined")) {
		url = mmQReplace(target.parameters.requestURL, target) + "&click=" + escape(target.rows[row][0]) + "&coID=" + escape(target.coID) + "&hash=" + Math.random();
		target.xmlhttp.open("GET", url, true);
		target.xmlhttp.send(null);
		window.setTimeout("mmRedirectClick(" + target_id + ", " + row + ", true)", 100);
		return;
	}
	if ((typeof target.parameters.onActivate != "undefined") && (target.parameters.onActivate != null) && (target.parameters.onActivate != "")) {
		if (target.parameters.onActivate(target, target.rows, row))
 			mmSubmitString(target, target.rows[row][0]);
	} else {
		mmSubmitString(target, target.rows[row][0]);
	}
}

function mmFillDiv(target, fieldnames, rows) {
	// remove previous elements
	while (target.suggBox.hasChildNodes()) {
		target.suggBox.removeChild(target.suggBox.firstChild);
	}
	var localize = (fieldnames.length != 2) || (fieldnames[0] != "Name") || (fieldnames[1] != "Key");
	if (localize && (typeof target.parameters.headerFunction != "undefined") && (target.parameters.headerFunction != null) && (target.parameters.headerFunction != "")) {
		var iDiv = target.parameters.document.createElement("div");
		var ih = target.parameters.headerFunction(iDiv, rows);
		iDiv.innerHTML = ih;
		target.suggBox.appendChild(iDiv);
	}
	if (localize && (typeof target.parameters.rowFunction != "undefined") && (target.parameters.rowFunction != null) && (target.parameters.rowFunction != "")) {
		// prepare field names array
		var field_index = new Array();
		for (j = 0; j < fieldnames.length; j++) {
			field_index[fieldnames[j]] = j;
		}
	}

	for (i = 0; i < rows.length; i++) {
		var iDiv = target.parameters.document.createElement("div");
		iDiv.id = "suggRow"  + target.mm_refcnt + "_" + i;
		iDiv.className = "suggRow";
		iDiv.style.cursor = "pointer";
		var addEvents = true;
		if (localize && (typeof target.parameters.rowFunction != "undefined") && (target.parameters.rowFunction != null) && (target.parameters.rowFunction != "")) {
			addEvents = target.parameters.rowFunction(target, rows, field_index, i, iDiv);
		} else {
			if ((rows[i].length > 1) && !target.parameters.oneColumn)
				iDiv.innerHTML = "<div class='suggItem'><span class='suggProduct'><nobr>" + replaceHTMLEntities(rows[i][0]) + "&nbsp;&nbsp;</nobr></span><span class='suggCat'><nobr>" + replaceHTMLEntities(rows[i][1]) + "</nobr></span></div>";
			else
				iDiv.innerHTML = "<div class='suggItem'><span class='suggProduct'><nobr>" + replaceHTMLEntities(rows[i][0]) + "&nbsp;&nbsp;</nobr></span></div>";
		}
		if (addEvents) {
			if (ie) iDiv.onmouseover = new Function("evt", "mmMouseEnter(" + target.mm_refcnt + "," + i + ")");
			if (!ie) iDiv.addEventListener('mouseover', new Function("evt", "mmMouseEnter(" + target.mm_refcnt + "," + i + ")"), false);
			iDiv.selectable = true;
			/* if (ie) */ iDiv.onmousedown = new Function("evt", "mmRedirectClick(" + target.mm_refcnt + ", '" + i + "')");
			if (!ie) iDiv.addEventListener('mousedown', new Function("evt", "mmRedirectClick(" + target.mm_refcnt + "," + i + ")"), false);
		}
		target.suggBox.appendChild(iDiv);
	}
	// Following code may only be removed or modified if contract regulations permit
	// Der folgende Code darf nur entfernt oder veraendert werden, falls die Vertragsbedingungen es gestatten
	if ((target.parameters.flags & AS_NOLOGO) == 0) {
		var eDiv = target.parameters.document.createElement("div");
		eDiv.style.cursor = "pointer";
		eDiv.onmousedown = new Function("", "window.location.href = 'http://www.exorbyte.de';");
		eDiv.innerHTML = "<div align=right style='padding: 0; margin: 0; border-top:thin solid gray; vertical-align: middle;'><nobr><font size=1 style='font-family: Verdana, Arial, Helvetica, Sans-Serif; vertical-align: middle;'>Powered by <img src='" + exorbyteLogo + "' style='padding: 0; margin: 0; vertical-align: middle;' align='texttop' alt='www.exorbyte.de'></font></nobr></div>";
		target.suggBox.appendChild(eDiv);
	}
	// Der vorstehende Code darf nur entfernt oder veraendert werden, falls die Vertragsbedingungen es gestatten
	// Previous code may only be removed or modified if contract regulations permit
	
	target.lastHighlightedId = -1;
}

function mmSuggestDeliver(target_id, fieldnames, rows, suggestions, coID) {
	if ((target < 0) || (target >= mm_inputs.length)) return;
	var target = mm_inputs[target_id];
	if (!target) return;
	// deferred creation for IE
	if (ie) mmCreateBox(target);	
	target.timeout = 0;
	target.suggestions = suggestions;
	target.coID = coID;
	if (target.dynamicNotification)
		window.status = "Received data after " + (new Date().getTime() - mm_qtime) + " ms";
	// lost focus in the mean time?
	if (target.lostFocus) {
		mmHideSuggBox(target);
		return;
	}
	// no results?
	if (rows.length == 0) {
		mmHideSuggBox(target);
		if (target.dynamicNotification)
			mmFlash(target.targetIndex, "gray", 3);		
		return;
	}
	// preprocessing?
	if ((typeof target.parameters.preFunction != "undefined") && (target.parameters.preFunction != null) && (target.parameters.preFunction != "")) {
		rows = target.parameters.preFunction(target, rows);
	}
	// check for new values
	var identical = (target.rows) && (target.rows.length == rows.length);
	if (identical) 
		for (var i = 0; i < rows.length; i++) {
			if (rows[i].length != target.rows[i].length)
				identical = false;
			if (!identical) break;
			for (var j = 0; j < rows[i].length; j++) 
				if (rows[i][j] != target.rows[i][j]) {
					identical = false;
					break;
				}
		}
	var insert_input = (target.parameters.flags & AS_DISPLAY_INPUT) == AS_DISPLAY_INPUT;
	var has_header = (typeof target.parameters.headerFunction != "undefined") && (target.parameters.headerFunction != null) && (target.parameters.headerFunction != "");
	// display only under following conditions
	if (!identical || insert_input || has_header) {
		if (insert_input) {
			rows.unshift(new Array(target.value, target.parameters.inputTitle));
		}
		target.rows = rows;
		target.suggCount = rows.length;
		mmFillDiv(target, fieldnames, rows);
	}
	mmShowSuggBox(target);
}

function mmCallSearch(target, delay) {
	// target has focus
	target.lostFocus = false;
	if (target.timeout != 0)
		window.clearTimeout(target.timeout);
	target.timeout = setTimeout("mmDoSearch(" + target.mm_refcnt + ")", delay);
}

function mmQReplace(url, target) {
	var v = target.value;
	// replace url parameters
	var turl = "";
	var inPar = false;
	var par = "";
	for (var i = 0; i < url.length; i++) {
		// in parameter?
		if (inPar) {
			// end of parameter?
			if (url.charAt(i) == '$') {
				// lookahead = 1
				if ((i < url.length - 1) && (url.charAt(i + 1) == '$')) {
					i++;
					turl += '$';
				} else {
					// evaluate parameter
					if (par != "") {
						// use input value?
						if (par == 'v') {
							turl += escape(v)
							inPar = false;
						} else {
							// evaluate parameter
							var pv = escape(eval(par));
							turl += pv;
							inPar = false;
						}
					}
				}
			} else
				// still in parameter
				par += url.charAt(i);
		} else 
		// out of parameter
		if (url.charAt(i) == '$') {
			// lookahead = 1
			if ((i < url.length - 1) && (url.charAt(i + 1) == '$')) {
				i++;
				turl += '$';
			} else {
				// start parameter
				par = "";
				inPar = true;
			}
		} else
			turl += url.charAt(i);
		}
	return turl;
}

function mmDoSearch(target_id, direct) {
	try {
		var target = mm_inputs[target_id];
		target.timeout = 0;
		// lost focus in the mean time?
		if (target.lostFocus) {
			return;
		}
		if (target.value.length < 2) {
			mmHideSuggBox(target);
			return;
		}
		if (!target.xmlhttp) return;
		// search running right now?
		if (target.xmlhttp.readyState != 0) {
			// cancel current search
			target.xmlhttp.abort();
		}
		var url = target.parameters.requestURL;
		url = mmQReplace(url, target) + "&target_id=" + target_id + 
			// bypass caching when clickout tracking is on
			(target.parameters.clickout ? "&hash=" + Math.random() : "");
		if (direct) {
			target.parameters.document.location.href = url;
		} else {
			target.xmlhttp.open("GET", url, true);
			target.xmlhttp.onreadystatechange = target.async_fn;
			if (target.parameters.debug) {
				alert("Sending request: " + url);
			}
			target.xmlhttp.send(null);
			mm_qtime = new Date().getTime();
		 }
	} catch (E) {
		if (target.parameters.debug) {
			alert("URL processing interrupted: " + E);
		}
	}
}

function mmNotifyError(target) {
	if (target.dynamicNotification) {
		mmFlash(target.targetIndex, "red", 3);
	}
}

function mmOverlapsObject(target, obj) {
	if (!obj) return false;
	var result = false;
	var l = mmGetParentProps(target.suggBox, "offsetLeft");
	var t = mmGetParentProps(target.suggBox, "offsetTop");
	var r = l + target.suggBox.offsetWidth;
	var b = t + target.suggBox.offsetHeight;
	var ol = mmGetParentProps(obj, "offsetLeft");
	var ot = mmGetParentProps(obj, "offsetTop");
	var or = ol + obj.offsetWidth;
	var ob = ot + obj.offsetHeight;
	if (ol <= l) {
		if (ot <= t)
			result = (ob > t) && (or > l);
		else 
			result = (ot <= b) && (or > l);
	} else if (ol <= r) {
		if (ot <= t)
			result = (ob > t);
		else
			result = (ot <= b);
	}
	return result;
}

function mmShowSuggBox(target) {
	if (!target.suggBox) return;
	target.suggBox.style.visibility = "visible";
	mmIgnoreFirstMouseEnter = firefox;
	target.suggVisible = true;
	if ((target.parameters.overlappedObjects != null) & ieZIndexBug) {
		for (var i = 0; i < target.parameters.overlappedObjects.length; i++) {
			if (mmOverlapsObject(target, target.parameters.overlappedObjects[i])) {
				target.parameters.overlappedObjects[i].style.visibility = "hidden";
			} else {
				target.parameters.overlappedObjects[i].style.visibility = "visible";
			}
		}
	}
}

function mmHideSuggBox(target) {
	target.suggVisible = false;
	if (!target.suggBox) return;
	target.suggBox.style.visibility = "hidden";
	if ((target.parameters.overlappedObjects != null) & ieZIndexBug) {
		for (var i = 0; i < target.parameters.overlappedObjects.length; i++)
			if (target.parameters.overlappedObjects[i])
				target.parameters.overlappedObjects[i].style.visibility = "visible";
	}
}

function mmSetDivSizes() {
	for (i = 0; i < mm_inputs.length; i++) {
		mmSetDivSize(mm_inputs[i]);
	}
}

function mmHierarchicalPreFunction(target, rows) {
	try {
		// compound detection
		var sRows = rows;
		// duplicate last row
		sRows.push(sRows[sRows.length - 1]);
		// prefix detection
		var prevcount = new Array();
		var prevparts = new Array();
		var cands = new Array();
		for (var z = 0; z < sRows.length; z++) {
			var v = sRows[z][0].replace(/^\s+/, "").replace(/\s+$/, "");
			var parts = v.split(" ");
			// extend prevcount list
			while (prevcount.length < parts.length) {
					prevcount.push(0);
					prevparts.push("");
			}
			// calculate count
			var count = new Array();
			var valuechanged = false;
			for (var i = 0; i < parts.length; i++) {
				if (!valuechanged) {
					if (prevparts[i] == parts[i]) {
						count.push(prevcount[i] + 1);
					} else {
						count.push(1)
						valuechanged = true;
					}
				} else {
					count.push(1);
				}
			}
			// pad count with zeroes up to the length of prevcount
			while (count.length < prevcount.length) {
					count.push(1);
			}
			for (i = count.length - 1; i >= 1; i--) {
				var pi = prevcount[i];
				if (pi >= 2) {
					var cand = prevparts.slice(0, i + 1).join(" ");
					cands.push(cand);
				}
			}
			prevparts = parts;
			prevcount = count;
		}
//		alert("detected " + cands.length + " compound candidates");
		// sort cands by length
		cands = cands.sort(sortLengthShorter);
		// generate tree
		var root = new Node("", target.parameters.hierarchicalSearchTermIndicator, target.value);
		var sRows = rows;
		for (var z = 0; z < sRows.length; z++) {
			// pre-populate with matching compounds
			for (var j = 0; j < cands.length; j++) {
				if (sRows[z][0].startsWith(cands[j]))
					root.sink(new Node(cands[j], target.parameters.hierarchicalSearchTermIndicator))
				else
					break;
			}
			var node = new Node(sRows[z][0], sRows[z][1], sRows[z][0]);
			node.row = sRows[z];
			root.sink(node);
		}
		var result = new Array();
		// contract
		root.contract();
//		root.sortChildren();
//		alert(root.dump());
		root.getAsArray(result);
//		result.shift();
		return result;
		
	} catch (E) {
		if (target.parameters.debug)
			alert("hierarchicalPreFunction error: " + E);
	}
}
	
function mmHierarchicalRowFunction(target, rowArray, field_index, row, iDiv) {
	try {
		var node = rowArray[row][2];
		var value = node.label;
		var cat = node.cat;
//		alert(cat);
		var icon = mmIconPath + "ordner.gif";
		var ph = "";
		if (node.children.length == 0) {
			if (node.isLastChild())
				icon = mmIconPath + "lastchild.gif";
			else
				icon = mmIconPath + "child.gif";
		} else if (node.parent != null) {
			if (node.isLastChild())
				ph = "<img align='middle' src='" + mmIconPath + "lastchild.gif'>";
			else
				ph = "<img align='middle' src='" + mmIconPath + "child.gif'>";
		}
		// insert placeholders depending on the node's level
		var parent = node;
		for (var i = node.level - 1; i > 0; i--) {
//			alert(node.value + " " + i);
			parent = parent.parent;
			if (parent.isLastChild()) {
				ph = "<img align='middle' src='" + mmIconPath + "platzhalter.gif'>" + ph;
			}
			else {
				ph = "<img align='middle' src='" + mmIconPath + "line.gif'>" + ph;
			}
		}
		var result = true;
		// one displayed column?
		if (target.parameters.oneColumn) {
			if (cat == target.parameters.hierarchicalSearchTermIndicator) {
				icon = mmIconPath + "ordner.gif";
				var ih = "<div class='suggItem'><span class='suggHierarchicalProduct'><nobr>" + ph + "<img align='middle' src='" + icon + "'>" +
						replaceHTMLEntities(value.replace(/,$/, "...")) + 
						"&nbsp;&nbsp;</nobr></span></div>";
				// determine whether events on these rows
				result = (target.parameters.flags & AS_GENERATED_CATEGORIES_NOT_SELECTABLE) != AS_GENERATED_CATEGORIES_NOT_SELECTABLE;
			} else {
				var ih = "<div class='suggItem'><span class='suggHierarchicalProduct'><nobr>" + ph + "<img align='middle' src='" + icon + "'>" +
						"<a href='javascript:'>" + replaceHTMLEntities(value.replace(/<.*>/, "")) + "</a>" +
						"&nbsp;&nbsp;</nobr></span></div>";					
			}
		} else {
			// two displayed columns
			if (cat == target.parameters.hierarchicalSearchTermIndicator) {
				icon = mmIconPath + "ordner.gif";
				var ih = "<div class='suggItem'><span class='suggHierarchicalProduct'><nobr>" + ph + "<img align='middle' src='" + icon + "'>" +
						replaceHTMLEntities(value.replace(/,$/, "...")) + 
						"&nbsp;&nbsp;</nobr></span><span class='suggHierarchicalCat'><nobr>" +
						replaceHTMLEntities(cat) +
						"&nbsp;&nbsp;</nobr></span></div>";
				// determine whether events on these rows
				result = (target.parameters.flags & AS_GENERATED_CATEGORIES_NOT_SELECTABLE) != AS_GENERATED_CATEGORIES_NOT_SELECTABLE;
			} else {
				var ih = "<div class='suggItem'><span class='suggHierarchicalProduct'><nobr>" + ph + "<img align='middle' src='" + icon + "'>" +
						"<a href='javascript:'>" + replaceHTMLEntities(value.replace(/<.*>/, "")) + "</a>" +
						"&nbsp;&nbsp;</nobr></span><span class='suggHierarchicalCat'><nobr>" +
						replaceHTMLEntities(cat) +
						"&nbsp;&nbsp;</nobr></span></div>";					
			}
		}
	 	iDiv.innerHTML = ih;
	 	return result;
	} catch (E) {
		alert("hierarchicalRowFunction error: " + E);
	}
}

function mmHierarchicalOnActivate(input, rowArray, row) {
	if ((typeof target.parameters.oldOnActivate != "undefined") && (target.parameters.oldOnActivate != null) && (target.parameters.oldOnActivate != "")) {
		// overriden behaviour
		if (target.parameters.oldOnActivate(target, target.rows, row))
			mmSubmitString(target, value);
	} else {
		// standard behaviour
		var node = rowArray[row][2];
		var value = node.value;
		if (node.cat == target.parameters.hierarchicalSearchTermIndicator) {
			input.value = value;
			setTimeout(function() {
				input.focus();
				input.suggest();
			}, 50);
		} else {
			mmSubmitString(target, value);
		}
	}
	// don't submit
	return false;
}

function mmGroupedPreFunction(target, rows) {
	try {
		// generate tree
		var root = new Node("", "", target.value);
		var sRows = rows;
		for (var z = 0; z < sRows.length; z++) {
			var node = new Node(sRows[z][0], sRows[z][1], sRows[z][0]);
			node.row = sRows[z];
			if ((target.parameters.flags & AS_GROUPED) == AS_GROUPED)
				root.group(node);
			else 
			if ((target.parameters.flags & AS_GROUPED_DISPLAYCAT) == AS_GROUPED_DISPLAYCAT)
				root.groupDisplayCat(node);
		}
		var result = new Array();
		root.getAsArray(result);
//		alert(root.dump());
//		result.shift();	// remove root node
		return result;
		
	} catch (E) {
		if (target.parameters.debug)
			alert("groupedPreFunction error: " + E);
	}
}
	
function mmGroupedRowFunction(target, rowArray, field_index, row, iDiv) {
	try {
		var node = rowArray[row][2];
		var value = node.label;
		var cat = node.cat;
		var icon = mmIconPath + "ordner.gif";
		var ph = "";
		if (node.children.length == 0) {
			if (node.isLastChild())
				icon = mmIconPath + "lastchild.gif";
			else
				icon = mmIconPath + "child.gif";
		} else if (node.parent != null) {
			if (node.isLastChild())
				ph = "<img align='middle' src='" + mmIconPath + "lastchild.gif'>";
			else
				ph = "<img align='middle' src='" + mmIconPath + "child.gif'>";
		}
		// insert placeholders depending on the node's level
		var parent = node;
		for (var i = node.level - 1; i > 0; i--) {
			parent = parent.parent;
			if (parent.isLastChild()) {
				ph = "<img align='middle' src='" + mmIconPath + "platzhalter.gif'>" + ph;
			} else {
				ph = "<img align='middle' src='" + mmIconPath + "line.gif'>" + ph;
			}
		}
		var result = true;
		// one displayed column?
		if (target.parameters.oneColumn) {
			if (node.children.length > 0) {
				icon = mmIconPath + "ordner.gif";
				var ih = "<div class='suggItem'><span class='suggGroupCaption'><nobr>" + ph + "<img align='middle' src='" + icon + "'>" +
							replaceHTMLEntities(rowArray[row][0]) + 
							"&nbsp;&nbsp;</nobr></span></div>";
				// determine whether events on these rows
				result = (target.parameters.flags & AS_GENERATED_CATEGORIES_NOT_SELECTABLE) != AS_GENERATED_CATEGORIES_NOT_SELECTABLE;
			} else {
				var ih = "<div class='suggItem'><span class='suggGroupProductOnly'><nobr>" + ph + "<img align='middle' src='" + icon + "'>" +
							replaceHTMLEntities(rowArray[row][0]) + 
							"&nbsp;&nbsp;</nobr></span></div>";
			}
		} else {
			// two displayed columns
			if (node.children.length > 0) {
				icon = mmIconPath + "ordner.gif";
				var ih = "<div class='suggItem'><span class='suggGroupCaption'><nobr>" + ph + "<img align='middle' src='" + icon + "'>" +
							replaceHTMLEntities(rowArray[row][0]) + 
							"&nbsp;&nbsp;</nobr></span><span class='suggGroupCat'><nobr>" +
							replaceHTMLEntities(cat) +
							"&nbsp;&nbsp;</nobr></span></div>";
				// determine whether events on these rows
				result = (target.parameters.flags & AS_GENERATED_CATEGORIES_NOT_SELECTABLE) != AS_GENERATED_CATEGORIES_NOT_SELECTABLE;
			} else {
				var ih = "<div class='suggItem'><span class='suggGroupProduct'><nobr>" + ph + "<img align='middle' src='" + icon + "'>" +
							replaceHTMLEntities(rowArray[row][0]) + 
							"&nbsp;&nbsp;</nobr></span><span class='suggGroupCat'><nobr>" +
							replaceHTMLEntities(cat) +
							"&nbsp;&nbsp;</nobr></span></div>";
			}
		}
	 	iDiv.innerHTML = ih;
	 	return result;
	} catch (E) {
		alert("mmGroupedRowFunction error: " + E);
	}
}

function mmGroupedOnActivate(input, rowArray, row) {
	if ((typeof target.parameters.oldOnActivate != "undefined") && (target.parameters.oldOnActivate != null) && (target.parameters.oldOnActivate != "")) {
		// overriden behaviour
		if (target.parameters.oldOnActivate(target, target.rows, row))
			mmSubmitString(target, value);
	} else {
		// standard behaviour
		var node = rowArray[row][2];
		var value = node.value;
		if (node.children.length > 0) {
			input.value = value;
			setTimeout(function() {
				input.focus();
				input.suggest();
			}, 50);
		} else {
			mmSubmitString(target, value);
		}
	}
	// don't submit
	return false;
}

function mmCreateBox(input) {
	
	if (input.created) return;
	
	// return if target document not yet loaded (only ie)?
	if (ie && (input.parameters.document.readyState != "complete")) return;

	// create visual element
	input.suggBox = input.parameters.document.createElement("div");
	input.suggBox.className = "suggBox";
	input.suggBox.name = "suggBox" + input.mm_refcnt;
	input.suggBox.id = "suggBox" + input.mm_refcnt;
	input.parameters.document.body.appendChild(input.suggBox);
	mmSetDivSize(input);
	input.created = true;
}

/** Returns true if setup is successful */
function SetupMMSuggest(input, args) {
	try {
		// create parameter container
		input.parameters = new mmSuggestParams();
		for (var i in args) {
			if (typeof input.parameters[i] != "undefined") {
				input.parameters[i] = args[i];
			} else {
				// parameter specification error
				alert("SetupMMSuggest parameter undefined: " + i + "=" + args[i]);
			}	
		}
		input.suggBox = null;			// suggestion box (DIV)
		input.suggVisible = false;
		input.xmlhttp = null;
		input.lastHighlightedId = -1;
		input.suggCount = -1;
		input.callcounter = 0;
		input.dynamicNotification = false;
		input.timeout = 0;	
		
		input.xmlhttp = mmGetXMLHTTP();
		if (!input.xmlhttp) {
			return false;
		}
	
		// setup input field	
		input.autocomplete = "off";
		input.setAttribute("autocomplete", "off");
		input.oldBlur = input.onblur;
		input.onblur = mmDoBlur;
		input.oldFocus = input.onfocus;
		input.onfocus = mmDoFocus;
		input.onkeydown = mmDoFieldKeyDown;
			
		// remember input field
		mm_inputs.push(input);
		input.targetIndex = mm_inputs.length - 1;
		// setup state change fn
		var fn_code = "" +
			"var target = mm_inputs[" + mm_refcnt + "];" +
			"if (target.xmlhttp.readyState == 4 && target.xmlhttp.responseText) {" + 
			" if (target.xmlhttp.responseText.charAt(0) == \"<\") {" +
			"		if (target.parameters.debug) {" +
			"			alert(\"Error: Received XML reply\"); " +
			"	    }" +
			" } else {" +
			"   try {" +
			"		if (!debug && target.parameters.debug) {" +
			"			alert(\"Received response!\"); " +
			"	    }" +
			"   	if (debug) { alert(target.xmlhttp.responseText); debug = false; }" +
			"  	eval(target.xmlhttp.responseText);" +
			"  } catch (e) {" +
			"  }" +
			"  	var txt = target.xmlhttp.responseText.replace(/\'/g, \"\\\\'\");" +
			"  	try {" +
			"  		eval(txt);" +
			"  	} catch (ie) {" +
			"		if (target.parameters.debug) {" +
			"			alert('Error executing the response: ' + ie + '\\nThe response text was:\\n' + txt); " +
			"	    }" +
			"		mmNotifyError(target);" +
			"  		mmHideSuggBox(target);" +
			"	}" +
			"  }" +
			" }" ;
		input.async_fn = new Function("", fn_code);
		input.xmlhttp.onreadystatechange = input.async_fn;
	
		input.mm_refcnt = mm_refcnt;
		mm_refcnt++;
		
		// deferred creation only for Internet Explorer
		if (ie)
			input.created = false;
		else
			mmCreateBox(input);
		
		// hierarchical setup?
		if ((input.parameters.flags & AS_HIERARCHICAL) == AS_HIERARCHICAL) {
			input.parameters.preFunction = mmHierarchicalPreFunction;
			input.parameters.rowFunction = mmHierarchicalRowFunction;
			input.parameters.oldOnActivate = input.parameters.onActivate;
			input.parameters.onActivate = mmHierarchicalOnActivate;
			if (input.parameters.debug) {
				alert("Hierarchical functions prepared for component " + input.name);
			}
		}

		// grouped setup?
		if (((input.parameters.flags & AS_GROUPED) == AS_GROUPED) || ((input.parameters.flags & AS_GROUPED_DISPLAYCAT) == AS_GROUPED_DISPLAYCAT)) {
			input.parameters.preFunction = mmGroupedPreFunction;
			input.parameters.rowFunction = mmGroupedRowFunction;
			input.parameters.oldOnActivate = input.parameters.onActivate;
			input.parameters.onActivate = mmGroupedOnActivate;
			if (input.parameters.debug) {
				alert("Grouped functions prepared for component " + input.name);
			}
			// switch to one/two column mode depending on group type
			if ((input.parameters.flags & AS_GROUPED_DISPLAYCAT) == AS_GROUPED_DISPLAYCAT)
				input.parameters.oneColumn = false;
			else
				input.parameters.oneColumn = true;
		}
	
		window.onresize = mmSetDivSizes;
		if (input.parameters.debug) {
			alert("Setup successful for component " + input.name);
		}
		
		input.suggest = function() {
			window.setTimeout(function() {
				input.lostFocus = false;
				mmDoSearch(input.mm_refcnt, false);
			}, 50)
		};
				
		return true;
		
	} catch (e) {
		// alert(e.message);
		return false;
	}
}

// provided for backwards compatibility
function SetupAutoSuggest(input, args) {
	return SetupMMSuggest(input, args);
}

