//
// Picker select boxes
//

function select_none( strSelect )
{
	var select = document.getElementById( strSelect );

	if ( !select )
		return false;
		
	if ( select.style )
		select.style.visibility = "hidden";

	var options = select.options;

	for ( var iLoop = 0; iLoop < options.length; iLoop++ )
	{
		options[ iLoop ].selected = false;
	}

	if ( select.style )
		select.style.visibility = "visible";

	return false;
}

function select_all( strSelect )
{
	var select = document.getElementById( strSelect );

	if ( !select )
		return false;
		
	if ( select.style )
		select.style.visibility = "hidden";

	var options = select.options;

	for ( var iLoop = 0; iLoop < options.length; iLoop++ )
	{
		options[ iLoop ].selected = true;
	}
	
	return false;
}

function select_selectValue( strSelect, strValue )
{
	var select = document.getElementById( strSelect );
	var options = select.options;

	for ( var iLoop = 0; iLoop < options.length; iLoop++ )
	{
		if ( options[ iLoop ].value == strValue )
		{
			select.value = strValue;
			return true;
		}
	}
	
	return false;
}

function select_add( strSelect, strSection, strText, strValue )
{
	var select = document.getElementById( strSelect );
	var options = select.options;
   	var iLast = options.length;      			
	
	// Simple insert
			
	if ( strSection == null )
	{
		options[ iLast ] = new Option( strText );
		options[ iLast ].value = strValue;
	}
	else
	{
		// Insert within existing OPTGROUP
		
		var optGroup = null;
	   	var child = select.firstChild;
	   	
	   	while( child )
	   	{
			if ( "OPTGROUP" == child.tagName && child.label == strSection )
			{
				optGroup = child;
				break;
		    }
		    
		    child = child.nextSibling;
		}
	
	    // Insert as new OPTGROUP

		if ( optGroup == null )
		{	      		
			optGroup = document.createElement( "OPTGROUP" );
			optGroup.label = strSection;
			select.appendChild( optGroup );
		}
	
		var option = new Option();
		option.appendChild( document.createTextNode( strText ));
		option.value = strValue;
						
		optGroup.appendChild( option );
	}

	// Move scroll bar down
	
	select.value = strValue;
					
	return false;			
}
		
function select_delete( strSelect )
{
	var select = document.getElementById( strSelect );	
	var options = select.options;
	
	var iSelectedIndex = select.selectedIndex;

	for ( var iLoop = 0; iLoop < options.length; iLoop++ )
	{
		option = options[ iLoop ];

		// ...if it's not selected, move on...

		if ( !option.selected )
			continue;
	
		// ...otherwise, delete it

		var parent = option.parentNode;

		options[ iLoop ] = null;
		iLoop--;

		// Clean up empty OPTGROUPs
		
		if ( "OPTGROUP" == parent.tagName )
		{
		   	var child = parent.firstChild;
		   	var bHasOptions = false;

		   	while( child )
   			{
   				if ( "OPTION" == child.tagName )   				
   				{
   					bHasOptions = true;
   					break;
   				}
   				
  				child = child.nextSibling;
  			}
  			
  			if ( !bHasOptions )
  			{
				parent.parentNode.removeChild( parent );
			}
		}
	}
	
	if ( iSelectedIndex < options.length )
		select.selectedIndex = iSelectedIndex;
	else
		select.selectedIndex = options.length - 1;

	return false;
}

function select_moveBetween( strSelectFrom, strSelectTo )
{
	// For each option in the list...

	var selectFrom = document.getElementById( strSelectFrom );
	var selectTo = document.getElementById( strSelectTo );
	
	var optionsFrom = selectFrom.options;
	var optionsTo = selectTo.options;

	for ( var iLoop = 0; iLoop < optionsFrom.length; iLoop++ )
	{
		optionFrom = optionsFrom[ iLoop ];

		// ...if it's not selected, move on...

		if ( !optionFrom.selected )
			continue;

		// ...otherwise, move it between

		optionsFrom[ iLoop ] = null;
		iLoop--;
		
		var iInsertAt = optionsTo.length;
		optionsTo[ iInsertAt ] = new Option( optionFrom.text );
		optionsTo[ iInsertAt ].value = optionFrom.value;
	}

	return false;
}

