/*!

 * jQuery Selectbox plugin 0.2

 *

 * Copyright 2011-2012, 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: Tue Jul 17 19:58:36 2012 +0300

 */

(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",

			classFocus: "sbFocus",

			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, sel,

					uid = this.attr("id").split("_")[1];

				for (key in self._state) {

					if (key !== uid) {

						if (self._state.hasOwnProperty(key)) {

							sel = $("select[sb='" + key + "']")[0];

							if (sel) {

								self._closeSelectbox(sel);

							}

						}

					}

				}

			}

			

			sbHolder = $("<div>", {

				"id": "sbHolder_" + inst.uid,

				"class": inst.settings.classHolder,

				"tabindex": $target.attr("tabindex")

			});

			

			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) {

						child = $("<a>", {

							"href": "#" + that.val(),

							"rel": that.val()

						}).text(that.text()).bind("click.sb", function (e) {

							if (e && e.preventDefault) {

								e.preventDefault();

							}

							var t = sbToggle,

							 	$this = $(this),

								uid = t.attr("id").split("_")[1];

							self._changeSelectbox(target, $this.attr("rel"), $this.text());

							self._closeSelectbox(target);

						}).bind("mouseover.sb", function () {

							var $this = $(this);

							$this.parent().siblings().find("a").removeClass(inst.settings.classFocus);

							$this.addClass(inst.settings.classFocus);

						}).bind("mouseout.sb", function () {

							$(this).removeClass(inst.settings.classFocus);

						});

						if (sub) {

							child.addClass(inst.settings.classSub);

						}

						if (that.is(":selected")) {

							child.addClass(inst.settings.classFocus);

						}

						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);

			

			sbHolder.data("uid", inst.uid).bind("keydown.sb", function (e) {

				var key = e.charCode ? e.charCode : e.keyCode ? e.keyCode : 0,

					$this = $(this),

					uid = $this.data("uid"),

					inst = $this.siblings("select[sb='"+uid+"']").data(PROP_NAME),

					trgt = $this.siblings(["select[sb='", uid, "']"].join("")).get(0),

					$f = $this.find("ul").find("a." + inst.settings.classFocus);

				switch (key) {

					case 37: //Arrow Left

					case 38: //Arrow Up

						if ($f.length > 0) {

							var $next;

							$("a", $this).removeClass(inst.settings.classFocus);

							$next = $f.parent().prevAll("li:has(a)").eq(0).find("a");

							if ($next.length > 0) {

								//$next.addClass(inst.settings.classFocus).focus();

								$("#sbSelector_" + uid).text($next.text());

							}

						}

						break;

					case 39: //Arrow Right

					case 40: //Arrow Down

						var $next;

						$("a", $this).removeClass(inst.settings.classFocus);

						if ($f.length > 0) {

							$next = $f.parent().nextAll("li:has(a)").eq(0).find("a");

						} else {

							$next = $this.find("ul").find("a").eq(0);

						}

						if ($next.length > 0) {

							//$next.addClass(inst.settings.classFocus).focus();

							$("#sbSelector_" + uid).text($next.text());

						}

						break;				

					case 13: //Enter

						if ($f.length > 0) {

							self._changeSelectbox(trgt, $f.attr("rel"), $f.text());

						}

						self._closeSelectbox(trgt);

						break;

					case 9: //Tab

						if (trgt) {

							var inst = self._getInst(trgt);

							if (inst/* && inst.isOpen*/) {

								if ($f.length > 0) {

									self._changeSelectbox(trgt, $f.attr("rel"), $f.text());

								}

								self._closeSelectbox(trgt);

							}

						}

						var i = parseInt($this.attr("tabindex"), 10);

						if (!e.shiftKey) {

							i++;

						} else {

							i--;

						}

						$("*[tabindex='" + i + "']").focus();

						break;

					case 27: //Escape

						self._closeSelectbox(trgt);

						break;

				}

				e.stopPropagation();

				return false;

			}).delegate("a", "mouseover", function (e) {

				$(this).addClass(inst.settings.classFocus);

			}).delegate("a", "mouseout", function (e) {

				$(this).removeClass(inst.settings.classFocus);	

			});

			

			sbSelector.appendTo(sbHolder);

			sbOptions.appendTo(sbHolder);			

			sbHolder.insertAfter($target);



            $("html").on('mousedown', function(e) {
                e.stopPropagation();
                $("select").selectbox('close');
            });

			$([".", inst.settings.classHolder, ", .", inst.settings.classSelector].join("")).mousedown(function(e) {    

				e.stopPropagation();

			});

		},

		/**

		 * 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 onChange,

				inst = this._getInst(target);

			if (inst) {

				onChange = this._get(inst, 'onChange');

				$("#sbSelector_" + inst.uid).text(text);

			}

			value = value.replace(/\'/g, "\\'");

			$(target).find("option[value='" + value + "']").attr("selected", TRUE);

			if (inst && onChange) {

				onChange.apply((inst.input ? inst.input[0] : null), [value, inst]);

			} else if (inst && inst.input) {

				inst.input.trigger('change');

			}

            $("#search_input").val(value);

		},

		/**

		 * 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"

			});

			inst.settings.effect === "fade" ? el.fadeIn(inst.settings.speed) : el.slideDown(inst.settings.speed);

			$("#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);

		},

		/**

		 * 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');

			inst.settings.effect === "fade" ? $("#sbOptions_" + inst.uid).fadeOut(inst.settings.speed) : $("#sbOptions_" + inst.uid).slideUp(inst.settings.speed);

			$("#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.2";

})(jQuery);



