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

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

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

Ajax.Autocompleter = function(elementID,listID,handlerURL,options) {
	// textbox element
	this.element = $(elementID);
	// UL results list
	this.list = $(listID);
	// URL to be called
	this.url = handlerURL; 
	
	//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.hasChanged = false; //made true in onKeyPress method, when list activity is detected
	
	// list index and items
	this.activeIndex = -1;
	this.listItems = null;
	this.listItemsNo = 0;
	
	// set options
	options.minChars = options.minChars || 1;
	options.is_triggered = options.is_triggered || false;
	options.paramName = options.paramName || this.element.name || this.element.id;
	options.noMatchText = options.noMatchText || '=No matches found='; // the text returned if no matches are found
	options.timeout = options.timeout || 60000; // timeout(ms) to signal ajax call timeout
	options.noAlertOnError = options.noAlertOnError || false;
	options.noFade = options.noFade || false;
	
	if(options.is_triggered) {
		// autocompleter is triggered by clicking on a button / image
		// trigger element
		this.trigger = $(options.triggerID);
		this.trigger.onclick = this.close(this,this.initSearch);
		// element keypress handler when list is open
		this.element.onkeydown = this.close(this,this.onKeyPress);
	} else {
		// autocompleter is triggered by typing characters in element textbox
		options.frequency = options.frequency || 500;
		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.options = options;
	this.is_triggered = this.options.is_triggered;
	this.defaultParams = this.options.parameters || '';
	this.replyReceived = true; // this is used to hide the onTimeout function if the reply was previously received
	
	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
	
}

Ajax.Autocompleter.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
	    }
	    this.isIE = is_ie;
	}
    return this.isIE5 || this.isIE55_or_6;
}

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

Ajax.Autocompleter.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.options.KeyPressed) this.options.KeyPressed(this.element);
	
	if(this.is_triggered) {
		if(!this.replyReceived) {
			alert('Please wait while the current request completes!');
			return false;
		}
		this.oldVal = this.newVal;
		this.newVal = trimString(this.element.value);
		if((this.newVal.length >= this.options.minChars)) {
			//alert('New value: '+this.newVal+'\nOld value: '+this.oldVal);
			//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();
		}
	} else {
		if(this.searchObserver) {
			clearTimeout(this.searchObserver);
		} else {
			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.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.callServer(this.newVal);
			} 
		} 
	}		
}

Ajax.Autocompleter.prototype.callServer = function(elemVal) {
	//show loader img
	if(this.options.loadingImgID){
		$(this.options.loadingImgID).style.display = 'inline';
	}
	//params :
	var defaultParams = this.defaultParams != '' ? '&'+this.defaultParams : '';
	// any function to be run before server call is made ?
	var dynamicParams = '';
	if(this.options.beforeCallServer) {
		dynamicParams = this.options.beforeCallServer();
	}
	var allParams = dynamicParams != '' ? defaultParams + '&' + dynamicParams : defaultParams;
	var ac = this;
	var ajaxURL = this.url + '?' + this.options.paramName + '=' + encodeURIComponent(elemVal) + allParams;
	var ajaxOpts = {
			type: 'GET',
			url : ajaxURL,
			timeout : ac.options.timeout,
			onSuccess: function(html) { 
				// replyReceived set inside of onSuccess!	
				ac.onSuccess(html); 
			},
			onError: function() {
				ac.replyReceived = true;
				ac.hideLoadingImg();
				if(!ac.options.noAlertOnError) alert('There was an error in processing your request.\nPlease try again!');
			},
			onTimeout: function() {
				if(!ac.replyReceived) {
					ac.hideLoadingImg();
					ac.replyReceived = true;
					if(!ac.options.noAlertOnError) alert('Server response timeout.\nPlease try again!');
				}
			}
	}
	// reset replyReceived on each call
	this.replyReceived = false;
	ajax(ajaxOpts);
}

Ajax.Autocompleter.prototype.onSuccess = function(ulContentHtml) {
		this.replyReceived = true;
		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;
}

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

Ajax.Autocompleter.prototype.showList = function() {
	this.isOpen = true;
	// set absolute position and positionate list if not already
	//if(!this.list.style.position){
		//alert('list position undefined => results div will be positionate!');
		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 = 1000000000;
	//} else {
		//alert('list position defined as "'+this.list.style.position+'"');
	//}
	
	//this.list.style.display='block';
	if(!this.options.noFade){
	    DHTML.fadeIn($(this.list.id));
	}else{
	    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);
}

Ajax.Autocompleter.prototype.hideList = function() {
	this.isOpen = false;
	this.hasChanged = false;
	//this.list.style.display='none';
	if(!this.options.noFade){
	    DHTML.fadeOut($(this.list.id));
	}else{
	    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;
			}
		}
	}
}

Ajax.Autocompleter.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);
	}
}

Ajax.Autocompleter.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,this); //custom update element (performs some actions before calling the default one);
						} else {
							this.updateElement(selectedLI);
						}
						break;
				default:
						break;
			}
			this.stopEvt(e);
		}
		
	}
}

Ajax.Autocompleter.prototype.updateElement = function(selectedLI) {
	this.hideList();
	this.hasChanged = true;
	this.oldVal = this.newVal;
	if(this.activeIndex) {
		this.newVal = this.listItems[this.activeIndex].innerHTML;
	} else {
		this.newVal = selectedLI.innerHTML;
	}
	if((this.listItemsNo == 1) && (this.listItems[0].innerHTML.indexOf(this.options.noMatchText)!=-1)) {
		// no matches found!
		this.newVal = this.element.value;
		this.oldVal = this.element.value;
	} else {
		this.element.value = this.newVal;
	}
	if(!this.is_triggered) {
		this.oldVal = this.newVal;
		if(this.searchObserver) {
			clearTimeout(this.searchObserver);
		}
	}
}

Ajax.Autocompleter.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],this);
		} 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";
		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.is_triggered) {
		if(this.searchObserver) {
			clearTimeout(this.searchObserver);
		}
		this.searchObserver = setTimeout(this.close(this,this.initSearch),this.options.frequency);
	}
}

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

Ajax.Autocompleter.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;
			}
		}
	}
}

Ajax.Autocompleter.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;
		}
	}
}
