/**
 * WARNING !!! This component works bad in quirks mode !!!
 */
if (!Combobox){
	/**
	 * Combobox class constructor. It contains different parameter
	 * definitions. Here are description of them:
	 * 
	 * id - unique identifier of Combobox instance
	 * path - url, where Combobox files is located
	 * width - width of Combobox
	 * height - height of Combobox
	 * borderColor - border color of Combobox
	 * textColor - color of the text of Combobox
	 * cursorColor - cursor color of Combobox
	 * listWidth - width of item list of Combobox
	 * itemLimit - max count of items that will be shown at one time
	 * className - CSS class name of outer combobox instance
	 * display - visibility of Combobox
	 * disabled - availibility of Combobox
	 * isSetRelations - alternative search list elements value contents key or not (default: true) 
	 *
	 * @param {Object} params
	 * @return Combobox
	 */
	var Combobox = function(params){
		// temporary
		this.listType = false;
		// temporary
		this.options = new Object();
		this.altOptions = new Object();
		this.hiddenOptions = new Object();
		this.filters = [];
		this.id = params.id;
		params.isSetRelations = params.isSetRelations === undefined ? true : params.isSetRelations; 
		this.setType(params);
		this.listId = this.id + 'List';
		this.onCustomDataEnter = params.onCustomDataEnter ? params.onCustomDataEnter : null; 
		this.isCustomData = false; 
		this.setFilters(params.filters);
		this.setPath(params.path);		
		this.setWidth(params.width);
		this.setHeight(params.height);
		this.setOnchange(params.onchange);
		this.setOnselect(params.onselect);
		this.setOnListShow(params.onlistshow);
		this.setOnListHide(params.onlisthide);
		this.setOnclear(params.onclear);
		this.setBorderColor(params.borderColor);
		this.setTextColor(params.textColor);
		this.setCursorColor(params.cursorColor);
		this.setListWidth(params.listWidth);
		this.setItemLimit(params.itemLimit);
		this.setClassName(params.className);
		this.initKeycodes();
		this.createCache();
		this.createDomNode();
		this.setDisplay(params.display);
		this.setDisabled(params.disabled);
		this.setReadonly(params.readonly);
		this.setValueByKey(params.value);
		this.setQuoteNewValue(params.quoteNewValue);
		this.setTrimed(params.trimed);
		
		this.onExitLaunched = false;
	}
	
	/**
	 * This is collection of all instances of Combobox
	 */
	Combobox.instances = {};
	
	/**
	 * Constructor wrapper that add particular Combobox instance
	 * to instance collection
	 * 
	 * @param {Object} params
	 * @return Combobox
	 */
	Combobox.create = function(params){
		params.id = params.id ? params.id : 'combobox' + Math.round(100000 * Math.random());
		Combobox.instances[params.id] = new Combobox(params);
		return Combobox.instances[params.id];
	}
	
	Combobox.get = function(id){
		if (!Combobox.instances[id]){
			throw new Error('There is no Combobox with id = "' + id + '"');
		}
		return Combobox.instances[id];
	}
	
	Combobox.prototype.htmlEscape = function(str) {
		return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
	}
	
	Combobox.prototype.htmlUnescape = function(str) {
		return str.replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&amp;/g, '&').replace(/&quot;/g, '"');
	}
	
	Combobox.clone = function(oldId, newId, whereNode){
		var divs = whereNode.getElementsByTagName('div');
		for (var i = 0; i < divs.length; i ++){
			if (divs[i].lang == 'combobox') if (divs[i].firstChild) if (divs[i].firstChild.nodeName.toLowerCase() == 'input'){
				var params = jsonDecode(divs[i].firstChild.value);
				if (params.id) if (params.id == oldId){
					params.id = newId;
					var combobox = Combobox.create(params);
					divs[i].parentNode.replaceChild(Combobox.create(params).getDomNode(), divs[i]);
					return;
				}
			}
		}
		throw new Error('Cannot found Combobox with id="' + oldId + '"');
	}
	/**
	 * Defines type of combobox from initial parameters. If combobox has
	 * options parameter, it will be of static type and if it has url
	 * parameter, it will be of dynamic type. If combobox has both
	 * options and url parameters or has not both, then the error will be thrown.
	 * 
	 * @param {Object} params
	 */
	Combobox.prototype.setType = function(params){
		if (params.url && params.options){
			throw new Error('Combobox "' + this.id + '" cannot be static and dynamic at one time.');
		}
		else if (!params.url && !params.options){
			throw new Error('Combobox "' + this.id + '" requires one of attribute: url or options.');
		}
		else if (!params.options && params.url){
			this.type = 'dynamic';
			this.setUrl(params.url);
		}
		else if (!params.url && params.options){
			this.type = 'static';
			this.listType = 'data_like';
			this.setOptions(params.options);
			if(params.altOptions) {
				this.setAltOptions(params.altOptions, params.isSetRelations);
			}
			if(params.hiddenOptions) {
				this.setHiddenOptions(params.hiddenOptions);
			}
		}
		this.updateInfoNode();
	}
	
	/**
	 * Sets options parameter if Combobox is of static type
	 * 
	 * @param {Object} options
	 */
	Combobox.prototype.setOptions = function(options){
		if (this.type == 'static'){
			/*
			var result = [];
			for (var key in options) if (typeof options[key] != 'function'){
				result[result.length] = {'key' : key, 'value' : options[key]};
			}
			this.options = result;
			*/
			var optionsLength = 0;
			for (var key in options) if (typeof options[key] != 'function'){
				optionsLength++;
				options[key] = new String(options[key]).replace(/\n|\r|\t/g, ' ');
			}
			if (optionsLength > 0) {
				options.length = --optionsLength;
			}
			this.options = options;			
		}
		this.updateInfoNode();
	}
	
	Combobox.prototype.addOption = function (key, value) {
		this.options[key] = value;
		this.options.length++;
		this.updateInfoNode();
	}
	
	/**
	 * Sets alternative options parameter if Combobox is of static type
	 * 
	 * @param {Object} options
	 * @param {Boolean} isSetRelations
	 */
	Combobox.prototype.setAltOptions = function(options, isSetRelations){
		if (this.type == 'static'){
			var result = new Object();
			var optionsLength = 0;
			for (var key in options) { 
				if (typeof options[key] != 'function') {
					var value = null;
					if(isSetRelations) {
						/*
						for(i in this.options) {
							var option = this.options[i];
							if(typeof(option) == 'function')
								continue;
							if(option.key === key) {
								value = option.value;
								break;
							}
						}
						*/
						if(this.options[key] != undefined) {
							value = this.options[key];
						}
					}
					optionsLength++;
					if(isSetRelations && value) 
						result[key] = options[key] + ' - ' + value;
					else
						result[key] = options[key];
					result[key] = new String(result[key]).replace(/\n|\r|\t/g, ' ');
				}
				result.length = --optionsLength;
			}
			this.altOptions = result;
		}
		this.updateInfoNode();
	}
	
	Combobox.prototype.setHiddenOptions = function(hiddenOptions, reset) {
		if (typeof reset != 'undefined' && reset == true) {
			this.hiddenOptions = {};	
		}
		for(var i = 0; i < hiddenOptions.length; i++) {
			this.hiddenOptions[hiddenOptions[i]] = true;
		}
	}
	
	Combobox.prototype.addAltOption = function (key, value, isSetRelations) {
		if (this.options[key]) {
			value += ' - ' + this.options[key];
		}
		this.altOptions[key] = value;
		this.altOptions.length++;
	}
	
	/**
	 * Sets filters to combobox
	 * 
	 * @param {Array} filters 
	*/
	Combobox.prototype.setFilters = function(filters) {
		this.filters = filters;
	}	
	
	/**
	 * Sets the Combobox root path
	 * 
	 * @param {String} path
	 */
	Combobox.prototype.setPath = function(path){
		this.path = path ? (path.length - 1 === path.indexOf('/') ? path : path + '/') : '/resources/js/combobox/';
		this.updateInfoNode();
	}
	
	/**
	 * Sets the Combobox width in pixels
	 * 
	 * @param {int} width
	 */
	Combobox.prototype.setWidth = function(width){
		this.width = width ? width : 150;
		this.updateInfoNode();
	}
	
	/**
	 * Sets the Combobox height in pixels
	 * 
	 * @param {int} height
	 */
	Combobox.prototype.setHeight = function(height){
		this.height = height ? height : 20;
		this.updateInfoNode();
	}
	
	/**
	 * Sets the border color
	 * 
	 * @param {String} borderColor
	 */
	Combobox.prototype.setBorderColor = function(borderColor){
		this.borderColor = borderColor ? borderColor : '#000000';
		this.updateInfoNode();
	}
	
	/**
	 * Sets the text color
	 * 
	 * @param {String} textColor
	 */
	Combobox.prototype.setTextColor = function(textColor){
		this.textColor = textColor ? textColor : '#000000';
		this.updateInfoNode();
	}
	
	/**
	 * Sets the cursor color
	 * 
	 * @param {String} cursorColor
	 */
	Combobox.prototype.setCursorColor = function(cursorColor){
		this.cursorColor = cursorColor ? cursorColor : '#000080';
		this.updateInfoNode();
	}
	
	/**
	 * Sets the width of combobox list
	 * 
	 * @param {int} listWidth
	 */
	Combobox.prototype.setListWidth = function(listWidth){
		this.listWidth = listWidth ? listWidth : 150;
		this.updateInfoNode();
	}
	
	/**
	 * Sets item visual limit for combobox
	 * 
	 * @param {int} itemLimit
	 */
	Combobox.prototype.setItemLimit = function(itemLimit){
		this.itemLimit = itemLimit ? itemLimit : 10;
		this.updateInfoNode();
	}
	
	/**
	 * Sets onchange handler for combobox
	 * 
	 * @param {Function} onchange
	 */
	Combobox.prototype.setOnchange = function(onchange){
		this.onchange = onchange;
	}
	
	/**
	 * Sets onselect handler for combobox
	 * 
	 * @param {Function} onselect
	 */
	Combobox.prototype.setOnselect = function(onselect){
		this.onselect = onselect;
	}
	
	/**
	 * Sets onlistshow handler for combobox
	 * 
	 * @param {Function} onlistshow
	 */
	Combobox.prototype.setOnListShow = function(onlistshow){
		this.onlistshow = onlistshow;
	}
	
	/**
	 * Sets onlisthide handler for combobox
	 * 
	 * @param {Function} onlisthide
	 */
	Combobox.prototype.setOnListHide = function(onlisthide){
		this.onlisthide = onlisthide;
	}
	
	/**
	 * Sets onselect handler for combobox
	 * 
	 * @param {Function} onselect
	 */
	Combobox.prototype.setOnclear = function(onclear){
		this.onclear = onclear;
	}
	
	/**
	 * Sets url for dynamic combobox
	 * 
	 * @param {String} url
	 */
	Combobox.prototype.setUrl = function(url){
		this.url = url;
		this.updateInfoNode();
	}
	
	/**
	 * Sets value to Combobox
	 * 
	 * @param {Object} value
	 */
	Combobox.prototype.setValue = function(value){
		if (value){
			this.textboxNode.value = value.value ? this.htmlUnescape(value.value) : '';
			this.fieldNode.value = value.key ? value.key : '';
		}
	}
	
	/**
	* Sets CSS class name of outer combobox instance
	*
	* @param {String} className
	*/
	
	Combobox.prototype.setClassName = function(className){
		this.className = className ? className : '';
		this.updateInfoNode();
	}
	
	/**
	 * Returns value of Combobox
	 * 
	 * @return {String}
	 */
	Combobox.prototype.getValue = function(){
		var value = this.fieldNode.value;
		return value ? value : '';
	}
	
	/**
	* Returns DOM instance of complete combobox
	*
	* @return {Object}
	*/
	Combobox.prototype.getDomNode = function(){
		return this.domNode;
	}
	
	/**
	 * Init key codes
	 */
	Combobox.prototype.initKeycodes = function(){
		this.UP = 38;
		this.DOWN = 40;
		this.LEFT = 37;
		this.RIGHT = 39;
		this.ENTER = 13;
		this.ESC = 27;
		this.DEL = 46;
		this.BACKSPACE = 8;
		this.magicKeys = [40, 38, 13, 27, 16, 17, 18, 91, 20, 112, 113, 114, 115, 116, 117, 118, 119, 120,
		121, 122, 123, 92, 93, 44, 145, 19, 45, 36, 33, 35, 34, 37, 39, 144, 12, 9];
	}
	
	/**
	 * Creates combobox key
	 */
	Combobox.prototype.createCache = function(){
		this.cache = {};
	}
	
	/**
	 * Put value to cache with key 'key'
	 * 
	 * @param {String} key
	 * @param {mixed} value
	 */
	Combobox.prototype.putToCache = function(key, value){
		this.cache[key] = value;
	}
	
	/**
	 * Checks if the key exists in cache
	 * 
	 * @param {String} key
	 * @return boolean
	 */
	Combobox.prototype.isInCache = function(key){
		return this.cache[key] ? true : false;
	}
	
	/**
	 * Extracts value by key from combobox cache
	 * 
	 * @param {String} key
	 * @return {mixed}
	 */
	Combobox.prototype.getFromCache = function(key){
		return this.isInCache(key) ? this.cache[key] : false;
	}
	
	/**
	 * Creates DOM instance of Combobox with all required
	 * inner instances
	 */
	Combobox.prototype.createDomNode = function(){
		this.textboxNode = document.createElement('input');
		this.textboxNode.type = 'text';
		this.textboxNode.readOnly = this.readonly;
		this.textboxNode.setAttribute('autocomplete', 'off');
		this.buttonNode = document.createElement('img');
		this.buttonNode.src = this.path + 'button.gif';
		this.fieldNode = document.createElement('input');
		this.fieldNode.type = 'hidden';
		this.fieldNode.name = this.id;
		this.fieldNode.id = this.id + 'Field';
		this.infoNode = document.createElement('input');
		this.infoNode.type = 'hidden';
		this.domNode = document.createElement('div');
		this.domNode.id = this.id + 'Node';
		this.domNode.lang = 'combobox';
		this.domNode.className = this.className;
		this.domNode.appendChild(this.infoNode);
		this.domNode.appendChild(this.textboxNode);
		this.domNode.appendChild(this.buttonNode);
		this.domNode.appendChild(this.fieldNode);
		this.createCss();
		this.createEvents();
		this.updateInfoNode();
	}
	
	Combobox.prototype.updateInfoNode = function(){
		if (this.infoNode){
			this.infoNode.value = jsonEncode({
				'id' : this.id,
				'url' : this.type == 'static' ? false : this.url,
				'options' : this.type == 'static' ? this.options : false,
				'path' : this.path,
				'width' : this.width,
				'height' : this.height,
				'borderColor' : this.borderColor,
				'textColor' : this.textColor,
				'cursorColor' : this.cursorColor,
				'listWidth'	: this.listWidth,
				'itemLimit'	: this.itemLimit,
				'className' : this.className,
				'display' : this.domNode.style.display == 'none' ? 'none' : 'block',
				'disabled' : this.domNode.disabled ? true : false
			});
		}
	}
	
	/**
	 * Create CSS design for Combobox
	 */
	Combobox.prototype.createCss = function(){
		var buttonWidth = 16;
		var buttonHeight = 16;
		this.domNode.style.position = 'relative';
		this.domNode.style.zIndex = 1;
		this.domNode.style.height = parseInt(this.height) - 2 + 'px';
		this.domNode.style.border = "1px solid " + this.borderColor;
		this.textboxNode.style.border = '0px';
		this.textboxNode.style.margin = '0px';
		this.textboxNode.style.padding = '0px';
		this.textboxNode.style.position = 'absolute';
		this.textboxNode.style.left = '0px';
		this.textboxNode.style.top = '0px';
		if (parseInt(this.width)) {
			this.domNode.style.width = parseInt(this.width) - 2 + 'px';
			this.textboxNode.style.width = parseInt(this.width) - 5 - buttonWidth + 'px';
		}
		this.textboxNode.style.height = this.height - 2 + 'px';
		this.buttonNode.width = buttonWidth;
		this.buttonNode.height = buttonHeight;
		this.buttonNode.style.position = 'absolute';
		this.buttonNode.style.right = '1px';
		this.buttonNode.style.top = Math.round((this.height - 2 - buttonHeight) / 2) + 'px';
		this.buttonNode.style.cursor = 'pointer';
	}
	
	Combobox.prototype.setExitTimeout = function () {
		var instance = this;
		
		this.exitTimeout = setTimeout(
			function () {
				if (typeof instance.onexit == 'function') {
					if(!instance.onExitLaunched) {
						instance.onExitLaunched = true;
						instance.onexit();
					}
				}
				instance.select(true);
			}, 100);
	}
	
	Combobox.prototype.clearExitTimeout = function () {
		clearTimeout(this.exitTimeout);
		this.onExitLaunched = false;
	}
	
	/**
	 * Create different events connected with combobox
	 */
	Combobox.prototype.createEvents = function(){
		var instance = this;
		this.addEvent(document, 'mousedown', function(event){
			instance.documentClickHandler(event);
		});
		this.buttonNode.onclick = function(evt){
			instance.buttonClickHandler(evt ? evt : event);
		}
		this.textboxNode.oncontextmenu = function(evt){
			return false;
		}
		this.textboxNode.onkeydown = function(evt){
			return instance.textboxKeydownHandler(evt ? evt : event);
		}
		this.textboxNode.onkeyup = function(evt){
			return instance.textboxKeyupHandler(evt ? evt : event);
		}
		this.textboxNode.onchange = function(evt){
			instance.change();
			/*var value = instance.inCache(this.value);
			if (!(value === false)){
				instance.setValue(value);
				instance.select();
			}*/
			var value = instance.getValueByKey(instance.fieldNode.value);
			if(typeof(instance.onCustomDataEnter) == 'function') {
				if(new String(value).trim() !== new String(this.value).trim()) {
					instance.onCustomDataEnter(new String(this.value).trim());
				} else {
					value != -1 ? this.value = value : instance.clear();
				}
			} else {
				value != -1 ? this.value = value : instance.clear();
			}
			instance.isCustomData = true;
		}
		/* This handler is needed only for Opera browser */
		this.textboxNode.onkeypress = function(evt){
			return (evt ? evt : event).keyCode != instance.ENTER;
		}
		this.textboxNode.onblur = function (evt) {
			instance.setExitTimeout();
		}
		this.textboxNode.onfocus = function (evt) {
			instance.textboxNode.select();
			if (typeof(instance.onfocus) == 'function') {
				instance.onfocus();
			}
			instance.clearExitTimeout();
		}
	}
	
	/**
	 * Handle event, when user clicks out of Combobox
	 * 
	 * @param {Object} event
	 */
	Combobox.prototype.documentClickHandler = function(event){
		var target = this.isIe() ? event.srcElement : event.target;
		while (target.parentNode){
			if (target.id === this.domNode.id){
				return;
			}
			target = target.parentNode;
		}
		this.hideList();
	}
	
	/**
	 * Handle event, when user clicks on button
	 * 
	 * @param {Object} event
	 */
	Combobox.prototype.buttonClickHandler = function(event){
		var instance = this;
		if (this.type == 'dynamic'){
			this.isList() ? this.hideList() : this.loadFirstSymbolList(function(list){
				//instance.setValue({'key' : '', 'value' : ''});
				instance.textboxNode.focus();
				instance.listType = 'first_symbol';
				instance.options = list;
				instance.showAndFillList(list);
			});
		}
		else if (this.type == 'static'){
			if (this.isList()){
				this.hideList();
			}
			else{
				this.listType = 'data_like';
				this.showAndFillList(this.getDataLike(''));
			}
			this.textboxNode.focus();
		}
	}
	
	/**
	 * Handle when user pre-press the key
	 * 
	 * @param {Object} event
	 */
	Combobox.prototype.textboxKeydownHandler = function(event){
		var keyCode = event.keyCode;
		if (keyCode == this.DOWN){
			this.isList() ? this.moveCursorDown() : this.buttonClickHandler(event);
		}
		else if (keyCode == this.UP){
			if (this.isList()){
				this.moveCursorUp();
			}
		}
		else if (keyCode == this.ESC){
			this.hideList();
		}
		else if (keyCode == this.ENTER){
			this.elementClick(this.currentElement);
			return false;
		}
	}
	
	/**
	 * Handle when user post-press the key
	 * 
	 * @param {Object} event
	 */
	Combobox.prototype.textboxKeyupHandler = function(event){
		if (this.inArray(this.magicKeys, event.keyCode)){
			return false;
		}
		if (this.textboxNode.value != ''){
			var value = this.htmlEscape(this.textboxNode.value);
			this.type == 'dynamic' ? this.loadDataLike(value) : this.showAndFillList(this.getDataLike(value));
		}
		else if (event.keyCode == this.BACKSPACE || event.keyCode == this.DEL){
			this.hideList();
		}
		this.change();
	}
	
	/**
	 * Add handler as event handler to instance
	 * 
	 * @param {Object} instance
	 * @param {String} event
	 * @param {Function} handler
	 */
	Combobox.prototype.addEvent = function(instance, event, handler){
		this.isIe() ? instance.attachEvent('on' + event, handler) : instance.addEventListener(event, handler, false);
	}
	
	/**
	 * Render combobox on a page
	 */
	Combobox.prototype.show = function(){
		document.write('<div id="' + this.id +'ComboboxWrapper"></div>');
		document.getElementById(this.id + 'ComboboxWrapper').appendChild(this.domNode);
	}
	
	/**
	 * Checks if combobox list is open
	 * 
	 * @return {boolean}
	 */
	Combobox.prototype.isList = function(){
		return document.getElementById(this.listId) ? true : false;
	}
	
	/**
	 * Show combobox list
	 */
	Combobox.prototype.showList = function(){
		var instance = document.getElementById(this.listId);
		if (!instance){
			instance = document.createElement('div');
			instance.id = this.listId;
			instance.style.position = 'absolute';
			instance.style.left = '-1px';
			instance.style.top = this.height + 'px';
			instance.style.height = '0px';
			if (parseInt(this.listWidth)) {
				instance.style.width = (parseInt(this.listWidth) - 2) + 'px';
			} /*else {
				instance.style.width = this.listWidth;
			}*/
			instance.style.overflow = 'auto';
			instance.style.background = '#FFFFFF';
			instance.style.border = '1px solid ' + this.borderColor;
			this.domNode.appendChild(instance);
			this.domNode.style.zIndex = 5;
		}
		this.clearExitTimeout();
		if (typeof this.onlistshow == 'function'){
			this.onlistshow();
		}
	}
	
	/**
	 * Hide combobox list
	 */
	Combobox.prototype.hideList = function(){
		var instance = document.getElementById(this.listId);
		if (instance){
			this.setExitTimeout();
			instance.parentNode.removeChild(instance);
			this.domNode.style.zIndex = 1;
		}
		this.currentElement = null;
		if (typeof this.onlisthide == 'function'){
			this.onlisthide();
		}
	}
	
	/**
	 * Fill combobox list. Elements is array of objects-pairs key-value
	 * 
	 * @param {Array} elements
	 */
	Combobox.prototype.fillList = function(elements){
		if (elements.length > 0){
			var isSelected = false;
			var firstElement = null;
			this.clearList();
			var list = $(this.listId);
//			for (var i = 0; i < elements.length; i ++){
			var i=0;
			for(var key in elements)  if(typeof elements[key] != 'function' && key != 'length' && !this.hiddenOptions[key]) {
				list.style.height = Math.min(i + 1, this.itemLimit) * (this.height - 2) + 'px';
				var element = this.addElement(key, elements[key]);
				if(!firstElement)
					firstElement = element;
				if(key == this.getValue()) {
					this.highlightElement(element);
					list.scrollTo(0, element.offsetTop);
					isSelected = true;
				}
				i++;
			}
			if(!isSelected)
				this.highlightElement(firstElement);
		}
		else{
			this.hideList();
		}
	}
	
	/**
	 * Clear combobox list
	 */
	Combobox.prototype.clearList = function(){
		var instance = document.getElementById(this.listId);
		while (instance.firstChild){
			instance.removeChild(instance.firstChild);
		}
	}
	
	/**
	 * 
	 * @param {Array} elements
	 */
	Combobox.prototype.showAndFillList = function(elements){
		this.showList();
		this.fillList(elements);
	}
	
	/**
	 * Add element to combobox list. Position is the element's position in list
	 * 
	 * @param {String} key
	 * @param {String} value
	 */
	Combobox.prototype.addElement = function(key, value){
		var instance = this;
		var list = document.getElementById(this.listId);
		if (list){
			var element = document.createElement('div');
			element.key = key;
			element.innerHTML = value;
			this.addEvent(element, 'mouseover', function(event){
				if (instance.currentElement){
					instance.unhighlightElement(instance.currentElement);
				}
				instance.highlightElement(element);
			});
			this.addEvent(element, 'click', function(event){
				instance.elementClick(element);
			});
			this.setElementStyle(element);
			this.unhighlightElement(element);
			list.appendChild(element);
			return element;
		}
	}
	
	/**
	 * List element click handler
	 * 
	 * @param {Object} element
	 */
	Combobox.prototype.elementClick = function(element){
		var instance = this;
		
		if (this.listType == 'first_symbol'){
			this.loadDataLike(element.innerHTML);
		}
		else if (this.listType == 'data_like'){
			this.selectElement(element);
			this.select();
			if (typeof(this.onelementclick)=="function") {
				this.onelementclick(element);
			}
		}
	}
	
	/**
	 * Selects element as current
	 * 
	 * @param {Object} element
	 */
	Combobox.prototype.selectElement = function(element){
		var instance = document.getElementById(this.listId);
		if (instance){
			this.hideList();
			
			var newValue = element.innerHTML;
			var comparedValue = this.htmlEscape(new String(element.innerHTML));
			for(key in this.altOptions) if(typeof this.altOptions[key] != 'function' && key != 'length'){
				if(this.altOptions[key] === comparedValue) {
					newValue = this.getValueByKey(key);
					break;
				}
			}
			
			this.textboxNode.value = this.htmlUnescape(newValue);
			this.textboxNode.focus();
			this.change();
		}
	}
	
	/**
	 * Sets CSS rules for element
	 * 
	 * @param {Object} element
	 */
	Combobox.prototype.setElementStyle = function(element){
		element.style.paddingLeft = '3px';
		element.style.paddingRight = '3px';
		element.style.height = this.height - 2 + 'px';
		element.style.lineHeight = this.height - 2 + 'px';
		element.style.textAlign = 'left';
		element.style.overflow = 'hidden';
		element.style.cursor = 'default';	
	}
	
	/**
	 * Highlights element when it is selected. Direction is needed to scroll combobox
	 * list when we use keyboard navigation
	 * 
	 * @param {Object} element
	 */
	Combobox.prototype.highlightElement = function(element){
		this.currentKey = element.key; 
		this.currentElement = element;
		element.style.color = '#FFFFFF';
		element.style.background = this.cursorColor;
	}
	
	/**
	 * Unhighlights element when it is unselected
	 * 
	 * @param {Object} element
	 */
	Combobox.prototype.unhighlightElement = function(element){
		element.style.background = '#FFFFFF';
		element.style.color = this.textColor;
	}
	
	/**
	 * Loads list of availible first symbols of server data
	 * 
	 * @param {Function} callback
	 */
	Combobox.prototype.loadFirstSymbolList = function(callback){
		var instance = this;
		jsonClient(this.url, {'command' : 'get_first_symbol_list'}, function(response){
			callback(response);
		});
	}
	
	/**
	 * Loads data with first symbols like 'like' string
	 * 
	 * @param {String} like
	 * @param {Function} callback
	 */
	Combobox.prototype.loadDataLike = function(like){
		// TODO refactor !
		var instance = this;
		var firstLetter = like.charAt(0).toLowerCase();
		if (!this.isInCache(firstLetter)){
			jsonClient(this.url, {'command' : 'get_data_like', 'like' : firstLetter}, function(response){
				instance.options = response;
				instance.putToCache(firstLetter, response);
				instance.textboxNode.focus();
				instance.listType = 'data_like';
				instance.showAndFillList(instance.getDataLike(like));
			});
		}
		else{
			this.options = this.getFromCache(firstLetter);
			this.textboxNode.focus();
			this.listType = 'data_like';
			this.showAndFillList(this.getDataLike(like));
		}
	}
	
	/**
	 * Extract all options where values begin with like
	 * 
	 * @param {String} like
	 * @return {Array}
	 */
	Combobox.prototype.getDataLike = function(like){
		var result = new Object();
		result.length = 0;
		var options = this.options;
		var altOptions = this.altOptions;
		var instance = this;

		function evaluteResult(like, ids) {
			function evalOne(data) {
				/*
				for(var i = 0; i < data.length; i++) {
					if(ids && ids.indexOf(data[i].key) == -1)
						continue;
					if(data[i].value.toLowerCase().indexOf(like.toLowerCase()) == 0) {
						result[result.length] = data[i];
					} 
				}
				*/
				for(var key in data) if(typeof data[key] != 'function'  && key != 'length') {
					if(ids && ids.indexOf(key) == -1) {
						continue;
					}
					var value = new String(data[key]);
					if(value.toLowerCase().indexOf(like.toLowerCase()) == 0) {
						result[key] = data[key];
						result.length++;
					}
				}
			}
			evalOne(options);
			if(like !== '')
				evalOne(altOptions);
		};
		evaluteResult(like);
		if(this.filters && like !== '') {
			for(key in this.filters) {
				var filter = this.filters[key];
				if(typeof(filter) == 'function')
					continue;
				if(like.slice(0, key.length) == key)
					evaluteResult(like.slice(key.length), filter);
			}
		}
		return result;
	}
	
	/**
	 * Checks, if the browser is Internet Explorer
	 * 
	 * @return {boolean}
	 */
	Combobox.prototype.isIe = function(){
		return navigator.userAgent.indexOf("MSIE") != -1;
	}
	
	/**
	 * Moves cursor down
	 */
	Combobox.prototype.moveCursorDown = function(){
		if (this.currentElement) if (this.currentElement.nextSibling){
			this.unhighlightElement(this.currentElement);
			var condition = this.currentElement.nextSibling.offsetTop + this.currentElement.nextSibling.offsetHeight - this.currentElement.parentNode.scrollTop - this.currentElement.parentNode.offsetHeight >= 0;
			if (condition){
				this.currentElement.parentNode.scrollTop += parseInt(this.currentElement.style.height, 10);
			}
			this.highlightElement(this.currentElement.nextSibling);
		}
	}
	
	/**
	 * Moves cursor up
	 */
	Combobox.prototype.moveCursorUp = function(){
		if (this.currentElement) if (this.currentElement.previousSibling){
			this.unhighlightElement(this.currentElement);
			var condition = this.currentElement.parentNode.scrollTop - this.currentElement.previousSibling.offsetTop > 1;
			if (condition){
				this.currentElement.parentNode.scrollTop -= parseInt(this.currentElement.style.height, 10);
			}
			this.highlightElement(this.currentElement.previousSibling);
		}
		else{
			this.hideList();
		}
	}
	
	/**
	 * Checks if test value exists in array 'array'
	 * 
	 * @param {Array} array
	 * @param {mixed} test
	 * @return {boolean}
	 */
	Combobox.prototype.inArray = function(array, test){
		for (var i = 0; i < array.length; i ++){
			if (array[i] === test){
				return true;
			}
		}
		return false;
	}
	
	Combobox.prototype.pos = function(obj, test){
		/*
		for (var i = 0; i < array.length; i ++){
			if (array[i].value === test){
				return array[i].key;
			}
		}
		*/
		for(var key in obj) if(typeof obj[key] != 'function'  && key != 'length') {
			if (obj[key] === test){
				return key;
			}
		}
		return -1;
	}
	
	Combobox.prototype.getValueByKey = function(key){
		/*
		for (var i = 0; i < this.options.length; i ++){
			if (this.options[i].key == key){
				return this.options[i].value;
			}
		}
		*/
		var value = this.options[key];
		if(value != undefined) {
			return value;
		}
		else {
			return -1;
		}
	}
	
	Combobox.prototype.change = function(){
		if (this.textboxNode.form) if (this.textboxNode.form.onchange) if (typeof this.textboxNode.form.onchange == 'function'){
			this.textboxNode.form.onchange();
		}
		if (typeof this.onchange == 'function'){
			this.onchange(false);//key === -1
		}
		if (this.textboxNode.value.length == 0){
			this.clear();
		}
	}
	
	Combobox.prototype.select = function(noEventHandler){
		if (this.isTrimed()) {
			var key = this.pos(this.options, this.htmlEscape(new String(this.textboxNode.value).trim()))
		} else {
			var key = this.pos(this.options, this.htmlEscape(new String(this.textboxNode.value)));
		}
		if (key === -1) {
			var value = this.textboxNode.value;
			if (this.isTrimed()) {
				value = new String(value).trim();
			}
			if (this.quoteNewValue) {
				this.fieldNode.value = 'ncval|' + value;
			} else {
				this.fieldNode.value = value;
			}
		} else {
			this.fieldNode.value = key;
			if (key != this.currentKey && this.currentKey) {
				this.fieldNode.value = this.currentKey;
			}
		}
		this.isCustomData = false;
		if (typeof this.onselect == 'function' && !noEventHandler){
			this.onselect();
		}
	}
	
	Combobox.prototype.setDisplay = function(display){
		this.domNode.style.display = display ? display : 'block';
		this.updateInfoNode();
	}
	
	Combobox.prototype.setQuoteNewValue = function (flag) {
		this.quoteNewValue = flag ? true : false;
	}
	
	Combobox.prototype.setTrimed = function (flag) {
		this.trimed = flag ? true : false;
	}
	
	Combobox.prototype.isTrimed = function () {
		return this.trimed;
	}
	
	Combobox.prototype.isQuoteNewValue = function () {
		return this.quoteNewValue;
	}
	
	Combobox.prototype.setDisabled = function(disabled){
		var instance = this;
		this.textboxNode.disabled = disabled;
		this.fieldNode.disabled = disabled;
		if (disabled){
			this.buttonNode.style.cursor = 'default';
			this.buttonNode.onclick = null;
		}
		else{
			this.buttonNode.style.cursor = 'pointer';
			this.buttonNode.onclick = function(evt){
				instance.buttonClickHandler(evt ? evt : event);
			}
		}
		this.updateInfoNode();
	}
	
	Combobox.prototype.isDisabled = function () {
		return this.textboxNode.disabled;
	}
	
	Combobox.prototype.inCache = function(value){
		for (var key in this.cache) if (typeof this.cache[key] != 'function'){
			for (var i = 0; i < this.cache[key].length; i ++){
				if (this.cache[key][i].value.toLowerCase() == value.toLowerCase()){
					return this.cache[key][i];
				}
			}
		}
		return false;
	}
	
	Combobox.prototype.setValueByKey = function(key){
		var instance = this;
		if (this.type == 'static'){
			var value = this.getValueByKey(key);
			value = value == -1 ? '' : value;
			this.setValue({'key' : key, 'value' : value});
		}
		else{
			if (key){
				this.fieldNode.value = key;
				jsonClient(this.url, {'command' : 'get_value_by_key', 'key' : key}, function(response){
					instance.setValue(response);
				});		
			}
		}
		this.isCustomData = false;		
	}
	
	Combobox.prototype.clear = function(){
		this.setValue({'key' : '', 'value' : ''});
		if (typeof this.onclear == 'function'){
			this.onclear();
		}
		this.isCustomData = false;		
	}
	
	Combobox.prototype.setReadonly = function(readonly){
		var instance = this;
		this.textboxNode.disabled = readonly;
		this.fieldNode.readonly = readonly;
		if (readonly){
			this.buttonNode.style.cursor = 'default';
			this.buttonNode.onclick = null;
		}
		else{
			this.buttonNode.style.cursor = 'pointer';
			this.buttonNode.onclick = function(evt){
				instance.buttonClickHandler(evt ? evt : event);
			}
		}
		this.updateInfoNode();
	}

}