// jQuery plugin for attaching a ajax autocompleter to a text input field

// requires jquery.js 

// Implement function.apply for browsers which don't support it natively (e.g IE5)
// Courtesy of Aaron Boodman - http://youngpup.net
if (!Function.prototype.apply) {
        Function.prototype.apply = function(oScope, args) {
                var sarg = [];
                var rtrn, call;

                if (!oScope) oScope = window;
                if (!args) args = [];

                for (var i = 0; i < args.length; i++) {
                        sarg[i] = "args["+i+"]";
                }

                call = "oScope.__applyTemp__(" + sarg.join(",") + ");";

                oScope.__applyTemp__ = this;
                rtrn = eval(call);
                oScope.__applyTemp__ = null;
                return rtrn;
        }
}

function alternativeScrollIntoView(parentDiv, elementIntoDiv) {
    var principal = parentDiv;
    principal.scrollTop = 0;
    var rects = principal.getClientRects()[0];
    var topFinal = rects.top;
    var bottomFinal = rects.bottom;
    var bottomActual = elementIntoDiv.getClientRects()[0].bottom;
    if (bottomActual == 0) {
        return;
    }
    while (bottomActual > bottomFinal || bottomActual < topFinal) {
        var direction = "down";
        if (bottomActual < topFinal) {
            direction = "up";
        }
        principal.doScroll(direction);
        bottomActual = elementIntoDiv.getClientRects()[0].bottom;
    }
}

