
/*
Script: cl_asuggest.js (Beta-Version)

Author:
	Heiko Pfefferkorn, <http://ifabrik.de>

License:
	MIT-style license.

Credits:
	Funktionalitaet basiert auf mootools.v1.00.js (<http://mootools.net>), MIT style license <http://www.opensource.org/licenses/mit-license.php>, <http://de.wikipedia.org/wiki/MIT-Lizenz>
*/


/*
Class: Cl_ASuggest

Arguments:
	fldId   - ID-Attribut des Autosuggestfeldes
 	options - optional, Optionen-Objekt

Options:
	asClass         - die CSS-Klasse fuer die Autosuggest-Elemente. Standard ist 'cl_as'.
	btnNext         - der Next-Button (Text oder HTML). Standard ist 'Next'.
	btnPrev         - der Previous-Button (Text oder HTML). Standard ist 'Previous'.
	height          - Hoehe des Treffer-Autosuggestfeldes. Standard ist 150.
	hideTimeout     - Timeout des Ausblendens des Treffer-Autosuggestfeldes (Millisekunden). Standard ist 2500.
	json            - nutze JSON-Abfragemethode (true oder false). Standard ist false.
	method          - HTTP-REQUEST-Methode (post oder get). Standard ist 'post'.
	minChars        - ab wieviel eingegebenen Zeichen soll die DB-Abfrage gestartet werden. Standard ist 2.
	offsets         - Autosuggestfeld-Offsets. Zusatz zu Position und/oder Dimension. Objekt mit x, y, w, h. Standard ist  {'x': 0, 'y': 0, 'w': -2, 'h': 0}.
	pageTurn        - soll bei mehr Ergebnissen als 'Option - showMax' Bl�tternoption genutzt werden (true oder false). Standard ist true.
	searchDelay     - nach wievielen Millisekunden soll bei Neueingabe die DB-Abfrage neu gestartet werden. Standard ist 500.
	showMax         - wieviel Ergebnisse sollen auf einmal angezeigt werden. Standard ist 5.
	showNoResults   - soll eine 'keine Ergebnisse'-Meldung angezeigt werden (true oder false). Standard ist true.
	stringNoResults - Meldungstext bei keinen Ergebnissen und 'showNoResults=true'. Standard ist 'Keine Ergebnisse!',
	url             - Ajax-Abfrage-Script. Standard ist ''.
	width           - Breite des Autosuggestfeldes. Standard ist 0. -> nicht benutzt
*/

