
/**
 * jQuery.ComboBox 1.0.0
 *
 * Copyright (c) 2008 Wagner B. Soares (soareswagner@hotmail.com)
 *
 * $Date: 2009-03-12
 */
jQuery.extend(
{
	ComboBox: function(cfg)
	{
		/**
		 * Atributo que recebe o id do combobox
		 */
		var id = jQuery.isString(cfg.id) ? cfg.id : null;
		/**
		 * Atributo que recebe o número de itens visiveis da lista de itens
		 */
		var numberVisibleItems = jQuery.isInt(cfg.visibleItems) ? cfg.visibleItems : 6;
		/**
		 * Atributo de configuração da visibilidade da sombra da lista de itens
		 */
		var shadow = jQuery.isNull(cfg.shadow) || !jQuery.isBool(cfg.shadow) ? true : cfg.shadow;
		/**
		 * Atributo que configura se o combobox estará habilitado ou desabilitado
		 */
		var disable = jQuery.isNull(cfg.disable) || !jQuery.isBool(cfg.disable) ? false : cfg.disable;
		/**
		 * Se o id passado não existir cancela a geração do combobox
		 */
		if((id == null) || (id.length == 0) || (jQuery("#"+id).length !== 1)) return false;
		/**
		 * Atributo que recebe a referência da própria classe ComboBox
		 */
	 	var obj = this;
	 	/**
	 	 * Atributo que recebe o texto de pesquisa do combobox
	 	 */
	 	var search = "";
	 	/**
	 	 * Atributo que recebe o tempo de espera para digitação da pesquisa
	 	 */
	 	var searchTime = 0;
	 	/**
	 	 * Atributo que recebe o indice do ultimo item selecionado
	 	 */
	 	var lastSelectedIndex = -1;
	 	/**
	 	 * Atributo que informa se o combo tem foco
	 	 */
	 	var focus = false;
		/**
		 * Atributo que recebe o combobox original
		 */
		var combo					= jQuery("#"+id).css("display","none");
		/**
		 * Atributo que recebe o container do combobox
		 */
		var combobox				= jQuery("<span />").attr({"id":id+"-container"}).addClass("combobox-container");
		/**
		 * Atributo que recebe o campo de texto que será utilizado no novo combobox
		 */
		var comboboxText			= jQuery('<input type="text" />').attr({"title":"","id":id+"-text","name":id+"-text","unselectable":"on","readonly":true}).addClass("combobox-text");
		/**
		 * Atributo que recebe a seta do combobox
		 */
		var comboboxArrow			= jQuery("<span />").attr({"id":id+"-arrow"}).addClass("combobox-arrow");
	    /**
	     * Atributo que recebe o container dos itens do combobox
	     */
	    var containerItems			= jQuery("<ul />").attr({"id":id+"-items"}).addClass("combobox-container-items");
	    /**
	     * Atributo que recebe a sombra do container dos itens do combobox
	     */
	    var containerItemsShadow	= jQuery("<div />").attr({"id":id+"-items-shadow"})
	    	.addClass("combobox-container-shadow")
	    	.css({
	    			"opacity": "0.5",
	    			"-moz-opacity": "0.5",
	    			"filter": "alpha(opacity=50)"
	    		});
	    /**
	     * elementos que recebem a referência do combo
	     */
	    combo[0].select			= combo;
	    combobox[0].select		= combo;
	    comboboxText[0].select	= combo;
	    
	    /***************************************** 
	     *      CONFIGURAÇÃO DOS EVENTOS         *
	     *****************************************/
	    
	    /**
	     * Método utilizado para verificar se houve algum evento externo do combobox,
	     * para fechar a lista de itens
	     */
	    var checkExternalEvent = function(event)
	    {
	    	event = jQuery.event.fix(event || window.event);
			if (jQuery(event.target).parents("#"+id+"-container").length === 0)
			{
				if(comboboxText.hasClass("combobox-text-focus")) comboboxText.removeClass("combobox-text-focus");
				if(comboboxArrow.hasClass("combobox-arrow-focus")) comboboxArrow.removeClass("combobox-arrow-focus");
				if(containerItems.is(":visible")) obj.hideContainerItems();
				
				if(focus) combo.blur();
				
				focus = false;
			}
		};
		/**
		 * Qualquer elemento que não for o próprio combobox que receber foco no documento,
		 * disparará a verificação de evento externo
		 */
		jQuery("*:not(#"+id+"-text)").focus(checkExternalEvent);
		/**
		 * Qualquer elemento do documento que não for o próprio combobox que receber um click do mouse,
		 * disparará a verificação de evento externo
		 */
		jQuery(document).mousedown(checkExternalEvent);
		/**
	     * Se o combobox original receber foco passa automaticamente para o novo combobox
	     */
		combo.focus(function()
		{
			if(!disable)
			{
				comboboxText.focus();
			}
		});
	    /**
	     * Ações que serão executas no click da seta do combobox
	     */
		comboboxArrow.click(function()
		{
			if(!disable)
			{
				containerItems.toggle();
				if(shadow)containerItemsShadow.toggle();
				comboboxText.focus();
				
				comboboxText.toggleClass("combobox-text-items-open");
				comboboxArrow.toggleClass("combobox-arrow-items-open");
			}
		});
		/**
	     * Ações que serão executas quando uma tecla for pressionada durante o foco do combobox
	     */
		comboboxText.keydown(function(event)
		{
			if(!disable)
			{
				event = jQuery.event.fix(event || window.event);
				
				switch(event.keyCode)
				{
					case 40:
						// ações que serão executadas ao precionar a tecla para baixo
						if(lastSelectedIndex < (combo[0].options.length - 1))
						{
							obj.showContainerItems();
							lastSelectedIndex++;
							obj.showSelectedItem();
						}
						
						obj.selectForIndex(lastSelectedIndex);
						break;
					case 38:
						// ações que serão executadas ao precionar a tecla para cima
						if (lastSelectedIndex > 0)
						{
							obj.showContainerItems();
							lastSelectedIndex--;
							obj.showSelectedItem();
						}
						
						obj.selectForIndex(lastSelectedIndex);
						break;
					case 13:
						// ações que serão executadas ao precionar a tecla ENTER
						if(containerItems.is(":visible"))
						{
							jQuery("li:eq("+lastSelectedIndex+")",containerItems).removeClass("over-item");
							obj.hideContainerItems();
							return false;
						}
						break;
					default:
						// ações que serão executadas ao precionar uma tecla
						searchTime = (searchTime == 0) ? (new Date()).getTime() : searchTime;
						
						var key = event.charCode||event.keyCode||event.which;						
						
						if(event.ctrlKey || event.altKey)
						{
							return true;
						}
						else if(((key >= 41) && (key <= 122)) || (key == 32) || (key > 186))
						{
							// se for tecla permitida inicia o processo de busca no combobox
							containerItems.show();
							if(shadow) containerItemsShadow.show();
							// se a hora for menor que o tempo limite, continua a pesquisa atual
							if((new Date()).getTime() <= (searchTime+1000))
							{
								search += String.fromCharCode(key);
							}
							// se a hora for maior que o tempo limite, inicia uma nova pesquisa
							else
							{
								searchTime = (new Date()).getTime();
								search = String.fromCharCode(key);
							}
							// expressão regular utilizada na pesquisa
							var regexp = eval("/^"+search+"/i");
							
							var options = jQuery("option",combo);
							options.each(function(index)
							{
								// se o valor digitado for encontrado no combobox seleciona o mesmo
								if(regexp.test(jQuery(this).text()))
								{
									lastSelectedIndex = index;
									obj.showSelectedItem();
									obj.selectForIndex(index);
									
									return false;
								}
							});
						}
						
						break;
				}
			}
		})
		.focus(function()
		{
			// ações que serão executadas quando o combobox receber foco
			if(!disable)
			{
				if(!comboboxText.hasClass("combobox-text-focus")) comboboxText.addClass("combobox-text-focus");
				if(!comboboxArrow.hasClass("combobox-arrow-focus")) comboboxArrow.addClass("combobox-arrow-focus");
				
				focus = true;
			}
		})
		.click(function()
		{
			// ações que serão executadas quando o combobox receber um clique do mouse
			if(!disable)
			{
				containerItems.toggle();
				if(shadow) containerItemsShadow.toggle();
				
				comboboxText.toggleClass("combobox-text-items-open");
				comboboxArrow.toggleClass("combobox-arrow-items-open");
			}
		});
		/**
	     * Ações que serão executas quando a lista de itens do combox receber foco
	     */
		containerItems.focus(function()
		{
			obj.hideContainerItems();
			comboboxText.focus();
		});
		
		/**
		 * Eventos que podem ser configurados pelo desenvolvedor
		 * 
		 * exemplo de como acessar os valores do combo:
		 * 
		 * var combo = new jQuery.ComboBox({id:"selCidades"});
		 * combo.render();
		 * combo.blur(function()
		 * {
		 * 		alert(jQuery(this.select).val());
		 * });
		 */
		jQuery.extend(this,
		{
		    blur: function(fn)
			{
	    		if(jQuery.isFunction(fn))
	    			comboboxText.blur(fn);
	    		else
	    			comboboxText.blur();
		    },
		    click: function(fn)
		    {
		    	if(jQuery.isFunction(fn))
		    		comboboxText.click(fn);
		    	else
		    		comboboxText.click();
		    },
		    focus: function(fn)
		    {
		    	if(jQuery.isFunction(fn))
		    		comboboxText.focus(fn);
		    	else
		    		comboboxText.focus();
		    },
		    keydown: function(fn)
		    {
		    	if(jQuery.isFunction(fn))
		    		comboboxText.keydown(fn);
		    	else
		    		comboboxText.keydown();
		    },
		    keyup: function(fn)
		    {
		    	if(jQuery.isFunction(fn))
		    		comboboxText.keyup(fn);
		    	else
		    		comboboxText.keyup();
		    },
		    keypress: function(fn)
		    {
		    	if(jQuery.isFunction(fn))
		    		comboboxText.keypress(fn);
		    	else
		    		comboboxText.keypress();
		    },
		    mouseover: function(fn)
		    {
		    	if(jQuery.isFunction(fn))
		    		combobox.mouseover(fn);
		    	else
		    		combobox.mouseover();
		    },
		    mouseout: function(fn)
		    {
		    	if(jQuery.isFunction(fn))
		    		combobox.mouseout(fn);
		    	else
		    		combobox.mouseout();
		    },
		    mousedown: function(fn)
		    {
		    	if(jQuery.isFunction(fn))
		    		combobox.mousedown(fn);
		    	else
		    		combobox.mousedown();
		    },
		    mouseup: function(fn)
		    {
		    	if(jQuery.isFunction(fn))
		    		combobox.mouseup(fn);
		    	else
		    		combobox.mouseup();
		    },
		    change: function(fn)
		    {
		    	if(jQuery.isFunction(fn))
		    		combo.change(fn);
		    	else
		    		combo.change();
		    }
		});
		
		/************************************************
		 *        FIM DA CONFIGURAÇÃO DOS EVENTOS       *
		 ************************************************/
		
		/**
		 * Método que configura a largura e altura da lista de itens
		 */
		var normalizeItemsSize = function()
		{
			var itemsWidth = containerItems.width();
			
			var itemsHeight = containerItems.height() ;
			
			var numberItems = jQuery("option",combo).length;
			
			numberVisibleItems = numberVisibleItems == 0 ? numberItems : numberVisibleItems;
	    	
	    	if(numberItems > numberVisibleItems)
	    	{
	    		itemsWidth += 17;
	    	}
	    	
	    	itemsHeight = numberVisibleItems * 20;
	    	
	    	var border = parseInt(containerItems.css("border-left-width"))+parseInt(containerItems.css("border-right-width"));
	    	
	    	itemsWidth = itemsWidth < (combobox.width()-border) ? (combobox.width()-border) : itemsWidth ;
	    	
	    	containerItems.css({"width":itemsWidth,"height":itemsHeight,"overflow":"auto"});
	    	if(shadow) containerItemsShadow.css({"width":itemsWidth,"height":itemsHeight});
		};
		/**
		 * Atribui novos métodos a classe ComboBox
		 */
		jQuery.extend(this,
		{
			/**
			 * Método para selecionar um item do combobox através do índice
			 */
		    selectForIndex: function(value)
		    {
		        if(!jQuery.isInt(value)){value = 0;}
		        
		        var index = combo[0].selectedIndex;
		        
		        lastSelectedIndex = value;

                comboboxText.val(jQuery("option:eq("+value+")",combo).attr("selected",true).text());

                jQuery("li",containerItems).removeClass("over-item");
				jQuery("li:eq("+value+")",containerItems).addClass("over-item");
		        
		        if(value != index) combo.change();
		    },
		    /**
			 * Método para selecionar um item do combobox através do valor
			 */
		    selectForValue: function(value)
		    {
		        if(value != combo.val())
		        {
		        	comboboxText.val(jQuery("option[value='"+value+"']:eq(0)",combo).attr("selected",true).text());
			        
			        combo.change();
		        }
		    },
		    /**
			 * Método que exibe os itens do combobox
			 */
		    showContainerItems: function()
		    {
		    	containerItems.show();
				if(shadow) containerItemsShadow.show();
				
				comboboxText.addClass("combobox-text-items-open");
				comboboxArrow.addClass("combobox-arrow-items-open");
		    },
		    /**
			 * Método que esconde os itens do combobox
			 */
		    hideContainerItems: function()
		    {
		    	containerItems.hide();
				if(shadow) containerItemsShadow.hide();
				
				comboboxText.removeClass("combobox-text-items-open");
				comboboxArrow.removeClass("combobox-arrow-items-open");
		    },
		    /**
			 * Método que exibe um item se o mesmo se estiver escondido
			 */
		    showSelectedItem: function()
		    {
		    	var offsetItems = containerItems.offset();
		    	var containerItemsHeight = containerItems.height();
		    	
		    	var offsetItem = jQuery("li:eq("+lastSelectedIndex+")",containerItems).offset();

		    	if(offsetItem.top > ((offsetItems.top+containerItemsHeight)-20))
		    	{
		    		containerItems[0].scrollTop += offsetItem.top - (offsetItems.top+containerItemsHeight-20);
		    	}
		    	else if(offsetItem.top < offsetItems.top)
		    	{
		    		containerItems[0].scrollTop -= offsetItems.top - offsetItem.top;
		    	}
		    },
		    /**
			 * Método que remove todos os itens do combobox
			 */
		    empty: function()
		    {
		    	containerItems.empty().css({"width":"auto","height":"auto"});
		    	
		    	combo.empty();
		    	
		    	comboboxText.val("");
		    	
		    	normalizeItemsSize();
		    },
		    /**
			 * Método para habilitar ou desabilitar o combobox
			 */
		    setDisabled: function(value)
		    {
		    	if(jQuery.isBool(value))
		    	{
		    		disable = value;
		    		combo.attr("disabled",value);
		    		comboboxText.attr("disabled",value);
		    	}
		    },
		    /**
			 * Método para atribuir novos itens para o combobox
			 */
		    setOptions: function(values)
		    {
		    	containerItems.css({"width":"auto","height":"auto","overflow":"visible"});
		    	
		    	if(jQuery.isArray(values))
		    	{
		    		for(var index in values)
		    		{
		    			combo.append(jQuery("<option />")
		    				.val(unescape(values[index].value))
		    				.html(unescape(values[index].text)));
		    		}
		    		
		    		combo.next().remove();
		    		
		    		obj.render();
		    	}
		    },
		    /**
			 * Método que renderiza o combobox na tela
			 */
		    render: function()
		    {
		    	var options = jQuery("option",combo);
		    	// Configuração dos options do combobox
		    	options.each(function(index)
		    	{
		    		// cria o novo item do combobox
		    		var item = jQuery("<li />")
	    				.attr({"index":index})
	    				.html("<span>"+jQuery(this).text()+"</span>");
					// configura os eventos de mouse do item
					item.hover(function()
					{
						jQuery("li",containerItems).removeClass("over-item");
						jQuery(this).addClass("over-item");
					},function()
					{
						jQuery(this).removeClass("over-item");
					})
					.click(function()
					{
						obj.selectForIndex(jQuery(this).attr("index"));
						containerItems.hide();
						if(shadow) containerItemsShadow.hide();
						
						comboboxText.toggleClass("combobox-text-items-open");
						comboboxArrow.toggleClass("combobox-arrow-items-open");
						
						comboboxText.focus();
					});
					// adiciona o item ao container
					containerItems.append(item);
		    	});
		    	// recebe a largura do combo original
		    	var width = combo.width();
		    	// configuração do combo
		    	combobox.css("width",width).append(containerItems);
		    	if(shadow) combobox.append(containerItemsShadow);
		    	combobox.append(comboboxText).append(comboboxArrow);
		    	// se o navegador que estiver executando for o IE o estilo apropriado para o navegador
		    	if(jQuery.browser.msie) combobox.css("display","inline-block");
		    	// renderiza o novo combo no navegador
		    	combo.before(combobox);
				var borderLeft = parseInt(comboboxText.css("border-left-width").replace(/\D/g,""),10);
				var borderRight = parseInt(comboboxText.css("border-right-width").replace(/\D/g,""),10);
				// configura a largura e posição dos elementos
				comboboxText.css("width",(width-(comboboxArrow.width()))-(3+(isNaN(borderLeft) ? 0 : borderLeft)+(isNaN(borderRight) ? 0 : borderRight)));
                comboboxArrow.css("margin-left",(width-comboboxArrow.width()));
		    	// configura as medidas do container dos itens
		    	normalizeItemsSize();
		    	// seleciona o primeiro elemento do combobox
		    	obj.selectForIndex(combo[0].selectedIndex);
		    	// configura o combobox como desabilitado ou habilitado através da pré-configuração
	    		comboboxText.attr("disabled",disable);
	    		combo.attr("disabled",disable);
		    }
		});
	},
	/**
	 * jQuery.empty
	 * 
     * Método que retorna true se a variável é vazia, senão false
     *
     * exemplo 1: jQuery.empty(null);
	 * retorno 1: true
	 * exemplo 2: jQuery.empty(undefined);
	 * retorno 2: true
	 * exemplo 3: jQuery.empty([]);
	 * retorno 3: true
	 * exemplo 4: jQuery.empty({});
	 * retorno 4: true
	 * exemplo 5: jQuery.empty({'aFunc' : function () { alert('humpty'); } });
	 * retorno 5: false
	 * 
     * @param {Mixed} mixed_var
     * @return {Boolean}
     */
	empty: function(mixed_var)
	{
	    // original by: Philippe Baumann
	    //    input by: Onno Marsman
	    // bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
	    //    input by: LH
	    // improved by: Onno Marsman
	    // improved by: Francesco
	    // improved by: Marc Jansen
	    
	    var key;
	    
	    if(mixed_var === ""
	        || mixed_var === 0
	        || mixed_var === "0"
	        || mixed_var === null
	        || mixed_var === false
	        || mixed_var === undefined)
	    {
	        return true;
	    }
	
	    if(typeof mixed_var == 'object')
	    {
	        for(key in mixed_var)
	        {
	            return false;
	        }
	        return true;
	    }
	
	    return false;
	},
    /**
     * jQuery.getType
     * 
     * Método que retorna o tipo de objeto passado
     *
     * string: Se o objeto passado é uma string
     * number: Se o objeto passado é um numero
     * boolean: Se o objeto passado é um boolean
     * function: Se o objeto passado é uma função
     * object: Se o objeto passado é um objecto
     * array: Se o objeto passado é um array
     * regexp: Se o objeto passado é uma expressão regular
     * null: Se o objeto passado é nulo ou indefinido
     * 
     * @param {Mixed} obj
     * @return {String}
     */
    getType : function(obj)
    {
        if(obj === undefined || obj === null)
        {
            return "null";
        }

        var type = typeof obj;
        
        if(type == 'object' || type == 'function')
        {
            if(obj.constructor == Array) return 'array';
            if(obj.constructor == RegExp) return 'regexp';
        }
        return type;
    },
    /**
     * jQuery.isArray
     * 
     * Método que retorna true se o objeto passado é um array, senão false.
     * 
     * @param {Object} value
     * @return {Boolean}
     */
    isArray: function(value)
    {
        return (jQuery.getType(value) == 'array');
    },
    /**
     * jQuery.isBool
     * 
     * Método que retorna true se o objeto passado é um booleano, senão false.
     * 
     * @param {Object} value
     * @return {Boolean}
     */
    isBool: function(value)
    {
        return (jQuery.getType(value) == 'boolean');
    },
    /**
     * jQuery.isFloat
     * 
     * Método que retorna true se o objeto passado é um número de ponto flutuante, senão false.
     * 
     * nota: 1.0 é simplificado para 1 pelo próprio javascript após acessar a função
     * 
     * @param {Object} value
     * @return {Boolean}
     */
    isFloat: function(value)
    {
        return (/^([\+\-]?(\d+)(.){1}(\d+)|0)$/g).test(value.toString());
    },
    /**
     * jQuery.isInt
     * 
     * Método que retorna true se o objeto passado é um número inteiro, senão false.
     * 
     * @param {Object} value
     * @return {Boolean}
     */
    isInt: function(value)
    {
        return /^[\+\-]?\d+$/.test(value);
    },
    /**
     * jQuery.isNull
     * 
     * Método que retorna true se o objeto passado é nulo, senão false.
     * 
     * @param {Object} value
     * @return {Boolean}
     */
    isNull: function(value)
    {
        return (jQuery.getType(value) == 'null');
    },
    /**
     * jQuery.isNumeric
     * 
     * Método que retorna true se o objeto passado é um número, senão false.
     * 
     * @param {Object} value
     * @return {Boolean}
     */
    isNumeric: function(value)
    {
        return !isNaN(value);
    },
    /**
     * jQuery.isSet
     * 
     * Método que retorna true se a variável foi iniciada, senão false.
     * 
     * @param {Object} value
     * @return {Boolean}
     */
    isSet: function()
    {
    	// original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
        // improved by: FremyCompany
        // improved by: Onno Marsman
        
        var a=arguments; var l=a.length; var i=0;
        
        if(l==0)
        { 
            throw new Error('Empty jQuery.isSet'); 
        }
        
        while(i != l)
        {
            if(jQuery.getType(value) == 'null')
            { 
                return false; 
            }
            else
            { 
                i++; 
            }
        }
        return true;
    },
    /**
     * jQuery.isString
     * 
     * Método que retorna true se o objeto passado é uma string, senão false.
     * 
     * @param {Object} value
     * @return {Boolean}
     */
    isString: function(value)
    {
        return (jQuery.getType(value) == 'string');
    }
});