function select_moveUp( strSelect )
{
	var select = document.getElementById( strSelect );
   	var child = select.firstChild;

	// For each child of the list (a list may contain
	// OPTIONs, OPTGROUPs, or other tags)...

	var bFirstChild = true;

   	while( child )
   	{
  		var childSibling = child.nextSibling;
  		var childPreviousOption;
  		var childPreviousOptGroup;
  		var childPreviousPreviousOptGroup;

  		// ...if it's an OPTION...

  		if ( "OPTION" == child.tagName )
  		{
			if ( child.selected )
			{
				// (abort rather than move the first child)
	
				if ( bFirstChild )
					return;
	
				select.insertBefore( child, childPreviousOption );
			}
			else
			{
				childPreviousOption = child;
			}

			bFirstChild = false;
  		}

  		// ...if it's an OPTGROUP...

  		else if ( "OPTGROUP" == child.tagName )
  		{
  			var childOption = child.firstChild;

			// The previous loop may have caused two OPTGROUPS to coalesce
			
  			if ( childPreviousOptGroup && child.label == childPreviousOptGroup.label )
  			{
  				while( childOption )
  				{
  					var childOptionSibling = childOption.nextSibling;
  					childPreviousOptGroup.appendChild( childOption );
  					childOption = childOptionSibling;
  				}
			}
  				
  			var bFirstOption = true;
 			var bEmpty = true;

  			// ...then consider each OPTION within the OPTGROUP

  			while( childOption )
  			{
  				var childOptionSibling = childOption.nextSibling;

  				if ( 'OPTION' == childOption.tagName )
  				{
      				bEmpty = false;

					// Special case for first OPTION (as it will be
					// leaving the OPTGROUP)...
	
					if ( bFirstOption )
					{
						if ( childOption.selected )
						{
							// (abort rather than move the first child)
	
							if ( bFirstChild )
								return;
	
							// The OPTION may coalesce with an existing OPTGROUP...
	
							var newOptGroup;
	
							if ( childPreviousPreviousOptGroup && childPreviousPreviousOptGroup.label == child.label )
							{
								newOptGroup = childPreviousPreviousOptGroup;
							}
	
							// ...or fragment into a new OPTGROUP
	
							else
							{
								newOptGroup = document.createElement( "OPTGROUP" );
								newOptGroup.label = child.label;
								childPreviousPreviousOptGroup = newOptGroup;
							}

							newOptGroup.appendChild( childOption );
							select.insertBefore( newOptGroup, childPreviousOptGroup );
	
							bEmpty = true;
						}
						else
						{
							bFirstOption = false;
						}
					}

	      			// ...and just shift the rest upwards
	
	  				else if ( childOption.selected )
	  				{
						child.insertBefore( childOption, childPreviousOption );
						childOption = childPreviousOption;
					}
					
					childPreviousOption = childOption;
					bFirstChild = false;
				}

				childOption = childOptionSibling;
			}

			if ( bEmpty )
			{
				select.removeChild( child );
			}
			else
			{
				childPreviousPreviousOptGroup = childPreviousOptGroup;
				childPreviousOptGroup = child;
			}
  		}

  		child = childSibling;
	}

	return false;
}

