/*!
 * jQuery Selectbox plugin 0.1.3
 *
 * Copyright 2011, Dimitar Ivanov (http://www.bulgaria-web-developers.com/projects/javascript/selectbox/)
 * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
 * 
 * Date: Wed Jul 29 23:20:57 2011 +0200
 */
(function ($, undefined) {
	var PROP_NAME = 'selectbox',
		FALSE = false,
		TRUE = true;
	/**
	* Selectbox manager.
	* Use the singleton instance of this class, $.selectbox, to interact with the select box.
	* Settings for (groups of) select boxes are maintained in an instance object,
	* allowing multiple different settings on the same page
	*/
	function Selectbox() {
		this._state = [];
		this._defaults = { // Global defaults for all the select box instances
			classHolder: "sbHolder",
			classHolderDisabled: "sbHolderDisabled",
			classSelector: "sbSelector",
			classOptions: "sbOptions",
			classGroup: "sbGroup",
			classSub: "sbSub",
			classDisabled: "sbDisabled",
			classToggleOpen: "sbToggleOpen",
			classToggle: "sbToggle",
			speed: 200,
			effect: "slide", // "slide" or "fade"
			onChange: null, //Define a callback function when the selectbox is changed
			onOpen: null, //Define a callback function when the selectbox is open
			onClose: null //Define a callback function when the selectbox is closed
		};
	}

	$.extend(Selectbox.prototype, {
		/**
		* Is the first field in a jQuery collection open as a selectbox
		* 
		* @param {Object} target
		* @return {Boolean}
		*/
		_isOpenSelectbox: function (target) {
			if (!target) {
				return FALSE;
			}
			var inst = this._getInst(target);
			return inst.isOpen;
		},
		/**
		* Is the first field in a jQuery collection disabled as a selectbox
		* 
		* @param {HTMLElement} target
		* @return {Boolean}
		*/
		_isDisabledSelectbox: function (target) {
			if (!target) {
				return FALSE;
			}
			var inst = this._getInst(target);
			return inst.isDisabled;
		},
		/**
		* Attach the select box to a jQuery selection.
		* 
		* @param {HTMLElement} target
		* @param {Object} settings
		*/
		_attachSelectbox: function (target, settings) {
			if (this._getInst(target)) {
				return FALSE;
			}
			var $target = $(target),
				self = this,
				inst = self._newInst($target),
				sbHolder, sbSelector, sbToggle, sbOptions,
				s = FALSE, optGroup = $target.find("optgroup"), opts = $target.find("option"), olen = opts.length;

			$target.attr("sb", inst.uid);

			$.extend(inst.settings, self._defaults, settings);
			self._state[inst.uid] = FALSE;
			$target.hide();

			function closeOthers() {
				var key, uid = this.attr("id").split("_")[1];
				for (key in self._state) {
					if (key !== uid) {
						if (self._state.hasOwnProperty(key)) {
							if ($(":input[sb='" + key + "']")[0]) {
								self._closeSelectbox($(":input[sb='" + key + "']")[0]);
							}
						}
					}
				}
			}

			sbHolder = $("<div>", {
				"id": "sbHolder_" + inst.uid,
				"class": inst.settings.classHolder
			});

			sbSelector = $("<a>", {
				"id": "sbSelector_" + inst.uid,
				"href": "#",
				"class": inst.settings.classSelector,
				"click": function (e) {
					e.preventDefault();
					closeOthers.apply($(this), []);
					var uid = $(this).attr("id").split("_")[1];
					if (self._state[uid]) {
						self._closeSelectbox(target);
					} else {
						self._openSelectbox(target);
					}
				}
			});

			sbToggle = $("<a>", {
				"id": "sbToggle_" + inst.uid,
				"href": "#",
				"class": inst.settings.classToggle,
				"click": function (e) {
					e.preventDefault();
					closeOthers.apply($(this), []);
					var uid = $(this).attr("id").split("_")[1];
					if (self._state[uid]) {
						self._closeSelectbox(target);
					} else {
						self._openSelectbox(target);
					}
				}
			});
			sbToggle.appendTo(sbHolder);

			sbOptions = $("<ul>", {
				"id": "sbOptions_" + inst.uid,
				"class": inst.settings.classOptions,
				"css": {
					"display": "none"
				}
			});

			$target.children().each(function (i) {
				var that = $(this), li, config = {};
				if (that.is("option")) {
					getOptions(that);
				} else if (that.is("optgroup")) {
					li = $("<li>");
					$("<span>", {
						"text": that.attr("label")
					}).addClass(inst.settings.classGroup).appendTo(li);
					li.appendTo(sbOptions);
					if (that.is(":disabled")) {
						config.disabled = true;
					}
					config.sub = true;
					getOptions(that.find("option"), config);
				}
			});

			function getOptions() {
				var sub = arguments[1] && arguments[1].sub ? true : false,
					disabled = arguments[1] && arguments[1].disabled ? true : false;
				arguments[0].each(function (i) {
					var that = $(this),
						li = $("<li>"),
						child;
					if (that.is(":selected")) {
						sbSelector.text(that.text());
						s = TRUE;
					}
					if (i === olen - 1) {
						li.addClass("last");
					}
					if (!that.is(":disabled") && !disabled) {
						var thatText = that.text();
						// NL *** fix for empty select values and text ***
						if (thatText == "")
							thatText = "&nbsp;";
						// NL *** fix for empty select values and text ***
						child = $("<a>", {
							"href": "#" + that.val(),
							"rel": that.val(),
							// NL *** fix for empty select values and text ***
							//"text": that.text(),
							"html": thatText,
							// NL *** fix for empty select values and text ***
							"click": function (e) {
								e.preventDefault();
								var t = sbToggle,
									uid = t.attr("id").split("_")[1];
								self._changeSelectbox(target, $(this).attr("rel"), $(this).text());
								self._closeSelectbox(target);
							}
						});
						if (sub) {
							child.addClass(inst.settings.classSub);
						}
						child.appendTo(li);
					} else {
						child = $("<span>", {
							"text": that.text()
						}).addClass(inst.settings.classDisabled);
						if (sub) {
							child.addClass(inst.settings.classSub);
						}
						child.appendTo(li);
					}
					li.appendTo(sbOptions);
				});
			}

			if (!s) {
				sbSelector.text(opts.first().text());
			}

			$.data(target, PROP_NAME, inst);

			sbSelector.appendTo(sbHolder);
			sbOptions.appendTo(sbHolder);
			sbHolder.insertAfter($target);
		},
		/**
		* Remove the selectbox functionality completely. This will return the element back to its pre-init state.
		* 
		* @param {HTMLElement} target
		*/
		_detachSelectbox: function (target) {
			var inst = this._getInst(target);
			if (!inst) {
				return FALSE;
			}
			$("#sbHolder_" + inst.uid).remove();
			$.data(target, PROP_NAME, null);
			$(target).show();
		},
		/**
		* Change selected attribute of the selectbox.
		* 
		* @param {HTMLElement} target
		* @param {String} value
		* @param {String} text
		*/
		_changeSelectbox: function (target, value, text) {
			var inst = this._getInst(target),
				onChange = this._get(inst, 'onChange');
			$("#sbSelector_" + inst.uid).text(text);
			var selectedItem;
			$(target).find("option").each(
				function () {
					if ($(this).val() == value) {
						$(this).attr('selected', 'selected');
					}
				}
			);
			if (onChange) {
				onChange.apply((inst.input ? inst.input[0] : null), [value, inst]);
			} else if (inst.input) {
				inst.input.trigger('change');
			}
		},
		/**
		* Enable the selectbox.
		* 
		* @param {HTMLElement} target
		*/
		_enableSelectbox: function (target) {
			var inst = this._getInst(target);
			if (!inst || !inst.isDisabled) {
				return FALSE;
			}
			$("#sbHolder_" + inst.uid).removeClass(inst.settings.classHolderDisabled);
			inst.isDisabled = FALSE;
			$.data(target, PROP_NAME, inst);
		},
		/**
		* Disable the selectbox.
		* 
		* @param {HTMLElement} target
		*/
		_disableSelectbox: function (target) {
			var inst = this._getInst(target);
			if (!inst || inst.isDisabled) {
				return FALSE;
			}
			$("#sbHolder_" + inst.uid).addClass(inst.settings.classHolderDisabled);
			inst.isDisabled = TRUE;
			$.data(target, PROP_NAME, inst);
		},
		/**
		* Get or set any selectbox option. If no value is specified, will act as a getter.
		* 
		* @param {HTMLElement} target
		* @param {String} name
		* @param {Object} value
		*/
		_optionSelectbox: function (target, name, value) {
			var inst = this._getInst(target);
			if (!inst) {
				return FALSE;
			}
			//TODO check name
			inst[name] = value;
			$.data(target, PROP_NAME, inst);
		},
		/**
		* Call up attached selectbox
		* 
		* @param {HTMLElement} target
		*/
		_openSelectbox: function (target) {
			var inst = this._getInst(target);
			//if (!inst || this._state[inst.uid] || inst.isDisabled) {
			if (!inst || inst.isOpen || inst.isDisabled) {
				return;
			}
			var el = $("#sbOptions_" + inst.uid),
				viewportHeight = parseInt($(window).height(), 10),
				offset = $("#sbHolder_" + inst.uid).offset(),
				scrollTop = $(window).scrollTop(),
				height = el.prev().height(),
				diff = viewportHeight - (offset.top - scrollTop) - height / 2,
				onOpen = this._get(inst, 'onOpen');
			el.css({
				"top": height + "px",
				"maxHeight": (diff - height) + "px"
			});
			// NL *** show not fadeIn or slideDown ***
			el.show();
			//inst.settings.effect === "fade" ? el.fadeIn(inst.settings.speed) : el.slideDown(inst.settings.speed);
			// NL *** show not fadeIn or slideDown ***
			$("#sbToggle_" + inst.uid).addClass(inst.settings.classToggleOpen);
			this._state[inst.uid] = TRUE;
			inst.isOpen = TRUE;
			if (onOpen) {
				onOpen.apply((inst.input ? inst.input[0] : null), [inst]);
			}
			$.data(target, PROP_NAME, inst);

			// NL *** fix to auto close drop list ***
			var _selectThis = this;
			(function bindEvents(target) {

				var EVENT_TIMEOUT = 1000;

				var evt = {
					timerId: null,
					bind: function () {
						var _this = this;
						$(document).click(function (e) { return _this.click(e); });
						el.mouseout(function (e) { return _this.mouseout(e); });
						el.mouseover(function (e) { return _this.mouseover(e); });
					},
					unbind: function () {
						var _this = this;
						$(document).unbind("click", _this.click);
						el.unbind("mouseout", _this.mouseout);
						el.unbind("mouseover", _this.mouseover);
					},
					click: function (e) {
						if ($(e.target).parent(".sbHolder").length < 1)
							this.close();
					},
					mouseout: function (e) {
						var _this = this;
						this.timerId = window.setTimeout(function () {
							_this.close();
						}, EVENT_TIMEOUT);
					},
					mouseover: function (e) {
						if (this.timerId) {
							window.clearTimeout(this.timerId);
							this.timerId = null;
						}
					},
					close: function () {
						if (this.timerId) {
							window.clearTimeout(this.timerId);
							this.timerId = null;
						}
						if (inst.isOpen)
							_selectThis._closeSelectbox(target);
						this.unbind();
					}
				};
				evt.bind();
			})(target);
			// NL *** fix to auto close drop list ***
		},
		/**
		* Close opened selectbox
		* 
		* @param {HTMLElement} target
		*/
		_closeSelectbox: function (target) {
			var inst = this._getInst(target);
			//if (!inst || !this._state[inst.uid]) {
			if (!inst || !inst.isOpen) {
				return;
			}
			var onClose = this._get(inst, 'onClose');
			// NL *** hide not fadeOut or slideUp ***
			$("#sbOptions_" + inst.uid).hide();
			//inst.settings.effect === "fade" ? $("#sbOptions_" + inst.uid).fadeOut(inst.settings.speed) : $("#sbOptions_" + inst.uid).slideUp(inst.settings.speed);
			// NL *** hide not fadeOut or slideUp ***
			$("#sbToggle_" + inst.uid).removeClass(inst.settings.classToggleOpen);
			this._state[inst.uid] = FALSE;
			inst.isOpen = FALSE;
			if (onClose) {
				onClose.apply((inst.input ? inst.input[0] : null), [inst]);
			}
			$.data(target, PROP_NAME, inst);
		},
		/**
		* Create a new instance object
		* 
		* @param {HTMLElement} target
		* @return {Object}
		*/
		_newInst: function (target) {
			var id = target[0].id.replace(/([^A-Za-z0-9_-])/g, '\\\\$1');
			return {
				id: id,
				input: target,
				uid: Math.floor(Math.random() * 99999999),
				isOpen: FALSE,
				isDisabled: FALSE,
				settings: {}
			};
		},
		/**
		* Retrieve the instance data for the target control.
		* 
		* @param {HTMLElement} target
		* @return {Object} - the associated instance data
		* @throws error if a jQuery problem getting data
		*/
		_getInst: function (target) {
			try {
				return $.data(target, PROP_NAME);
			}
			catch (err) {
				throw 'Missing instance data for this selectbox';
			}
		},
		/**
		* Get a setting value, defaulting if necessary
		* 
		* @param {Object} inst
		* @param {String} name
		* @return {Mixed}
		*/
		_get: function (inst, name) {
			return inst.settings[name] !== undefined ? inst.settings[name] : this._defaults[name];
		}
	});

	/**
	* Invoke the selectbox functionality.
	* 
	* @param {Object|String} options
	* @return {Object}
	*/
	$.fn.selectbox = function (options) {

		var otherArgs = Array.prototype.slice.call(arguments, 1);
		if (typeof options == 'string' && options == 'isDisabled') {
			return $.selectbox['_' + options + 'Selectbox'].apply($.selectbox, [this[0]].concat(otherArgs));
		}

		if (options == 'option' && arguments.length == 2 && typeof arguments[1] == 'string') {
			return $.selectbox['_' + options + 'Selectbox'].apply($.selectbox, [this[0]].concat(otherArgs));
		}

		return this.each(function () {
			typeof options == 'string' ?
				$.selectbox['_' + options + 'Selectbox'].apply($.selectbox, [this].concat(otherArgs)) :
				$.selectbox._attachSelectbox(this, options);
		});
	};

	$.selectbox = new Selectbox(); // singleton instance
	$.selectbox.version = "0.1.3";
})(jQuery);
