/*
Namespace: Formulate
	Generated custom form elements.

Plugin Name: jquery.formulate.js

About: Version
	1.0

Description:
	A jQuery plugin for shinier form elements

Requires:
	jQuery 1.3 <http://jquery.com>
	$.event.special.wheel <http://blog.threedubmedia.com/2008/08/eventspecialwheel.html>

Optional:
	Text Resizing with jQuery <http://www.shopdev.co.uk/blog/text-resizing-with-jquery/>
	(a customized version was used for Logitech, with a more descriptive "onTextResize" namespace)

*/

/*global jQuery, window, document */
/*use strict*/
(function($) {
		
	// Local variables
	var $settings,
	
	    // Globally Shared Class Names
	    $prefix = "ui-formulate",
	    $hover = $prefix + "-hover",
	    $active = $prefix + "-active",
	    $redraw = $prefix + "-redraw",
	    $current = $prefix + "-current",
	    $scroller = $prefix + "-scroller",
	    $selected = $prefix + "-selected",
	    $disabled = $prefix + "-disabled",
	    $listwrap = $prefix + "-list-wrap",
	    $scrollable = $prefix + "-scrollable",
	    $noformulate = $prefix + "-ignore",
		$previousEvent, $winHeight, $winTop,
	    $keyDownElement, $keyDownTarget, $winLeft;
	
	// Globally Shared Markup
	var HTML = {
		
		// All form elements share this wrapper
		WRAPPER : '<div class="' + $prefix + '"><div class="' + $prefix + '-wrap"></div></div>',
		
		// Submit value element
		SUBMIT : '<span class="' + $prefix + '-value"></span>',
		
		// Select elements
		LIST : {
			
			// Custom select wrapper
			WRAP : '<div class="' + $listwrap + '"><ul></ul></div>',
			
			// Custom <option> elements
			ITEM : '<li><a href="#"></a></li>',
			
			// Current value element
			VALUE : '<span class="' + $current + '"></span>'
		},
		
		// Select scroller elements
		SCROLL : {
			
			// Up element
			UP : '<span class="' + $scroller + ' ' + $prefix + '-up" rel="up"></span>',
			
			// Down element
			DOWN : '<span class="' + $scroller + ' ' + $prefix + '-down" rel="down"></span>'
		}
	};
	
	var CACHE = [];
	
	// Global vars shared between functions
	var WIN = $(window),
	    DOC = $(document);
	
	// Because I use it about a metric ton in this plugin
	var parseNum = window.parseFloat;
	
	var inlineBlock = (function() {
		if (typeof $.support.inlineBlock !== "undefined") {
			return $.support.inlineBlock;
		}
		
		var dummy = $('<div></div>').css("display", "inline-block");
		
		// Extend $.support to include inlineBlock test
		$.extend($.support, {
			inlineBlock : (dummy.css("display") === "inline-block")
		});
		
		// Remove dummy node
		dummy.remove();
		
		// Return
		return $.support.inlineBlock;
	})();
	
	var textOverflow = (function() {
		if (typeof $.support.textOverflow !== "undefined") {
			return $.support.textOverflow;
		}
		
		var dummy = $('<div></div>').css("text-overflow", "ellipsis");
		
		// Extend $.support to include textOverflow test
		$.extend($.support, {
			textOverflow : (dummy.css("text-overflow") === "ellipsis") && !$.browser.msie
		});
		
		// Remove dummy node
		dummy.remove();
		
		// Return
		return $.support.textOverflow;
	})();
	
	/* Ok, so you're wondering why on earth I'm overriding jQuery's default :checked selector.
	 * jQuery's had a bug for about 2 years now (http://dev.jquery.com/ticket/1192)
	 * where .is(":checked") fails if you've triggered the click event on a checkbox:
	 * 
	 * checkbox.click(function() {
	 * 		alert($(this).is(":checked")); // true if user-initiated, false if triggered
	 * });
	 * 
	 * This replacement method uses a flag added with this plugin to report the reversed (or 'correct')
	 * value when triggering click events on checkboxes / radios
	*/ 
	var overrideChecked = (function() {
		$.extend($.expr[":"], {
			checked : function(elem) {
				var el = $(elem);
				var value = (elem.checked === true);
				
				return !el.data("eventTriggered") ? value : !value;
			}
		});
	})();
	
	var triggeredEventFix = function(e) {
		e.stopPropagation();
		var el = $(this);
		
		// Set a flag to check if event was tiggered or user-initiated
		// This is a workaround for jQuery's bug with triggered click events
		el.data("eventTriggered", !e.originalEvent);
		
		if (el.is(":radio") && (this.checked === true)) {
			el.data("eventTriggered", !el.data("eventTriggered"));
		}
		
		// The flag needs to disappear a split second later (because the event has been fired)
		window.setTimeout(function() {
			el.removeData("eventTriggered");
		}, 10);
	};
	
	var fadeOut = function(el, timer, callback) {
		if ($.browser.msie) {
			callback();
		} else {
			el.fadeOut(timer, callback);
		}
	};
	
	var scrollIntoView = function(el, list) {
		var posTop = el.position().top,
		    ul = list.find("ul"),
		    ulTop = parseNum(ul.css("top")),
		    offsetTop = (posTop + ulTop),
		    trueHeight = list.height() - ($settings.windowPadding * 6);

		if (offsetTop < 0) {
			list.trigger("wheel", "down", offsetTop);
		} else if (trueHeight < offsetTop) {
			list.trigger("wheel", "up", el.height());
		}
	};

	var selectOption = function(parent, selected, callback) {
		window.setTimeout(function() {
			selected.addClass($prefix + "-interstitial");

			window.setTimeout(function() {
				selected.removeClass($prefix + "-interstitial");
				fadeOut(parent, $settings.fadeOutTimer, callback);
			}, 70);
		}, 70);
	};

	var repositionList = function(items, selected, list, height) {
		// Set current index
		var i = items.index(list.find("." + $selected));
		list.css("top", -(i * height));
	};

	var expandList = function(dir, list, ul) {

		var target = list;

		var el = $(this),

		    up = (dir == "up"),
		    padding = $settings.windowPadding,

		    listTop = parseNum(list.css("top")),
		    listHeight = target.height(),

		    ulTop = parseNum(ul.css("top")),
		    ulHeight = ul.height(),

		    absTop = list.parent().offset().top - $winTop - padding,
		    offsetHeight = ($winHeight - (padding * 5)),
		    topDiff = (offsetHeight - listHeight);

		if (offsetHeight > ulHeight) {
			list.data("fitInPage", true);

			offsetHeight = (ulHeight);

			if (!up) {
				absTop = (ulHeight - listHeight) - listTop;
			} else {
				topDiff = (offsetHeight - listHeight);
			}
		}

		// Reset list to original position
		list.css("top", listTop);
		
		list.animate({
			top : -(absTop),
			height : offsetHeight
		}, {
			duration : (absTop * 1.5),
			easing : "easeInQuad",
			step : $settings.onexpand,
			complete : function() {
				list.data("expanded", true);

				if (!list.data("fitInPage")) {
					list.find("." + (up ? ($prefix + "-down") : ($prefix + "-up"))).show();
				}

				el.trigger("mouseover");
			}
		});

		if (up) {
			ul.animate({
				top : ulTop + topDiff
			}, {
				duration : (absTop * 1.5),
				easing : "easeInQuad"
			});
		}

	};

	var generateButtons = function(list, ul) {
		
		var target = list;

		var up = $(HTML.SCROLL.UP),
		    down = $(HTML.SCROLL.DOWN);
		
		target.append(up, down);
		
		var buttons = target.find("." + $scroller),
		    currTop = parseNum(ul.css("top")),
		    globalTimer = (500 * (target.find("li").length / 10));

		var animate = function(dir, to, timer) {
			ul.animate({
				top : to
			}, {
				duration : timer,
				easing : "easeOutQuad",
				complete : function() {
					target.find("." + $prefix + "-" + dir).hide();
				}
			});
		};

		buttons.mouseover(function() {
			var el = $(this),
			    dir = el.attr("rel"),

			    listHeight = target.height(),

			    ulTop = parseNum(ul.css("top")),
			    ulHeight = ul.height();

			if (!list.data("expanded")) {
				expandList.call(this, dir, list, ul);
			} else {

				if (!list.data("fitInPage")) {
					buttons.not(this).show();
				}

				var diff, percent, time, to;

				if (dir == "down") {
					to = ulHeight - listHeight;
					diff = (ulTop + to);

					to = -(to);
				} else {
					to = 0;
					diff = -(ulTop);
				}

				percent = (diff / ulHeight);

				time = globalTimer * percent;
				time = (time < 250) ? 250 : time;

				animate(dir, to, time);
			}
		});
		
		buttons.click(function(e) {
			e.preventDefault();
			e.stopPropagation();
		});

		buttons.mouseout(function() {
			list.stop();
			ul.stop();
		});

		return {
			up : up,
			down : down
		};
	};

	var scrollEvent = function(list, ul) {

		list.bind("wheel", function(e, delta, value) {
			
			if (list.hasClass($scrollable)) {
				e.preventDefault();

				var increment, dir;

				if (typeof delta == "string") {
					increment = (delta == "down") ? 40 : -40;
					dir = delta;
				} else {
					increment = (delta > 0) ? 40 : -40;
					dir = (delta > 0) ? "down" : "up";
				}
				
				var listTop = parseNum(list.css("top")),
				    listHeight = list.height(),

				    ulTop = parseNum(ul.css("top")),
				    ulHeight = ul.height(),

				    maxHeight = -(ulHeight - listHeight);
				
				if (!list.data("expanded")) {
					var inactive = list.find("." + $scroller + ":hidden"),
					    to = (inactive.is("." + $prefix + "-up")) ? "up" : "down";

					if (!inactive[0]) {
						list.data("expanded", true);
					} else if ((dir === to)) {

						var padding = $settings.windowPadding,
						    up = (dir === "up"),

						    top = Math.round(listTop),
						    absTop = Math.round(-(list.parent().offset().top - $winTop - padding));

						var offsetHeight = ($winTop - (padding * 5));

						if (ulHeight < offsetHeight) {

							// Toggle flag to hide buttons when fully expanded
							list.data("fitInPage", true);

							// FIXED: This is a given. Need to figure out absTop.
							offsetHeight = ulHeight;

							// FIXED: Finally solved! Took way too many tries.
							absTop = absTop + ($winTop - ulHeight) - (padding * 5);
						}

						if (value) {
							increment = value;
						}
						
						if (!up) {
							offsetHeight = $winHeight - (padding * 5);
						}

						if (up) {
							increment = ((top + increment) < absTop) ? (absTop - top) : increment;
						} else {
							increment = ((listHeight + increment) > offsetHeight) ? (offsetHeight - listHeight) : increment;
						}
						
						if ((top > absTop) && up) {
							list.css({
								top : top + increment,
								height : listHeight - increment
							});

							if (parseNum(list.css("top")) == absTop) {
								list.data("expanded", true);
							}
						} else if (listHeight < offsetHeight) {
							ul.css("top", parseNum(ul.css("top")) + increment);
							
							list.height(listHeight + increment);

							if (list.height() == offsetHeight) {
								list.data("expanded", true);
							}
						}

					}
				} else {
					var buttons = list.find("." + $scroller);
					value = value || (ulTop + increment);

					if (!list.data("fitInPage")) {
						buttons.show();
					}

					if (value >= 0) {
						value = 0;
						buttons.filter("." + $prefix + "-up").hide();
					} else if (value <= maxHeight) {
						value = maxHeight;
						buttons.filter("." + $prefix + "-down").hide();
					}
					
					ul.css("top", value);
				}

			}
		});
	};

	var resetDimensions = function(list, ul) {
		$.each([list, ul], function() {
			this.css({
				height : "",
				top : "0"
			});
		});
	};

	var makeScrollable = function(items, list, ul, height) {
		var padding = $settings.windowPadding,
		    minHeight = (height * 5);
		

		list.mousedown(function(e) {
			
			// KLUDGE: Clean this up later?
			var target = list,
			    wrap = list;
			
			var up, down;

			if (!wrap.hasClass($redraw)) {
				var wrapper = list.parent();
			    height = wrapper.height();
				repositionList(items, list.find("." + $selected), list, height);
			}
			
			wrap.addClass($redraw);

			var top, left, trueHeight;

			if (list.height() > ($winHeight / 3)) {
				if (!list.hasClass($scrollable)) {

					if (list.offset().top < $winTop) {
						list.addClass($scrollable);

						var offset = parseNum(list.css("top"));

						list.css("top", 0);
						list.css("top", -(list.offset().top - $winTop - padding));

						top = (offset - parseNum(list.css("top")));
						ul.css("top", top);

						trueHeight = top + ul.height();

						if (trueHeight < minHeight) {
							var diff = (minHeight - trueHeight);

							ul.css("top", top + diff);
							list.css("top", parseNum(list.css("top")) - diff);

							trueHeight = minHeight;
						}
						list.height(trueHeight);

						list.data("showUpArrow", true);
					}

					var offsetTop = list.offset().top,
					    topDiff = (offsetTop - $winTop),
					    offsetHeight = list.height() + topDiff;

					if (offsetHeight > $winHeight) {
						list.addClass($scrollable);

						trueHeight = $winHeight - topDiff - (padding * 4);
						trueHeight = (trueHeight < minHeight) ? minHeight : trueHeight;
						list.height(trueHeight);

						list.data("showDownArrow", true);
					}
				}

				if (list.hasClass($scrollable) && !list.data("animating")) {

					up = list.find("." + $prefix + "-up");
					down = list.find("." + $prefix + "-down");

					if (!up[0] && !down[0]) {
						var buttons = generateButtons(list, ul);
						scrollEvent(list, ul);

						up = buttons.up;
						down = buttons.down;
					}
					
					top = parseNum(ul.css("top"));

					if (!list.data("showUpArrow") && !top) {
						up.hide();
					} else {
						up.show();
					}

					if (!list.data("showDownArrow") && top) {
						down.hide();
					} else {
						down.show();
					}

				}
			}

			top = list.offset().top;
			
			left = list.offset().left + list.get(0).offsetWidth;
			$winLeft = WIN.width();

			var bottom = top + list.height(),
			    scrollBottom = $winTop + $winHeight,
			    trueTop;
				
			if (top < $winTop) {
				trueTop = ($winTop - top) + parseNum(list.css("top"));

				list.animate({
					"top" : trueTop + padding // Add some padding
				}, 350);
			} else if (bottom > scrollBottom) {
				trueTop = (scrollBottom - bottom) + parseNum(list.css("top"));
				
				list.animate({
					"top" : trueTop - (padding * 4) // Add some padding
				}, 350);
			}
			
			if (left > $winLeft) {
				list.animate({
					"left" : -((left - $winLeft) + padding)
				}, 350);
			}
		});

		if (!DOC.data("mouseWheel")) {
			DOC.bind("wheel", function(e) {
				var isActive = $("." + $prefix + "-select." + $active);

				if (isActive[0]) {
					e.preventDefault();
				}
			});

			DOC.data("mouseWheel", true);
		}
	};
	
	var hideActiveSelects = function(parent) {
		var active = $("." + $prefix + "-select" + "." + $prefix + "-focus");
		if (active[0] && (!parent || (parent && !parent.hasClass($active)))) {
			active.find("select").blur();
		}
	};
	
	var checkIfDisabled = function(parent) {
		return parent.hasClass($disabled);
	};
	
	var truncateIfNecessary = function(select, selected, force) {
		
		var text = selected.text();
		var clone, remove = 12;
		
		if (!$.support.textOverflow && (select.css("display") == "block")) {
			var parent = select.parent();
			
			var sWidth = function() {
				return select.get(0).offsetWidth;
			}();
			
			clone = selected.clone(true).css({
				"width" : "",
				"min-width" : "0",
				"display" : "inline"
			}).insertAfter(select);
			
			var padding = parseFloat(clone.css("padding-left")) + parseFloat(clone.css("padding-right"));
			
			var cWidth = function() {
				return clone.width() + padding;
			}();
			
			if ((cWidth > (sWidth - remove)) || force) {
				
				if (!force) {
					var array = text.split(""),
					    i = 0;
			
					clone.text("");
			
					while (clone.width() < sWidth) {
						clone.text(clone.text() + array[i]);
						i++;
					}
			
					array = clone.text().split("");
					array.splice(array.length - remove, array.length, "\u2026");
			
					selected.text(array.join(""));
				}
				
				var value = "";
				var ul = parent.find("ul");
				
				if ($.browser.msie) {
					value = "100%";
				}
				
				var list = ul.closest("." + $listwrap);
				
				list.width(value);
			}
			
			clone.remove();
		}
		
	};
	
	var setIEWidth = function(parent, current, list) {
		var parentWidth = parent.get(0).offsetWidth,
		    currentWidth = current.get(0).offsetWidth;
		
		if (currentWidth > parentWidth) {
			parent.width(currentWidth);
			
			var ul = parent.find("ul");
			
			list = ul.closest("." + $listwrap);
			
			var ulWidth = ul.width();
			currentWidth = (ulWidth > currentWidth) ? ulWidth : currentWidth;
			
			list.width(currentWidth);
		}
	};
	
	// Element namespace
	var elements = {

		// Wrap all targetted elements
		wrap : function(el) {
			
			// Manipulation before DON
			var clone = el.clone(true);
			
			// Set element tagName / typeName as class
			var tag = clone[0].nodeName.toLowerCase();
			var name = $prefix + "-" + ((tag === "input") ? clone.attr("type") : tag);
			
			clone.wrap(HTML.WRAPPER).parent().parent().addClass(name).addClass(clone.attr("class"));
			
			return clone;
		},
		
		// Check if disabled
		disabled : function(el) {
			var wrapper = el.parent(),
			    parent = wrapper.parent();
			
			if (el.attr("disabled")) {
				parent.addClass($disabled);
			}
		},

		style : function(el, o) {
			// Local vars
			var height = el[0].offsetHeight,
			    wrapper = el.parent(),
			    parent = wrapper.parent();
			
			height = ($settings.minHeight > height) ? $settings.minHeight : height;

			if (!parent.data("styled")) {
				var props = [
					"margin-left",
					"margin-top",
					"margin-right",
					"margin-bottom",
					"float"
				];

				// Give default margins to wrapper element
				$.each(props, function(i, prop) {
					parent.css(prop, o.css(prop));
					el.css(prop, (prop == "float") ? "none" : 0);
				});
				
				if (el.css("display") == "block") {
					parent.css("display", "block");
					parent.css("width", el.data("originalWidth") || el.width());
				}

				// Now hide the original element
				el.addClass($prefix + "-hide");

				parent.data("styled", true);
			}

			// Set a standard line-height
			wrapper.css("line-height", height + "px");
		},

		accessibility : function(el, o) {
			var keyDownEvent = function(e) {
				if (e.shiftKey || e.ctrlKey || e.altKey || e.metaKey) {
					return true;
				}

				e.preventDefault();
				
				// Key is down
				if (WIN.data("keytime")) {
					window.clearTimeout(WIN.data("keytime"));
					WIN.removeData("keytime");
				}
				
				WIN.data("keydown", true);

				$previousEvent = e.type;

				// Local vars
				var el = $keyDownElement,
				    parent = el.parent().parent(),
				    list = parent.find("." + $listwrap),
				    hover = list.find("." + $hover),
				    name = $keyDownTarget, newEl;
				
				if (!hover.get(0)) {
					hover = list.find("." + $selected);
				}

				switch (e.which) {
					case 9 : // Tab
						parent.removeClass($active);
						el.blur();
					break;

					case 13 : // Enter
						hover.trigger("mousedown");
						list.trigger("mousedown");
					break;

					case 27 : // Esc
						if (name == "select") {
							DOC.trigger("mousedown");
						}
					break;

					case 32 : // Space Bar
						if (name == "select") {
							if (parent.hasClass($active)) {
								hover.trigger("mousedown");
							} else {
								parent.addClass($active);
							}
							
							list.trigger("mousedown");
						}
					break;

					case 38 : // Up
					case 40 : // Down
						if (name == "select") {
							if (parent.hasClass($active)) {
								newEl = (e.which == 38) ? hover.prev() : hover.next();
								if (newEl[0]) {
									hover.removeClass($hover);
									newEl.addClass($hover);

									if (list.hasClass($scrollable)) {
										scrollIntoView(newEl, list);
									}
								}
							} else {
								hover.trigger("mousedown");
								list.trigger("mousedown");
							}
						}
					break;
					
					default :
					break;
				}
			};

			// For Firefox, which doesn't fire a keydown event if key is held down
			var keyPressEvent = function(e) {

				if (e.shiftKey || e.ctrlKey || e.altKey || e.metaKey) {
					return true;
				}

				e.preventDefault();

				if ($previousEvent == e.type) {
					e.which = e.which || e.keyCode || e.charCode;
					keyDownEvent.call(this, e);
				}

				$previousEvent = e.type;
			};
			
			var keyUpEvent = function(e) {
				// Key is no longer down
				WIN.data("keytime", window.setTimeout(function() {
					WIN.removeData("keydown");
				}, 250));
			};

			var mouseDownEvent = function(e) {

				// Local vars
				var target = $(e.target),
				    parent = target.parents().andSelf().filter("." + $prefix);

				if (!parent[0]) {
					$keyDownElement.blur();
					$(this).unbind("mousedown", mouseDownEvent);
				}
			};

			// Stop propagation of label wrappers
			var label = o.closest("label");
			
			if (label.get(0)) {
				var evt = function(e) {
					var el = $(this),
					    wrap = "." + $prefix;

					e.preventDefault();
					
					if (!$(e.target).closest(wrap)[0]) {
						el.find(wrap).trigger(e.type);
					}
				};
				
				label.click(evt);
				label.mousedown(evt);
				label.mouseout(evt);
			}

			el.focus(function(e) {
				e.stopPropagation();

				// Local vars
				var el = $(this),
					name = (el.is("input")) ? el.attr("type") : el[0].tagName.toLowerCase();

				switch (name) {
					case "select" :
					case "radio" :
					case "checkbox" :
						$keyDownElement = el;
						$keyDownTarget = name;
						
						if (!DOC.data("eventsBound")) {
							DOC.keydown(keyDownEvent).keyup(keyUpEvent).keypress(keyPressEvent).mousedown(mouseDownEvent);
						}
						
						DOC.data("eventsBound", true);
					break;
					
					default :
					break;
				}
				
				if (name !== "select") {
					// Check for active drop downs
					hideActiveSelects();
				}

				el.parent().parent().addClass($prefix + "-focus");
			});

			el.blur(function(e) {
				e.stopPropagation();

				// Local vars
				var el = $(this),
				    wrapper = el.parent(),
				    parent = wrapper.parent(),
					name = (el.is("input")) ? el.attr("type") : el[0].tagName.toLowerCase();

				switch (name) {
					case "select" :
						DOC.unbind("keydown", keyDownEvent).unbind("keyup", keyUpEvent).unbind("keypress", keyPressEvent);
						DOC.removeData("eventsBound");
						
						var list = parent.find("." + $listwrap).closest("." + $prefix + "-wrap");
						
						var datas = ["expanded", "showUpArrow", "showDownArrow", "fitInPage"];
						$.each(datas, function() {
							list.removeData(this);
						});
						
						list.find("." + $hover).removeClass($hover);
						list.find("." + $scroller).css("display", "");
						
						list.removeClass($scrollable).removeClass($redraw);
						list.show();
					break;

					case "radio" :
					case "checkbox" :
						DOC.unbind("keydown", keyDownEvent).unbind("keyup", keyUpEvent).unbind("keypress", keyPressEvent);
					break;
					
					default :
					break;
				}
				
				parent.removeClass($active).removeClass($prefix + "-focus");

			});
		},
		
		// Checkboxes and Radios
		checks : function(el) {
			// Local vars
			var wrapper = el.parent(),
			    parent = wrapper.parent();
			
			var type = el.attr("type");

			if (el.is(":checked")) {
				parent.addClass($prefix + "-" + type + "-checked");
			}

			parent.mousedown(function(e) {
				e.preventDefault();
				e.stopPropagation();
				
				if (checkIfDisabled(parent)) {
					return false;
				}
				
				if (el.is(":checked")) {
					parent.addClass($prefix + "-" + type + "-active-checked");
				} else {
					parent.addClass($prefix + "-" + type + "-active");
				}
				
				// Check for active drop downs
				hideActiveSelects();
			});

			parent.mouseout(function(e) {
				e.preventDefault();
				e.stopPropagation();
				
				if (checkIfDisabled(parent)) {
					return false;
				}
				
				parent.removeClass($prefix + "-" + type + "-active").removeClass($prefix + "-" + type + "-active-checked");
			});

			parent.click(function(e) {
				e.preventDefault();
				e.stopPropagation();
				
				if (checkIfDisabled(parent)) {
					return false;
				}
				
				el.click();
			});
			
			// Apply trigger event fix
			el.click(triggeredEventFix);
			
			el.click(function(e) {
				e.stopPropagation();
				
				if (checkIfDisabled(parent)) {
					return false;
				}
				
				var checked = el.is(":checked");

				if ((type == "radio") && el.attr("name")) {
					var els = el.closest("form").find("input[type=" + type + "][name=" + el.attr("name") + "]").not(el);
					els.closest("." + $prefix).removeClass($prefix + "-" + type + "-checked");
				}
				
				if (checked) {
					parent.addClass($prefix + "-" + type + "-checked");
				} else {
					parent.removeClass($prefix + "-" + type + "-checked");
				}
				
				parent.removeClass($prefix + "-" + type + "-active").removeClass($prefix + "-" + type + "-active-checked");
			});
		},

		// Submit and Reset Buttons
		submits : function(el) {
			// Local vars
			var wrapper = el.parent(),
			    height = wrapper.height(),
			    parent = wrapper.parent();
			
			wrapper.prepend($(HTML.SUBMIT).text(el.val()));
			
			parent.mousedown(function(e) {
				e.preventDefault();
				e.stopPropagation();
				
				if (checkIfDisabled(parent)) {
					return false;
				}
				
				// Check for active drop downs
				hideActiveSelects();
			});
			
			el.click(function(e) {
				e.stopPropagation();
				
				if (el.attr("type") == "reset") {

					var form = el.closest("form"),
					    inputs = form[0].getElementsByTagName("input");

					$.each(inputs, function() {
						if (this.type == "text" || this.type == "password") {
							this.value = "";
						}

						if (this.type == "checkbox" || this.type == "radio") {
							var parent = $(this).closest("." + $prefix + "-checkbox-checked");
							parent.trigger("click");
						}
					});
				}
				
			});
			
			parent.click(function(e) {
				e.preventDefault();
				e.stopPropagation();
				
				if (checkIfDisabled(parent)) {
					return false;
				}
				
				// Trigger native click
				el.click();
			});
		},
		
		// Drop Downs
		selects : function(el, o) {

			// Local vars
			var wrapper = el.parent(),
			    height = wrapper.height(),
			    parent = wrapper.parent(),
			    list = $(HTML.LIST.WRAP).prependTo(wrapper),
			    ul = list.find("ul"),
			    options = el.children();
			
			// Temporarily make the list active to check for li widths
			parent.addClass($active);
			
			var minWidth = o[0].offsetWidth,
			    prop = "min-width";
			
			if ($.browser.safari) {
				minWidth += 2;
			}
			
			parent.css(prop, minWidth);
			list.css(prop, minWidth);
			
			parent.removeClass($active);
			
			var selected = el.find(":selected");
			
			var current = $(HTML.LIST.VALUE);
			current.attr("rel", selected.val()).text(selected.text()).appendTo("body");
			
			var padding = parseNum(current.css("padding-right"));
			
			var newProp = $.support.textOverflow ? "width" : prop;
			current.css(newProp, (minWidth - padding));
			current.insertAfter(list);
			
			// Don't bind/render/generate until user interacts with custom drop down (cuts down initial load time)
			parent.one("mousedown", function(e) {
				e.preventDefault();
				
				options.each(function() {
					var opt = $(this),
					    li = $(HTML.LIST.ITEM).attr("rel", opt.val()).appendTo(ul);
					
					// Append text to link
					li.find("a").attr("href", "#" + opt.val()).text(opt.text());
				
					if (opt.is(":selected")) {
						li.addClass($selected);
					}
				});
				
				var items = list.find("li"),
				    links = items.find("a");
				
				// Set current index
				height = wrapper.height();
				
				makeScrollable(items, list, ul, height);
				
				// Focus element on click
				parent.click(function(e) {
					e.preventDefault();
					
					if (checkIfDisabled(parent)) {
						return false;
					}

					el.focus();
				});
				
				items.mouseover(function(e) {
					
					if (checkIfDisabled(parent)) {
						return false;
					}

					var keyIsDown = WIN.data("keydown");
					
					if (!keyIsDown) {
						items.removeClass($prefix + "-hover");
						$(this).addClass($prefix + "-hover");
					}
				});
				
				items.mouseout(function(e) {
					
					if (checkIfDisabled(parent)) {
						return false;
					}

					var keyIsDown = WIN.data("keydown");
					
					if (!keyIsDown) {
						$(this).removeClass($prefix + "-hover");
					}
				});
				
				items.mousedown(function(e) {
					e.preventDefault();
					
					if (checkIfDisabled(parent)) {
						return false;
					}
					
					// Store properties that will be static through this event
					$winHeight = WIN.height();
					$winTop = WIN.scrollTop();
					
					// Check for active drop downs
					hideActiveSelects(parent);
				
					// Store variable
					var selected = $(this),
					    height = wrapper.height();
				
					if ((parent.hasClass($prefix + "-focus") && parent.hasClass($active)) || (e.originalEvent && e.originalEvent.force)) {
						
						// Set dummy element
						var current = wrapper.find("." + $current),
						    title = current.find("span");
						
						current.attr("rel", selected.attr("rel")).text(selected.text()).prepend(title);
						truncateIfNecessary(parent.find("select"), current);
						
						// Hide up/down arrows
						list.data("animating", true);
				
						selectOption(list, selected, function() {
				
							items.removeClass($selected).removeClass($hover);
							selected.addClass($selected);
				
							// Set current index
							list.removeClass($redraw);
							
							// Remove animating 
							list.removeData("animating");
							
							var datas = ["expanded", "showUpArrow", "showDownArrow", "fitInPage"];
							$.each(datas, function() {
								list.removeData(this);
							});
							
							list.find("." + $scroller).css("display", "");
							list.removeClass($scrollable);
							
							resetDimensions(list, ul);
						
							// Make sure actual change has occurred (Gobama)
							var currVal = el.val(),
							    selectedVal = selected.attr("rel");
				
							if (currVal !== selectedVal) {
								
								// Set select value
								el.val(selectedVal);
				
								// Trigger change event
								el.trigger("change", true);
				
							}
				
							list.css("display", "");
							
							parent.removeClass($active);
						});
					} else {
						parent.addClass($active);
					}
				
					// Trigger focus element
					el.focus();
				
				});
				
				// Treat right-clicking on our drop-down as regular click
				items.bind("contextmenu", function(e) {
					e.preventDefault();
					e.stopPropagation();
				});
				
				// Drop down links do nothing
				links.click(function(e) {
					e.preventDefault();
				});
				
				// Trigger mousedown on render
				truncateIfNecessary(el, current, true);
				ul.find("." + $selected).trigger("mousedown");
			});
			
			current.bind("mousedown", function() {
				
				if (checkIfDisabled(parent)) {
					return false;
				}
				
				// Check for active drop downs
				var active = $("." + $prefix + "-select." + $active);
				if (active[0] && !parent.hasClass($active)) {
					active.find("select").blur();
				}
				
				ul.find("." + $selected).trigger("mousedown");
			});
			
			// Make sure selected value changes on change
			el.change(function(e, breaker) {
				e.stopPropagation();
				
				if (breaker) {
					e.preventDefault();
					return false;
				}
				
				var el = $(this),
				    wrapper = el.parent(),
				    parent = wrapper.parent(),
				    current = parent.find("." + $current);
				
				var val = el.val(),
				    selected = parent.find("li[rel=" + val + "]");
				
				var evt = $.Event({
					type : "mousedown",
					force : true
				});
				
				if (!selected.get(0) && !parent.find("li").get(0)) {
					parent.find(".ui-formulate-current").text(el.children().filter(function() {
						return $(this).attr("value") == val;
					}).text());
				} else {
					selected.trigger(evt);
				}
			});
		},

		// Bells & whistles
		inject : function(el) {
			if ($settings.inject) {
				// Local vars
				var wrapper = el.parent(),
				    parent = wrapper.parent();
				
				$settings.inject.call(el, parent, wrapper);
			}
		},
		
		replace : function(clone, el) {
			var parent = clone.parent().parent();
			
			/* AND NOW IT'S TIME FOR ANOTHER LONG RANT BY RICHARD HERRERA
			 * 
			 * Safari has borked DOMContentLoaded support.
			 * 
			 * At least, that's what I think. If I straight-replace this element with the clone,
			 * Safari will mangle the text if you return to this page via back-button. I have no
			 * clue as to why. I tested as far down as removing DOMContentLoaded support for
			 * Webkit in jQuery, and that seemed to solve the problem. But of course, I can't do
			 * that in a real-world scenario.
			 * 
			 * So for Safari, I will keep a cache of my replacements, and attach an unload event.
			 * This event will replace my edits with the original node.
			*/ 
			if ($.browser.safari) {
				CACHE.push({
					node : el,
					clone : parent
				});
			}
			
			// Replace node.
			el.replaceWith(parent);
			
			// truncate if necessary
			if (clone.is("select")) {
				
				var loaded = $(window).data("loaded");
				var current = parent.find("." + $current);
				
				var onReady = function() {
					truncateIfNecessary(clone, current);
				};
				
				if (loaded) {
					onReady();
				} else {
					$(window).load(function() {
						onReady();
						
						if ($.browser.msie) {
							setIEWidth(parent, current);
						}
						
						$(window).data("loaded", true);
					});
				}
				
			}
		},
		
		unload : function() {
			$.each(CACHE, function(i, obj) {
				obj.clone.replaceWith(obj.node);
			});
		}
	};
	
	$.fn.enable = function(disable) {
		return this.each(function() {
			var el = $(this),
			    parent = el.closest("." + $prefix + "-select");
			
			if (!disable) {
				el.removeAttr("disabled");
				parent.removeClass($disabled);
			} else {
				el.attr("disabled", "disabled");
				parent.addClass($disabled);
			}
		});
	};
	
	$.fn.disable = function() {
		return $(this).enable(true);
	};
	
	$.fn.formulate = function(options) {
		
		// Sorry IE6, you're too buggy.
		if (!$.support.inlineBlock || ($.browser.msie && $.browser.version <= 6)) {
			// If no support, bind trigger fix only
			this.filter(function() {
				return $(this).is(":checkbox") || $(this).is(":radio");
			}).click(triggeredEventFix);
			
			return this;
		}
		
		// build main options before element iteration
		$settings = $.extend({}, $.fn.formulate.defaults, options);
		
		// Strip out already-converted elements
		return this.filter(function() {
			var el = $(this),
			    type = el.attr("type"),
			    supported = !(/button|file|hidden|image/).test(type);
			
			return !el.hasClass($noformulate) && supported && el.is(":visible");
		}).each(function() {
			
			var el = $(this);
			
			// Style elements ('cept checkboxes & radios)
			var type = el.attr("type");
			
			// Standard wrapper
			var clone = elements.wrap(el);
			
			if (!el.hasClass("ui-formulate-button")) {
				
				// Store original width
				el.data("originalWidth", el.width());
				clone.data("originalWidth", el.data("originalWidth"));
			
				// Check if disabled
				elements.disabled(clone);
			
				if ((type !== "checkbox") && (type !== "radio")) {
					elements.style(clone, el);
			
					if (type == "submit" || type == "reset") {
						elements.submits(clone);
					} else if (clone[0].nodeName.toLowerCase() == "select") {
						elements.selects(clone, el);
					}
			
				// Code for radios & checkboxes
				} else {
					clone.addClass($prefix + "-hide");
					elements.checks(clone);
				}
			
				// Accessibility events
				elements.accessibility(clone, el);
			
			} else {
				clone.removeClass($prefix + "-button");
				clone.addClass($prefix + "-value");
			}
			
			// Inject custom code
			if ($settings.inject) {
				elements.inject(clone);
			}
			
			elements.replace(clone, el);
			
			// Fucking Safari man
			if ($.browser.safari) {
				$(window).bind("unload", elements.unload);
			}
		});
	};
	
	$.fn.formulate.defaults = {
		windowPadding : 8,
		minHeight : 22,
		fadeOutTimer : 250,
		onexpand : null,
		inject : function(wrapper, parent) {
			wrapper.append('<span class="ui-formulate-cap"></span>');
		}
	};
	
})(jQuery);