function select_moveDown( strSelect )
{
	var select = document.getElementById( strSelect );
   	var child = select.lastChild;

	// For each child of the list (a list may contain
	// OPTIONs, OPTGROUPs, or other tags)...

	var bLastChild = true;

      	while( child )
      	{
      		var childSibling = child.previousSibling;
      		var childNextOption;
      		var childNextOptGroup;
      		var childNextNextOptGroup;

      		// ...if it's an OPTION...

      		if ( 'OPTION' == child.tagName )
      		{
				if ( child.selected )
				{
					// (abort rather than move the last child)
	
					if ( bLastChild )
						return;
	
					select.insertBefore( child, childNextOption );
				}
				else
				{
					childNextOption = child;
				}
				
				bLastChild = false;
      		}

      		// ...if it's an OPTGROUP...

      		else if ( "OPTGROUP" == child.tagName )
      		{
      			var childOption = child.lastChild;

				// The previous loop may have caused two OPTGROUPS to coalesce
				
	  			if ( childNextOptGroup && child.label == childNextOptGroup.label )
	  			{
	  				while( childOption )
	  				{
	  					var childOptionSibling = childOption.previousSibling;
	  					childNextOptGroup.insertBefore( childOption, childNextOptGroup.firstChild );
	  					childOption = childOptionSibling;
	  				}
				}

      			var bEmpty = true;
      			var bLastOption = true;

      			// ...then consider each OPTION within the OPTGROUP

      			while( childOption )
      			{
      				var childOptionSibling = childOption.previousSibling;

      				if ( 'OPTION' == childOption.tagName )
      				{
	      				bEmpty = false;

						// Special case for last OPTION (as it will be
						// leaving the OPTGROUP)...

						if ( bLastOption )
						{
							if ( childOption.selected )
							{
								// (abort rather than move the last child)

								if ( bLastChild )
									return;

								// The OPTION may coalesce with an existing OPTGROUP...

								var newOptGroup;

								if ( childNextNextOptGroup && childNextNextOptGroup.label == child.label )
								{
									newOptGroup = childNextNextOptGroup;
								}

								// ...or fragment into a new OPTGROUP

								else
								{
									newOptGroup = document.createElement( "OPTGROUP" );
									newOptGroup.label = child.label;
									childNextNextOptGroup = newOptGroup;
								}

								newOptGroup.insertBefore( childOption, newOptGroup.firstChild );
								select.insertBefore( newOptGroup, childNextOptGroup.nextSibling );

								bEmpty = true;
							}
							else
							{
								bLastOption = false;
							}
						}

		      			// ...and just shift the rest downwards

	      				else if ( childOption.selected )
	      				{
							child.insertBefore( childOption, childNextOption.nextSibling );
							childOption = childNextOption;
						}

						bLastChild = false;
						childNextOption = childOption;
					}

					childOption = childOptionSibling;
				}

				if ( bEmpty )
				{
					select.removeChild( child );
				}
				else
				{
					childNextNextOptGroup = childNextOptGroup;
					childNextOptGroup = child;
				}
      		}

      		child = childSibling;
	}

	return false;
}

//
// Functions to match based on partially entered words
//

// Store the keystrokes in a variable, and clear
// that variable regularly if there is no
// activity. This is quite safe because JavaScript
// is not multi-threaded.

var selectMatcher_keystrokes = "";
var selectMatcher_delay = 0;
var selectMatcher_max_delay = 3;

// selectMatcher_value is a kludge used to handle an unfortunate
// sequence of select box events

var selectMatcher_value = "";

function selectMatcher_timer()
{
	// Guard against constantly clearing the keystrokes

	if ( selectMatcher_delay < selectMatcher_max_delay )
	{
		if ( selectMatcher_delay == ( selectMatcher_max_delay - 1 ))
		{
			selectMatcher_clearKeystrokes();
		}
		else
		{
			selectMatcher_delay++;
		}
	}
}

function selectMatcher_clearKeystrokes()
{	
	// Guard against constantly clearing the keystrokes

	if ( selectMatcher_delay < selectMatcher_max_delay )
	{
		selectMatcher_keystrokes = "";
		window.status = "";
		selectMatcher_delay = selectMatcher_max_delay;
	}
}

function selectMatcher_onKeyDown( event )
{
	if ( !event ) event = window.event;

	// Get the character that was pressed

	var key = event.keyCode;
	
	switch( key )
	{
		// ENTER key - navigate to the parent
		// form, then 'click' its submit button
		// (some of our forms define 'onSubmit'
		//  to be 'false', so we must click the
		//  button through code)

		case 13:
			var form = event.srcElement;

			while( form.tagName != "FORM" )
			{
				form = form.parentNode;
			}
			
			if ( form.onsubmit )
				form.onsubmit();
					
			form.submit();
			break;

		// ESC, PG UP, PG DOWN, HOME, END, up arrow and
		// down arrow, respectively

		case 27:
		case 33:
		case 34:
		case 35:
		case 36:
		case 38:
		case 40:
			selectMatcher_clearKeystrokes();
			selectMatcher_value = "";
			break;
			
		// Backspace key

		case 8:
			selectMatcher_keystrokes = selectMatcher_keystrokes.substring( 0, selectMatcher_keystrokes.length - 1 );
			selectMatcher_match( event );
			break;
	}
}

function selectMatcher_onKeyPress( event )
{
	if ( !event ) event = window.event;

	// Get the character that was pressed

	var key = event.keyCode;
	var select = event.srcElement;

	if ( key == 13 )
	{
		if ( selectMatcher_value != "" )
		{
			select.value = selectMatcher_value;
			select.fireEvent( 'onchange' );
			selectMatcher_value = ""
		}
	}

	// Ignore illegal characters

	if ( key < 32 || key > 126 )
		return;

	selectMatcher_keystrokes += String.fromCharCode( key ).toLowerCase();
	selectMatcher_match( event );
}

