// Ajax Autocompleter script

// 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;
        }
}

// encodeURIComponent substitute if not defined
if(typeof encodeURIComponent != "function") {
	var encodeURIComponent = function(str) { 
		return escape(str);	
	}
}

if(typeof trimString != "function") {
	var trimString = function(str) {
		return str.replace(/^\s+/,'').replace(/\s+$/,'');
	}
}

if(typeof $ != "function") {
	var $ = function(elemId) {	
		return document.getElementById(elemId);
	}
}

// global Autocompleter namespace
if(typeof Autocompleter == 'undefined') {
	var Autocompleter = {};
}

Autocompleter.Local = function(elementID,listElementsArray,listID,options) {
	
	// autocompleter is always triggered by typing characters in element textbox
	
	// textbox element
	this.element = $(elementID);
	// UL results list
	this.list = $(listID);
	// array with all list entries/elements
	this.listElem = listElementsArray; 
	
	//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 = -1;
	this.listItems = null;
	this.listItemsNo = 0;
	
	// set options
	options.minChars = options.minChars || 1;
	options.noMatchText = options.noMatchText || '=No matches found='; // the text returned if no matches are found
	options.frequency = options.frequency || 500; // interval to attempt search launch (ms)
	options.match = options.match || 'all' ; // how to match typed word with list items text ('all' <=> everywhere OR 'start' <=> only at the item beginning)
	options.highlightClass = options.highlightClass || '';
	
	this.options = options;
	
	// 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.setAttribute('autocomplete','off');
	this.list.style.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
	
}

Autocompleter.Local.prototype.isIEFix = function() {
	if(typeof this.isIE5 == 'undefined') {
		// for IE 5-6, must return true (the iframe trick will be used for them)
		// convert all characters to lowercase to simplify testing
	    var agt=navigator.userAgent.toLowerCase();
	
	    // *** BROWSER VERSION ***
	    // Note: On IE5, these return 4, so use is_ie5up to detect IE5.
	    var is_major = parseInt(navigator.appVersion);
	    var is_minor = parseFloat(navigator.appVersion);
	
	    var is_ie     = ((agt.indexOf("msie") != -1) && (agt.indexOf("opera") == -1));
	    var is_ie3    = (is_ie && (is_major < 4));
	    var is_ie4    = (is_ie && (is_major == 4) && (agt.indexOf("msie 4")!=-1) );
	    var is_ie4up  = (is_ie && (is_major >= 4));
	    var is_ie5    = (is_ie && (is_major == 4) && (agt.indexOf("msie 5.0")!=-1) );
	    var is_ie5_5  = (is_ie && (is_major == 4) && (agt.indexOf("msie 5.5") !=-1));
	    var is_ie5up  = (is_ie && !is_ie3 && !is_ie4);
	    var is_ie5_5up =(is_ie && !is_ie3 && !is_ie4 && !is_ie5);
	    var is_ie6    = (is_ie && (is_major == 4) && (agt.indexOf("msie 6.")!=-1) );
	    var is_ie6up  = (is_ie && !is_ie3 && !is_ie4 && !is_ie5 && !is_ie5_5);
	    
	    this.isIE5 = is_ie5;
	    this.isIE55_or_6 = (is_ie && (is_major == 4) && ((agt.indexOf("msie 5.5") !=-1) || (agt.indexOf("msie 6.")!=-1)));
	    if(this.isIE5) {
	    	this.hideElemId = this.options.hideElemId || []; // array with ids to hide
	    }
	}
    return this.isIE5 || this.isIE55_or_6;
}

Autocompleter.Local.prototype.close = function(context, func, params) {
	if (null == params) {
		return function() {
			return func.apply(context, arguments);
		}
	} else {
		return function() {
			return func.apply(context, params);
		}
	}
}

