/*
// jQuery multiSelect
//
// Version 1.2.2 beta
//
// Cory S.N. LaViska
// A Beautiful Site (http://abeautifulsite.net/)
// 09 September 2009
//
// Visit http://abeautifulsite.net/notebook/62 for more information
//
// (Amended by Andy Richmond, Letters & Science Deans' Office, University of California, Davis)
//
// Usage: $('#control_id').multiSelect( options, callback )
//
// Options:  selectAll          - whether or not to display the Select All option; true/false, default = true
//           selectAllText      - text to display for selecting/unselecting all options simultaneously
//           noneSelected       - text to display when there are no selected items in the list
//           oneOrMoreSelected  - text to display when there are one or more selected items in the list
//                                (note: you can use % as a placeholder for the number of items selected).
//                                Use * to show a comma separated list of all selected; default = '% selected'
//           optGroupSelectable - whether or not optgroups are selectable if you use them; true/false, default = false
//           listHeight         - the max height of the droptdown options
//
// Dependencies:  jQuery 1.2.6 or higher (http://jquery.com/)
//
// Change Log:
//
//		1.0.1	- Updated to work with jQuery 1.2.6+ (no longer requires the dimensions plugin)
//				- Changed $(this).offset() to $(this).position(), per James' and Jono's suggestions
//
//		1.0.2	- Fixed issue where dropdown doesn't scroll up/down with keyboard shortcuts
//				- Changed '$' in setTimeout to use 'jQuery' to support jQuery.noConflict
//				- Renamed from jqueryMultiSelect.* to jquery.multiSelect.* per the standard recommended at
//				  http://docs.jquery.com/Plugins/Authoring (does not affect API methods)
//
//		1.0.3	- Now uses the bgiframe plugin (if it exists) to fix the IE6 layering bug.
//              - Forces IE6 to use a min-height of 200px (this needs to be added to the options)
//
//		1.1.0	- Added the ability to update the options dynamically via javascript: multiSelectOptionsUpdate(JSON)
//              - Added a title that displays the whole comma delimited list when using oneOrMoreSelected = *
//              - Moved some of the functions to be closured to make them private
//              - Changed the way the keyboard navigation worked to more closely match how a standard dropdown works
//              - ** by Andy Richmond **
//
//		1.2.0	- Added support for optgroups
//              - Added the ability for selectable optgroups (i.e. select all for an optgroup)
//              - ** by Andy Richmond **
//
//		1.2.1	- Fixed bug where input text overlapped dropdown arrow in IE (i.e. when using oneOrMoreSelected = *)
//              - Added option "listHeight" for min-height of the dropdown
//              - Fixed bug where bgiframe was causing a horizontal scrollbar and on short lists extra whitespace below the options
//              - ** by Andy Richmond **
//
//		1.2.2	- Fixed bug where the keypress stopped showing the dropdown because in jQuery 1.3.2 they changed the way ':visible' works
//              - Fixed some other bugs in the way the keyboard interface worked
//              - Changed the main textbox to an <a> tag (with 'display: inline-block') to prevent the display text from being selected/highlighted
//              - Added the ability to jump to an option by typing the first character of that option (simular to a normal drop down)
//              - ** by Andy Richmond **
//				- Added [] to make each control submit an HTML array so $.serialize() works properly
//
// Licensing & Terms of Use
// 
// This plugin is dual-licensed under the GNU General Public License and the MIT License and
// is copyright 2008 A Beautiful Site, LLC. 
//	
*/
$(document).ready(function() {
	if(jQuery) (function($){
		
		// render the html for a single option
		function renderOption(id, option)
		{
			var html = '<label><input type="checkbox" name="' + id + '[]" value="' + option.value + '"';
			if( option.selected ){
				html += ' checked="checked"';
			}
			html += ' />' + option.text + '</label>';
			
			return html;
		}
		
		// render the html for the options/optgroups
		function renderOptions(id, options, o)
		{
			var html = "";
			
			for(var i = 0; i < options.length; i++) {
				if(options[i].optgroup) {
					html += '<label class="optGroup">';
					
					if(o.optGroupSelectable) {
						html += '<input type="checkbox" class="optGroup" />' + options[i].optgroup;
					}
					else {
						html += options[i].optgroup;
					}
					
					html += '</label><div class="optGroupContainer">';
					
					html += renderOptions(id, options[i].options, o);
					
					html += '</div>';
				}
				else {
					html += renderOption(id, options[i]);
				}
			}
			
			return html;
		}
		
		// Building the actual options
		function buildOptions(options)
		{
			var multiSelect = $(this);
			var multiSelectOptions = multiSelect.next('.multiSelectOptions');
			var o = multiSelect.data("config");
			var callback = multiSelect.data("callback");
	
			// clear the existing options
			multiSelectOptions.html("");
			var html = "";
	
			// if we should have a select all option then add it
			if( o.selectAll ) {
				html += '<label class="selectAll"><input type="checkbox" class="selectAll" />' + o.selectAllText + '</label>';
			}
	
			// generate the html for the new options
			html += renderOptions(multiSelect.attr('id'), options, o);
			
			multiSelectOptions.html(html);
			
			// variables needed to account for width changes due to a scrollbar
			var initialWidth = multiSelectOptions.width();
			var hasScrollbar = false;
			
			// set the height of the dropdown options
			if(multiSelectOptions.height() > o.listHeight) {
				multiSelectOptions.css("height", o.listHeight + 'px');
				hasScrollbar = true;
			} else {
				multiSelectOptions.css("height", '');
			}
			
			// if the there is a scrollbar and the browser did not already handle adjusting the width (i.e. Firefox) then we will need to manaually add the scrollbar width
			var scrollbarWidth = hasScrollbar && (initialWidth == multiSelectOptions.width()) ? 17 : 0;
	
			// set the width of the dropdown options
			if((multiSelectOptions.width() + scrollbarWidth) < multiSelect.outerWidth()) {
				multiSelectOptions.css("width", multiSelect.outerWidth() - 2/*border*/ + 'px');
			} else {
				multiSelectOptions.css("width", (multiSelectOptions.width() + scrollbarWidth) + 'px');
			}
			
			// Apply bgiframe if available on IE6
			if( $.fn.bgiframe ) multiSelect.next('.multiSelectOptions').bgiframe( { width: multiSelectOptions.width(), height: multiSelectOptions.height() });
	
			// Handle selectAll oncheck
			if(o.selectAll) {
				multiSelectOptions.find('INPUT.selectAll').click( function() {
					// update all the child checkboxes
					multiSelectOptions.find('INPUT:checkbox').attr('checked', $(this).attr('checked')).parent("LABEL").toggleClass('checked', $(this).attr('checked'));
				});
			}
			
			// Handle OptGroup oncheck
			if(o.optGroupSelectable) {
				multiSelectOptions.addClass('optGroupHasCheckboxes');
			
				multiSelectOptions.find('INPUT.optGroup').click( function() {
					// update all the child checkboxes
					$(this).parent().next().find('INPUT:checkbox').attr('checked', $(this).attr('checked')).parent("LABEL").toggleClass('checked', $(this).attr('checked'));
				});
			}
			
			// Handle all checkboxes
			multiSelectOptions.find('INPUT:checkbox').click( function() {
				// set the label checked class
				$(this).parent("LABEL").toggleClass('checked', $(this).attr('checked'));
				
				updateSelected.call(multiSelect);
				multiSelect.focus();
				if($(this).parent().parent().hasClass('optGroupContainer')) {
					updateOptGroup.call(multiSelect, $(this).parent().parent().prev());
				}
				if( callback ) {
					callback($(this));
				}
			});
			
			// Initial display
			multiSelectOptions.each( function() {
				$(this).find('INPUT:checked').parent().addClass('checked');
			});
			
			// Initialize selected and select all 
			updateSelected.call(multiSelect);
			
			// Initialize optgroups
			if(o.optGroupSelectable) {
				multiSelectOptions.find('LABEL.optGroup').each( function() {
					updateOptGroup.call(multiSelect, $(this));
				});
			}
			
			// Handle hovers
			multiSelectOptions.find('LABEL:has(INPUT)').hover( function() {
				$(this).parent().find('LABEL').removeClass('hover');
				$(this).addClass('hover');
			}, function() {
				$(this).parent().find('LABEL').removeClass('hover');
			});
			
			// Keyboard
			multiSelect.keydown( function(e) {
			
				var multiSelectOptions = $(this).next('.multiSelectOptions');
	
				// Is dropdown visible?
				if( multiSelectOptions.css('visibility') != 'hidden' ) {
					// Dropdown is visible
					// Tab
					if( e.keyCode == 9 ) {
						$(this).addClass('focus').trigger('click'); // esc, left, right - hide
						$(this).focus().next(':input').focus();
						return true;
					}
					
					// ESC, Left, Right
					if( e.keyCode == 27 || e.keyCode == 37 || e.keyCode == 39 ) {
						// Hide dropdown
						$(this).addClass('focus').trigger('click');
					}
					// Down || Up
					if( e.keyCode == 40 || e.keyCode == 38) {
						var allOptions = multiSelectOptions.find('LABEL');
						var oldHoverIndex = allOptions.index(allOptions.filter('.hover'));
						var newHoverIndex = -1;
						
						// if there is no current highlighted item then highlight the first item
						if(oldHoverIndex < 0) {
							// Default to first item
							multiSelectOptions.find('LABEL:first').addClass('hover');
						}
						// else if we are moving down and there is a next item then move
						else if(e.keyCode == 40 && oldHoverIndex < allOptions.length - 1)
						{
							newHoverIndex = oldHoverIndex + 1;
						}
						// else if we are moving up and there is a prev item then move
						else if(e.keyCode == 38 && oldHoverIndex > 0)
						{
							newHoverIndex = oldHoverIndex - 1;
						}
	
						if(newHoverIndex >= 0) {
							$(allOptions.get(oldHoverIndex)).removeClass('hover'); // remove the current highlight
							$(allOptions.get(newHoverIndex)).addClass('hover'); // add the new highlight
							
							// Adjust the viewport if necessary
							adjustViewPort(multiSelectOptions);
						}
						
						return false;
					}
	
					// Enter, Space
					if( e.keyCode == 13 || e.keyCode == 32 ) {
						var selectedCheckbox = multiSelectOptions.find('LABEL.hover INPUT:checkbox');
						
						// Set the checkbox (and label class)
						selectedCheckbox.attr('checked', !selectedCheckbox.attr('checked')).parent("LABEL").toggleClass('checked', selectedCheckbox.attr('checked'));
						
						// if the checkbox was the select all then set all the checkboxes
						if(selectedCheckbox.hasClass("selectAll")) {
							multiSelectOptions.find('INPUT:checkbox').attr('checked', selectedCheckbox.attr('checked')).parent("LABEL").addClass('checked').toggleClass('checked', selectedCheckbox.attr('checked')); 
						}
	
						updateSelected.call(multiSelect);
						
						if( callback ) callback($(this));
						return false;
					}
	
					// Any other standard keyboard character (try and match the first character of an option)
					if( e.keyCode >= 33 && e.keyCode <= 126 ) {
						// find the next matching item after the current hovered item
						var match = multiSelectOptions.find('LABEL:startsWith(' + String.fromCharCode(e.keyCode) + ')');
						
						var currentHoverIndex = match.index(match.filter('LABEL.hover'));
						
						// filter the set to any items after the current hovered item
						var afterHoverMatch = match.filter(function (index) {
							return index > currentHoverIndex;
						});
	
						// if there were no item after the current hovered item then try using the full search results (filtered to the first one)
						match = (afterHoverMatch.length >= 1 ? afterHoverMatch : match).filter("LABEL:first");
	
						if(match.length == 1) {
							// if we found a match then move the hover
							multiSelectOptions.find('LABEL.hover').removeClass('hover');								
							match.addClass('hover');
							
							adjustViewPort(multiSelectOptions);
						}
					}
				} else {
					// Dropdown is not visible
					if( e.keyCode == 38 || e.keyCode == 40 || e.keyCode == 13 || e.keyCode == 32 ) { //up, down, enter, space - show
						// Show dropdown
						$(this).removeClass('focus').trigger('click');
						multiSelectOptions.find('LABEL:first').addClass('hover');
						return false;
					}
					//  Tab key
					if( e.keyCode == 9 ) {
						// Shift focus to next INPUT element on page
						multiSelectOptions.next(':input').focus();
						return true;
					}
				}
				// Prevent enter key from submitting form
				if( e.keyCode == 13 ) return false;
			});
		}
		
		// Adjust the viewport if necessary
		function adjustViewPort(multiSelectOptions)
		{
			// check for and move down
			var selectionBottom = multiSelectOptions.find('LABEL.hover').position().top + multiSelectOptions.find('LABEL.hover').outerHeight();
			
			if(selectionBottom > multiSelectOptions.innerHeight()){		
				multiSelectOptions.scrollTop(multiSelectOptions.scrollTop() + selectionBottom - multiSelectOptions.innerHeight());
			}
			
			// check for and move up						
			if(multiSelectOptions.find('LABEL.hover').position().top < 0){		
				multiSelectOptions.scrollTop(multiSelectOptions.scrollTop() + multiSelectOptions.find('LABEL.hover').position().top);
			}
		}
		
		// Update the optgroup checked status
		function updateOptGroup(optGroup)
		{
			var multiSelect = $(this);
			var o = multiSelect.data("config");
			
			// Determine if the optgroup should be checked
			if(o.optGroupSelectable) {
				var optGroupSelected = true;
				$(optGroup).next().find('INPUT:checkbox').each( function() {
					if( !$(this).attr('checked') ) {
						optGroupSelected = false;
						return false;
					}
				});
				
				$(optGroup).find('INPUT.optGroup').attr('checked', optGroupSelected).parent("LABEL").toggleClass('checked', optGroupSelected);
			}
		}
		
		// Update the textbox with the total number of selected items, and determine select all
		function updateSelected() {
			var multiSelect = $(this);
			var multiSelectOptions = multiSelect.next('.multiSelectOptions');
			var o = multiSelect.data("config");
			
			var i = 0;
			var selectAll = true;
			var display = '';
			multiSelectOptions.find('INPUT:checkbox').not('.selectAll, .optGroup').each( function() {
				if( $(this).attr('checked') ) {
					i++;
					display = display + $(this).parent().text() + ', ';
				}
				else selectAll = false;
			});
			
			// trim any end comma and surounding whitespace
			display = display.replace(/\s*\,\s*$/,'');
			
			if( i == 0 ) {
				multiSelect.find("span").html( o.noneSelected );
			} else {
				if( o.oneOrMoreSelected == '*' ) {
					multiSelect.find("span").html( display );
					multiSelect.attr( "title", display );
				} else {
					multiSelect.find("span").html( o.oneOrMoreSelected.replace('%', i) );
				}
			}
	
			// Determine if Select All should be checked
			if(o.selectAll) {
				multiSelectOptions.find('INPUT.selectAll').attr('checked', selectAll).parent("LABEL").toggleClass('checked', selectAll);
			}
		}
		
		$.extend($.fn, {
			multiSelect: function(o, callback) {
				// Default options
				if( !o ) o = {};
				if( o.selectAll == undefined ) o.selectAll = true;
				if( o.selectAllText == undefined ) o.selectAllText = "Select All";
				if( o.noneSelected == undefined ) o.noneSelected = 'Select options';
				if( o.oneOrMoreSelected == undefined ) o.oneOrMoreSelected = '% selected';
				if( o.optGroupSelectable == undefined ) o.optGroupSelectable = false;
				if( o.listHeight == undefined ) o.listHeight = 150;
	
				// Initialize each multiSelect
				$(this).each( function() {
					var select = $(this);
					var html = '<a href="javascript:;" class="multiSelect"><span></span></a>';
					html += '<div class="multiSelectOptions" style="position: absolute; z-index: 99999; visibility: hidden;"></div>';
					$(select).after(html);
					
					var multiSelect = $(select).next('.multiSelect');
					var multiSelectOptions = multiSelect.next('.multiSelectOptions');
					
					// if the select object had a width defined then match the new multilsect to it
					multiSelect.find("span").css("width", $(select).width() + 'px');
					
					// Attach the config options to the multiselect
					multiSelect.data("config", o);
					
					// Attach the callback to the multiselect
					multiSelect.data("callback", callback);
					
					// Serialize the select options into json options
					var options = [];
					$(select).children().each( function() {
						if(this.tagName.toUpperCase() == 'OPTGROUP')
						{
							var suboptions = [];
							options.push({ optgroup: $(this).attr('label'), options: suboptions });
							
							$(this).children('OPTION').each( function() {
								if( $(this).val() != '' ) {
									suboptions.push({ text: $(this).html(), value: $(this).val(), selected: $(this).attr('selected') });
								}
							});
						}
						else if(this.tagName.toUpperCase() == 'OPTION')
						{
							if( $(this).val() != '' ) {
								options.push({ text: $(this).html(), value: $(this).val(), selected: $(this).attr('selected') });
							}
						}
					});
					
					// Eliminate the original form element
					$(select).remove();
					
					// Add the id that was on the original select element to the new input
					multiSelect.attr("id", $(select).attr("id"));
					
					// Build the dropdown options
					buildOptions.call(multiSelect, options);
	
					// Events
					multiSelect.hover( function() {
						$(this).addClass('hover');
					}, function() {
						$(this).removeClass('hover');
					}).click( function() {
						// Show/hide on click
						if( $(this).hasClass('active') ) {
							$(this).multiSelectOptionsHide();
						} else {
							$(this).multiSelectOptionsShow();
						}
						return false;
					}).focus( function() {
						// So it can be styled with CSS
						$(this).addClass('focus');
					}).blur( function() {
						// So it can be styled with CSS
						$(this).removeClass('focus');
					});
					
					// Add an event listener to the window to close the multiselect if the user clicks off
					$(document).click( function(event) {
						// If somewhere outside of the multiselect was clicked then hide the multiselect
						if(!($(event.target).parents().andSelf().is('.multiSelectOptions'))){
							multiSelect.multiSelectOptionsHide();
						}
					});
				});
			},
			
			// Update the dropdown options
			multiSelectOptionsUpdate: function(options) {
				buildOptions.call($(this), options);
			},
			
			// Hide the dropdown
			multiSelectOptionsHide: function() {
				$(this).removeClass('active').removeClass('hover').next('.multiSelectOptions').css('visibility', 'hidden');
			},
			
			// Show the dropdown
			multiSelectOptionsShow: function() {
				var multiSelect = $(this);
				var multiSelectOptions = multiSelect.next('.multiSelectOptions');
				var o = multiSelect.data("config");
			
				// Hide any open option boxes
				$('.multiSelect').multiSelectOptionsHide();
				multiSelectOptions.find('LABEL').removeClass('hover');
				multiSelect.addClass('active').next('.multiSelectOptions').css('visibility', 'visible');
				multiSelect.focus();
				
				// reset the scroll to the top
				multiSelect.next('.multiSelectOptions').scrollTop(0);
	
				// Position it
				var offset = multiSelect.position();
				multiSelect.next('.multiSelectOptions').css({ top:  offset.top + $(this).outerHeight() + 'px' });
				multiSelect.next('.multiSelectOptions').css({ left: offset.left + 'px' });
			},
			
			// get a coma-delimited list of selected values
			selectedValuesString: function() {
				var selectedValues = "";
				$(this).next('.multiSelectOptions').find('INPUT:checkbox:checked').not('.optGroup, .selectAll').each(function() {
					selectedValues += $(this).attr('value') + ",";
				});
				// trim any end comma and surounding whitespace
				return selectedValues.replace(/\s*\,\s*$/,'');
			}		
		});
		
		// add a new ":startsWith" search filter
		$.expr[":"].startsWith = function(el, i, m) {
			var search = m[3];        
			if (!search) return false;
			return eval("/^[/s]*" + search + "/i").test($(el).text());
		};
		
	})(jQuery);
});
