/*
Object based AJAX auto-suggest library
======================================
(c) 2008 Seb Francis www.burntIT.co.uk

In the following example all properties are optional, and are set here with their default values...


// CONSTRUCTOR

// 'searchfor' is the id of the input box to which the auto-suggest will be attached
// Optionally 'searchpos' specifies the id of the element under which the auto-suggest box will appear (defaults to the input box)
var myAjs = new ajs('searchfor' [,'searchpos']);


// GENERAL SETTINGS

// The page to call on the server to do the search
// This page should return a list of results, each followed with "\n"
myAjs.searchPage = 'autosearch.php?searchfor=';

// If true then caching of AJAX request is disabled (necessary for IE)
myAjs.disableCaching = false;

// If true then html in the search results is allowed, otherwise & < > are escaped
myAjs.allowHtml = false;

// Number of characters to be typed before query is sent
myAjs.numChars = 2;

// If set to true the search result of the selected suggestion is taken from between <!--s--> and <!--e-->
myAjs.extractSearchResult = false;

// If true then a single result will be automatically selected so the user can just press Enter to choose
myAjs.autoSelectSingleResult = true;

// If specified the form with this id is submitted when a result is chosen
myAjs.autoSubmitFormId = '';

// Optionally a callback function which will be called when suggestion is chosen.
// The form element in question is passed as a parameter.
myAjs.onchoose = null;


// STYLE SETTINGS FOR SUGGEST RESULTS

// Number of results visible in the suggest box at one time
myAjs.visibleResults = 7;

// Suggest box position (either 'below' or 'above')
myAjs.position = 'below';

// Left offset of suggest box
myAjs.left = '0px';

// Top offset of suggest box (or if .position is 'above' then this is the bottom offset)
myAjs.top = '19px';

// Width of suggest box (e.g. '234px')
myAjs.width = 'auto';

// Maximum height of suggest box (units must be 'px' or it won't work properly in IE)
// This isn't used if ajs.width is 'auto'
myAjs.maxHeight = '300px';

// Text color of suggest box
myAjs.color = '#333333';

// Border of suggest box
myAjs.border = '1px solid #CDCDBA';

// Padding of suggest box
myAjs.padding = '0px';

// Background color of suggest box
myAjs.background = '#F9F9F2';

// Background color of unselected search result
myAjs.backgroundUnsel = '#F9F9F2';

// Background of selected search result
myAjs.backgroundSel = '#F2F2E5';

// If set to true then disables line wrapping at whitespace in each search result
myAjs.noWrap = true;

// If multiple auto-suggests are used on a page the zIndex property may need to be set if
// suggest results overlap another form
myAjs.zIndex = 10;

// This boolean can be used to disable/enable auto-suggest at run time
myAjs.enabled = true;


// INITIALISE

// After setting properties call this function to initialise auto-suggest
// The input box element must exist at the time when this is called
myAjs.init();

*/



var ajs_numberOfInstances = 0;
var ajs_objects = new Array();