function selectMatcher_match( event )
{
	if ( !event ) event = window.event;

	// Reset the delay

	selectMatcher_delay = 0;

	window.status = "Search: " + selectMatcher_keystrokes;
	event.returnValue = false;

	// Fetch the array from the SELECT box

	var select = event.srcElement;
	var options = select.options;

	// Initialize our variables

	var low = 0;
	var high = options.length - 1;
	var max = high;

	// Do a binary search

	var optionIndex;
	var optionText;

	while ( low <= high )
	{
		optionIndex = Math.round(( low + high ) / 2 );

		// Look for a matching lowercase substring

		optionText = options[ optionIndex ].text;
		optionText = optionText.toLowerCase();

		if ( optionText < selectMatcher_keystrokes )
		{
			low = optionIndex + 1;
			continue;
		}
		else if ( optionText > selectMatcher_keystrokes )
		{
			high = optionIndex - 1;
			continue;
		}

		// Found it - break out

		break;
	}

	// While we may not have an EXACT match, we are probably
	// right next to it. If so, select it anyway
	//
	// Note: we set 'select.selectedIndex' rather than
	// 'option.selected', to support multi-select boxes

	if ( low <= max )
	{
		if ( options[ low ].text.toLowerCase().indexOf( selectMatcher_keystrokes ) == 0 )
		{
			if ( select.selectedIndex != low )
			{
				select.selectedIndex = low;
				select.fireEvent( 'onchange' );
				selectMatcher_value = options[ low ].value;
			}			
			return;
		}
	}

	// If we STILL haven't found it, do a brute force search - this
	// then works even for unsorted lists. For unsorted lists, we
	// assume they will be short. For sorted lists, we assume the
	// user will be typing something that the binary search can find
	// (worst case therefore is a slight performance hit if we reach here)

	for ( var i = 0; i < options.length; i++ )
	{
		if ( options[ i ].text.toLowerCase().indexOf( selectMatcher_keystrokes ) == 0 )
		{
			if ( select.selectedIndex != i )
			{
				select.selectedIndex = i;
				select.fireEvent( 'onchange' );
				selectMatcher_value = options[ i ].value;
			}
			return;
		}
	}
}

// Loop through the document and assign handlers
// to all SELECT boxes. This 'double loop' seems
// quicker than using 'document.all'

function selectMatcher_init()
{
	// We need JavaScript timer support
	
	if ( !window.setInterval )
		return;

	// Attach to all SELECT boxes
	
	for ( var i = 0; i < document.forms.length; i++ )
	{
		var form = document.forms[i];

		for ( var j = 0; j < form.elements.length; j++ )
		{
			var element = form.elements[j];

			if ( element.type == "select-one" || element.type == "select-multiple" )
			{
				addEvent( element, 'keydown', selectMatcher_onKeyDown );
				addEvent( element, 'keypress', selectMatcher_onKeyPress );
				addEvent( element, 'blur', selectMatcher_onBlur );
				addEvent( element, 'focus', selectMatcher_clearKeystrokes );
				addEvent( element, 'click', selectMatcher_onClick );
			}
		}
	}

	// Note that we must use setInterval not
	// setTimeout, because there is no way to REset
	// setTimeout. Also, we fire every half-second
	// to increase the resolution of our timer
	// method (ie. margin of maximum delay)

	window.setInterval( "selectMatcher_timer()", 500 );
}

function selectMatcher_onBlur( event )
{
	if ( !event ) event = window.event;

	var select = event.srcElement;
		
	if ( selectMatcher_value != "" )
	{
		select.value = selectMatcher_value;
		select.fireEvent( 'onchange' );
		selectMatcher_value = "";
	}
	
	selectMatcher_clearKeystrokes();
}

function selectMatcher_onClick( event )
{
	if ( !event ) event = window.event;

	var select = event.srcElement;

	if ( selectMatcher_value != "" )
	{
		select.fireEvent( 'onchange' );
		selectMatcher_value = "";
	}
	
	selectMatcher_clearKeystrokes();
}

// Add an event handler but preserve what is already there

function addEvent( p_element, p_type, p_function )
{
	if ( p_element.attachEvent )
	{ 
		p_element[ 'e' + p_type + p_function] = p_function; 
		p_element[ p_type + p_function ] = function(){ p_element[ 'e' + p_type + p_function ]( window.event );} 
		p_element.attachEvent( 'on' + p_type, p_element[ p_type + p_function ] ); 
	}
	else 
	{
		p_element.addEventListener( p_type, p_function, false ); 
	}
}