(function($) {
	
	$.ajaxAutocompleter = function(elementID, ssURL, opt) {
		
		if( !elementID || !document.getElementById(elementID) ) {
			throw new Error('Input field missing or invalid');
		}
		
		if( !ssURL) {
			throw new Error('Autocompleter requires server side URL');
		}
		
		// textbox element
		this.element = $('#'+elementID).get(0);
		
		this.url = ssURL;
		
		// array with all list entries/elements - as objects {'ac_value':'...', 'attr1':'...', ...}
		this.listElem = [];
		
		this.opt = $.extend( {
				listID: 'list_container_div', 				// div containing the resulted UL list
				listClassName: 'autocomplete_menu',			// div container class
				minChars: 1, 								// number of chars after the list items filter is triggered
				noMatchText: '::No matches found::',  		// the text displayed if no matches are found
				frequency: 500, 							// interval to attempt search launch (ms)
				match: 'all', 								// how to match typed word with list items text ('all' <=> everywhere OR 'start' <=> only at the item beginning)
				highlightClass: 'highlight',
				listWidth: 350,
				listHeight: 100,
				listZIndex: 100,
				hideElemId: [],								// IDs of elements to be hidden for IE5 when the suggestion list is displayed
				loadingImgID: null,
				updateElement: null,						// callback function when the input element is updated
				beforeXHRCallback: null,					// callback to be executed before the Ajax call is made (e.g update URL if necessary)
				global_var_name: elementID+'_autocomplete', // global name of autocompleter as variable
				listValueProperty: 'ac_value',				// the name of the property in the listElemsArray objects array that will be considered for match / display in the results list
															// e.g if objects in  listElemsArray are like {ac_value: '...', attr: '...'}, value will be used for match and added as 'ac_value' property of the LI, and the rest will be appended as LI's attributes, e.g. <LI value=".." attr="..">value_highlighted</LI>
				closeListOnEscape: true,					// allows the options list to be closed when ESC key is pressed (keycode = 27)
				method: 'GET',								// method used for Ajax calls
				post_data: {},								// params when POST method required
				headers: {},								// additional headers to be sent
				ZF_params_style: false,						// set it true for ZF (to send match and value params in ZF style)
				hide_no_results: false,						// do not show results list if no results (hide "noMatchText" list)
				onEnterCallback: function() {},				// function to be executed when pressing Enter on text field in IE
				firstItemHighlighted: true,					// auto-highlight and select the first item in the suggestions list
				showDirectionsDiv: false,					// show or not the directions ("To close the list, press ESC or click outside of it") div on the bottom of the suggestions list 
				dummy: ''									// dummy property to prevent IE problems :)
			},
			opt || {}
		);
		
		// create DIV container for UL results list
		this.list = $('<div id="'+this.opt['listID']+'" class="'+this.opt['listClassName']+'" style="width:'+this.opt['listWidth']+'px;height:'+this.opt['listHeight']+'px;"></div>').appendTo('body').get(0);
		var list_title = "To close the list, click outside of it";
		if(this.opt.closeListOnEscape) {
			list_title += " or press Esc";
		}
		this.list.title = list_title;
		
		window[this.opt['global_var_name']] = this;
		
		//old and new textbox value
		this.oldVal = '';
		this.newVal = '';
		
		//status vars
		this.isActive = false;
		this.isOpen = false; // results list is hidden (true if shown)
		this.launchSearch = false; // made true in onKeyPress method, when list activity is detected
		this.hasChanged = false;
		
		// list index and items
		this.activeIndex = undefined;
		this.listItems = null;
		this.listItemsNo = 0;
		
		// element typing handler
		this.element.onkeyup = this.close(this, this.initSearch);
		// element keypress handler when list is open
		this.element.onkeypress = this.close(this, this.onKeyPress);
		
		this.searchObserver = null; // returned by setTimeout
		$(this.element).attr('autocomplete', 'off');
		$(this.list).css('display', 'none');
		
		this.blurObserver = null;
		this.element.hasFocus = false;
		this.element.onblur = this.close(this, this.onBlur);
		this.list.onblur = this.close(this, this.onBlur);
		this.list.onfocus = this.close(this, this.onFocus);
		this.element.onfocus = this.close(this, this.onFocus);
		
		this.ifr = null; // IE fix Iframe
		
		this.browser_ver = parseFloat($.browser.version.substring(0,3));
		this.isIE = $.browser.msie;
		this.isIE7 = this.isIE && (this.browser_ver >= 7);
		
		// if MSIE and the list can be closed using ESC key, we must prevent the typed text from being erased (in IE, the typed text in a textbox can be erased with ESC, and this happens after keydown - first keyboard event)
		if(this.isIE && this.opt.closeListOnEscape) {
			this.element.onkeydown = this.close(this, this.onKeyDown);
		}
		
		// is Chrome?
		var chromeRegexp = /Chrome/;
		this.isChrome = chromeRegexp.test(navigator.userAgent);
		
		var safariRegexp = /Apple/;
		this.isSafari = safariRegexp.test(navigator.vendor);
		
		// on scroll handler to reposition the list if visible when the page is scrolled down
		var global_obj_varname = this.opt['global_var_name'];
		$(window).scroll(
			function() {
				var this_obj = window[global_obj_varname];
				this_obj.onScroll();	
			}
		);
		
	}
	
	$.ajaxAutocompleter.prototype = {
		
		close: function(context, func, params) {
			if (null == params) {
				return function() {
					return func.apply(context, arguments);
				}
			} else {
				return function() {
					return func.apply(context, params);
				}
			}
		},
		
		isIEFix: function() {
			
			// for IE 5-6, must return true (the iframe trick will be used for them)
			
			if(typeof this.isIE5 == 'undefined') {
				this.isIE5 = $.browser.msie && (this.browser_ver == 5);
			}
			
			if( this.isIE5 && (typeof this.hideElemId == 'undefined') ) {
			   	this.hideElemId = this.opt['hideElemId']; // array with ids of elems to hide
			}
			
			if(typeof this.useIframe == 'undefined') {
				this.useIframe = $.browser.msie && (this.browser_ver >=5) && (this.browser_ver < 7);
			}
			
			return this.useIframe;
			
		},
		
		initSearch: function(e) {
			//alert(this.url);
			var evt = e || window.event;
			// test for arrow keys in IE and fuckin Chrome / Safari (they do not trigger keypress event!)
			if(evt && ($.browser.msie || this.isChrome || this.isSafari) && this.isOpen) {
				var k = evt.keyCode;
				if (k==37 || k==38 || k==39 || k==40) {
					// call onKeyPress instead!
					this.onKeyPress(evt);
					//this.hasChanged = true;
					return;
				}
			}
			
			if(this.searchObserver) {
				clearTimeout(this.searchObserver);
			} else {
				// initiate search observer timer
				this.searchObserver = setTimeout(this.close(this,this.initSearch),this.opt['frequency']);
				return;
			}
			this.oldVal = this.newVal;
			this.newVal = $.trim(this.element.value);
			if(this.newVal != this.oldVal) {
				this.launchSearch = false;
				this.hasChanged = false;
				this.searchObserver = setTimeout(this.close(this,this.initSearch),this.opt['frequency']);
			} else if(!this.hasChanged) {
				if(this.isOpen) {
					this.hideList();
				}
				if(this.newVal.length >= this.opt['minChars']) { 
					this.performSearch(this.newVal);
				} 
			} 
					
		},
		
		performSearch: function(elemVal) {
			//show loader img
			if(this.opt['loadingImgID']){
				$('#'+this.opt['loadingImgID']).show();
			}
			var _this = this;
			this.listElem = []; // reset elements
			if( this.opt.beforeXHRCallback ) {
				this.opt.beforeXHRCallback(); // e.g can dynamically update URL, etc.
			}
			
			if( !this.opt['ZF_params_style'] ) {
				if(this.url.indexOf('?') == -1) {
					this.url_end = '?';
				} else {
					this.url_end = '&';
				}
			} else {
				if(this.url.indexOf('/') == this.url.length) {
					this.url_end = '';
				} else {
					this.url_end = '/';
				}
			}
			
			var and = '&', 
				equal = '=';
			if(this.opt['ZF_params_style']) {
				and = equal = '/';
			}
//			alert(_this.url+_this.url_end+'value'+equal+encodeURIComponent(elemVal)+and+'match'+equal+_this.opt.match);
			$.ajax(
				{
					url: _this.url+_this.url_end+'value'+equal+encodeURIComponent(elemVal)+and+'match'+equal+_this.opt.match,
					type: _this.opt.method,
					dataType: 'json',
					beforeSend: function(xhr) {
						// set additional headers if any
						var hdr;
						for(h in _this.opt.headers) {
							hdr = _this.opt.headers[h];
							xhr.setRequestHeader(h, hdr);
						}
					},
					success: function(data) {
						// perform search in the list items array
						_this.listElem = data;
						var ulStyle = 'style="';
						if(_this.opt['listWidth']) {
							ulStyle += 'width:'+_this.opt['listWidth']+'px;';
						}
						if(_this.opt['listHeight']) {
							ulStyle += 'height:'+_this.opt['listHeight']+'px;';
						}
						ulStyle += '"';
						var ulContentHtml = '<ul '+ulStyle+'>';
						var itemRegex = (_this.opt['match'] == 'all' ? new RegExp(elemVal,"i") : new RegExp("^"+elemVal,"i"));
						var highlightedVal; // = '<span class="'+this.options.highlightClass+'">'+elemVal+'</span>';
						var i;
						var crtElem, crtElemValue;
						var matchFound = false;
						var crtMatches;
						var LIAttribs;
						for(i=0; i<_this.listElem.length; i++) {
							crtElem = _this.listElem[i];
							crtElemValue = crtElem[_this.opt.listValueProperty].toString(); // usually crtElem['value'];
							crtMatches = crtElemValue.match(itemRegex);
							if(crtMatches !== null) {
								//alert(crtElemValue);
								// create the string of the rest of the attributes
								LIAttribs = '';
								for(var atr in crtElem) {
									if(crtElem[atr] != '') {
										if( atr != _this.opt.listValueProperty ) {
											LIAttribs += ' '+atr+'="'+crtElem[atr]+'"';
										}
									}
								}
								highlightedVal = '<span class="'+_this.opt['highlightClass']+'">'+crtMatches[0]+'</span>';
								ulContentHtml += '<li '+_this.opt.listValueProperty+'="'+_this.htmlspecialchars(crtElemValue)+'"'+LIAttribs+'>'+crtElemValue.replace(itemRegex,highlightedVal)+'</li>';
								matchFound = true;
							}
						}
						if(!matchFound) {
							// add not found message
							ulContentHtml += '<li '+_this.opt.listValueProperty+'="'+elemVal+'">'+_this.opt['noMatchText']+'</li>';
						}
						ulContentHtml += '</ul>';
						_this.onFinishSearch(ulContentHtml, matchFound);
					}
//					,error: function(xhr, err_status, err_thrown){
//						alert(err_status + " " + err_thrown.toString());
//					}
				}
			);
		},
		
		onFinishSearch: function(ulContentHtml, matchFound) {
			
			this.hideLoadingImg();
			
			// do not show the list if no matches were found and the options requires so
			if(!matchFound && this.opt['hide_no_results']) {
				return;
			}
			
			this.list.innerHTML = ulContentHtml;
			
			this.listItems = this.list.getElementsByTagName("li");
			this.listItemsNo = this.listItems.length;
			
			if(this.firstItemHighlighted) {
				this.activeIndex = 0;
				this.listItems[this.activeIndex].className = "selected";
			} else {
				this.activeIndex = undefined;
			}
			
			if((this.listItemsNo >= 2) || (this.listItems[0].innerHTML.indexOf(this.opt['noMatchText'])==-1)) {
				this.attachListMouseEvents();
			} else {
				this.listItems[0].index = 0;
				this.listItems[0].onclick = this.close(this,this.hideList);
				this.listItems[0].title = 'Click to close';
			}
			this.showList();
			this.element.focus();
			this.element.hasFocus = true;
			this.list_ul = this.list.getElementsByTagName('ul')[0];
			this.list_ul.scrollTop = 0;
			if(this.isChrome || this.isSafari) {
				// attach onscroll property for Chrome - set focus on the list when scrolling it to keep it visible!
				this.list_ul.onscroll = this.close(this,this.onFocus);
			}
		},
		
		hideLoadingImg: function() {
			if(this.opt['loadingImgID']){
				$('#'+this.opt['loadingImgID']).hide();
			}
		},
		
		showList: function() {
			this.isOpen = true;
			// set absolute position and positionate list if not already
			if(!this.list.style.position){
				//alert('list position undefined');
				this.list.style.position = 'absolute';
				// get element position
				var elem_pos = $(this.element).offset();
				//put list just under the text element
				//DHTML.setX(this.list,DHTML.pageX(this.element));
				$(this.list).css('top', (elem_pos['top'] + $(this.element).outerHeight())+'px');
				$(this.list).css('left', elem_pos['left'] + 'px');
				//alert(parseInt(DHTML.pageY(this.element),10)+1);
				//DHTML.setY(this.list,DHTML.pageY(this.element)+DHTML.fullHeight(this.element)+1);
				this.list.style.zIndex = this.opt['listZIndex']; // default 100
			}
			
			// attach direction div if required
			if( this.opt.showDirectionsDiv ) {
				$(this.list).append('<div style="font-size:10px;background-color:#dcdcdc;">*'+this.list.title+'</div>');
			}
			
			$(this.list).fadeIn('fast');
			
			if(this.isIEFix()) {
				//alert('ie fix');
				if(this.isIE5) {
					// hide elements in this.hideElemId array if any
					for (var i=0; i < this.hideElemId.length; i++) {	
						$('#'+this.hideElemId[i]).hide();	
					}
				} else { //IE 5.5 0r 6
					
					// put iframe under the list div
					
					var list_pos = $(this.list).offset();
					var list_h = $(this.list).outerHeight();
					var list_w = $(this.list).outerWidth();
					
					var ifr = document.createElement("IFRAME");
					ifr.setAttribute("src","");
					ifr.scrolling = "no";
					ifr.frameBorder = "0";
					ifr.style.zIndex = this.list.style.zIndex - 10;
					//ifr.style.backgroundColor = '#dcdcdc';
					ifr.style.position = 'absolute';
					ifr.style.border = 0;
					
					$(ifr).css('left', list_pos['left'] + 'px');
					$(ifr).css('top', list_pos['top'] + 'px');
					$(ifr).css('width', list_w + 'px');
					$(ifr).css('height', list_h + 'px');
					
					//DHTML.setOpacity(ifr,0); //make it transparent
					ifr.style.filter='progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)';
					ifr.style.display = 'block';
					this.ifr = ifr;
					document.body.appendChild(this.ifr);
				}
			}
			//this.listItems[this.activeIndex].scrollIntoView(true);
		},
		
		hideList: function() {
			this.isOpen = false;
			this.hasChanged = false;
			//this.list.style.display='none';
			$(this.list).hide();
			if( this.isIEFix() ) {
				if( this.isIE5 ) {
					// show elements in this.hideElemId array if any
					for (var i=0; i<this.hideElemId.length; i++) {	
						$('#'+this.hideElemId[i]).show();	
					}
				} else { // IE 5.5 or 6
					// remove iframe & reset
					if( this.ifr ) {
						document.body.removeChild(this.ifr);
						this.ifr = null;
					}
				}
			}
		},
		
		attachListMouseEvents: function() {
			var i;
			var args;
			var _this = this;
			for(i=0; i<this.listItemsNo; i++) {
				this.listItems[i].index = i;
				this.listItems[i].onmouseover = this.close(this,this.handleListMouseEvents);
				this.listItems[i].onmouseout = this.close(this,this.handleListMouseEvents);
//				this.listItems[i].onclick = this.close(this,this.handleListMouseEvents);
				this.listItems[i].onmousedown = this.close(this,this.handleListMouseEvents);
//				$(this.listItems[i]).find('span.'+this.opt['highlightClass']).click(
				$(this.listItems[i]).find('span.'+this.opt['highlightClass']).mousedown(
					function(evt) {
						(_this.close(_this, _this.handleListMouseEvents, [evt]))();
					}
				);
			}
		},
		
		handleListMouseEvents: function(evt) {
			var e = evt || window.event;
			if(e) {
				var evtType = e.type;
				var evtSrc = e.srcElement ? e.srcElement : (e.target ? e.target : null);
				if( evtSrc ) {
					var evtSrcTagName = evtSrc.tagName.toLowerCase();
					var selectedLI = ( evtSrcTagName == 'li' ) ? evtSrc : evtSrc.parentNode;  
					if( selectedLI && (selectedLI.tagName.toLowerCase() == 'li') ) {
						switch(evtType) {
							case 'mouseover':
									if((typeof this.activeIndex!=='undefined') && (this.listItems[this.activeIndex].className=="selected")) {
										this.listItems[this.activeIndex].className="";
									}
									selectedLI.className = "selected";
									this.activeIndex = selectedLI.index;
									//selectedLI.scrollIntoView();
									break;
							case 'mouseout':
									selectedLI.className = "";
									break;	
							case 'click':
							case 'mousedown':
									if(this.opt['updateElement']) {
										(this.opt['updateElement'])(selectedLI); //custom update element (performs some actions before calling the default one);
									} else {
										this.updateElement(selectedLI);
									}
									this.element.focus();
									break;
							default:
									break;
						}
						this.stopEvt(e);
					}
				}
			}
		},
		
		updateElement: function(selectedLI) {
			this.hideList();
			this.hasChanged = true;
			this.oldVal = this.newVal;
			
			if(this.activeIndex) {
				this.newVal = this.listItems[this.activeIndex].getAttribute(this.opt.listValueProperty);
			} else {
				this.newVal = selectedLI.getAttribute(this.opt.listValueProperty);
			}
			
			this.newVal = this.stripslashes(this.newVal);
			
			this.element.value = this.newVal;
			this.oldVal = this.newVal;
			if(this.searchObserver) {
				clearTimeout(this.searchObserver);
			}
			
		},
		
		onKeyDown: function(evt) {
			// this function is attached just for IE to prevent the text from being erased when ESC is pressed
			var e = evt || window.event; // just in case, only window.event should be available since this function deals only with IE issue of erasing textbox when Esc is pressed
			if ( e.keyCode == 27 ) {
				// escape pressed, call onKeyPress and return false for keydown
				this.onKeyPress(e);
				return false;
			}
			if( e.keyCode == 13 ) {
				// if the source is the A/C textbox, call whatever function was set
				if( e.srcElement.id == this.element.id ) { // && !this.isOpen
					if(this.opt['onEnterCallback']) {
						return this.opt['onEnterCallback'].call();
					}
					return false;
				}
			}
		},
		
		onKeyPress: function(evt) {
			//alert(this.isOpen);
			if(!this.isOpen) {
//				alert('not open=> return');
				return;
			}
			var e = evt || window.event;
//			alert(e.keyCode);return;
			// If the [TAB] or [Enter] keys are pressed
			if ( e.keyCode == 9 || e.keyCode == 13 ) {
//				alert('Enter or tab pressed');
				if(typeof this.activeIndex != 'undefined') {
					if(this.opt['updateElement']) {
						(this.opt['updateElement'])(this.listItems[this.activeIndex]);
					} else {
						this.updateElement(this.listItems[this.activeIndex]);
					}
					return false; // prevents form submit in FF (IE?)
				}
			// If the up key is presssed
			} else if ( e.keyCode == 38 ) {
				// Select the previous item, or the last item (if we're at the beginning)
				if(typeof this.activeIndex != 'undefined') {
					this.listItems[this.activeIndex].className = "";
					this.activeIndex = this.activeIndex==0 ? this.listItemsNo-1 : this.activeIndex-1;
				} else {
					this.activeIndex = this.listItemsNo-1;
				}
				this.listItems[this.activeIndex].className = "selected";
				//this.listItems[this.activeIndex].scrollIntoView(false);
				/*
				if(!this.isIE || this.isIE7 ) {
					this.listItems[this.activeIndex].scrollIntoView(false);
				} else {
					alternativeScrollIntoView(this.listItems[this.activeIndex].parentNode,this.listItems[this.activeIndex]);
				}
				//*/
				this.list_ul = this.list.getElementsByTagName('ul')[0];
				if( this.activeIndex == this.listItemsNo-1 ) {
					this.list_ul.scrollTop = this.list_ul.scrollHeight + this.list_ul.offsetHeight;
					if($.browser.msie) {
						this.listItems[this.activeIndex].scrollIntoView(false);
					}
				}
				var selection_top = this.listItems[this.activeIndex].offsetTop;
				if(selection_top < this.list_ul.scrollTop){
					this.list_ul.scrollTop = this.list_ul.scrollTop - this.listItems[this.activeIndex].offsetHeight;
				}
			// If the down key is pressed
			} else if ( e.keyCode == 40 ) {
				// Select the next item, or the first item (if we're at the end)
				if(typeof this.activeIndex != 'undefined') {
					this.listItems[this.activeIndex].className = "";
					this.activeIndex = this.activeIndex==(this.listItemsNo-1) ? 0 : this.activeIndex+1;
				} else {
					this.activeIndex = 0;
				}
				this.listItems[this.activeIndex].className = "selected";
				//this.listItems[this.activeIndex].scrollIntoView(false);
				/*
				if(!this.isIE || this.isIE7) {
					this.listItems[this.activeIndex].scrollIntoView(false);
				} else {
					alternativeScrollIntoView(this.listItems[this.activeIndex].parentNode,this.listItems[this.activeIndex]);
				}
				//*/
				this.list_ul = this.list.getElementsByTagName('ul')[0];
				if( this.activeIndex == 0 ) {
					this.list_ul.scrollTop = 0;	
				}
				var selection_bottom = this.listItems[this.activeIndex].offsetTop + this.listItems[this.activeIndex].offsetHeight;
				if( selection_bottom > (this.list_ul.scrollTop + this.list_ul.offsetHeight) ) {
					this.list_ul.scrollTop = this.list_ul.scrollTop + this.listItems[this.activeIndex].offsetHeight;
				}
			// If left / right arrow key is pressed
			} else if(e.keyCode ==37 || e.keyCode == 39) {
				this.hasChanged = true;
				return;
			} else if( (e.keyCode == 27) && this.opt.closeListOnEscape ) {
				// close the options list if it's open (and it is), let the text as it is, don't complete anything
				if(this.searchObserver) {
					clearTimeout(this.searchObserver);
				}
				this.stopEvt(e);
				this.element.blur();
				this.hideList();
				return;
			}
			this.hasChanged = true;
			this.stopEvt(e);
			if(this.searchObserver) {
				clearTimeout(this.searchObserver);
			}
			this.searchObserver = setTimeout(this.close(this,this.initSearch),this.opt['frequency']);
			
		},
		
		stopEvt: function(e) {
			if(e.stopPropagation) {
				e.stopPropagation();
			}
			e.cancelBubble = true;
		},
		
		onBlur: function(evt) {
//			alert('blur');
			if(this.isOpen) {
				var e = evt || window.event;
				if(e) {	
					var evtSrc = e.srcElement ? e.srcElement : (e.target ? e.target : null);
					//if((evtSrc.id==this.list.id) || !this.element.hasFocus) {
					this.blurObserver = setTimeout(this.close(this,this.hideList),250);
					//}
					if(evtSrc.id==this.element.id) {
						this.element.hasFocus = false;
					}
				}
			}
		},
		
		onFocus: function(evt) {
//			alert('focus');
			if(this.blurObserver) {
				clearTimeout(this.blurObserver);
			}
			var e = evt || window.event;
			if(e) {	
				var evtSrc = e.srcElement ? e.srcElement : (e.target ? e.target : null);
				if(evtSrc.id==this.element.id) {
					this.element.hasFocus = true;
				} else {
					this.element.hasFocus = false;
				}
				if(this.isChrome && e.type=='scroll') {
					this.element.focus();
				}
			}
		},
		
		addslashes: function(str) {
			return (str+'').replace(/([\\"'])/g, "\\$1").replace(/\0/g, "\\0");
		},
		
		stripslashes: function(str) {
			return (str+'').replace(/\0/g, '0').replace(/\\([\\'"])/g, '$1');
		},
		
		htmlspecialchars: function(string, quote_style) {
			
			// *     example 1: htmlspecialchars("<a href='test'>Test</a>", 'ENT_QUOTES');
			// *     returns 1: '&lt;a href=&#039;test&#039;&gt;Test&lt;/a&gt;'
			
			var histogram = {}, symbol = '', tmp_str = '', entity = '';
			tmp_str = string.toString();
			
			if (false === (histogram = this.get_html_translation_table('HTML_SPECIALCHARS', quote_style))) {
			    return false;
			}
			
			for (symbol in histogram) {
			    entity = histogram[symbol];
			    tmp_str = tmp_str.split(symbol).join(entity);
			}
			
			return tmp_str;
		},
		
		get_html_translation_table: function(table, quote_style) {
			
			// *     example 1: get_html_translation_table('HTML_SPECIALCHARS');
			// *     returns 1: {'"': '&quot;', '&': '&amp;', '<': '&lt;', '>': '&gt;'}
			
			var entities = {}, histogram = {}, decimal = 0, symbol = '';
			var constMappingTable = {}, constMappingQuoteStyle = {};
			var useTable = {}, useQuoteStyle = {};
			
			// Translate arguments
			constMappingTable[0]      = 'HTML_SPECIALCHARS';
			constMappingTable[1]      = 'HTML_ENTITIES';
			constMappingQuoteStyle[0] = 'ENT_NOQUOTES';
			constMappingQuoteStyle[2] = 'ENT_COMPAT';
			constMappingQuoteStyle[3] = 'ENT_QUOTES';
			
			useTable     = !isNaN(table) ? constMappingTable[table] : table ? table.toUpperCase() : 'HTML_SPECIALCHARS';
			useQuoteStyle = !isNaN(quote_style) ? constMappingQuoteStyle[quote_style] : quote_style ? quote_style.toUpperCase() : 'ENT_COMPAT';
			
			if (useTable !== 'HTML_SPECIALCHARS' && useTable !== 'HTML_ENTITIES') {
			    throw Error("Table: "+useTable+' not supported');
			    // return false;
			}
			
			// ascii decimals for better compatibility
			entities['38'] = '&amp;';
			if (useQuoteStyle !== 'ENT_NOQUOTES') {
			    entities['34'] = '&quot;';
			}
			if (useQuoteStyle === 'ENT_QUOTES') {
			    entities['39'] = '&#039;';
			}
			entities['60'] = '&lt;';
			entities['62'] = '&gt;';
			
			if (useTable === 'HTML_ENTITIES') {
			  entities['160'] = '&nbsp;';
			  entities['161'] = '&iexcl;';
			  entities['162'] = '&cent;';
			  entities['163'] = '&pound;';
			  entities['164'] = '&curren;';
			  entities['165'] = '&yen;';
			  entities['166'] = '&brvbar;';
			  entities['167'] = '&sect;';
			  entities['168'] = '&uml;';
			  entities['169'] = '&copy;';
			  entities['170'] = '&ordf;';
			  entities['171'] = '&laquo;';
			  entities['172'] = '&not;';
			  entities['173'] = '&shy;';
			  entities['174'] = '&reg;';
			  entities['175'] = '&macr;';
			  entities['176'] = '&deg;';
			  entities['177'] = '&plusmn;';
			  entities['178'] = '&sup2;';
			  entities['179'] = '&sup3;';
			  entities['180'] = '&acute;';
			  entities['181'] = '&micro;';
			  entities['182'] = '&para;';
			  entities['183'] = '&middot;';
			  entities['184'] = '&cedil;';
			  entities['185'] = '&sup1;';
			  entities['186'] = '&ordm;';
			  entities['187'] = '&raquo;';
			  entities['188'] = '&frac14;';
			  entities['189'] = '&frac12;';
			  entities['190'] = '&frac34;';
			  entities['191'] = '&iquest;';
			  entities['192'] = '&Agrave;';
			  entities['193'] = '&Aacute;';
			  entities['194'] = '&Acirc;';
			  entities['195'] = '&Atilde;';
			  entities['196'] = '&Auml;';
			  entities['197'] = '&Aring;';
			  entities['198'] = '&AElig;';
			  entities['199'] = '&Ccedil;';
			  entities['200'] = '&Egrave;';
			  entities['201'] = '&Eacute;';
			  entities['202'] = '&Ecirc;';
			  entities['203'] = '&Euml;';
			  entities['204'] = '&Igrave;';
			  entities['205'] = '&Iacute;';
			  entities['206'] = '&Icirc;';
			  entities['207'] = '&Iuml;';
			  entities['208'] = '&ETH;';
			  entities['209'] = '&Ntilde;';
			  entities['210'] = '&Ograve;';
			  entities['211'] = '&Oacute;';
			  entities['212'] = '&Ocirc;';
			  entities['213'] = '&Otilde;';
			  entities['214'] = '&Ouml;';
			  entities['215'] = '&times;';
			  entities['216'] = '&Oslash;';
			  entities['217'] = '&Ugrave;';
			  entities['218'] = '&Uacute;';
			  entities['219'] = '&Ucirc;';
			  entities['220'] = '&Uuml;';
			  entities['221'] = '&Yacute;';
			  entities['222'] = '&THORN;';
			  entities['223'] = '&szlig;';
			  entities['224'] = '&agrave;';
			  entities['225'] = '&aacute;';
			  entities['226'] = '&acirc;';
			  entities['227'] = '&atilde;';
			  entities['228'] = '&auml;';
			  entities['229'] = '&aring;';
			  entities['230'] = '&aelig;';
			  entities['231'] = '&ccedil;';
			  entities['232'] = '&egrave;';
			  entities['233'] = '&eacute;';
			  entities['234'] = '&ecirc;';
			  entities['235'] = '&euml;';
			  entities['236'] = '&igrave;';
			  entities['237'] = '&iacute;';
			  entities['238'] = '&icirc;';
			  entities['239'] = '&iuml;';
			  entities['240'] = '&eth;';
			  entities['241'] = '&ntilde;';
			  entities['242'] = '&ograve;';
			  entities['243'] = '&oacute;';
			  entities['244'] = '&ocirc;';
			  entities['245'] = '&otilde;';
			  entities['246'] = '&ouml;';
			  entities['247'] = '&divide;';
			  entities['248'] = '&oslash;';
			  entities['249'] = '&ugrave;';
			  entities['250'] = '&uacute;';
			  entities['251'] = '&ucirc;';
			  entities['252'] = '&uuml;';
			  entities['253'] = '&yacute;';
			  entities['254'] = '&thorn;';
			  entities['255'] = '&yuml;';
			}
			
			// ascii decimals to real symbols
			for (decimal in entities) {
			    symbol = String.fromCharCode(decimal);
			    histogram[symbol] = entities[decimal];
			}
			
			return histogram;
		},
		
		onScroll: function() {
			try {
				if(this.isOpen) {
					var elem_pos = $(this.element).offset();
					//put list just under the text element
					$(this.list).css('top', (elem_pos['top'] + $(this.element).outerHeight())+'px');
					$(this.list).css('left', elem_pos['left'] + 'px');
				}
			} catch(e){}
		},
		
		dummy: ''
		
	}
	
})(jQuery);