/* Auto-suggest control, version 2.4, October 10th 2009.
 *
 * (c) 2007-2009 Dmitriy Khudorozhkov (dmitrykhudorozhkov@yahoo.com)
 *
 * Latest version download and documentation:
 * http://www.codeproject.com/KB/scripting/AutoSuggestControl.aspx
 *
 * Based on "Auto-complete Control" by zichun:
 * http://www.codeproject.com/KB/scripting/jsactb.aspx
 *
 * This software is provided "as-is", without any express or implied warranty.
 * In no event will the author be held liable for any damages arising from the
 * use of this software.
 *
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 *
 * 1. The origin of this software must not be misrepresented; you must not
 *    claim that you wrote the original software. If you use this software
 *    in a product, an acknowledgment in the product documentation would be
 *    appreciated but is not required.
 *
 * 2. Altered source versions must be plainly marked as such, and must not be
 *    misrepresented as being the original software.
 *
 * 3. This notice may not be removed or altered from any source distribution.
 */ 

var autosuggest_url = ""; // Global link to the server-side script, that gives you the suggestion list.
			  // Used for controls that do not define their own server script urls.

function autosuggest(id, array, url, onSelect)
{
	var field  = document.getElementById(id);
	var exists = field.autosuggest;

array= ['comision', 'cnsc','decretos', 'resoluciones', 'lista de elegibles', 'resultados', 'convocatoria 001 de 2005', 'convocatoria',
								'resolucion', 'decreto', 'circular', 'auto', 'tutela', 'radicado', 'aplicacion', 'fase',								'universidades', 'acreditadas', 'comisiones', 'personal', 'opec', 'elegible', 'acuerdo 137 de 2010', 'instrumento de evaluacion', 'acuerdo 138 de 2010', 'por el cual se establece', 'sistema tipo de evaluacion del desempeno laboral', 'servidores de carrera administrativa', 'periodo de prueba', 'criterios tecnicos', 'fundamentan', 'sistema de evaluacion', 'empleados de carrera', 'elementos minimos', 'desarrollo de sistemas de evaluacion',
'instrumento', 'desempeno laboral', 'acuerdo 027', 'comision nacional del servicio civil', 'normas vigentes', 'acuerdo 27 de diciembre de 2008', 'acuerdo 17 de enero de 2008', 'acuerdo 18 de enero de 2008', 'formato sistema tipo', 'antecedentes', 'acuerdo 07 de 2006', 'resolucion 1534 de 2006', 'sistema tipo', 'guia evaluacion', 'acuerdo 09', 'sistema transitorio', 'acuerdo 09 de 2007', 'sistema transitorio', 'formulario 1', 'acuerdo 09 de 2007', 'formulario 2', 'ejercicio pedagogico', 'circular 036 del 2007', 'ejercicio pedagogico', 'circular 036 del 2007', 'formato evaluacion', 'resolucion 060 del 2007', 'consolidado', 'resultados', 'aspirantes', 'solicitudes de correcciones', 'errores', 'documento de identidad', 'errores de digitacion', 'calificacion definitiva', 'prueba', 'resultados pruebas', 'radicaciones', 'correcciones', 'resultados', 'Universidades', 'cumplimiento', 'requisitos minimos', 'proceso de solicitud', 'aspirante', 'concurso', 'icfes', 'invalidacion', 'aptitudes y competencias basicas', 'psicotecnica', 'resoluciones', 'antecedentes', 'prueba de entrevista', 'eliminatorio', 'aptitudes y competencias basicas', 'docente y directivo docente', 'clasificatorias', 'lista de elegibles', 'solicitudes de correccion', 'datos','conformidad', 'empleo', 'conformacion', 'publicacion', 'listas de elegibles', 'portal del estado colombiano', 'Universidad de pamplona', 'dafp', 'icfes', 'imprenta nacional', 'portal Unico de contratacion', 'consejo superior de la judicatura', 'resena', 'vision', 'mision', 'valores', 'objetivos', 'estructura interna', 'planes y proyectos', 'informe ejecutivo de gestion', 'gestion admon', 'financiera', 'gestion admon', 'financiera', 'funcionarios', 'sistema general', 'sistemas especificos', 'sistemas especiales', 'convocatoria 001 de 2005', 'convocatoria 002 de 2006 inpec', 'convocatoria 003 de 2006 dian', 'convocatoria 004-052 de 2006', 'directivos convocatoria 004-052 de 2006', 'directivos convocatoria 053 de 2007 das', 'convocatoria 053 de 2007 das', 'convocatoria 054 de 2008 inpec', 'convocatoria 055 de 2008 das', 'convocatoria 055 de 2008 das', 'convocatoria 056-122 de 2009', 'directivos', 'convocatoria 056-122 de 2009', 'concurso de meritos directivos docentes concurso de meritos', 'magdalena', 'concurso de meritos magdalena', 'criterios', 'directrices', 'criterios y directrices', 'Universidades acreditadas', 'corte constitucional', 'consejo de estado', 'corte suprema de justicia', 'orden tutelas', 'acuerdos', 'circulares', 'resoluciones', 'dr eduardo gonzalez', 'dr fridole ballen', 'dra luz patricia trujillo', 'desplazados por razones de violencia', 'empleados con derecho a reincorporacion', 'listas de elegibles', 'registro publico de empleados de carrera', 'comisiones de personal', 'teleconferencias', 'seminarios cnsc', 'jornadas y encuentros', 'investigaciones en curso', 'normas de carrera', 'sistema general', 'leyes','909', 'ley de empleo', 'carrera y gerencia publica', '996', 'ley de garantias', '1033', 'modificaciones ley 909 de 2004', 'carrera administrativa especial', 'modificaciones ley 909 de 2004', 'carrera general', 'decretos', '1746', 'decreto 1227 de 2005', '760/05', 'procedimientos', 'funciones y requisitos', 'entidades del orden nacional', 'funciones y requisitos', 'entidades del nivel territorial', 'reglamentario', 'ley 909 de 2004', 'reglamentario', 'ley 909/2004', 'comisiones de personal', 'funciones y requisitos', 'entidades del orden nacional', 'competencias aborales', 'modifica', 'decreto 1227', 'reglamenta', 'ley 443', 'sistemas especificos', 'aerocivil', 'decreto 790', 'sistema especifico', 'carrera administrativa', 'aerocivil', 'ciencia Y tecnologia', 'decreto 1767', 'estatuto', 'ciencia y tecnologia', 'decreto 585'];



	if(exists) return exists;

	// "Public" variables:

	this.time_out      = 0;		// autocomplete timeout, in milliseconds (0: autocomplete never times out)
	this.response_time = 500;	// time, in milliseconds, between the last char typed and the actual query
	this.entry_limit   = 10;	// number of entries autocomplete will show at a time

	this.limit_start     = true;	// should the auto complete be limited to the beginning of keyword?
	this.match_first     = false;	// if previous is false, should the exact matches be displayed first?
	this.restrict_typing = false;	// restrict to existing members of array
	this.full_refresh    = false;	// should the script re-send the AJAX request after each typed character?

	this.use_iframe  = true;	// should the control use an IFrame element to fix suggestion list positioning (MS IE only)?
	this.use_scroll  = true;	// should the control use a scroll bar (true) or a up/down arrow-buttons (false)?
	this.use_mouse   = true;	// enable mouse support
	this.no_default  = false;	// should the control omit selecting the 1st item in a suggestion list?
	this.start_check = 0;		// show widget only after this number of characters is typed in (effective if >1)

	this.text_delimiter = [";", ","];	// delimiter for multiple autocomplete entries. Set it to empty array ( [] ) for single autocomplete.
	this.ajax_delimiter = "|"; 			// character that delimits entries in the string returned by AJAX call
	this.item_delimiter = ","; 			// character that delimits key and value for the suggestion item in the string returned by AJAX call

	this.selectedIndex = -1;	// index (zero-based) of the entry last selected

	// "Private" variables:

	this.suggest_url = url || (array ? "" : autosuggest_url);	// URL the server-side script that gives you the suggestion list
	this.msie = (document.all && !window.opera);

	this.displayed = false;

	this.delim_words  = [];
	this.current_word = 0;
	this.delim_char   = [];

	this.current    = 0;
	this.total      = 0;
	this.range_up   = 0;
	this.range_down = 0;

	this.previous = 0;
	this.timer    = 0;
	this.rebuild  = false;
	this.evsetup  = false;

	this.bool = [];
	this.rows = [];

	this.onSelect = onSelect || null;

	this.cur_x = 0;
	this.cur_y = 0;
	this.cur_w = 0;
	this.cur_h = 0;

	this.mouse_x = 0;
	this.mouse_y = 0;

	this.mouse_on_list = 0;
	this.caret_moved = false;

	this.field_id = id;
	this.field    = field;
	this.lastterm = field.value;

	this.keywords = [], this.keywords_init = [];
	this.values   = [], this.values_init   = [];

	return this.construct(array || []);
};