// On mousewheel plugin
(function($) { // secure $ jQuery alias

	/*******************************************************************************************/	
	// jquery.event.wheel.js - rev 1 
	// Copyright (c) 2008, Three Dub Media (http://threedubmedia.com)
	// Liscensed under the MIT License (MIT-LICENSE.txt)
	// http://www.opensource.org/licenses/mit-license.php
	// Created: 2008-07-01 | Updated: 2008-07-14
	/*******************************************************************************************/

	// jquery method
	$.fn.wheel = function(fn) {
		return this[ fn ? "bind" : "trigger" ]("wheel", fn);
	};

	// special event config
	$.event.special.wheel = {
		setup : function() {
			$.event.add(this, wheelEvents, wheelHandler, {});
		},
	
		teardown : function() {
			$.event.remove(this, wheelEvents, wheelHandler);
		}
	};

	// events to bind ( browser sniffed... )
	var wheelEvents = !$.browser.mozilla ? "mousewheel" : // IE, opera, safari
		"DOMMouseScroll" + ($.browser.version < "1.9" ? " mousemove" : "" ); // firefox

	// shared event handler
	var wheelHandler = function(event) {
		switch (event.type) {
			case "mousemove" : // FF2 has incorrect event positions
				return $.extend(event.data, { // store the correct properties
					clientX : event.clientX,
					clientY : event.clientY,
					pageX : event.pageX,
					pageY : event.pageY
				});
			
			case "DOMMouseScroll" : // firefox
				$.extend(event, event.data); // fix event properties in FF2
				event.delta = -event.detail / 3; // normalize delta
			break;
		
			case "mousewheel" : // IE, opera, safari
				event.delta = event.wheelDelta / 120; // normalize delta
				if ($.browser.opera) {
					event.delta *= -1; // normalize delta
				}
			break;
		}
		event.type = "wheel"; // hijack the event
		return $.event.handle.call(this, event, event.delta);
	};
	
})(jQuery);