Autocompleter.Local.prototype.initSearch = function(e) {
	//alert(this.url);
	var evt = e || window.event;
	// test for arrow keys in IE (they do not trigger keypress event!)
	if(evt && (navigator.userAgent.indexOf('MSIE')!=-1) && 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.is_triggered) {
		if(!this.replyReceived) {
			alert('Please wait while the current request completes!');
			return false;
		}
		this.newVal = trimString(this.element.value);
		if((this.newVal.length >= this.options.minChars)) {
			if(this.newVal != this.oldVal) {
				if(this.isOpen) {
					this.hideList();
				}
				this.callServer(this.newVal);
			} else {
				// re-display list
				this.showList();
				if(this.activeIndex) {
					this.listItems[this.activeIndex].className = "selected";
				}
			}
		} else {
			alert("Please enter "+this.options.minChars+" characters to be searched for!");
			this.element.focus();
		}
	} 
	*/
	if(this.searchObserver) {
		clearTimeout(this.searchObserver);
	} else {
		// initiate search observer timer
		this.searchObserver = setTimeout(this.close(this,this.initSearch),this.options.frequency);
		return;
	}
	this.oldVal = this.newVal;
	this.newVal = trimString(this.element.value);
	if(this.newVal != this.oldVal) {
		this.launchSearch = false;
		this.hasChanged = false;
		this.searchObserver = setTimeout(this.close(this,this.initSearch),this.options.frequency);
	} else if(!this.hasChanged) {
		if(this.isOpen) {
			this.hideList();
		}
		if(this.newVal.length >= this.options.minChars) { 
			this.performSearch(this.newVal);
		} 
	} 
			
}

Autocompleter.Local.prototype.performSearch = function(elemVal) {
	//show loader img
	if(this.options.loadingImgID){
		$(this.options.loadingImgID).style.display = 'block';
	}
	// perform search in the list items array
	var ulStyle = 'style="';
	if(this.options.listWidth) {
		ulStyle += 'width:'+this.options.listWidth+'px;';
	}
	if(this.options.listHeight) {
		ulStyle += 'height:'+this.options.listHeight+'px;';
	}
	ulStyle += '"';
	var ulContentHtml = '<ul '+ulStyle+'>';
	var itemRegex = (this.options.match == 'all' ? new RegExp(elemVal,"i") : new RegExp("^"+elemVal,"i"));
	var highlightedVal; // = '<span class="'+this.options.highlightClass+'">'+elemVal+'</span>';
	var i;
	var crtElem;
	var matchFound = false;
	var crtMatches;
	for(i=0; i<this.listElem.length; i++) {
		crtElem = this.listElem[i];
		crtMatches = crtElem.match(itemRegex);
		if(crtMatches !== null) {
			highlightedVal = '<span class="'+this.options.highlightClass+'">'+crtMatches[0]+'</span>';
			ulContentHtml += '<li value="'+crtElem+'">'+crtElem.replace(itemRegex,highlightedVal)+'</li>';
			matchFound = true;
		}
	}
	if(!matchFound) {
		// add not found message
		ulContentHtml += '<li value="'+elemVal+'">'+this.options.noMatchText+'</li>';
	}
	ulContentHtml += '</ul>';
	this.onFinishSearch(ulContentHtml);
}

Autocompleter.Local.prototype.onFinishSearch = function(ulContentHtml) {
		this.hideLoadingImg();
		this.list.innerHTML = ulContentHtml;
		this.activeIndex = 0;
		this.listItems = this.list.getElementsByTagName("li");
		this.listItemsNo = this.listItems.length;
		this.listItems[this.activeIndex].className = "selected";
		if((this.listItemsNo >= 2) || (this.listItems[0].innerHTML.indexOf(this.options.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;
}

Autocompleter.Local.prototype.hideLoadingImg = function() {
	if(this.options.loadingImgID){
		$(this.options.loadingImgID).style.display = 'none';
	}
}

Autocompleter.Local.prototype.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';
		//put list just under the text element
		DHTML.setX(this.list,DHTML.pageX(this.element));
		//alert(parseInt(DHTML.pageY(this.element),10)+1); // parseInt(DHTML.getHeight(this.element),10)+
		DHTML.setY(this.list,DHTML.pageY(this.element)+DHTML.fullHeight(this.element)+1);
		this.list.style.zIndex = 100;
	}
	
	//this.list.style.display='block';
	DHTML.show($(this.list.id));
	
	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++) {	
				DHTML.hide($(this.hideElemId[i]));	
			}
		} else { //IE 5.5 0r 6
			// put iframe under the list div
			var ifr = document.createElement("IFRAME");
			ifr.setAttribute("src","");
			ifr.scrolling = "no";
			ifr.frameBorder = "0";
			ifr.style.zIndex = this.list.style.zIndex-10;
			ifr.style.position = 'absolute';
			ifr.style.border = 0;
			ifr.style.left = DHTML.pageX(this.list)+'px';
			ifr.style.top = DHTML.pageY(this.list)+'px';
			ifr.style.width = DHTML.fullWidth(this.list)+'px';
			ifr.style.height = DHTML.fullHeight(this.list)+'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);
}

