/*
	
	jQuery selectBox (version 0.1.8)
	
		A cosmetic, styleable replacement for SELECT elements
		
		Homepage:   http://abeautifulsite.net/blog/2011/01/jquery-selectbox-plugin/
		Demo page:  http://labs.abeautifulsite.net/projects/js/jquery/selectBox/
		
		Copyright 2011 A Beautiful Site, LLC.
	
	License:
		
		Licensed under both the MIT license and the GNU GPL (same as jQuery)
	
	
	Usage:
		
		Link to the JS file:
			
			<script src="jquery.selectbox.min.js" type="text/javascript"></script>
		
		Add the CSS file (or append contents of your own):
		
			<link href="jquery.selectbox.css" rel="stylesheet" type="text/css" />
		
		To create:
			
			jQuery("SELECT").selectBox()
			
		To create with options:
			
			jQuery("SELECT").selectBox({
				autoWidth: [true|false]
				transition: ['none'|'fade'|'slide']
			});
		
		To destroy:
		
			jQuery("SELECT").selectBox('destroy');
			
		To update the options on the fly:
			
			jQuery("SELECT").selectBox('setOptions', {
				
				// Options are created like this
				'value' : 'displayText',
				'value' : 'displayText',
				
				// Optgroups are created like this
				'optgroupLabel' : {
					'value' : 'displayText',
					'value' : 'displayText'
				}
				
			});
		
		To change the value:
		
			jQuery("SELECT").selectBox('value', 'optionValue');
		
		Note: you can use any valid selector in lieu of "SELECT".
	
	
	Events:
		
		The focus, blur, and change events fire on the *orignal* SELECT element.
	
	
	Freebies:
		
		- Includes keyboard support (tab in/out, arrows, page up/down, home/end, enter/esc)
		
		- Supports jQuery UI .jqs-ui-corner-x classes (http://jqueryui.com/docs/Theming/API#Corner_Radius_helpers)
		
		- Uses CSS3 techniques (fully customizable via CSS)
	
	
	Change log:
	
		v0.1 (2011-01-24)   - Initial release
		v0.1.1 (2011-02-09)   - Added setOptions method for changing options on the fly
						    - UI control now inherits all classes of the original control
		v0.1.2 (2011-02-23) - UI control now inherits the style and title attribute of the original control
		v0.1.3 (2011-02-24) - Added autoWidth option to simulate default browser behavior; fixed bug
		                      that caused the UI control to display as inline instead of inline-block 
		                      after destroy/create; fixed version numbers (old 0.2 = 0.1.1, old 0.3 = 0.1.2)
		v0.1.4 (2011-02-25) - Added 'value' method; added return jQuery(this) to setOptions method
		v0.1.5 (2011-03-11) - Fixed bug where special HTML characters did not get escaped properly in the UI control
		v0.1.6 (2011-03-21) - Fixed bug where initial settings were forgotten when setOptions was called
		v0.1.7 (2011-03-25) - Added transition option (possible values: 'fade', 'slide', or 'none'; default = 'none')
		v0.1.8 (2011-03-28) - Removed 'ui-' prefixes to prevent confusion with jQuery UI libraries
		
	Known issues:
		
		- The change event fires every time an option is changed using the keyboard. This differs
		  from the way change events occur on normal select elements (on blur).
		
		- Disabled controls will technically accept focus (but no event will be trigger) when tabbed 
		  over. This differs from the default browser behavior where the control would normally be 
		  skipped.
		
		- If using the keyboard while the mouse is hovering over the dropdown, the hover events
		  sometimes conflict making it seem like the keyboard selection is buggy (move the mouse 
		  out and the behavior goes away)
		  
		- The plugin cannot poll for changes to the original control (i.e. disabling it dynamically). 
		  Since the dropdown gets re-generated each time it is shown, this isn't an issue with 
		  optgroups and options. Calling scripts should be aware of this.
		  
		- Safari doesn't currently allow focus via tabbing (Chrome does; possible WebKit bug?)
		  
		- Does not support multiple="multiple"
		 
		- Not tested in IE6
	
		
	Wish list:
		
		- Enforce that dropdowns always appear in the viewport
		
		- Predictive selection (auto-selecting of elements while typing)
		  
		  Issue: keypress doesn't fire on non-input elements (only in Firefox, 
		  but this is against the standard), so we have to use the keydown event.
		  There isn't a reliable way to map extended (i.e. non-ASCII) characters 
		  without using the keypress event.
		  
		  Aside from that, it should be easy enough to set a timer that waits 
		  about two seconds after each keystroke before clearing the filter. 
		  Then we just select the first option that matches the filter. This
		  feature should be available with or without the dropdown showing.
	
*/
if(jQuery) (function($) {
	jQuery.extend(jQuery.fn, {
		
		selectBox: function(o, data) {
			
			
			var _show = function(event) {
				
				var select = event.data.select;
				var control = event.data.control;
				
				// Don't show disabled controls
				if( jQuery(control).hasClass('selectBox-disabled') ) return false;
				
				// Hide if the control is selected when the dropdown is already open
				if( jQuery(control).hasClass('selectBox-focus') && jQuery("#selectBox-dropdown").size() === 1 ) {
					_hide(event, true);
					return false;
				}
				
				// Remove focus and dropdown from any/all other selectBoxes
				jQuery('.selectBox').not(control).trigger('blur');
				
				_focus(event);
				
				event.stopPropagation();
				
				// Generate the dropdown
				var dropdown = jQuery("#selectBox-dropdown");
				if(dropdown.size() == 0){
					dropdown = jQuery('<div id="selectBox-dropdown" class="jqs-ui-corner-bottom" style="display: none;" />').appendTo('body');
					dropdown.addClass('selectBox-hasOptgroups');
				}else{
					dropdown.hide().empty();
				}
				var options = jQuery('<ul />');
				
				if( jQuery(select).children('optgroup').size() === 0 ) {
				
					jQuery(select).children('option').each( function() {
						var text = jQuery(this).text() !== '' ? jQuery(this).text() : '\u00A0';
						var extraClasses = '';
						if( jQuery(this).attr('disabled') ) extraClasses += ' selectBox-disabled';
						options.append('<li class="selectBox-option' + extraClasses + '">' + _htmlspecialchars(text) + '</li>');
					});
				
				} else {
					
					
					jQuery(select).children('optgroup').each( function() {
						options.append('<li class="selectBox-optgroup">' + _htmlspecialchars(jQuery(this).attr('label')) + '</li>');
						jQuery(this).children('option').each( function() {
							var text = jQuery(this).text() !== '' ? jQuery(this).text() : '\u00A0';
							var extraClasses = '';
							if( jQuery(this).attr('disabled') ) extraClasses += ' selectBox-disabled';
							options.append('<li class="selectBox-option' + extraClasses + '">' + _htmlspecialchars(text) + '</li>');
						});
					});
					
				}
				
				// Add the options
				dropdown.append(options);
				// Select the appropriate option
				var selectedIndex = jQuery(select)[0].selectedIndex;
				dropdown.find('LI.selectBox-option').eq(selectedIndex).addClass('selectBox-initial selectBox-current');
				
				// Add option events
				dropdown.find('LI.selectBox-option').hover( function() {
					dropdown.find('.selectBox-current').removeClass('selectBox-current');
					jQuery(this).addClass('selectBox-current');
				}, function() {
					jQuery(this).removeClass('selectBox-current');
				}).click( { select: select, control: control }, function(event) {
					_select(event);
				}).mouseup( { select: select, control: control }, function(event) {
					jQuery(event.target).trigger('click');
				});				
				
				// Position and display
				var cPos = jQuery(control).offset();
				var cHeight = jQuery(control).outerHeight();
				var cWidth = jQuery(control).outerWidth();
				
				var borderAdjustment = parseInt(dropdown.css('borderLeftWidth')) + parseInt(dropdown.css('borderRightWidth'));
				newHeight = ( 27 * options.find('li').size());
				if(newHeight > 200){
					newHeight = 200;
				}
				
				var oStyle = {
					top: (cPos.top + cHeight)+'px',
					left: (cPos.left)+'px',
					width: (cWidth - borderAdjustment)+'px',
					height: newHeight+'px'
				}
				dropdown.css(oStyle).show();
				
				jQuery(control).removeClass('jqs-ui-corner-all').addClass('jqs-ui-corner-top');
				
				_disableSelection(dropdown);
				_dropdownScrollFix(true);
				
			};
			
			
			var _hide = function(event, preventBlur) {
				
				var select = event.data.select;
				var control = event.data.control;
				
				var dropdown = jQuery("#selectBox-dropdown");
				
				dropdown.hide();
				
				jQuery(control).removeClass('jqs-ui-corner-top').addClass('jqs-ui-corner-all');
				
				if( !preventBlur ) {
					_blur(event);
				} else {
					jQuery(control).focus();
				}
				
			};
			
			
			var _select = function(event, option) {
				
				var select = event.data.select;
				var control = event.data.control;				
				
				option = option ? option : event.target;
				
				if( jQuery(option).hasClass('selectBox-disabled') ) return false;
				
				var oldSelectedIndex = jQuery(select)[0].selectedIndex;
				jQuery('#selectBox-dropdown .selectBox-optgroup').remove();
				var newSelectedIndex = jQuery('#selectBox-dropdown').find('LI.selectBox-current').index();				
				
				if( oldSelectedIndex !== newSelectedIndex ) {
					jQuery(select)[0].selectedIndex = newSelectedIndex;
					jQuery(control).find('.selectBox-label').text( jQuery(option).text() );
					jQuery(select).trigger('change');
				}
				
				_hide(event, true);
				
			};
			
			
			var _focus = function(event) {
				
				var select = event.data.select;
				var control = event.data.control;				
				
				if( jQuery(control).hasClass('selectBox-disabled') ) return true;
				if( jQuery(control).hasClass('selectBox-focus') ) return false;
				
				// Remove dropdown and other focuses
				jQuery(".selectBox.selectBox-focus").removeClass("selectBox-focus");
				jQuery("#selectBox-dropdown").hide();
				
				jQuery(control).addClass('selectBox-focus');
				jQuery(document).bind('mousedown', { select: select, control: control }, _blur);
				jQuery(document).bind('keydown', { select: select, control: control }, _key);
				jQuery(select).trigger('focus');
				jQuery(control).focus();
				
			};
			
			
			var _blur = function(event) {
				
				var select = event.data.select;
				var control = event.data.control;
				
				// Prevent blur if the click was on the dropdown
				if( event.target.id === 'selectBox-dropdown' || 
					jQuery(event.target).parents('#selectBox-dropdown').size() === 1 ) {
					jQuery(control).trigger('focus');
					return false;
				}
				
				if( jQuery(control).hasClass('selectBox-focus') ) {
					jQuery(control).removeClass('selectBox-focus');
					jQuery(document).unbind('mousedown', _blur);
					jQuery(document).unbind('keydown', _key);
					jQuery(select).trigger('blur');
					_hide(event);
				}
				
			};
			
			
			var _key = function(event) {
				
				var select = event.data.select;
				var control = event.data.control;
				var dropdown = jQuery("#selectBox-dropdown");
				
				if( jQuery(control).hasClass('selectBox-disabled') ) return false;
				
				switch( event.keyCode ) {
					
					case 9: // tab
						_blur(event);
						break;
					
					case 13: // enter
						
						if( dropdown.size() === 0 ) return false;
						
						var siblings = dropdown.find('.selectBox-option');
						var currentIndex = -1;
						jQuery.each(siblings, function(index, option) {
							if( jQuery(option).hasClass('selectBox-current') ) {
								currentIndex = index;
								return;
							}
						});
						
						if( currentIndex >= 0 ) {
							_select(event, jQuery(siblings).eq(currentIndex));
						}
						
						return false;
						
						break;
						
					case 27: // esc
						_hide(event, true);
						break;
						
					case 38: // up
					case 37: // left
					case 33: // page up
						
						var interval = event.keyCode === 33 ? 20 : 1;
						
						if( dropdown.size() === 0 ) {
							
							if( event.altKey ) {
								_show(event);
								return false;
							}
							
							// Previous selection
							var totalIndexes = jQuery(select).find('OPTION').size(),
								oldSelectedIndex = jQuery(select)[0].selectedIndex,
								newSelectedIndex = jQuery(select)[0].selectedIndex - interval;
							
							// Look for non-disabled option
							while( jQuery(select).find('OPTION').eq(newSelectedIndex).attr('disabled') === true && newSelectedIndex >= 0 ) {
								newSelectedIndex--;
							}
							
							// Look for first enabled option
							if( newSelectedIndex < 0 ) {
								newSelectedIndex = jQuery(select).find('OPTION:not([disabled]):first').index();
							}
							
							jQuery(select)[0].selectedIndex = newSelectedIndex;
							if( jQuery(select)[0].selectedIndex === -1 ) {
								newSelectedIndex = 0;
								jQuery(select)[0].selectedIndex = newSelectedIndex;
							}
							var label = jQuery(select).find('OPTION:selected').text();
							if( label === '' ) label = '\u00A0'; // &nbsp;
							jQuery(control).find('.selectBox-label').text(label);
							
							if( newSelectedIndex !== oldSelectedIndex ) jQuery(select).trigger('change');
							
							return false;
							
						}
						
						// Determine currently selected index (ignoring optgroup LIs)
						var siblings = dropdown.find('.selectBox-option');
						var currentIndex = -1;
						jQuery.each(siblings, function(index, option) {
							if( jQuery(option).hasClass('selectBox-current') ) {
								currentIndex = index;
								return;
							}
						});
						
						currentIndex = currentIndex - interval;
						if( currentIndex < 0 ) currentIndex = 0;
						
						jQuery(siblings).removeClass('selectBox-current');
						jQuery(siblings).eq(currentIndex).addClass('selectBox-current');						
						
						_dropdownScrollFix();
						
						return false;
						
						break;
						
					case 40: // down
					case 39: // right
					case 34: // page down
						
						var interval = event.keyCode === 34 ? 20 : 1;
						
						if( dropdown.size() === 0 ) {
							
							if( event.altKey ) {
								_show(event);
								return false;
							}
							
							var totalIndexes = jQuery(select).find('OPTION').size(),
								oldSelectedIndex = jQuery(select)[0].selectedIndex,
								newSelectedIndex = jQuery(select)[0].selectedIndex + interval;
							
							// Look for non-disabled option
							while( jQuery(select).find('OPTION').eq(newSelectedIndex).attr('disabled') === true  && newSelectedIndex <= jQuery(select).find('OPTION').size() ) {
								newSelectedIndex++;
							}
							
							// Look for last enabled option
							if( newSelectedIndex > totalIndexes - 1 ) {
								newSelectedIndex = jQuery(select).find('OPTION:not([disabled]):last').index();
							}
							
							jQuery(select)[0].selectedIndex = newSelectedIndex;
							if( jQuery(select)[0].selectedIndex === -1 ) {
								newSelectedIndex = jQuery(select).find('OPTION').size() - 1;
								jQuery(select)[0].selectedIndex = newSelectedIndex;
							}
							var label = jQuery(select).find('OPTION:selected').text();
							if( label === '' ) label = '\u00A0'; // &nbsp;
							jQuery(control).find('.selectBox-label').text(label);
							
							if( newSelectedIndex != oldSelectedIndex ) jQuery(select).trigger('change');
							
							return false;
							
						}
						
						// Determine currently selected index (ignoring optgroup LIs)
						var siblings = dropdown.find('.selectBox-option');
						var currentIndex = -1;
						jQuery.each(siblings, function(index, option) {
							if( jQuery(option).hasClass('selectBox-current') ) {
								currentIndex = index;
								return;
							}
						});
						
						currentIndex = currentIndex + interval;
						if( currentIndex > jQuery(siblings).size() - 1 ) currentIndex = jQuery(siblings).size() - 1;
						
						jQuery(siblings).removeClass('selectBox-current');
						jQuery(siblings).eq(currentIndex).addClass('selectBox-current');
						
						_dropdownScrollFix();
						
						return false;
						
						break;
						
					case 36: // home
					case 35: // end
						
						if( dropdown.size() === 0 ) {
							
							if( event.altKey ) {
								_show(event);
								return false;
							}
							
							var oldSelectedIndex = jQuery(select)[0].selectedIndex,
								newSelectedIndex;
							
							if( event.keyCode === 36 ) {
								// First
								newSelectedIndex = 0;
							} else {
								// Last
								newSelectedIndex = jQuery(select).find('OPTION').size() - 1;
							}
							
							// Handle disabled options
							if( jQuery(select).find('OPTION').eq(newSelectedIndex).attr('disabled') === true ) {
								if( event.keyCode === 36 ) {
									newSelectedIndex = jQuery(select).find('OPTION:not([disabled]):first').index();	
								} else {
									newSelectedIndex = jQuery(select).find('OPTION:not([disabled]):last').index();
								}
							}
							
							jQuery(select)[0].selectedIndex = newSelectedIndex;
							var label = jQuery(select).find('OPTION:selected').text();
							if( label === '' ) label = '\u00A0'; // &nbsp;
							jQuery(control).find('.selectBox-label').text(label);
							
							if( newSelectedIndex != oldSelectedIndex ) jQuery(select).trigger('change');
							
							return false;
						}					
						
						dropdown.find('.selectBox-current').removeClass('selectBox-current');
						if( event.keyCode === 36 ) {
							// First
							dropdown.find('.selectBox-option:first').addClass('selectBox-current');
						} else {
							// Last
							dropdown.find('.selectBox-option:last').addClass('selectBox-current');
						}
						
						_dropdownScrollFix();
						
						return false;
						
						break;
						
				}
				
			};
			
			
			var _dropdownScrollFix = function(centerSelection) {
				
				var dropdown = jQuery("#selectBox-dropdown");
				if( dropdown.size() === 0 ) return false;
				
				var target = dropdown.find('.selectBox-current');
				if( jQuery(target).size() === 0 ) return false;
				
				var targetTop = parseInt(jQuery(target).offset().top - dropdown.position().top);
				var targetBottom = parseInt(targetTop + jQuery(target).outerHeight());
				
				if( centerSelection ) {
					
					dropdown.scrollTop(
						jQuery(target).offset().top - dropdown.offset().top + dropdown.scrollTop() - (dropdown.height() / 2)
					);
					
				} else {
				
					if( targetTop < 0 ) {
						dropdown.scrollTop(
							jQuery(target).offset().top - dropdown.offset().top + dropdown.scrollTop()
						);
					}
					
					if( targetBottom > dropdown.height() ) {
						dropdown.scrollTop(
							(jQuery(target).offset().top + jQuery(target).outerHeight() ) - dropdown.offset().top + dropdown.scrollTop() - dropdown.height()
						);
					}
				
				}
				
			};
			
			
			var _disableSelection = function(selector) {
				
				jQuery(selector)
					.css('MozUserSelect', 'none')
					.bind('selectstart', function() {
						return false;
					})
					.bind('mousedown', function() {
						return false;
					});
				
				return true;
				
			};
			
			
			var _htmlspecialchars = function(string) {
				return( string.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#039;') );
			};
			
			
			switch( o ) {
				
				
				case 'destroy':
					
					jQuery(this).each( function() {
						
						var select = jQuery(this);
						var control = jQuery(this).next('.selectBox');
						
						if( jQuery(select)[0].tagName.toLowerCase() === 'select' ) {
							jQuery(control).remove();
							jQuery(select).removeData('selectBox-options').show();
						}
						
					});
					
					return jQuery(this);
					
					break;
				
				
				case 'disable':
					
					jQuery(this).each( function() {
						
						var select = jQuery(this);
						var control = jQuery(this).next('.selectBox');
						
						jQuery(select).attr('disabled', true);
						jQuery(control).addClass('selectBox-disabled');
						
					});
					
					return jQuery(this);
					
					break;
				
				
				case 'enable':
					
					jQuery(this).each( function() {
						
						var select = jQuery(this);
						var control = jQuery(this).next('.selectBox');
						
						jQuery(select).attr('disabled', false);
						jQuery(control).removeClass('selectBox-disabled');
						
					});
					
					return jQuery(this);
					
					break;				
				
				
				case 'setOptions':
					if( !data ) return jQuery(this);
					
					jQuery(this).each( function() {
						
						var select = jQuery(this);
						var control = jQuery(this).next('.selectBox');
						
						switch( typeof(data) ) {
							
							case 'string':
								
								jQuery(select).html(data);
								
								break;
								
							case 'object':
								
								jQuery(select).html('');
								
								for( var i in data ) {
									
									if( data[i] === null ) continue;
									
									if( typeof(data[i]) === 'object' ) {
										// OPTGROUP
										var optgroup = jQuery('<optgroup label="' + i + '" />');
										for( var j in data[i] ) {
											jQuery(optgroup).append('<option value="' + j + '">' + data[i][j] + '</option>');
										}
										jQuery(select).append(optgroup);
									} else {
										// OPTION
										var option = jQuery('<option value="' + i + '">' + data[i] + '</option>');
										jQuery(select).append(option);
									}
									
								}
								
								break;
							
						}
						
						// Refresh the options
						//var options = jQuery(select).data('selectBox-options');
						//jQuery(select).selectBox('destroy');
						//jQuery(select).selectBox(options);
						
					});
					
					return jQuery(this);
					
					break;
				
				
				case 'value':
					
					// Remove dropdown
					jQuery("#selectBox-dropdown").hide();
					
					jQuery(this).each( function() {
						
						var select = jQuery(this);
						var control = jQuery(this).next('.selectBox');
						
						// Update value
						jQuery(select).val(data);
						
						// Fix corners and update label
						var label = jQuery(select).find(':selected').text();
						if( label === '' ) label = '\u00A0'; // &nbsp;
						jQuery(control).removeClass('jqs-ui-corner-top').addClass('jqs-ui-corner-all').find('.selectBox-label').text(label);
						
					});
					
					return jQuery(this);
					
					break;
				
				
				default:
					// Create the control
					jQuery(this).each( function() {
						
						// Default options
						if( !o ) o = {};
						var options = jQuery.extend({
							autoWidth: false
						}, o);
						
						var select = jQuery(this);
						
						if( jQuery(this).next('.selectBox').size() === 0 ) {
							
							// Generate new control
							var control = jQuery('<a href="#" class="selectBox jqs-ui-corner-all" tabindex="' + parseInt(jQuery(select).attr('tabindex')) + '" />');
							
							// Inherit class names, style, and title attributes
							jQuery(control).addClass(jQuery(select).attr('class')).attr({
								style: (jQuery(select).attr('style') + '').replace(/inline/, 'inline-block'),
								title: jQuery(select).attr('title')
							});
							
							// Store options for later use
							jQuery(select).data('selectBox-options', options);
							
							// Auto-width based on longest option
							if( options.autoWidth ) {
								
								// Determine character count of longest option
								var longestOption = '';
								jQuery(select).find('OPTION').each( function() {
									if( jQuery(this).text().length > longestOption.length ) longestOption = jQuery(this).text();
								});
								
								// Create a fake option, measure it, set the width, and remove the fake option
								var div = jQuery('<div class="selectBox-dropdown" style="position: absolute; top: -9999em; left: -9999em; width: auto; display: inline-block;" />');
								var li = jQuery('<li class="selectBox-option">' + _htmlspecialchars(longestOption) + '</li>');
								jQuery(div).append(li);
								jQuery('BODY').append(div);
								jQuery(control).width(li.outerWidth());
								jQuery(div).remove();
								
							}
							
							if( jQuery(select)[0].tagName.toLowerCase() !== 'select' || jQuery(select).attr('multiple') === true ) return;
							if( jQuery(select).attr('disabled') === true ) jQuery(control).addClass('selectBox-disabled');
							
							var label = jQuery(select).find('OPTION:selected').text();
							if( label === '' ) label = '\u00A0'; // &nbsp;
							
							// Add label and arrow
							jQuery(control).append('<span class="selectBox-label">' + _htmlspecialchars(label) + '</span>');
							jQuery(control).append('<span class="selectBox-arrow"></span>');
							jQuery(select).hide().after(control);
							
							_disableSelection(control);
							
							jQuery(control)
								.bind('click', function() { return false; })
								.bind('mousedown', { select: select, control: control }, _show)
								.bind('focus', { select: select, control: control }, _focus)
								.bind('blur', { select: select, control: control }, _blur);
							
						}
						
					});
					
					return jQuery(this);
					
					break;
				
			}

				
		}
		
			
	});
	
})(jQuery);
