﻿/*** fs autocompleter ***/

var Autocomplete = function(field, multi, listFun, selectFun) {
	
	this.listFunction = listFun;
	this.selectFun = selectFun;

	this.multi = multi ? true : false;	// to make sure it is not null

	this.fieldElement = $(field);
	this.autocompleteElement = null;
	
	this.autocompleterInterval = null;
	this.autocompleterLastWasBackspace = null;
	this.autocompleterMaxItems = 8;

	// create the list element
	this.autocompleteElement = $(document.createElement("div"));
	this.autocompleteElement.addClass("fsAutocompleterList");
	$(document.body).append(this.autocompleteElement);
	this.autocompleteElement.css('position', 'absolute');
	this.autocompleteElement.selectedIndex = -1;
	
	this.currentBlockIndex = -1;

	this.fieldElement.attr('autocomplete','off');

	this.init = function() {
		var me = this;
		this.fieldElement.keydown(function(event) {
			return me.keyPress(event);
		});
	
		var delayedHideFunction = function() {
			return this.hideAutocompleter();
		}

		if(document.all) $(document.body).click(function(e){return me.hideAutocompleter(e)});
		else this.fieldElement.blur(function(e){return me.hideAutocompleter(e)});

		// make invisible
		this.hideAutocompleter();
	}

	this.keyPress = function(event) {
		if(event == undefined) event = window.event;
		if(this.autocompleterInterval != null) clearTimeout(this.autocompleterInterval);
		this.autocompleterLastWasBackspace = false;
		
		var me = this;
		// codes: 8 = backspace, 13 = enter, 188 = , 38 = up, 40 = down, 27 = esc, 9 = tab
		if(event.keyCode == 13) {
			// enter pressed
			this.insertResult(null, false);
			this.hideAutocompleter();
			return false;
		}
		else if(event.keyCode == 9) {
			// tab pressed
			this.insertResult(null, true);	// prevent focus return
			this.hideAutocompleter();
			return true;
		}
		else if(event.keyCode == 27) {
			// esc or tab
			this.hideAutocompleter();
		}
		else if(event.keyCode == 38 && !event.shiftKey) {
			this.selectResult(this.autocompleteElement.selectedIndex-1);
			return this.autocompleteElement.children().length == 0;	// make key usable if no result
		}
		else if(event.keyCode == 40 && !event.shiftKey) {
			this.selectResult(this.autocompleteElement.selectedIndex+1);
			return this.autocompleteElement.children().length == 0;	// make key usable if no result
		}
		else if(event.keyCode == 8) {
			// backspace
			this.autocompleterLastWasBackspace = true;
			this.autocompleterInterval = setTimeout(function() {
				me.autocomplete(-1);
			}, 100);
		}
		else if(event.keyCode != 91 && (event.keyCode < 16 || event.keyCode > 18) && event.keyCode != 37 && event.keyCode != 39) { // no shift, ctrl, alt, left, right
			// start an autocompleter in 100 ms
			this.autocompleterInterval = setTimeout(function() {
				me.autocomplete();
			}, 100);
		}
		return true;
	}
	
	this.showAutocompleter = function() {
		// position
		var dimensions = {width: this.fieldElement.outerWidth(), height: this.fieldElement.outerHeight()};
		var position = this.fieldElement.offset();

		var firstElement = this.autocompleteElement.children("div").eq(0);

		if(firstElement != null && firstElement.width) {
			if (document.all) var elemDim = {width: firstElement.width(), height: firstElement.height()};

			var elemHeight = (parseInt(firstElement.css('height')) || 0) + (parseInt(firstElement.css('padding-top')) || 0) + (parseInt(firstElement.css('padding-bottom')) || 0) + (parseInt(firstElement.css('border-top')) || 0) + (parseInt(firstElement.css('border-bottom')) || 0);

			// adjust height
			if(this.autocompleteElement.children().length > this.autocompleterMaxItems) {
				this.autocompleteElement.css('height', (this.autocompleterMaxItems*elemHeight) + 'px');
			}
			else {
				this.autocompleteElement.css('height', 'auto');
			}
		}
		else {
			this.autocompleteElement.css('height', 'auto');
		}
		var paddingTotal = parseInt(this.autocompleteElement.css('padding-left')) + parseInt(this.autocompleteElement.css('padding-right'));
		
		var acTop = (dimensions.height + position.top);
		var acLeft = position.left;
		
		if(document.all) {
			acTop += 14;
			acLeft += 10;
		}
		
		var borderTotal = parseInt(this.autocompleteElement.css('border-left-width')) + parseInt(this.autocompleteElement.css('border-right-width'));
		this.autocompleteElement.css('width', (dimensions.width - paddingTotal - borderTotal) + 'px');
		this.autocompleteElement.css('top', acTop + 'px');
		this.autocompleteElement.css('left', acLeft + 'px');
		this.autocompleteElement.css('visibility', 'visible');
	
		if(document.all) {
			//document.getElementById("selectRadius").css('visibility', 'hidden');
		}
		
	}
	
	this.hideAutocompleter = function() {
		this.autocompleteElement.css('visibility', 'hidden');
		this.autocompleteElement.selectedIndex = -1;
	
		if(document.all) {
			//document.getElementById("selectRadius").css('visibility', 'visible');
		}
		
		return true;
	}
	
	this.hideAutocompleterDelayed = function() {
		setTimeout(function() {
			this.hideAutocompleter();
		}, 200);
		
		return true;
	}
	
	
	
	this.insertResult = function(i, keepCaretPos, caretPosFix) {
		if(i == null) i = this.autocompleteElement.selectedIndex;
		if(i >= this.autocompleteElement.children().length || i < 0) return;
	
		if(caretPosFix == null) caretPosFix = 0;
		var currentCaretPos = this.getCaretPosition(this.fieldElement.get(0)) + caretPosFix;
	
		var selectedValue = this.autocompleteElement.children().get(i).value;
		var selectedText = this.autocompleteElement.children().get(i).text;

		var f = this.fieldElement.get(0);
		if(this.multi) {
			// replace block
			var txt = f.value;
			var txtBlocks = txt.split(/ *[,;] */);
			if(txtBlocks.length > this.currentBlockIndex) txtBlocks[this.currentBlockIndex] = selectedText;
			else txtBlocks.push(selectedText);
			f.value = txtBlocks.join(", ");
			
			// set carret position to the end of the newly inserted block
			var newCarPos = txtBlocks[0].length;
			for(var k = 1; k <= this.currentBlockIndex && k < txtBlocks.length; k++) newCarPos += (2 + txtBlocks[k].length);

			this.setCaretPosition(f, newCarPos, 0);
		}
		else {
			f.value = selectedText;
			if(keepCaretPos) {
				// set position again
				this.setCaretPosition(f, currentCaretPos, selectedValue.length - currentCaretPos);
			}
		}
		
		// call the select function
		if(this.selectFun != null) this.selectFun(selectedValue, selectedText);
	}
	
	this.selectResult = function(i) {
		if(i >= this.autocompleteElement.children().length || i < 0) {
			return;
		}
		
		// de-highlight last selection
		if(this.autocompleteElement.selectedIndex >= 0 && this.autocompleteElement.selectedIndex < this.autocompleteElement.children().length) {
			this.autocompleteElement.children().get(this.autocompleteElement.selectedIndex).className = 'fsAutocompleterOption';
		}
		
		// highlight new
		this.autocompleteElement.children().get(i).className = 'fsAutocompleterOptionSelected';
		
		// store selection
		this.autocompleteElement.selectedIndex = i;
		
		// make sure it is visible
		if(this.autocompleteElement.children().length > this.autocompleterMaxItems) {
			// objDiv.scrollTop = objDiv.scrollHeight;
			var firstElement = this.autocompleteElement.children().eq(0);

			var elemHeight = (parseInt(firstElement.css('height')) || 0) + (parseInt(firstElement.css('padding-top')) || 0) + (parseInt(firstElement.css('padding-bottom')) || 0) + (parseInt(firstElement.css('border-top')) || 0) + (parseInt(firstElement.css('border-bottom')) || 0);

			var elemHeight = elemDim.height;
			var minScrollTop = (i-this.autocompleterMaxItems+1) * elemHeight;
			var maxScrollTop = i * elemHeight;

			if(this.autocompleteElement.get(0).scrollTop < minScrollTop) this.autocompleteElement.get(0).scrollTop = minScrollTop;
			else if(this.autocompleteElement.get(0).scrollTop > maxScrollTop) this.autocompleteElement.get(0).scrollTop = maxScrollTop;
	
		}
	}
	
	this.autocomplete = function(caretPosFix) {
		// get the current val
		var txt = this.fieldElement.get(0).value;
		var me = this;

		if(caretPosFix == null) caretPosFix = 0;
	
		if(caretPosFix < 0 && txt.length > (-caretPosFix)) {
			txt = txt.substring(0, txt.length + caretPosFix);
		}
		
		// if multi, only use a part
		if(this.multi) {
			var f = this.fieldElement.get(0);
			var caretPos = this.getCaretPosition(f);
			var txtBlocks = txt.split(/[,;]/);
			
			// find the correct one
			var blockIndex;
			var posCnt = 0;
			for(blockIndex = 0; blockIndex < txtBlocks.length; blockIndex++) {
				var nxtPos = posCnt + txtBlocks[blockIndex].length;
				if(posCnt <= caretPos && nxtPos >= caretPos) {
					break;
				}
				posCnt = nxtPos+1;
			}

			if(blockIndex < txtBlocks.length) {
				txt = trim(txtBlocks[blockIndex]);
				// store block index
				this.currentBlockIndex = blockIndex;
			}
			else {
				txt = "";
				this.currentBlockIndex = -1;
			}
		}

		if(txt.length == 0) {
			this.hideAutocompleter();
			return;
		}

		// get the elements through the list function
		this.listFunction(txt, function(data) {
			me.autocompleterPopulate(data, caretPosFix);
		});
	}
	
	this.autocompleterPopulate = function(resultSet, caretPosFix) {
		// populate the autocompleteElement
		this.autocompleteElement.empty();
		this.autocompleteElement.selectedIndex = -1;

		if(resultSet.length == 0) {
			this.hideAutocompleter();
			return;
		}
		
		var me = this;

		// only show items that are not already entered
		var valuesInList = new Array();
		if(this.multi) {
			var f = this.fieldElement.get(0);
			var valuesInList = f.value.split(/\s*[,;]+\s*/);
		}

		var itemCnt = 0;
		for(var i = 0; i < resultSet.length; i++) {
			var r = resultSet[i];
			
			// check if value not already entered
			var isInList = false;
			for(var k = 0; k < valuesInList.length; k++) {
				if(valuesInList[k] == r.VALUE) {
					// yes, don't use
					isInList = true;
					break;
				}
			}
			
			if(!isInList) {
				var optionElement = document.createElement("div");
				optionElement.className = 'fsAutocompleterOption';
				optionElement.innerHTML = r.TEXT.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
				optionElement.value = r.VALUE;
				optionElement.text = r.TEXT;
				optionElement.index = i;
				
				this.autocompleteElement.append(optionElement);
				
				// attach the click event
				optionElement.onmousedown = function(e) {
					me.insertResult(this.index, false);
				}
				
				itemCnt++;
			}
		}

		if(itemCnt == 0) {
			this.hideAutocompleter();
			return;
		}

		// show
		this.showAutocompleter();

		// if only one, select
		if(itemCnt == 1 && this.autocompleterLastWasBackspace != true) {
			this.selectResult(0);
			if(!this.multi) this.insertResult(0, true, caretPosFix);
		}
	}

	this.getCaretPosition = function(input) {
		if(input.selectionStart) {
			return input.selectionStart;
		}
		else if(document.selection) {
			// ie
			
			var range = document.selection.createRange();
			var bookmark = range.getBookmark();
			var caret_pos = bookmark.charCodeAt(2) - 2;
			
			return caret_pos;
			
			/*
			var range = document.selection.createRange(); // We'll use this as a 'dummy'
			var stored_range = range.duplicate(); // Select all text
			stored_range.moveToElementText(input); // Now move 'dummy' end point to end point of original
			stored_range.setEndPoint('EndToEnd', range); // Now we can calculate start and end points
			return stored_range.text.length - range.text.length;
			// element.selectionEnd = element.selectionStart + range.text.length;
			*/
		}
		return -1;
	}
	this.setCaretPosition = function(node, pos, len) { 
		var selStart = pos;
		var selEnd = pos + len;
		if(node.setSelectionRange) { 
			node.focus(); 
			node.setSelectionRange(selStart, selEnd); 
		}
		else if(node.createTextRange) { 
			var range = node.createTextRange(); 
			range.collapse(true); 
			range.moveEnd('character', selEnd); 
			range.moveStart('character', selStart); 
			range.select(); 
		} 
	}
	
	this.getPositionedOffset = function(element) {
		var valueT = 0, valueL = 0;
		do {
			valueT += element.offsetTop  || 0;
			valueL += element.offsetLeft || 0;
			element = element.offsetParent;
			if(element) {
				if(element.tagName == 'BODY') break;
				var p = element.style.position;
				if(p == 'relative' || p == 'absolute') break;
			}
		} while (element);
		return [valueL, valueT];
	}

	this.init();
}