Autocompleter.Local.prototype.hideList = function() {
	this.isOpen = false;
	this.hasChanged = false;
	//this.list.style.display='none';
	DHTML.hide($(this.list.id));
	if(this.isIEFix()) {
		if(this.isIE5) {
			// show elements in this.hideElemId array if any
			for (var i=0; i<this.hideElemId.length; i++) {	
				DHTML.show($(this.hideElemId[i]));	
			}
		} else { // IE 5.5 or 6
			// remove iframe & reset
			if(this.ifr) {
				document.body.removeChild(this.ifr);
				this.ifr = null;
			}
		}
	}
}

Autocompleter.Local.prototype.attachListMouseEvents = function() {
	var i;
	var args;
	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);
	}
}

Autocompleter.Local.prototype.handleListMouseEvents = function(evt) {
	var e = evt || window.event;
	if(e) {
		var evtType = e.type;
		var selectedLI = e.srcElement ? e.srcElement : (e.target ? e.target : null);
		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':
						if(this.options.updateElement) {
							this.options.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);
		}
		
	}
}

Autocompleter.Local.prototype.updateElement = function(selectedLI) {
	this.hideList();
	this.hasChanged = true;
	this.oldVal = this.newVal;
	
	if(this.activeIndex) {
		this.newVal = this.listItems[this.activeIndex].getAttribute("value");
	} else {
		this.newVal = selectedLI.getAttribute("value");
	}
	
	this.element.value = this.newVal;
	this.oldVal = this.newVal;
	if(this.searchObserver) {
		clearTimeout(this.searchObserver);
	}
	
}

Autocompleter.Local.prototype.onKeyPress = function(evt) {
	//alert(this.isOpen);
	if(!this.isOpen) {
		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 ) {
		if(this.options.updateElement) {
			this.options.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)
		this.listItems[this.activeIndex].className = "";
		this.activeIndex = this.activeIndex==0 ? this.listItemsNo-1 : this.activeIndex-1;
		this.listItems[this.activeIndex].className = "selected";
		//this.listItems[this.activeIndex].scrollIntoView(false);
		if(!this.isIE) {
			this.listItems[this.activeIndex].scrollIntoView(false);
		} else {
			alternativeScrollIntoView(this.listItems[this.activeIndex].parentNode,this.listItems[this.activeIndex]);
		}
	// If the down key is pressed
	} else if ( e.keyCode == 40 ) {
		// Select the next user, or the first user (if we're at the end)
		this.listItems[this.activeIndex].className = "";
		this.activeIndex = this.activeIndex==(this.listItemsNo-1) ? 0 : this.activeIndex+1;
		this.listItems[this.activeIndex].className = "selected";
		//this.listItems[this.activeIndex].scrollIntoView(false);
		if(!this.isIE) {
			this.listItems[this.activeIndex].scrollIntoView(false);
		} else {
			alternativeScrollIntoView(this.listItems[this.activeIndex].parentNode,this.listItems[this.activeIndex]);
		}
	// If left / right arrow key is pressed
	} else if(e.keyCode ==37 || e.keyCode == 39) {
		this.hasChanged = true;
		return;
	}
	this.hasChanged = true;
	this.stopEvt(e);
	if(this.searchObserver) {
		clearTimeout(this.searchObserver);
	}
	this.searchObserver = setTimeout(this.close(this,this.initSearch),this.options.frequency);
	
}

Autocompleter.Local.prototype.stopEvt = function(e) {
	if(e.stopPropagation) {
		e.stopPropagation();
	}
	e.cancelBubble = true;
}

Autocompleter.Local.prototype.onBlur = function(evt) {
	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;
			}
		}
	}
}

Autocompleter.Local.prototype.onFocus = function(evt) {
	//alert('on focus');return;
	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;
		}
	}
}