function ajs(inputId, wrapId) {
	// Defaults for public properties
	this.searchPage = 'autosearch.php?searchfor=';
	this.disableCaching = false;
	this.allowHtml = false;
	this.numChars = 2;
	this.extractSearchResult = false;
	this.autoSelectSingleResult = true;
	this.autoSubmitFormId = '';
	this.onchoose = null;
	this.visibleResults = 7;
	this.position = 'below';
	this.left = '0px';
	this.top = '19px';
	this.width = 'auto';
	this.maxHeight = '300px';
	this.border = '1px solid #CDCDBA';
	this.padding = '0px';
	this.color = '#333333';
	this.background = '#F9F9F2';
	this.backgroundUnsel = '#F9F9F2';
	this.backgroundSel = '#F2F2E5';
	this.noWrap = true;
	this.zIndex = 10;
	this.enabled = true;
	
	
	// Assign unique id to this instance and point back to this instance from global Array
	this.id = ajs_numberOfInstances++;
	ajs_objects[this.id] = this;
	
	// Initial state of XMLHttpRequest object
	this.request = null;
	
	// Initialise
	this.init = function () {
		var inputEl = $('#'+inputId);
		inputEl.attr('autocomplete','off');
		
		if (!wrapId) {
			wrapId = inputId;
		}
		// Create wrapper div and results div
		var wrapEl = $('#'+wrapId);
		wrapEl.wrap('<div style="position: relative; overflow: visible;"></div>');
		var resultsDiv = $('<div id="ajs_results_'+this.id+'"></div>').css({
			display: 'none',
			position: 'absolute',
			left: this.left,
			width: this.width,
			border: this.border,
			padding: this.padding,
			color: this.color,
			background: this.background,
			'z-index': this.zIndex
		});
		if (this.position == 'above') {
			resultsDiv.css('bottom', this.top);
		} else {
			resultsDiv.css('top', this.top);
		}
		if (this.width == 'auto') {
			resultsDiv.css({
				overflow: 'visible'
			});
		} else {
			resultsDiv.css({
				'max-height': this.maxHeight,
				overflow: 'hidden',
				'overflow-y': 'auto'
			});
		}
		wrapEl.after(resultsDiv);
	
		// Input element
		this.sf = document.getElementById(inputId);
		// Point input element back at object
		this.sf.obj = this;
		// Results div
		this.as = document.getElementById('ajs_results_'+this.id);
		
		// Add max-height hack for IE
		if (this.width != 'auto' && this.as.style.setExpression) {
			this.as.style.setExpression( 'height', 'this.scrollHeight > '+parseInt(this.maxHeight)+' ? "'+this.maxHeight+'" : "auto"' );
		}
		
		// Add event handlers
		inputEl.keyup(this.suggest);
		inputEl.keydown(this.keydown);
		inputEl.blur(this.blur);
	}
	
	// onkeyup handler
	this.suggest = function() {
		var obj = this.obj;
		if (!obj.enabled) {
			return;
		}
		if (obj.timer) {
			clearTimeout(obj.timer);
		}
		obj.timer = setTimeout(function() { obj.suggest_wait(obj.id); }, 200);
	}
	
	// Starts the AJAX request, 200ms after last keypress
	this.suggest_wait = function(id) {
		var obj = ajs_objects[id];
		var query = obj.sf.value;
		query = query.replace(/^\s+/, '').replace(/\s+$/, '');
		if (query.length < obj.numChars) {
			obj.emptyResults();
			obj.currentQuery = '';
			return;
		}
		if (obj.currentQuery && obj.currentQuery == query) {
			return;
		}
		
		/* If request already in progress then abort */
		if (obj.request) {
			obj.request.abort();
		}
		
		obj.currentQuery = query;
		
		// Set up XMLHttpRequest object
		try {
			// Firefox, Opera 8.0+, Safari
			obj.request = new XMLHttpRequest();
		} catch (e) {
			// Internet Explorer
			try {
				obj.request = new ActiveXObject("Msxml2.XMLHTTP");
			} catch (e) {
				try {
					obj.request = new ActiveXObject("Microsoft.XMLHTTP");
				} catch (e) {
					return;
				}
			}
		}
		if (obj.disableCaching) {
			var nocache = '&_='+(new Date()).getTime();
		} else {
			var nocache = '';
		}
		obj.request.open("GET", obj.searchPage + encodeURIComponent(query) + nocache, true);
		obj.request.onreadystatechange = function() {obj.handleResponse(obj)};
		obj.request.send(null);
	}
	
	// Called when the AJAX response is returned
	this.handleResponse = function(obj) {
		if (obj.request.readyState != 4 || obj.request.status != 200 ) {
			return;
		}
		obj.emptyResults();
		var res = obj.request.responseText.split("\n");
		obj.request = null;
		if (res.length > 1) {
			var idx;
			var resultText, resultDiv;
			for(i=0; i < res.length - 1; i++) {
				idx = obj.as.numResults;
				obj.as.results[obj.as.numResults++] = res[i];
				if (obj.allowHtml) {
					resultText = res[i];
				} else {
					resultText = res[i].replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
				}
				resultDiv = $('<div>'+resultText+'</div>').css({
					background: obj.backgroundUnsel
				});
				eval( 'resultDiv.mouseover( function() {obj.mouseover(' +idx+ ');} )' );
				eval( 'resultDiv.click( function() {obj.mouseclick(' +idx+ ');} )' );
				if (obj.noWrap) {
					resultDiv.css({
						'white-space': 'nowrap'
					});
				}
				$(obj.as).append(resultDiv);
			}
			obj.showResults();
			if (obj.autoSelectSingleResult && res.length == 2) {
				obj.handledown();
			}
		}
	}
	
	this.keydown = function(e) {
		var obj = this.obj;
		if (!obj.enabled) {
			return;
		}
		e = e || window.event;
		switch (e.keyCode) {
		case 13: // return
			if (!isNaN(obj.as.selectedIndex) && obj.as.selectedIndex != -1) {
				obj.mouseclick(obj.as.selectedIndex);
				return false;
			}
			break;
		case 27: // escape
			obj.hideResults();
			return false;
			break;
		case 38: // up arrow
			obj.handleup();
			return false;
			break;
		case 40: // down arrow
			obj.handledown();
			return false;
			break;
		}
		return true;
	}
	
	this.blur = function(e) {
		var obj = this.obj;
		if (!obj.enabled) {
			return;
		}
		setTimeout(function() { obj.blur_wait(obj.id); }, 500);
		return true;
	}
	
	this.blur_wait = function(id) {
		var obj = ajs_objects[id];
		obj.emptyResults();
		obj.currentQuery = '';
	}
	
	this.hideResults = function() {
		this.as.style.display = 'none';
	}
	
	this.isHidden = function() {
		return this.as.style.display == 'none';
	}
	
	this.showResults = function() {
		this.as.style.display = 'block';
		this.as.selectedIndex = -1;
		this.as.scrollTop = 0;
	}
	
	this.emptyResults = function() {
		this.hideResults();
		this.as.innerHTML = '';
		this.as.numResults = 0;
		this.as.selectedIndex = -1;
		this.as.results = [];
	}
	
	
	this.mouseover = function(idx) {
		this.as.selectedIndex = idx;
		this.sf.focus();
		
		this.highlightSel();
	}
	
	this.mouseclick = function(idx) {
		var str = this.as.results[idx];
		if (this.extractSearchResult) {
			var matches = str.match(/<!--s-->(.+)<!--e-->/);
			this.sf.value = matches[1];
		} else {
			this.sf.value = str;
		}
		if (this.onchoose) {
			this.onchoose(this.sf);
		}
		if (this.autoSubmitFormId != '') {
			document.getElementById(this.autoSubmitFormId).submit();
		}
	}
	
	this.handleup = function() {
		if (this.as.numResults > 0 && this.isHidden()) {
			this.showResults();
			return;
		}
		
		var item_height = this.as.offsetHeight/this.visibleResults;
	
		if (this.as.selectedIndex == 0) {
			return;
		} else if (this.as.selectedIndex < 0) {
			this.as.selectedIndex = this.as.numResults - 1;
			this.as.scrollTop = this.as.numResults*item_height - this.as.offsetHeight;
		} else {
			this.as.selectedIndex--;
		}
			
		this.highlightSel();
		
		/* Check scroll pos is in range */
		if (this.as.scrollTop > item_height*(this.as.selectedIndex+this.visibleResults) - this.as.offsetHeight) {
			this.as.scrollTop -= item_height;
		}
	}
	
	this.handledown = function() {
		if (this.as.numResults > 0 && this.isHidden()) {
			this.showResults();
			return;
		}
	
		if (this.as.selectedIndex == this.as.numResults - 1)
			return;
		else if (this.as.selectedIndex < 0)
			this.as.selectedIndex = 0;
		else
			this.as.selectedIndex++;
			
		this.highlightSel();
		
		/* Check scroll pos is in range */
		var item_height = this.as.offsetHeight/this.visibleResults;
		if (this.as.scrollTop < item_height*(this.as.selectedIndex+1) - this.as.offsetHeight) {
			this.as.scrollTop += item_height;
		}
	}
	
	this.highlightSel = function() {
		var divs = this.as.getElementsByTagName('div');
	
		for (i = 0; i < divs.length; i++) {
			if (i == this.as.selectedIndex) {
				$(divs[i]).css({
					background: this.backgroundSel,
					cursor: 'pointer'
				});
			} else {
				$(divs[i]).css({
					background: this.backgroundUnsel,
					cursor: 'auto'
				});
			}
		}
	}

}