autosuggest.prototype = {

	construct: function(array)
	{
		function callLater(func, obj, param1, param2) { return function() { func.call(obj, param1 || null, param2 || null) }; }

		this.field.autosuggest = this;

		// Initialize the control from JS array, if any:

		this.bindArray(array);

		// Create event handlers:

		this.funcClick = this.mouseClick;
		this.funcCheck = this.checkKey;
		this.funcPress = this.keyPress;

		this.funcHighlight = this.highlightTable;

		this.funcClear = callLater(this.clearEvents, this);

		this.funcUp   = callLater(this.scroll, this, true,  1);
		this.funcDown = callLater(this.scroll, this, false, 1);

		this.funcFocus   = callLater(this.focusTable,   this);
		this.funcUnfocus = callLater(this.unfocusTable, this);

		this.addEvent(this.field, "focus", callLater(this.setupEvents, this));
		this.addEvent(window, "resize", callLater(this.reposition, this));

		return this;
	},

	bindArray: function(array)
	{
		if(!array || !array.length) return;

		this.suggest_url = "";

		this.keywords = [], this.keywords_init = [];
		this.values   = [], this.values_init   = [];

		for(var i = 0, cl = array.length; i < cl; i++)
		{
			var item = array[i];

			if(item.constructor == Array)
			{
				this.keywords[i] = this.keywords_init[i] = item[0];
				this.values[i]   = this.values_init[i]   = item[1];
			}
			else
			{
				this.keywords[i] = this.keywords_init[i] = item;
				this.values[i]   = this.values_init[i]   = "";
			}
		}
	},

	bindURL: function(url)
	{
		if(!url)
			url = autosuggest_url;

		this.suggest_url = url;
	},

	setupEvents: function()
	{
		if(!this.evsetup)
		{
			this.evsetup = true;

			this.addEvent(document,   "keydown",  this.funcCheck);
			this.addEvent(this.field, "blur",     this.funcClear);
			this.addEvent(document,   "keypress", this.funcPress);
		}
	},

	clearEvents: function()
	{
		// Removes an event handler:
		function removeEvent(obj, event_name, func_ref)
		{
			if(obj.removeEventListener && !window.opera)
			{
				obj.removeEventListener(event_name, func_ref, true);
			}
			else if(obj.detachEvent)
			{
				obj.detachEvent("on" + event_name, func_ref);
			}
			else
			{
				obj["on" + event_name] = null;
			}
		}

		var event = window.event;

		if(event && this.cur_h)
		{
			var elem = event.srcElement || event.target;

			var x = this.mouse_x + (document.documentElement.scrollLeft || document.body.scrollLeft || 0);
			var y = this.mouse_y + (document.documentElement.scrollTop  || document.body.scrollTop  || 0);

			if((elem.id == this.field_id) && (x > this.cur_x && x < (this.cur_x + this.cur_w)) && (y > this.cur_y && y < (this.cur_y + this.cur_h)))
			{
				this.field.focus();
				return;
			}
		}

		removeEvent(document,   "keydown",  this.funcCheck);
		removeEvent(this.field, "blur",     this.funcClear);
		removeEvent(document,   "keypress", this.funcPress);

		this.hide();
		this.evsetup = false;
	},

	parse: function(n, plen, re)
	{
		if(!n || !n.length)	return "";
		if(!plen) return n;

		var tobuild = [], c = 0, p = n.search(re);

		tobuild[c++] = n.substr(0, p);
		tobuild[c++] = "<span class=\"match\">";
		tobuild[c++] = n.substring(p, plen + p);
		tobuild[c++] = "</span>";
		tobuild[c++] = n.substring(plen + p, n.length);

		return tobuild.join("");
	},

	build: function()
	{
		if(this.total == 0)
		{
			this.displayed = false;
			return;
		}

		this.rows = [];
		this.current = this.no_default ? -1 : 0;

		var that = this;

		this.addEvent(document, "mousemove", function(event)
		{
			event = event || window.event;

			that.mouse_x = event.x;
			that.mouse_y = event.y;
		});

		var body = document.getElementById("suggest_table_" + this.field_id);
		if(body)
		{
			this.displayed = false;
			document.body.removeChild(body);

			var helper = document.getElementById("suggest_helper_" + this.field_id);
			if(helper)
				document.body.removeChild(helper);
		}		

		var bb = document.createElement("div");
		bb.id  = "suggest_table_" + this.field_id;
		bb.className = "autosuggest-body";

		this.cur_y = this.curPos(this.field, "Top") + this.field.offsetHeight;
		bb.style.top = this.cur_y + "px";

		this.cur_x = this.curPos(this.field, "Left");
		bb.style.left = this.cur_x + "px";

		this.cur_w = this.field.offsetWidth - (this.msie ? 2 : 6);
		bb.style.width = this.cur_w + "px";

		this.cur_h = 1;
		bb.style.height = "1px";

		var cc = null;
		if(this.msie && this.use_iframe)
		{
			var cc = document.createElement("iframe");
			cc.id = "suggest_helper_" + this.field_id;

			cc.src = "javascript:\"<html></html>\";";
			cc.scrolling = "no";
			cc.frameBorder = "no";
		}

		var that = this;
		var showFull = (this.total > this.entry_limit);

		if(cc)
		{
			document.body.appendChild(cc);

			cc.style.top = this.cur_y + "px";
			cc.style.left = this.cur_x + "px";

			cc.style.width = bb.offsetWidth + 2;
		}

		document.body.appendChild(bb);

		var first = true, dispCount = showFull ? this.entry_limit : this.total;
		var str = [], cn = 0;

		// cellspacing and cellpadding were not moved to css - IE doesn't understand border-spacing.
		str[cn++] = "<table cellspacing=\"1px\" cellpadding=\"2px\" id=\"suggest_table2_";
		str[cn++] = this.field_id;
		str[cn++] = "\">";

		bb.innerHTML = str.join("");
		var table = bb.firstChild;

		if(this.use_mouse)
		{
			table.onmouseout  = this.funcUnfocus;
			table.onmouseover = this.funcFocus;
		}

		var real_height = 0, real_width = 0;

		function createArrowRow(dir)
		{
			var row = table.insertRow(-1);
			row.className = dir ? "up" : "down";

			var cell = row.insertCell(0);
			real_height += cell.offsetHeight + 1;

			return cell;
		}

		if(!this.use_scroll && showFull)
			createArrowRow(true).parentNode.className = "up-disabled";

		var kl = this.keywords.length, counter = 0, j = 0;

		// For "parse" call:
		var t, plen;
		if(this.text_delimiter.length > 0)
		{
			var word = this.delim_words[this.current_word];

			   t = this.trim(this.addSlashes(word));
			plen = this.trim(word).length;
		}
		else
		{
			var word = this.field.value;

			   t = this.addSlashes(word);
			plen = word.length;
		}

		var re = new RegExp((this.limit_start ? "^" : "") + t, "i");

		function addSuggestion(index, _first)
		{
			var row = that.rows[j] = table.insertRow(-1);
			row.className = (_first || (that.previous == index)) ? "selected" : "";

			var cell = row.insertCell(0);
			cell.innerHTML = that.parse(that.keywords[index], plen, re);
			cell.setAttribute("pos", j++);
			cell.autosuggest = that; 

			if(that.use_mouse)
			{
				that.addEvent(cell, "click", that.funcClick);
				cell.onmouseover = that.funcHighlight;
			}

			return [row.offsetWidth, row.offsetHeight];
		}

		for(var i = 0; i < kl; i++)
		{
			if(this.bool[i])
			{
				var dim = addSuggestion(i, (first && !this.no_default && !this.rebuild));
				first = false;

				if(counter <= this.entry_limit)
					real_height += dim[1] + 1;

				if(real_width < dim[0])
					real_width = dim[0];

				if(++counter == this.entry_limit)
				{
					++i;
					break;
				}
			}
		}

		var last = i;

		if(showFull)
		{
			if(!this.use_scroll)
			{
				var cell = createArrowRow(false);

				if(this.use_mouse)
					this.addEvent(cell, "click", this.funcDown);
			}
			else
			{
				bb.style.height    = real_height + "px";
				bb.style.overflow  = "auto";
				bb.style.overflowX = "hidden";
			}
		}

		this.cur_h = real_height + 1;
		bb.style.height = this.cur_h + "px";

		this.cur_w = ((real_width > bb.offsetWidth) ? real_width : bb.offsetWidth) + (this.msie ? -2 : 2);
		bb.style.width  = this.cur_w + "px";

		if(cc)
		{
			cc.style.height = this.cur_h + "px";
			cc.style.width  = this.cur_w + "px";
		}

		this.range_up   = 0;
		this.range_down = j - 1;
		this.displayed  = true;

		if(this.use_scroll)
		{
			setTimeout(function()
			{
				counter = 0;

				for(var i = last; i < kl; i++)
				{
					if(!that.displayed) return;

					if(that.bool[i])
					{
						addSuggestion(i);

						if(++counter == that.entry_limit)
						{
							++i;
							break;
						}
					}
				}

				last = i;

				if(j < that.total) setTimeout(arguments.callee, 25);
			},
			25);
		}
	},

	remake: function()
	{
		this.rows = [];

		var a = document.getElementById("suggest_table2_" + this.field_id);
		var k = 0, first = true;

		function adjustArrow(obj, which, cond, handler)
		{
			var r = a.rows[k++];
			r.className = which ? (cond ? "up" : "up-disabled") : (cond ? "down" : "down-disabled");

			var c = r.firstChild;

			if(cond && handler && obj.use_mouse)
				obj.addEvent(c, "click", handler);
		}

		if(this.total > this.entry_limit)
		{
			var b = (this.range_up > 0);
			adjustArrow(this, true, b, this.funcUp);
		}

		// For "parse" call:
		var t, plen;
		if(this.text_delimiter.length > 0)
		{
			var word = this.delim_words[this.current_word];

			   t = this.trim(this.addSlashes(word));
			plen = this.trim(word).length;
		}
		else
		{
			var word = this.field.value;

			   t = this.addSlashes(word);
			plen = word.length;
		}

		var re = new RegExp((this.limit_start ? "^" : "") + t, "i");
		var kl = this.keywords.length, j = 0;

		for(var i = 0; i < kl; i++)
		{
			if(this.bool[i])
			{
				if((j >= this.range_up) && (j <= this.range_down))
				{
					var r = this.rows[j] = a.rows[k++];
					r.className = "";

					var c = r.firstChild;
					c.innerHTML = this.parse(this.keywords[i], plen, re);
					c.setAttribute("pos", j);
				}

				if(++j > this.range_down) break;
			}
		}

		if(kl > this.entry_limit)
		{
			var b = (j < this.total);
			adjustArrow(this, false, b, this.funcDown);
		}

		if(this.msie)
		{
			var helper = document.getElementById("suggest_helper_" + this.field_id);
			if(helper) helper.style.width = a.parentNode.offsetWidth + 2;
		}
	},

	reposition: function()
	{
		if(this.displayed)
		{
			this.cur_y = this.curPos(this.field, "Top") + this.field.offsetHeight;
			this.cur_x = this.curPos(this.field, "Left");

			var control = document.getElementById("suggest_table_" + this.field_id);
			control.style.top = this.cur_y + "px";
			control.style.left = this.cur_x + "px";
		}
	},

	startTimer: function(on_list)
	{
		if(this.time_out > 0)
			this.timer = setTimeout(function() { this.mouse_on_list = on_list; this.hide(); }, this.time_out);
	},

	stopTimer: function()
	{
		if(this.timer)
		{
			clearTimeout(this.timer);
			this.timer = 0;
		}
	},

	getRow: function(index)
	{
		if(typeof(index) == "undefined") index = this.current;

		return (this.rows[index] || null);
	},

	fixArrows: function(base)
	{
		if(this.total <= this.entry_limit) return;

		var table = base.firstChild, at_start = (this.current == 0), at_end = (this.current == (this.total - 1));

		var row = table.rows[0];
		row.className = at_start ? "up-disabled" : "up";

		row = table.rows[this.entry_limit + 1];
		row.className = at_end ? "down-disabled" : "down";
	},

	scroll: function(direction, times)
	{
		if(!this.displayed) return;

		this.field.focus();
		if(this.current == (direction ? 0 : (this.total - 1))) return;

		if(!direction && (this.current < 0))
		{
			this.current = -1;
		}
		else
		{
			var t = this.getRow();

			if(t && t.style)
				t.className = "";
		}

		this.current += times * (direction ? -1 : 1);
		if(direction)
		{
			if(this.current < 0)
				this.current = 0;
		}
		else
		{
			if(this.current >= this.total)
				this.current = this.total - 1;

			if(this.use_scroll && (this.current >= this.rows.length))
				this.current = this.rows.length - 1;
		}

		var t = this.getRow(), base = document.getElementById("suggest_table_" + this.field_id);

		if(this.use_scroll)
		{
			if(direction)
			{
				if(t.offsetTop < base.scrollTop)
					base.scrollTop = t.offsetTop;
			}
			else
			{
				if((t.offsetTop + t.offsetHeight) > (base.scrollTop + base.offsetHeight))
				{
					var ndx = this.current - this.entry_limit + 1;
					if(ndx > 0)
						base.scrollTop = this.getRow(ndx).offsetTop;
				}
			}
		}
		else
		{
			if(direction)
			{
				if(this.current < this.range_up)
				{
					this.range_up -= times;
					if(this.range_up < 0) this.range_up = 0;

					this.range_down = this.range_up + this.entry_limit - 1;

					this.remake();
				}
				else this.fixArrows(base);
			}
			else
			{
				if(this.current > this.range_down)
				{
					this.range_down += times;
					if(this.range_down > (this.total - 1)) this.range_down = this.total - 1;

					this.range_up = this.range_down - this.entry_limit + 1;

					this.remake();
				}
				else this.fixArrows(base);
			}

			t = this.getRow();
		}

		if(t && t.style)
			t.className = "selected";

		this.stopTimer();
		this.startTimer(1);

		this.field.focus();
	},

	mouseClick: function(event)
	{
		event = event || window.event;
		var elem = event.srcElement || event.target;

		if(!elem.id) elem = elem.parentNode;

		var obj = elem.autosuggest;

		if(!obj)
		{
			var tag = elem.tagName.toLowerCase();
			elem = (tag == "tr") ? elem.firstChild : elem.parentNode;

			obj = elem.autosuggest;
		}

		if(!obj || !obj.displayed) return;

		obj.mouse_on_list = 0;
		obj.current = parseInt(elem.getAttribute("pos"), 10);
		obj.choose();
	},

	focusTable: function()
	{
		this.mouse_on_list = 1;
	},

	unfocusTable: function()
	{
		this.mouse_on_list = 0;

		this.stopTimer();
		this.startTimer(0)
	},

	highlightTable: function(event)
	{
		event = event || window.event;
		var elem = event.srcElement || event.target;

		var obj = elem.autosuggest;
		if(!obj) return;

		obj.mouse_on_list = 1;

		var row = obj.getRow();
		if(row && row.style)
			row.className = "";

		obj.current = parseInt(elem.getAttribute("pos"), 10);

		row = obj.getRow();
		if(row && row.style)
			row.className = "selected";

		obj.stopTimer();
		obj.startTimer(0);
	},
 
 	choose: function()
	{
		if(!this.displayed) return;
		if(this.current < 0) return;

		this.displayed = false;

		var kl = this.keywords.length;

		for(var i = 0, c = 0; i < kl; i++)
			if(this.bool[i] && (c++ == this.current))
				break;

		this.selectedIndex = i;
		this.insertWord(this.keywords[i]);

		if(this.onSelect)
			this.onSelect(i, this);
	},

	insertWord: function(a)
	{
		// Sets the caret position to l in the object
		function setCaretPos(obj, l)
		{
			obj.focus();

			if(obj.setSelectionRange)
			{
				obj.setSelectionRange(l, l);
			}
			else if(obj.createTextRange)
			{
				var m = obj.createTextRange();
				m.moveStart("character", l);
				m.collapse();
				m.select();
			}
		}

		if(this.text_delimiter.length > 0)
		{
			var str = "", word = this.delim_words[this.current_word], wl = word.length, l = 0;

			for(var i = 0; i < this.delim_words.length; i++)
			{
				if(this.current_word == i)
				{
					var prespace = "", postspace = "", gotbreak = false;

					for(var j = 0; j < wl; ++j)
					{
						if(word.charAt(j) != " ")
						{
							gotbreak = true;
							break;
						}

						prespace += " ";
					}

					for(j = wl - 1; j >= 0; --j)
					{
						if(word.charAt(j) != " ")
							break;

						postspace += " ";
					}

					str += prespace;
					str += a;
					l = str.length;

					if(gotbreak) str += postspace;
				}
				else
				{
					str += this.delim_words[i];
				}

				if(i != this.delim_words.length - 1)
					str += this.delim_char[i];
			}

			this.field.value = str;
			setCaretPos(this.field, l);
		}
		else
		{
			this.field.value = a;
		}

		this.mouse_on_list = 0;
		this.hide();
	},

	hide: function()
	{
		if(this.mouse_on_list == 0)
		{
			this.displayed = false;

			var base = document.getElementById("suggest_table_" + this.field_id);
			if(base)
			{
				var helper = document.getElementById("suggest_helper_" + this.field_id);
				if(helper)
					document.body.removeChild(helper);

				document.body.removeChild(base);
			}

			this.stopTimer();

			this.cur_x = 0;
			this.cur_y = 0;
			this.cur_w = 0;
			this.cur_h = 0;

			this.rows = [];
		}
	},

	keyPress: function(event)
	{
		// On firefox there is no way to distingish pressing shift-8 (asterix)
		// from pressing 8 during the keyDown event, so we do restrict_typing
		// whilest handling the keyPress event

		event = event || window.event;

		var code = window.event ? event.keyCode : event.charCode;
		var obj = event.srcElement || event.target;

		obj = obj.autosuggest;

		if(obj.restrict_typing && !obj.suggest_url.length && (code >= 32))
		{
			var caret_pos = obj.getCaretEnd(obj.field);
            var new_term = obj.field.value.substr(0, caret_pos).toLowerCase();
			var isDelimiter = false;

            if(obj.text_delimiter.length > 0)
			{
                // check whether the pressed key is a delimiter key
                var delim_split = "";
                for(var j = 0; j < obj.text_delimiter.length; j++)
				{
                    delim_split += obj.text_delimiter[j];

                    if(obj.text_delimiter[j] == String.fromCharCode(code))
                        isDelimiter = true;
                }

                // only consider part of term after last delimiter
                delim_split = obj.addSlashes(delim_split);

                var lastterm_rx = new RegExp(".*([" + delim_split + "])");
                new_term = new_term.replace(lastterm_rx, '');
            }

            var keyw_len = obj.keywords.length;
            var i = 0;

            if(isDelimiter)
			{
                // pressed key is a delimiter: allow if current term is complete
                for(i = 0; i < keyw_len; i++)
                    if(obj.keywords[i].toLowerCase() == new_term)
                        break;
            }
			else
			{
                new_term += String.fromCharCode(code).toLowerCase();
                for(i = 0; i < keyw_len; i++)
                    if(obj.keywords[i].toLowerCase().indexOf(new_term) != -1)
                        break;
            }

            if(i == keyw_len)
			{
                obj.stopEvent(event);
                return false;
            }
		}

		if(obj.caret_moved) obj.stopEvent(event);
		return !obj.caret_moved; 
	},

	checkKey: function(event)
	{
		event = event || window.event;

		var code = event.keyCode;
		var obj = event.srcElement || event.target;

		obj = obj.autosuggest; 
		obj.caret_moved = 0;

		var term = "";

		obj.stopTimer();

		switch(code)
		{
			// Up arrow:
			case 38:
				if(obj.current <= 0)
				{
					obj.stopEvent(event);
					obj.hide();
				}
				else
				{
					obj.scroll(true, 1);
					obj.caret_moved = 1;
					obj.stopEvent(event);
				}
				return false;

			// Down arrow:
			case 40:
				if(!obj.displayed)
				{
					obj.timer = setTimeout(function()
					{
						obj.preSuggest(-1);
					},
					25);
				}
				else
				{
					obj.scroll(false, 1);
					obj.caret_moved = 1;
				}
				return false;

			// Page up:
			case 33:
				if(obj.current == 0)
				{
					obj.caret_moved = 0;
					return false;
				}

				obj.scroll(true, (obj.use_scroll || (obj.getRow() == obj.rows[obj.range_up])) ? obj.entry_limit : (obj.current - obj.range_up));
				obj.caret_moved = 1;
				break;

			// Page down:
			case 34:
				if(obj.current == (obj.total - 1))
				{
					obj.caret_moved = 0;
					return false;
				}

				obj.scroll(false, (obj.use_scroll || (obj.getRow() == obj.rows[obj.range_down])) ? obj.entry_limit : (obj.range_down - obj.current));
				obj.caret_moved = 1;
				break;

			// Home
			case 36:
				if(obj.current == 0)
				{
					obj.caret_moved = 0;
					return false;
				}

				obj.scroll(true, obj.total);
				obj.caret_moved = 1;
				break;

			// End
			case 35:
				if(obj.current == (obj.total - 1))
				{
					obj.caret_moved = 0;
					return false;
				}

				obj.scroll(false, obj.total);
				obj.caret_moved = 1;
				break;

			// Esc:
			case 27:
				term = obj.field.value;

				obj.mouse_on_list = 0;
				obj.hide();
				break;

			// Enter:
			case 13:
				if(obj.displayed)
				{
					obj.caret_moved = 1;
					obj.choose();
					return false;
				}
				break;

			// Tab:
			case 9:
				if((obj.displayed && (obj.current >= 0)) || obj.timer)
				{
					obj.caret_moved = 1;
					obj.choose();

					setTimeout(function() { obj.field.focus(); }, 25);
					return false;
				}
				break;

			case 16: //shift
				break;

			default:
				obj.caret_moved = 0;
				obj.timer = setTimeout(function()
				{
					obj.preSuggest(code);
				},
				(obj.response_time < 10 ? 10 : obj.response_time));
		}

		if(term.length) setTimeout(function() { obj.field.value = term; }, 25);
		return true;
	},

	preSuggest: function(kc)
	{
		if(!this.timer)
			return;

		this.stopTimer();

		if(this.displayed && (this.lastterm == this.field.value)) return;
		this.lastterm = this.field.value;

		if(kc == 38 || kc == 40 || kc == 13) return;

		var c = 0;
		if(this.displayed && (this.current >= 0))
		{
			for(var i = 0; i < this.keywords.length; i++)
			{
				if(this.bool[i]) ++c;

				if(c == this.current)
				{
					this.previous = i;
					break;
				}
			}
		}
		else
		{
			this.previous = -1;
		}

		if(!this.field.value.length && (kc != -1))
		{
			this.mouse_on_list = 0;
			this.hide();
		}

		var ot, t;

		if(this.text_delimiter.length > 0)
		{
			var caret_pos = this.getCaretEnd(this.field);

			var delim_split = "";
			for(var i = 0; i < this.text_delimiter.length; i++)
				delim_split += this.text_delimiter[i];

			delim_split = this.addSlashes(delim_split);
			var delim_split_rx = new RegExp("([" + delim_split + "])");
			c = 0;
			this.delim_words = [];
			this.delim_words[0] = "";

			for(var i = 0, j = this.field.value.length; i < this.field.value.length; i++, j--)
			{
				if(this.field.value.substr(i, j).search(delim_split_rx) == 0)
				{
					var ma = this.field.value.substr(i, j).match(delim_split_rx);
					this.delim_char[c++] = ma[1];
					this.delim_words[c] = "";
				}
				else
				{
					this.delim_words[c] += this.field.value.charAt(i);
				}
			}

			var l = 0;
			this.current_word = -1;

			for(i = 0; i < this.delim_words.length; i++)
			{
				if((caret_pos >= l) && (caret_pos <= (l + this.delim_words[i].length)))
					this.current_word = i;

				l += this.delim_words[i].length + 1;
			}

			ot = this.trim(this.delim_words[this.current_word]); 
			 t = this.trim(this.addSlashes(this.delim_words[this.current_word]));
		}
		else
		{
			ot = this.field.value;
			 t = this.addSlashes(ot);
		}

		if(ot.length == 0 && (kc != -1))
		{
			this.mouse_on_list = 0;
			this.hide();
		}
		else if((ot.length == 1) || this.full_refresh ||
		       ((ot.length > 1) && !this.keywords.length) ||
		       ((ot.length > 1) && (this.keywords[0].charAt(0).toLowerCase() != ot.charAt(0).toLowerCase())))
		{
			var ot_ = ((ot.length > 1) && !this.full_refresh) ? ot.charAt(0) : ot;

			if(this.suggest_url.length)
			{

				// create xmlhttprequest object:
				var http = null;
				if(typeof(XMLHttpRequest) != "undefined")
				{
					try
					{
						http = new XMLHttpRequest();
					}
					catch (e) { http = null; }
				}
				else
				{
					try
					{
						http = new ActiveXObject("Msxml2.XMLHTTP") ;
					}
					catch (e)
					{
						try
						{
							http = new ActiveXObject("Microsoft.XMLHTTP") ;
						}
						catch (e) { http = null; }
					}
				}

				if(http)
				{
					// Uncomment for local debugging in Mozilla/Firefox:
					// try { netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead"); } catch (e) { }

					if(http.overrideMimeType)
						http.overrideMimeType("text/xml");

					http.open("GET", this.suggest_url + ot_, true);

					var that = this;
					http.onreadystatechange = function(n)
					{
						if(http.readyState == 4)
						{
							if((http.status == 200) || (http.status == 0))
							{
								var text = http.responseText;

								var index1 = text.indexOf("<listdata>");
								var index2 = (index1 == -1) ? text.length : text.indexOf("</listdata", index1 + 10);

								index1 += (index1 != -1) ? 10 : 1;

								var tmpinfo = text.substring(index1, index2);

								if(tmpinfo)
								{
									that.keywords = tmpinfo.split(that.ajax_delimiter);

									if(that.item_delimiter && that.item_delimiter.length)
									{
										var keyword_number = that.keywords.length;
										for(var i = 0; i < keyword_number; i++)
										{
											var ca = that.keywords[i], comma = ca.indexOf(that.item_delimiter);

											if(comma != -1)
											{
												var ci = ca.split(that.item_delimiter);

												that.keywords[i] = that.keywords_init[i] = ci[0];
												that.values[i]   = that.values_init[i]   = ci[1];
											}
											else
											{
												that.keywords[i] = that.keywords_init[i] = ca;
												that.values[i] = that.values_init[i] = "";
											}
										}
									}

									that.suggest(ot_, t);
								}
							}
						}
					}

					http.send(null);
				}
			}
			else this.suggest(ot, t);
		}
		else this.suggest(ot, t);
	},

	suggest: function(ot, t)
	{
		if(ot.length < this.start_check) return;

		var al = this.keywords.length;
		this.total = 0, this.rebuild = false;

		for(var i = 0; i < al; i++)
		{
			this.keywords[i] = this.keywords_init[i];
			this.values[i] = this.values_init[i];
			this.bool[i] = true;
		}

		if(!this.field.value.length)
		{
			this.total = al;
		}
		else
		{
			var re1 = new RegExp(((!this.limit_start && !this.match_first) ? "" : "^") + t, "i");
			var re2 = new RegExp(t, "i");

			var after = (!this.limit_start && this.match_first);

			var matchArray = [], matchVArray = [];
			var afterArray = [], afterVArray = [];
			var otherArray = [], otherVArray = [];

			for(var i = 0; i < al; i++)
			{
				var key = this.keywords[i];
				var value = this.values[i];

				if(re1.test(key))
				{
					++this.total;

					matchArray[matchArray.length] = key;
					matchVArray[matchVArray.length] = value;
				}
				else if(after && re2.test(key))
				{
					++this.total;

					afterArray[afterArray.length] = key;
					afterVArray[afterVArray.length] = value;
				}
				else
				{
					otherArray[otherArray.length] = key;
					otherVArray[otherVArray.length] = value;
				}
			}

			this.keywords = matchArray.concat(afterArray).concat(otherArray);
			this.values = matchVArray.concat(afterVArray).concat(otherVArray);

			for(i = 0; i < al; i++)
				this.bool[i] = (i < this.total);
		}

		if(this.previous != -1)
			this.rebuild = true;

		if(this.total)
		{
			this.startTimer(0);
			this.build();
		}
		else this.hide();
	},

	// Utility methods:

	// Setup an event handler for the given event and DOM element
	// event_name refers to the event trigger, without the "on", like click or mouseover
	// func_name refers to the function callback that is invoked when event is triggered
	addEvent: function(obj, event_name, func_ref)
	{
		if(obj.addEventListener && !window.opera)
		{
			obj.addEventListener(event_name, func_ref, true);
		}
		else if(obj.attachEvent)
		{
			obj.attachEvent("on" + event_name, func_ref)
		}
		else
		{
			obj["on" + event_name] = func_ref;
		}
	},

	// Stop an event from bubbling up the event DOM
	stopEvent: function(event)
	{
		event = event || window.event;

		if(event)
		{
			if(event.stopPropagation) event.stopPropagation();
			if(event.preventDefault) event.preventDefault();

			if(typeof(event.cancelBubble) != "undefined")
			{
				event.cancelBubble = true;
				event.returnValue = false;
			}
		}

		return false;
	},

	// Get the end position of the caret in the object. Note that the obj needs to be in focus first.
	getCaretEnd: function(obj)
	{
		if(typeof(obj.selectionEnd) != "undefined")
		{
			return obj.selectionEnd;
		}
		else if(document.selection && document.selection.createRange)
		{
			var M = document.selection.createRange(), Lp;

			try
			{
				Lp = M.duplicate();
				Lp.moveToElementText(obj);
			}
			catch(e)
			{
				Lp = obj.createTextRange();
			}

			Lp.setEndPoint("EndToEnd", M);
			var rb = Lp.text.length;

			if(rb > obj.value.length)
				return -1;
		
			return rb;
		}

		return -1;
	},

	// Get offset position from the top/left of the screen:
	curPos: function(obj, what)
	{
		var coord = 0;
		while(obj)
		{
			coord += obj["offset" + what];
			obj = obj.offsetParent;
		}

		return coord;
	},

	// String functions:

	addSlashes: function(str) { return str.replace(/(["\\\.\|\[\]\^\*\+\?\$\(\)])/g, "\\$1"); },

	trim: function(str) { return str.replace(/^\s*(\S*(\s+\S+)*)\s*$/, "$1"); }
};