var Cl_ASuggest = new Class({
	setOptions: function(options){
		this.sToSearch        = '',
		this.nSearchChars     = 0,
		this.aData            = [],
		this.iHighlighted     = 0,
		this.ident            = 'cl_as',
		this.heightMinimum    = 100;
		this.widthMinimum     = 100;
		this.maxCount         = 0;
		this.maxSite          = 0;
		this.startSite        = 0;
		this.startPos         = 0;
		this.endPos           = 0;

		this.options = {
			asClass        : 'cl_as',
			btnNext        : 'Next',
			btnPrev        : 'Previous',
			height         : 150,
			hideTimeout    : 2500,
			json           : false,
			method         : 'post',
			minChars       : 2,
			offsets        : {'x': 0, 'y': 0, 'w': -2, 'h': 0},
			pageTurn       : true,
			searchDelay    : 500,
			showMax        : 5,
			showNoResults  : true,
			stringNoResults: 'No results!',
			url            : '',
			width          : 0
		};
		Object.extend(this.options, options || {});
	},
	initialize: function(fldId, options){
		this.setOptions(options);

		this.fld = $(fldId);

		var pointer = this;

		if (this.options.url=='')
			return;

		this.fld.onkeypress = function(ev) { return pointer.onKeyPress(ev); };
		this.fld.onkeyup    = function(ev) { return pointer.onKeyUp(ev); };
		this.fld.setAttribute("autocomplete","off");
	},

	/* set responses to keydown events in the field this allows the user to use the arrow keys to scroll through the results
	 * ESCAPE clears the list TAB sets the current highlighted value
	 */
	onKeyPress: function(ev) {
		var key    = (window.event) ? window.event.keyCode : ev.keyCode;
		var RETURN = 13;
		var TAB    = 9;
		var ESC    = 27;
		var flag   = true;

		switch (key) {
			case RETURN:
				this.setHighlightedValue();
				return false;
				break;

			case ESC:
				this.timeoutHideList();
				return true;
				break;
		}
	},

	/* set responses to keydown events in the field this allows the user to use the arrow keys to scroll through the results
	 * ESCAPE clears the list TAB sets the current highlighted value
	 */
	onKeyUp: function(ev) {
		var key = (window.event) ? window.event.keyCode : ev.keyCode;
		var ARRUP     = 38;
		var ARRDN     = 40;
		var BACKSPACE = 8;
		var DELETE    = 46;
		var bubble    = true;

		switch (key) {
			case ARRUP:
				this.changeHighlight(key);
				bubble = false;
				break;

			case ARRDN:
				this.changeHighlight(key);
				bubble = false;
				break;

			case BACKSPACE:
			case DELETE:
				// pass selected object to callback function, if exists
				if (typeof(this.options.callback)=="function") this.options.callback({});
				this.timeoutHideList();
				//bubble = false;
				//break;

			default:
				this.checkTypedInput(this.fld.value);
		}

		return bubble;
	},
	checkTypedInput: function(val) {
		// Benutzereingabe noch die Gleiche wie vorher?
		// -> brich ab
		if (val==this.sToSearch) return false;

		// Zeichenl�nge der Benutzereingabe kleiner als geforderte?
		// -> setze 'this.sToSearch' (gespeicherter String) zur�ck und brich ab
		if (val.length<this.options.minChars) {
			this.sToSearch = '';
			return false;
		}

		this.sToSearch    = val;
		this.nSearchChars = val.length;

		var pointer = this;
		clearTimeout(this.ajID);
		this.ajID = setTimeout(function() { pointer.doRequest() }, this.options.searchDelay);

		return false;
	},

	doRequest: function() {
		var pointer      = this;
		var ajax_options = {};
		var url          = pointer.options.url;
		var method       = pointer.options.method.toLowerCase();
		var params       = { autosuggest: escape(pointer.fld.value) };
		if (pointer.options.json) params.json = true;

		switch (pointer.method) {
			case 'get':
				url+= '?'+Object.toQueryString(params);
				ajax_options.method = 'get';
				break;

			default:
				ajax_options.method   = 'post';
				ajax_options.postBody = Object.toQueryString(params);
				break;
		}

		ajax_options.onFailure  = function(e) { alert("AJAX error: "+e); };
		ajax_options.onComplete = function(responseText,responseXML) { pointer.processRequest(responseText,responseXML); };

		var agetAjax = new Ajax(url, ajax_options).request();
		this.result_site = 0;
	},

	processRequest: function(reqText,reqXML) {
		this.aData = [];

		if (this.options.json==true) {
			var jsondata = eval('('+reqText+')');

			for (var i=0; i<jsondata.results.length; i++) {
				this.aData.push({
					'id'    : jsondata.results[i].id,
					'value' : jsondata.results[i].value,
					'info'  : jsondata.results[i].info,
					'str'  : jsondata.results[i].str,
					'nr'  : jsondata.results[i].nr,
					'plz'  : jsondata.results[i].plz,
					'ort'  : jsondata.results[i].ort
				});
			}
		} else {
			var xml     = reqXML;
			var results = xml.getElementsByTagName('results')[0].childNodes;

			for (var i=0; i<results.length; i++) {
				if (results[i].hasChildNodes())

				this.aData.push({
					'id'   : results[i].getAttribute('id'),
					'value': results[i].childNodes[0].nodeValue,
					'info' : results[i].getAttribute('info'),
					'str' : results[i].getAttribute('str'),
					'nr' : results[i].getAttribute('nr'),
					'plz' : results[i].getAttribute('plz'),
					'ort' : results[i].getAttribute('ort')
				});
			}
		}

		this.maxCount  = this.aData.length;
		this.startSite = 0;
		this.startPos  = 0;
		this.endPos    = (this.options.showMax>0) ? this.options.showMax : this.maxCount;

		this.initList();
	},

	initList: function() {
		var pointer = this;

		this.hideList();
		this.killTimeout();

		this.identContainer  = this.ident+'_'+this.fld.id;
		this.identHolder     = this.ident+'_holder_'+this.fld.id;
		this.identInner      = this.ident+'_inner_'+this.fld.id;
		this.identUl         = this.ident+'_list_'+this.fld.id;

		// Initialisiere die Hauptelemente
		this.div_container = new Element('div'); // div - main container
		this.div_container.setProperty('id', this.identContainer);
		this.div_container.addClass(this.options.asClass);

		this.div_holder = new Element('div'); // div - holder
		this.div_holder.setProperty('id', this.identHolder);
		this.div_holder.addClass(this.options.asClass+'_holder');

		this.div_inner = new Element('div'); // div - inner
		this.div_inner.setProperty('id', this.identInner);
		this.div_inner.addClass(this.options.asClass+'_inner');

		this.ul = new Element('ul');
		this.ul.setProperty('id', this.identUl);
		this.ul.addClass(this.options.asClass+'_list_ul');

		if (this.maxCount>0) {
			if (this.options.pageTurn==true && this.options.showMax>0)
				this.endPos = (this.options.showMax<this.maxCount) ? this.options.showMax : this.maxCount;
			this.updateList();
		} else
			if (this.options.showNoResults==true)
				this.updateListNoResult();

		this.buildList();
	},

	clearListContent: function() {
		this.clearHighlight();

		if ($$('#'+this.identUl+' li'))
			$$('#'+this.identUl+' li').each(function(e) {
				$(e).remove();
			});
	},

	updateList: function() {
		var pointer = this;

		this.clearListContent();

		var count = 0;
		for (var i=this.startPos; i<this.endPos; i++) {
			var li  = new Element('li');
			var val = this.aData[i].value;
			var st  = val.toLowerCase().indexOf(this.sToSearch.toLowerCase());
			var a   = new Element('a').setProperties({
				href: "javascript:;",
				name: count+1
			});

			new Element('em').appendText(val.substring(st, st+this.sToSearch.length)).injectInside(a);
			a.appendText(val.substring(st+this.sToSearch.length));

			if (this.aData[i].info!='')
				new Element('small').setHTML('<br>'+this.aData[i].info).injectInside(a);

			a.addEvent('click', function() {
				pointer.setHighlightedValue();
				return false;
			}).addEvent('mouseover', function() {
				pointer.setHighlight(this.name);
			});

			a.injectInside(li.injectInside(this.ul));
			count++;
		}
	},

	updateListNoResult: function() {
		var pointer = this;

		this.clearListContent();

		var li = new Element('li');
		li.addClass('as_warning');
		li.appendText(pointer.options.stringNoResults);
		li.injectInside(pointer.ul);
	},

	buildList: function() {
		var pointer = this;

		this.ul.injectInside(this.div_inner);
		this.div_inner.injectInside(this.div_holder);
		this.div_holder.injectInside(this.div_container);

		this.div_container.addEvent('mouseover', function(){
			pointer.killTimeout()
		}).addEvent('mouseout', function(){
			pointer.resetTimeout()
		});

		// currently no item is highlighted
		this.iHighlighted = 0;

		// remove list after an interval
		this.toID = setTimeout(function() { pointer.timeoutHideList() }, pointer.options.hideTimeout);

		// Bl�ttern setzen?
		if (this.options.pageTurn==true && this.options.showMax>0 && this.maxCount>this.options.showMax)
			this.addPageTurn();
		/*else {
			if (this.options.height>=this.heightMinimum)
				this.div_holder.setStyles({
					height  : this.options.height+'px',
					overflow: 'hidden'
				});
		}*/

		this.setListPosition();

		this.showList();
	},

	setListPosition: function() {
		var pointer  = this;
		var size     = $(this.fld).getSize();
		var position = $(this.fld).getPosition();

		this.div_container.setStyles({
			left  : (position.x.toInt()+pointer.options.offsets.x)+'px',
			top   : (position.y.toInt()+size.size.y.toInt()+pointer.options.offsets.y)+'px',
			width : (pointer.options.width>=pointer.widthMinimum) ? pointer.options.width+'px' : (size.size.x.toInt()+pointer.options.offsets.w)+'px'
		});
	},

	addPageTurn: function() {
		var pointer = this;

		this.maxSite = Math.ceil(this.maxCount/this.options.showMax);

		var div_btn_container = new Element('div'); // div - main container
		div_btn_container.setProperty('id', this.ident+'_pt_'+this.fld.id);
		div_btn_container.addClass(this.options.asClass+'_pt');

		var btn_prev = new Element('div');
		btn_prev.setProperty('id', this.ident+'_pt_prev_'+this.fld.id);
		btn_prev.addClass(this.options.asClass+'_pt_prev');
		btn_prev.onmouseover = function(){ this.addClass(pointer.ident+'_pt_highlight'); };
		btn_prev.onmouseout  = function(){ this.removeClass(pointer.ident+'_pt_highlight'); };
		btn_prev.onclick     = function(){ pointer.pageTurnPrevious(); };
		btn_prev.setStyle('visibility','hidden');
		btn_prev.setHTML(this.options.btnPrev);

		var btn_next = new Element('div');
		btn_next.setProperty('id', this.ident+'_pt_next_'+this.fld.id);
		btn_next.addClass(this.options.asClass+'_pt_next');
		btn_next.onmouseover = function(){ this.addClass(pointer.ident+'_pt_highlight'); };
		btn_next.onmouseout  = function(){ this.removeClass(pointer.ident+'_pt_highlight'); };
		btn_next.onclick     = function(){ pointer.pageTurnNext(); };
		btn_next.setHTML(this.options.btnNext);

		var div_clear = new Element('div');
		div_clear.addClass(this.options.asClass+'_clear');

		btn_prev.injectInside(div_btn_container);
		btn_next.injectInside(div_btn_container);
		div_clear.injectInside(div_btn_container);
		div_btn_container.injectInside(this.div_container);
	},

	pageTurnPrevious: function() {
		this.clearHighlight();
		this.startSite--;

		if (this.startSite<0)
			this.startSite = 0;

       	this.startPos = this.startSite*this.options.showMax;
        this.endPos   = this.startPos+this.options.showMax;

		this.updateList();
		this.checkPageTurn();
	},

	pageTurnNext: function() {
		this.clearHighlight();
		this.startSite++;

        this.startPos = this.startSite*this.options.showMax;
		this.endPos   = ((this.startPos+this.options.showMax)>this.maxCount) ? this.maxCount : this.startPos+this.options.showMax;

		this.updateList();
		this.checkPageTurn();
	},

	checkPageTurn: function() {
		// Previous
		if (this.startSite<=0)
			$(this.ident+'_pt_prev_'+this.fld.id).setStyle('visibility','hidden');
		else
			$(this.ident+'_pt_prev_'+this.fld.id).setStyle('visibility','visible');

		// Next
		if (this.startSite<(this.maxSite-1))
			$(this.ident+'_pt_next_'+this.fld.id).setStyle('visibility','visible');
		else
			$(this.ident+'_pt_next_'+this.fld.id).setStyle('visibility','hidden');
	},

	changeHighlight: function(key) {
		if (!$(this.identUl))
			return false;

		var list = $(this.identUl);
		var n    = '';

		if (key==40)
			n = this.iHighlighted + 1;
		else if (key==38)
			n = this.iHighlighted - 1;

		if (n>list.childNodes.length)
			n = list.childNodes.length;

		if (n<1)
			n = 1;

		this.setHighlight(n);
	},

	setHighlight: function(n) {
		if (!$(this.identUl))
			return false;

		var list = $(this.identUl);

		if (this.iHighlighted>0)
			this.clearHighlight();

		this.iHighlighted = n.toInt();

		list.childNodes[this.iHighlighted-1].addClass(this.options.asClass+"_li_highlight");
		this.killTimeout();
	},

	clearHighlight: function() {
		var pointer = this;

		if (!$(this.identUl))
			return false;

		if (this.iHighlighted>0) {
			if ($$('#'+this.identUl+' li'))
				$$('#'+this.identUl+' li').each(function(e) {
					$(e).removeClass(pointer.options.asClass+"_li_highlight");
				});

			this.iHighlighted = 0;
		}
	},

	setHighlightedValue: function() {
		if (this.iHighlighted) {
			//var data_pos = this.iHighlighted-1;
			var data_pos = (this.iHighlighted-1)+this.startPos;

			this.sToSearch = this.fld.value = this.aData[data_pos].value;

			// move cursor to end of input (safari)
			this.fld.focus();

			if (this.fld.selectionStart)
				this.fld.setSelectionRange(this.sToSearch.length, this.sToSearch.length);

			this.timeoutHideList();

			// pass selected object to callback function, if exists
			if (typeof(this.options.callback)=="function")
				this.options.callback(this.aData[data_pos]);
		}
	},

	killTimeout: function() {
		clearTimeout(this.toID);
	},

	resetTimeout: function() {
		var pointer = this;
		this.killTimeout();
		this.toID = setTimeout(function() { pointer.timeoutHideList() }, pointer.options.hideTimeout);
	},

	timeoutHideList: function() {
		this.killTimeout();
		this.hideList();
	},

	hideList: function() {
		if ($(this.identContainer)) $(this.identContainer).remove();
	},

	showList: function() {
		this.div_container.injectInside(document.body);
	}
});

Cl_ASuggest.implement(new Chain);
