/******************************************************************************
 * Common functions
 *****************************************************************************/

	// Opens a folder in the TTRadioList control.
	function TTRadioList_OpenFolder( id ) {
		var div1 = document.getElementById( id + "-close" );
		var div2 = document.getElementById( id + "-open" );

		if ( div1 != null && div2 != null ) {
			div1.style.display = "none";
			div2.style.display = "";
		}
	}
	
	// Closes a folder in the TTRadioList control.
 	function TTRadioList_CloseFolder( id ) {
		var div1 = document.getElementById( id + "-open" );
		var div2 = document.getElementById( id + "-close" );

		if ( div1 != null && div2 != null ) {
			div1.style.display = "none";
			div2.style.display = "";
		}
	}
	
	// Writes a specified text to the "debug" DIV tag:
 	function TTRadioList_Debug( text ) {
		var div = document.getElementById( "debug" );
		if ( div != null ) div.innerHTML += text + "<br/>";
	}
	
	// custom string comparison function to handle unicode characters, returns str1 > str2 basically
	function strCompare_UniCode(str1, str2) {
		var minLength;
		str1 = str1.toLowerCase();
		str2 = str2.toLowerCase();
		if (str1.length < str2.length) minLength = str1.length; else minLength = str2.length;		 
		for (var i=0;i<minLength-1;i++)
		{		
			if (str1.charCodeAt(i) == str2.charCodeAt(i)) continue;
			return(mapUnicodeChar(str1.charCodeAt(i)) > mapUnicodeChar(str2.charCodeAt(i)));
		}		
		return (str1.length > str2.length);
	}
	
	function mapUnicodeChar(chrCode)
	{
		switch(chrCode)
		{	
			case 224: return 97.1;	
			case 225: return 97.2;  
			case 226: return 97.3;  
			case 228: return 97.4; 			 
			case 229: return 97.5;  
			case 230: return 97.7;  
			case 231: return 99.5;  
			case 232: return 100.3; 
			case 233: return 100.5; 
			case 234: return 100.7;
			case 244: return 110.3; 
			case 246: return 110.5; 
			case 214: return 110.5;			
			case 252: return 117.5; 
			default:  return chrCode;														
		}
	}

/******************************************************************************
 * TTRadioList
 *****************************************************************************/

	/*
	Creates a new instance of the TTRadioList class.
	folders ... Array of folders to add to the TTRadioList class.
	items ... Array of top-level items to add to the TTRadioList class.
	Each element in the "folders" array is an two-element array, containing folder name and
	array of folder items. Each element in the "items" array is a two element array containing
	item value and item name. Arrays were chosen to reduce the amount of Javascript code
	which is sent from server to initialize the TTRadioList on client side.
	*/ 
	function TTRadioList( name, folders, items ) {
		this.name = name;
		this.folders  = new Array();
		this.items    = new Array();
		this.validators = new Array();
		this.changeEventHandlers = new Array();
		this.errorEventHandlers = new Array();
		if ( window[ name ] == null ) window[ name ] = this;
		for ( var i = 0; i < folders.length; i++ ) if ( folders[ i ] != null ) this.folders.push( new TTRadioList_Folder( this.name, this.nextFolderID(), folders[ i ] ) );
		for ( var i = 0; i < items.length; i++ ) if ( items[ i ] != null ) this.items.push( new TTRadioList_Item( this.name, null, this.nextItemID(), items[ i ] ) );
	}

	TTRadioList.prototype.nextFolderID = function() {
		if ( this.folderCnt == null ) this.folderCnt = 0;
		return this.name + "-" + this.folderCnt++;
	}

	TTRadioList.prototype.nextItemID = function() {
		if ( this.itemCnt == null ) this.itemCnt = 0;
		return this.name + "--" + this.itemCnt++;
	}
	
	// Renders the contect of the TTRadioList control into a element with a specified ID.
	TTRadioList.prototype.render = function( divID ) {
		if ( divID != null ) {
			this.divID = divID;
		}

		if ( this.divID == null ) return;
		
		var html = new Array();

		// Render only items in this specific folder:
		if ( this.folderToRender != null ) {
			for ( var i = 0; i < this.folderToRender.items.length; i++ ) this.renderItem( this.folderToRender.items[ i ], html );	
		}

		// Render all folders and items:
		else {
			if ( this.renderItemsFirst ) {
				for ( var i = 0; i < this.items.length; i++ ) this.renderItem( this.items[ i ], html );	
				for ( var i = 0; i < this.folders.length; i++ ) this.renderFolder( this.folders[ i ], html );	
			}
                
			else {
				for ( var i = 0; i < this.folders.length; i++ ) this.renderFolder( this.folders[ i ], html );	
				for ( var i = 0; i < this.items.length; i++ ) this.renderItem( this.items[ i ], html );	
			}
		}

		var element = document.getElementById( this.divID );
		element.innerHTML = html.join( "" );
		this.element = element;
		
		// MSIE fix: for some reason IE does not render entire radiolist without this workaround.
		/*
		if ( document.all && this.folders.length > 0 && this.doMsieFix ) {
			TTRadioList_OpenFolder( this.folders[ 0 ].id );
			setTimeout( "TTRadioList_CloseFolder( '" + this.folders[ 0 ].id + "' );", 1 );
		}
		*/
		
		this.attachEvents();
		this.onChange();
	}
	
	// Selects (checks) a specified item.
	TTRadioList.prototype.select = function( value, checked ) {
		var found = false;
		
		for ( var i = 0; i < this.folders.length; i++ ) {
			var folder = this.folders[ i ];
			
			for ( var j = 0; j < folder.items.length; j++ ) {
				var item = folder.items[ j ];
				if ( item.value == value ) {
					item.select( checked );
					found = true;
					break;
				}
			}
			
			if ( found ) break;
		}

		for ( var j = 0; j < this.items.length; j++ ) {
			var item = this.items[ j ];
			if ( item.value == value ) {
				item.select( checked );
				found = true;
				break;
			}
		}
	}
	
	// Returns the list of all selected items organized into folders.
	TTRadioList.prototype.getSelection = function() {
		if ( this.selection == null ) {
			this.selection = new TTRadioList_Selection();
		
			// Get selected items from the folders:
			for ( var i = 0; i < this.folders.length; i++ ) {
				var folder = this.folders[ i ];
				var selectedFolder = null;
				
				for ( var j = 0; j < folder.items.length; j++ ) {
					var item = folder.items[ j ];
					
					if ( item.isSelected() ) {
						if ( selectedFolder == null ) {
							selectedFolder = new TTRadioList_Folder( this.name, folder.id, [ folder.name, [], folder.folderInfo ] );
							this.selection.folders.push( selectedFolder );
						}
						
						selectedFolder.items.push( item );
						this.selection.itemCount++;
					}
				}
			}

			// Get top-level selected items:
			for ( var i = 0; i < this.items.length; i++ ) {
				var item = this.items[ i ];
				if ( item.isSelected() ) {
					this.selection.items.push( item );
					this.selection.itemCount++;
				}
			}
		}
		
		return this.selection;
	}
	
	// Gets the comma-separated list of selected values:
	TTRadioList.prototype.getValue = function() {
		if ( this.value == null ) {
			var selectedValues = new Array();
			
			for ( var i = 0; i < this.getSelection().folders.length; i++ ) {
				for ( var j = 0; j < this.getSelection().folders[ i ].items.length; j++ ) {
					selectedValues.push( this.getSelection().folders[ i ].items[ j ].value );
				}
			}
			
			for ( var i = 0; i < this.getSelection().items.length; i++ ) {
				selectedValues.push( this.getSelection().items[ i ].value );
			}
			
			this.value = selectedValues.join( "," )
		}
		
		return this.value;
	}
	
	// Attaches event handlers.
	TTRadioList.prototype.attachEvents = function() {
		if ( !this.disabled ) {
			var Radioes = document.forms[ this.formID ].elements[ this.name + "_values" ];
			var k = 0;
			var Radio;
			
			var onClick = function() {
				var RadioList = window[ this.RadioListName ];
				RadioList.selection = null;
				RadioList.value = null;
				if ( RadioList.validate() ) RadioList.onChange( this.getItem() );
				else this.checked = false;
			}

			if ( this.folderToRender != null ) {
				var getItem = function() { return window[ this.RadioListName ].folderToRender.items[ this.itemIndex ]; }
				
				for ( var j = 0; j < this.folderToRender.items.length; j++ ) {
					Radio = ( Radioes.length != null ) ? Radioes[ k++ ] : Radioes;
					this.folderToRender.items[ j ].Radio = Radio;
					document.title = Radio ;
					Radio.itemIndex = j;
					Radio.RadioListName = this.name
					Radio.getItem = getItem;
					Radio.onclick = onClick;
				}
			}

			else {
				var getItem1 = function() { return window[ this.RadioListName ].folders[ this.folderIndex ].items[ this.itemIndex ]; }
				var getItem2 = function() { return window[ this.RadioListName ].items[ this.itemIndex ]; }

				if ( this.renderItemsFirst ) {
					for ( var j = 0; j < this.items.length; j++ ) {
						Radio = ( Radioes.length != null ) ? Radioes[ k++ ] : Radioes;
						this.items[ j ].Radio = Radio;
						Radio.itemIndex = j;
						Radio.RadioListName = this.name
						Radio.getItem = getItem2;
						Radio.onclick = onClick;
					}
				}
                        
				for ( var i = 0; i < this.folders.length; i++ ) {
					var folder = this.folders[ i ];
					
					for ( var j = 0; j < folder.items.length; j++ ) {
						Radio = ( Radioes.length != null ) ? Radioes[ k++ ] : Radioes;
						folder.items[ j ].Radio = Radio;
						Radio.folderIndex = i;
						Radio.itemIndex = j;
						Radio.RadioListName = this.name
						Radio.getItem = getItem1;
						Radio.onclick = onClick;
					}
				}
                        
				if ( !this.renderItemsFirst ) {
					for ( var j = 0; j < this.items.length; j++ ) {
						Radio = ( Radioes.length != null ) ? Radioes[ k++ ] : Radioes;
						this.items[ j ].Radio = Radio;
						Radio.itemIndex = j;
						Radio.RadioListName = this.name
						Radio.getItem = getItem2;
						Radio.onclick = onClick;
					}
				}
			}
		}
	}

	// Adds the specified folders to the radiolist. The folders and items in the RadioList 
	// should be ordered by name.
	TTRadioList.prototype.addFolders = function( folders ) {
		var modified = false;
		if (folders == undefined) return false;
		for ( var i = 0; i < folders.length; i++ ) {
			if ( folders[ i ] == null ) continue;
 
			// The name of the folder to insert:
			var folderName = folders[ i ][ 0 ];

			// The items in the folder to insert:
			var items = folders[ i ][ 1 ];

			// The existing or newly created folder:
			var folder = null;
			
			for ( var ii = 0; ii < this.folders.length; ii++ ) {
				// The specified folder already exists in the control, therefore we will
				// add the items to this existing folder. 
				if ( this.folders[ ii ].name.toLowerCase() == folderName.toLowerCase() ) {
					folder = this.folders[ ii ];
					
					// Add items that do not exist in the folder:
					for ( var j = 0; j < items.length; j++ ) {
						if ( items[ j ] == null ) continue;

						// The name of the item to insert:
						var itemName  = items[ j ][ 1 ];

						// The existing or newly created item:
						var item = null;
						
						for ( var jj = 0; jj < folder.items.length; jj++ ) {
							// If the item already exists in the folder, then do nothing.
							if ( folder.items[ jj ].name.toLowerCase() == itemName.toLowerCase() ) {
								item = folder.items[ jj ];
								break;
							}

							// Otherwise insert the new item to the folder.
							// We need to preserve the alphabetical order of the item names.							
							if ( strCompare_UniCode(folder.items[ jj ].name, itemName) ) {
								item = new TTRadioList_Item( this.name, folder, folder.nextItemID(), items[ j ] );
								folder.items.splice( jj, 0, item );
								modified = true;
								break;
							}
						}
							
						if ( item == null ) {
							item = new TTRadioList_Item( this.name, folder, folder.nextItemID(), items[ j ] );
							folder.items.push( item );
							modified = true;
						}
					}
					
					break;
				}
				
				// The specified folder does not exist in the radio list, therefore
				// we will insert the new folder with all its items. We need to preserve the alphabetical
				// order of the folder names.
				if (strCompare_UniCode(this.folders[ ii ].name, folderName)){
					folder = new TTRadioList_Folder( this.name, this.nextFolderID(), folders[ i ] );
					this.folders.splice( ii, 0, folder );
					modified = true;
					break;
				}
			}

			if ( folder == null ) {
				folder = new TTRadioList_Folder( this.name, this.nextFolderID(), folders[ i ] );
				this.folders.push( folder );
				modified = true;
			}
		}

		return modified;
	}

	// Adds the specified top-level items to the radiolist:
	TTRadioList.prototype.addItems = function( items ) {
		var modified = false;
		if (items == undefined) return false;
		for ( var j = 0; j < items.length; j++ ) {
			if ( items[ j ] == null ) continue;

			// The name of the item to insert:
			var itemName  = items[ j ][ 1 ];

			// The existing or newly created item:
			var item = null;
			
			for ( var jj = 0; jj < this.items.length; jj++ ) {
				// If the item already exists in the radio list, then do nothing.
				if ( this.items[ jj ].name.toLowerCase() == itemName.toLowerCase() ) {
					item = this.items[ jj ];
					break;
				}

				// Otherwise insert the new item to the radio list.
				// We need to preserve the alphabetical order of the item names.
				if ( this.items[ jj ].name.toLowerCase() > itemName.toLowerCase() ) {
					item = new TTRadioList_Item( this.name, null, this.nextItemID(), items[ j ] );
					this.items.splice( jj, 0, item );
					modified = true;
					break;
				}
			}
							
			if ( item == null ) {
				item = new TTRadioList_Item( this.name, null, this.nextItemID(), items[ j ] );
				this.items.push( item );
				modified = true;
			}
		}

		return modified;
	}

	// Removes folders from the radiolist:
	TTRadioList.prototype.removeFolders = function( folders ) {
		var modified = false;
		var selectionChanged = false;
		if (folders == undefined) return false;
		for ( var i = 0; i < folders.length; i++ ) {
			// The name of the folder to remove:
			var folderName = folders[ i ][ 0 ];

			// The items in the folder to remove:
			var items = folders[ i ][ 1 ];
			
			for ( var ii = 0; ii < this.folders.length; ii++ ) {
				// The folders are ordered by name, so we do not need to scan all folders:				
				if ( strCompare_UniCode(this.folders[ ii ].name, folderName) ) break;
				
				// If the specified folder exists in the control, we will
				// remove the items to this existing folder. 
				if ( this.folders[ ii ].name.toLowerCase() == folderName.toLowerCase() ) {
					var folder = this.folders[ ii ];
					
					for ( var j = 0; j < items.length; j++ ) {
						if (items[ j ]== undefined) break;
						// The name of the item to remove:
						var itemName  = items[ j ][ 1 ];
						
						for ( var jj = 0; jj < folder.items.length; jj++ ) {
							// The items are ordered by name so we do not need to scan all items.							
							if ( strCompare_UniCode(folder.items[ jj ].name, itemName) ) break;

							// If we found the item, we will remove it from the folder.
							if ( folder.items[ jj ].name.toLowerCase() == itemName.toLowerCase() ) {
								selectionChanged = selectionChanged || folder.items[ jj ].isSelected();
								folder.items[ jj ].folder = null;
								folder.items.splice( jj, 1 );
								modified = true;
								break;
							}
						}
					}

					// If there are no more items left in the folder, then remove the folder itself:
					if ( folder.items.length == 0 ) {
						this.folders.splice( ii, 1 );
						modified = true;
					}
					
					break;
				}
			}
		}

		if ( selectionChanged ) {
			this.selection = null;
			this.value = null;
			this.onChange();
		}
		
		return modified;
	}

	// Removes items from the radiolist:
	TTRadioList.prototype.removeItems = function( items ) {
		var modified = false;
		var selectionChanged = false;
		if (items == undefined) return false;
		for ( var j = 0; j < items.length; j++ ) {
			// The name of the item to remove:
			var itemName  = items[ j ][ 1 ];
			
			for ( var jj = 0; jj < this.items.length; jj++ ) {
				// The items are ordered by name so we do not need to scan all items.
				if ( this.items[ jj ].name.toLowerCase() > itemName.toLowerCase() ) break;

				// If we found the item, we will remove it from the radiolist.
				if ( this.items[ jj ].name.toLowerCase() == itemName.toLowerCase() ) {
					selectionChanged = selectionChanged || this.items[ jj ].isSelected();
					this.items.splice( jj, 1 );
					modified = true;
					break;
				}
			}
		}

		if ( selectionChanged ) {
			this.selection = null;
			this.value = null;
			this.onChange();
		}

		return modified;
	}

	// Removes all folders and items from the radiolist:
	TTRadioList.prototype.clear = function() {
		// Remove circular references
		for ( var i = 0; i < this.folders.length; i++ ) {
			for ( var j = 0; j < this.folders[ i ].items.length; j++ ) {
				this.folders[ i ].items[ j ].folder = null;
			}
		}

		this.folders = new Array();
		this.items = new Array();
	}

	// Unselects all selected items:
	TTRadioList.prototype.clearSelection = function() {
		var selectedValues = this.getValue().split( /,/g );
		for ( var i = 0; i < selectedValues.length; i++ ) this.select( selectedValues[ i ], false );
	}

	// Adds a new validator.
 	TTRadioList.prototype.addValidator = function( validator ) {
		this.validators.push( validator );
	}

	// Validates the current state of the RadioList.
	TTRadioList.prototype.validate = function() {
		for ( var i = 0; i < this.validators.length; i++ ) {
			if ( !this.validators[ i ].validate( this ) ) {
				this.onError( this.validators[ i ].errorMessage );
				this.selection = null;
				return false;
			}
		}
		
		return true;
	}

	// Adds a new event handler for the "Change" event.
 	TTRadioList.prototype.addChangeEventHandler = function( eventHandler ) {
		this.changeEventHandlers.push( eventHandler );
	}

	// Raises the "Change" event.
	TTRadioList.prototype.onChange = function( item ) {
		for ( var i = 0; i < this.changeEventHandlers.length; i++ ) {
			this.changeEventHandlers[ i ]( this, item );
		}
	}

	// Adds a new event handler for the "Error" event.
 	TTRadioList.prototype.addErrorEventHandler = function( eventHandler ) {
		this.errorEventHandlers.push( eventHandler );
	}

	// Raises the "Error" event.
	TTRadioList.prototype.onError = function( errorMessage ) {
		if ( this.errorEventHandlers.length > 0 ) {
			for ( var i = 0; i < this.errorEventHandlers.length; i++ ) {
				this.errorEventHandlers[ i ]( this, errorMessage );
			}
		}
		
		else {
			alert( errorMessage );
		}
	}
	
	// Handles the event when the item is programmatically selected:
	TTRadioList.prototype.onItemSelected = function( item ) {
		this.selection = null;
		this.value = null;
		
		if ( this.validate() ) {
			this.onChange( item );
					
			if ( item.isSelected() && this.openFolderOnSelect && item.folder != null ) {
				TTRadioList_OpenFolder( item.folder.id );
				//item.Radio.focus();
			}
					
			return true;
		}
		
		return false;
	}
	
	// Specifies whether the control is disabled:
	TTRadioList.prototype.disabled = false;

	// Sets the "disabled" property to a specified value.
	TTRadioList.prototype.setDisabled = function( value ) {
		if ( this.disabled != value ) {
			this.disabled = ( value == true );
			if ( this.disabled ) this.clearSelection();
			this.render();
		}
	}

	// Defines the layout of the folder in the TTRadioList control.
	TTRadioList.prototype.folderFormatString = "<div id=\"{Folder.ID}-close\" class=\"folder\"  onClick=\"TTRadioList_OpenFolder('{Folder.ID}');\"><div class=\"expand\"><code>+</code></div><div class=\"cbl_folder\">{Folder.Name}</div></div>\n<div id=\"{Folder.ID}-open\" class=\"folder\" style=\"display:none;\"><div onClick=\"TTRadioList_CloseFolder('{Folder.ID}');\"><div class=\"collapse\"><code>-</code></div><div class=\"cbl_item\">{Folder.Name}</div></div>{Folder.Items}</div>";

	// Defined the layout of the item in the TTRadioList control.
	TTRadioList.prototype.itemFormatString = "<div class=\"item\">{Radio}<div class=\"cbl_item\"><label for=\"{Item.ID}\">{Item.Name}</label></div></div>\n";
	
	// Specifies whether we want to open a folder whenever we select an item.
	TTRadioList.prototype.openFolderOnSelect = false;
		
	// Specifies whether we want to render the top-level items before the folders.
	TTRadioList.prototype.renderItemsFirst = false;
	
	// Specifies whether we want to do the fix for IE.
	// When the first folder is opened at startup, we do not want to do this fix.
	TTRadioList.prototype.doMsieFix = true;

	// Specifies the folder to render. If this property is set to a reference to a folder,
	// the control will render only this folder instead of all folders:
	TTRadioList.prototype.folderToRender = null;

	// The zero-based index or name of the Form which contains the control:
	TTRadioList.prototype.formID = 0;

	// Renders a specified folder:
	TTRadioList.prototype.renderFolder = function( folder, html ) {
		if ( this.folderFormatFunction == null ) {
			var code = "html.push(\"" + this.folderFormatString.replace( /"/g, "\\\"" ).replace( /\n/g, "\\n" ) + "\");";
			code = code.replace( /\{Name\}/g, "\");html.push(\"" + this.name + "\");html.push(\"" );
			code = code.replace( /\{Folder\.ID\}/g, "\");html.push(folder.id);html.push(\"" );
			code = code.replace( /\{Folder\.Name\}/g, "\");html.push(folder.name);html.push(\"" );
			code = code.replace( /\{Folder\.Items\}/g, "\");for(var i=0;i<folder.items.length;i++)this.renderItem(folder.items[i],html);html.push(\"" );
			this.folderFormatFunction = new Function( "folder", "html", code );
		}

		// Do not render empty folders:
		if ( folder.items.length > 0 ) {
			this.folderFormatFunction( folder, html );
		}
	}

	// Renders a specified item:
	TTRadioList.prototype.renderItem = function( item, html ) {
		if ( this.itemFormatFunction == null ) {
			var code = "html.push(\"" + this.itemFormatString.replace( /"/g, "\\\"" ).replace( /\n/g, "\\n" ) + "\");";
			code = code.replace( /\{Radio}/g, "<input type=\\\"radio\\\" name=\\\"{Name}_values\\\" value=\\\"{Item.Value}\\\" id=\\\"{Item.ID}\\\"{Disabled}>" );
			code = code.replace( /\{Name\}/g, "\");html.push(\"" + this.name + "\");html.push(\"" );
			code = code.replace( /\{Item\.ID\}/g, "\");html.push(item.id);html.push(\"" );
			code = code.replace( /\{Item\.Name\}/g, "\");html.push(item.name);html.push(\"" );
			code = code.replace( /\{Item\.Value\}/g, "\");html.push(item.value);html.push(\"" );
			code = code.replace( /\{Disabled\}/g, "\");html.push( this.disabled ? \" disabled='Yes'\" : \"\" );html.push(\"" );
			this.itemFormatFunction = new Function( "item", "html", code );
		}

		this.itemFormatFunction( item, html );
	}
	
	// Gets a list of all items whose name contains the specified text.
	TTRadioList.prototype.find = function( text ) {
		var result = new Array();
		result.names = new Object();
		result.duplicates = new Object();
		var re = new RegExp( "\\b" + text, "ig" );
		
		for ( var i = 0; i < this.folders.length; i++ ) {
			var folder = this.folders[ i ];

			for ( var j = 0; j < folder.items.length; j++ ) {
				var item = folder.items[ j ];
				
				if ( item.name.match( re ) ) {
					result.push( item );
					result.duplicates[ item.name ] = ( result.names[ item.name ] == true );
					result.names[ item.name ] = true;
				}
			}
		}

		for ( var j = 0; j < this.items.length; j++ ) {
			var item = this.items[ j ];
				
			if ( item.name.match( re ) ) {
				result.push( item );
				result.duplicates[ item.name ] = ( result.names[ item.name ] == true );
				result.names[ item.name ] = true;
			}
		}
		
		// The result will be sorted alphabetically by item name.
		// Duplicate items will be sorted by folder name.
		result.sort( function( a, b ) { 
			if ( a.name > b.name ) return 1;
			if ( a.name < b.name ) return -1;
			if ( a.folder.name > b.folder.name ) return 1;
			if ( a.folder.name < b.folder.name ) return -1;
			return 0;
		} );
		
		return result;
	}
	
/******************************************************************************
 * TTRadioList_Folder
 *****************************************************************************/
 
	// Initializes a new instance of the TTRadioList_Folder class.
	function TTRadioList_Folder( RadioListName, id, folder ) {
		this.RadioListName = RadioListName;
		this.id = id;
		this.name = folder[ 0 ];
		this.items = new Array();
		for ( var i = 0; i < folder[ 1 ].length; i++ ) if ( folder[ 1 ][ i ] != null ) this.items.push( new TTRadioList_Item( RadioListName, this, this.nextItemID(), folder[ 1 ][ i ] ) );
		if ( folder.length > 2 ) this.folderInfo = folder[ 2 ]; 
	}

	TTRadioList_Folder.prototype.nextItemID = function() {
		if ( this.itemCnt == null ) this.itemCnt = 0;
		return this.id + "-" + this.itemCnt++;
	}

	TTRadioList_Folder.prototype.isOpened = function() {
		var element = document.getElementById( this.id + "-open" );
		return ( element != null && element.style.display == "" );
	}

	// Copies the array of folder initializers and returns the copy:
	TTRadioList_Folder.copy = function( folders ) {
		var result = new Array();

		for ( var i = 0; i < folders.length; i++ ) {
			var newFolder = [ folders[ i ][ 0 ], TTRadioList_Item.copy( folders[ i ][ 1 ] ) ];
			if ( folders[ i ].length > 2 ) newFolder.push( folders[ i ][ 2 ] );
			result.push( newFolder );
		}

		return result;
	}

	// Adds folders from the "foldersToAdd" list to the "existingFolders" list.
	// We assume the folders are ordered by name.
	TTRadioList_Folder.add = function( existingFolders, foldersToAdd ) {
		for ( var i = 0; i < foldersToAdd.length; i++ ) {
			// The name and value of the folder to insert:
			var folderName  = foldersToAdd[ i ][ 0 ];

			// The existing or newly created folder:
			var folder = null;
			
			for ( var j = 0; j < existingFolders.length; j++ ) {
				// If the folder already exists in the folder, then do add items to this folder.
				if ( existingFolders[ j ][ 0 ].toLowerCase() == folderName.toLowerCase() ) {
					folder = existingFolders[ j ];
					TTRadioList_Item.add( existingFolders[ j ][ 1 ], foldersToAdd[ i ][ 1 ] )
					break;
				}
	
				// Otherwise insert the new folder, but make sure to preserve.
				// the alphabetical order of the folder names.
				if ( existingFolders[ j ][ 0 ].toLowerCase() > folderName.toLowerCase() ) {
					folder = [ folderName, TTRadioList_Item.copy( foldersToAdd[ i ][ 1 ] ) ];
					existingFolders.splice( j, 0, folder );
					break;
				}
			}
				
			// If we have not found the folder in the "existingFolders" list nor found
			// a right place to insert new folder, then append the new folder
			// at the end of the "existingFolders" list
			if ( folder == null ) {
				var newFolder = [ folderName, TTRadioList_Item.copy( foldersToAdd[ i ][ 1 ] ) ];
				if ( foldersToAdd[ i ].length > 2 ) newFolder.push( foldersToAdd[ i ][ 2 ] );
				existingFolders.push( newFolder );
			}
		}
	}

	// Removes folders from the "foldersToRemove" list from the "existingFolders" list.
	// We assume the folders are ordered by name.
	TTRadioList_Folder.remove = function( existingFolders, foldersToRemove ) {
		for ( var i = 0; i < foldersToRemove.length; i++ ) {
			// The name of the folder to remove:
			var folderName  = foldersToRemove[ i ][ 0 ];
			
			for ( var j = 0; j < existingFolders.length; j++ ) {
				// The folders to remove does not exist in the "existingFolders" list:
				if ( existingFolders[ j ][ 0 ].toLowerCase() > folderName.toLowerCase() ) break;
				
				if ( existingFolders[ j ][ 0 ].toLowerCase() == folderName.toLowerCase() ) {
					// Remove items from the folder
					TTRadioList_Item.remove( existingFolders[ j ][ 1 ], foldersToRemove[ i ][ 1 ] );

					// If there are no more items left then remove the folder itself:
					if ( existingFolders[ j ][ 1 ].length == 0 ) existingFolders.splice( j, 1 );
					break;
				}
			}
		}
	}

/******************************************************************************
 * TTRadioList_Item
 *****************************************************************************/
 
	// Initializes a new instance of the TTRadioList_Item class.
	function TTRadioList_Item( RadioListName, folder, id, item ) {
		this.RadioListName = RadioListName;
		this.folder = folder;
		this.id = id;
		this.value = item[ 0 ];
		this.name = item[ 1 ];
		if ( item.length > 2 ) this.itemInfo = item[ 2 ]; 
	}
	
	// Gets the reference to the TTRadioList control which owns the current item:
	TTRadioList_Item.prototype.getRadioList = function() { return window[ this.RadioListName ]; }

	// Tests whether the item is selected.
	TTRadioList_Item.prototype.isSelected = function() {
		if ( this.Radio == null ) return false;
		return this.Radio.checked;
	}

	// Selects the current item.
	TTRadioList_Item.prototype.select = function( value ) {
		//document.title=this.Radio;
		if ( this.Radio != null && value != this.isSelected() ) {
			this.Radio.checked = value;
			//document.title = value;
			if ( !this.getRadioList().onItemSelected( this ) ) this.Radio.checked = false;
		}
	}

	// Copies the array of item initializers and returns the copy:
	TTRadioList_Item.copy = function( items ) {
		var result = new Array();

		for ( var i = 0; i < items.length; i++ ) {
			if (items[i]!= undefined)
			{
				var newItem = [ items[ i ][ 0 ], items[ i ][ 1 ] ];
				if ( items[ i ].length > 2 ) newItem.push( items[ i ][ 2 ] );
				result.push( newItem );
			}
		}

		return result;
	}

	// Adds items from the "itemsToAdd" list to the "existingItems" list.
	// We assume the items are ordered by name.
	TTRadioList_Item.add = function( existingItems, itemsToAdd ) {
		for ( var i = 0; i < itemsToAdd.length; i++ ) {
			// The name and value of the item to insert:
			var itemValue = itemsToAdd[ i ][ 0 ];
			var itemName  = itemsToAdd[ i ][ 1 ];

			// The existing or newly created item:
			var item = null;
			
			for ( var j = 0; j < existingItems.length; j++ ) {
				// If the item already exists in the folder, then do nothing.
				if ( existingItems[ j ][ 1 ].toLowerCase() == itemName.toLowerCase() ) {
					item = existingItems[ j ];
					break;
				}
	
				// Otherwise insert the new item, but make sure to preserve.
				// the alphabetical order of the item names.
				if ( existingItems[ j ][ 1 ].toLowerCase() > itemName.toLowerCase() ) {
					item = [ itemValue, itemName ];
					existingItems.splice( j, 0, item );
					break;
				}
			}
				
			// If we have not found the item in the "existingItems" list nor found
			// a right place to insert new item, then append the new item
			// at the end of the "existingItems" list
			if ( item == null ) {
				var newItem = [ itemValue, itemName ];
				if ( itemsToAdd[ i ].length > 2 ) newItem.push( itemsToAdd[ i ][ 2 ] );
				existingItems.push( newItem );
			}
		}
	}

	// Removes items from the "itemsToRemove" list from the "existingItems" list.
	// We assume the items are ordered by name.
	TTRadioList_Item.remove = function( existingItems, itemsToRemove ) {
		for ( var i = 0; i < itemsToRemove.length; i++ ) {
			// The name of the item to remove:
			var itemName  = itemsToRemove[ i ][ 1 ];
			
			for ( var j = 0; j < existingItems.length; j++ ) {
				// The item to remove does not exist in the "existingItems" list:
				if ( existingItems[ j ][ 1 ] > itemName ) break;
				
				// If we found the item, we will remove it from the "existingItems" list.
				if ( existingItems[ j ][ 1 ] == itemName ) {
					existingItems.splice( j, 1 );
					break;
				}
			}
		}
	}

/******************************************************************************
 * TTRadioList_Selection
 *****************************************************************************/
 
	// Initializes a new instance of the TTRadioList_Selection class.
	function TTRadioList_Selection() {
		this.folders = new Array();
		this.items = new Array();
		this.itemCount = 0;
	}

/******************************************************************************
 * TTRadioList_TallyBox
 *****************************************************************************/
 
	// Initializes a new instance of the TTRadioList_TallyBox class.
	function TTRadioList_TallyBox( RadioListName ) {
		this.RadioListName = RadioListName;
	}

	// Defines the layout of the folders within the TallyBox.	
	TTRadioList_TallyBox.prototype.folderFormatString = "<div class=\"folder\">\n<div class=\"header\">{Folder.Name}</div>\n{Folder.Items}</div>";
	
	// Defines the layout of the items within the TallyBox.
	TTRadioList_TallyBox.prototype.itemFormatString = "<div class=\"item\"><div class=\"remove\"><a href=\"javascript:{Name}.select('{Item.Value}',false);\">X</a></div>{Item.Name}</div>\n";
	
	// Gets the reference to the TTRadioList object which owns the current TallyBox:
	TTRadioList_TallyBox.prototype.getRadioList = function() { return window[ this.RadioListName ]; }

	// Renders a specified folder:
	TTRadioList_TallyBox.prototype.renderFolder = function( folder, html ) {
		if ( this.folderFormatFunction == null ) {
			var code = "var code=new Array();code.push(\"" + this.folderFormatString.replace( /"/g, "\\\"" ).replace( /\n/g, "\\n" ) + "\");html.push(code.join(\"\"));";
			code = code.replace( /\{Name\}/g, "\");code.push(\"" + this.RadioListName + "\");code.push(\"" );
			code = code.replace( /\{Folder\.Name\}/g, "\");code.push(folder.name);code.push(\"" );
			code = code.replace( /\{Folder\.Items\}/g, "\");for(var i=0;i<folder.items.length;i++)this.renderItem(folder.items[i],code);code.push(\"" );
			this.folderFormatFunction = new Function( "folder", "html", code );
		}

		this.folderFormatFunction( folder, html );
	}

	// Renders a specified item:
	TTRadioList_TallyBox.prototype.renderItem = function( item, html ) {
		if ( this.itemFormatFunction == null ) {
			var code = "var code=new Array();code.push(\"" + this.itemFormatString.replace( /"/g, "\\\"" ).replace( /\n/g, "\\n" ) + "\");html.push(code.join(\"\"));";
			code = code.replace( /\{Name\}/g, "\");code.push(\"" + this.RadioListName + "\");code.push(\"" );
			code = code.replace( /\{Item\.Name\}/g, "\");code.push(item.name);code.push(\"" );
			code = code.replace( /\{Item\.Value\}/g, "\");code.push(item.value);code.push(\"" );
			this.itemFormatFunction = new Function( "item", "html", code );
		}

		this.itemFormatFunction( item, html );
	}

	// Renders the tally box itself.
	TTRadioList_TallyBox.prototype.render = function( id ) {
		var html = new Array();
		var selection = this.getRadioList().getSelection();

		for ( var i = 0; i < selection.folders.length; i++ ) this.renderFolder( selection.folders[ i ], html );
		for ( var i = 0; i < selection.items.length; i++ ) this.renderItem( selection.items[ i ], html );

		var element = document.getElementById( id );
		element.innerHTML = html.join( "\n" );
	}

/******************************************************************************
 * TTRadioList_QuickFind
 *****************************************************************************/
 
	// Initializes a new instance of the TTRadioList_QuickFind class.
	function TTRadioList_QuickFind( id, RadioListName ) {
		this.id = id;
		this.RadioListName = RadioListName;
	}
	
	// Max number of results returned by the find() method;
	TTRadioList_QuickFind.prototype.maxFindResults = 10;
	
	// Gets the reference to the TTRadioList control which owns the current TallyBox:
	TTRadioList_QuickFind.prototype.getRadioList = function() {	return window[ this.RadioListName ]; }
	
	// Gets the reference to the hidden field containing the value of the selected item.
	TTRadioList_QuickFind.prototype.getHiddenField = function() { return window[ "hdn" + this.id ]; }
	
	// Gets the reference to the text box containing the name of the selected item.
	TTRadioList_QuickFind.prototype.getTextBox = function() { return window[ "txt" + this.id ]; }
	
	// Gets the reference to the button.
	TTRadioList_QuickFind.prototype.getButton = function() { return window[ "btn" + this.id ]; }
	
	// Gets the reference to the menu containing all matching items.
	TTRadioList_QuickFind.prototype.getMenu = function() { return window[ "mnu" + this.id ]; }
	
	// Attaches event handlers:
	TTRadioList_QuickFind.prototype.attachEvents = function() {
	
		// If the user changes focus and clicks anywhere else on the page, the drop-down list should disappear
		if ( document.clickHandlers == null ) {
			document.clickHandlers = new Array();
			if ( document.onclick != null ) document.clickHandlers.push( document.onclick );
			document.onclick = function() { for ( var i = 0; i < document.clickHandlers.length; i++ ) document.clickHandlers[ i ](); }	
		}
		
		document.clickHandlers.push( new Function( this.getMenu().id + ".close();" ) );
				
		// Menu:
		this.getMenu().quickFind = this;
		this.getMenu().open = function()  { document.getElementById( this.id ).style.display = ""; }
		this.getMenu().close = function() { document.getElementById( this.id ).style.display = "none"; selectedItemId=""; selectedIndex = -1;}
		
		// TextBox:
		this.getTextBox().quickFind = this;
		var updateMenu = function( e ) {
		
			if ( this.value != null && this.value.length >= 3 ){
				this.quickFind.render();
				this.quickFind.navigation( e );					
			}
			else this.quickFind.getMenu().close();
			
			return true;			
		}
		
		var selectQuickFindItem = function( e ){
			var keyCode = ( e != null ) ? e.keyCode : window.event.keyCode;

			switch (keyCode)
			{
				case 9:		//Tab key							
					TTRadioList.prototype.SelectDropDownItem();				
					break;
			}
		}
		
		this.getTextBox().onkeyup = updateMenu;
		this.getTextBox().onkeydown = selectQuickFindItem;
		this.getTextBox().onfocus = updateMenu;
		
		// Button:
		this.getButton().quickFind = this;
		this.getButton().onclick = function() {
			this.quickFind.getRadioList().select( this.quickFind.getHiddenField().value, true );
			this.quickFind.getHiddenField().value = "";
			this.quickFind.getTextBox().value = "";
			this.quickFind.getMenu().close();
			this.disabled = true;
		}
	}
	
	var renderedItemsArray = new Array();
	var selectedItemId = "";
	var selectedIndex = -1;
	
	TTRadioList_QuickFind.prototype.navigation = function( e ){
		var code = ( e != null ) ? e.keyCode : window.event.keyCode;				

		switch (code)
		{
			case 38:								
				if(renderedItemsArray.length >0 && selectedIndex-1>=0 && selectedIndex-1<=renderedItemsArray.length-1)
				{
					selectedIndex--;
					TTRadioList.prototype.SetItemHighlight(renderedItemsArray[selectedIndex][0]);
				}	
				break;			
				
			case 40:				
				if(renderedItemsArray.length >0 && (selectedIndex+1 < renderedItemsArray.length))
				{	
					selectedIndex++;
					TTRadioList.prototype.SetItemHighlight(renderedItemsArray[selectedIndex][0]);
				}		
				break;
				
			case 13:	//Enter key							
				TTRadioList.prototype.SelectDropDownItem();
				this.getButton().focus();
				break;
				
			default:
		}
	} 
	
	TTRadioList.prototype.SetItemHighlight = function(id){
		
		//Unselect already selected item before selecting new one.
		if(selectedItemId!="" && selectedItemId!=null && document.getElementById(selectedItemId))
		{
			document.getElementById(selectedItemId).className = 'Item';
		}	
		
		//Select the item.	
		document.getElementById(id).className = 'Item MouseOver';
		selectedItemId = id;		
	}	
	
	TTRadioList.prototype.SelectDropDownItem = function(id){
		
		var index = selectedIndex;
		
		var id = renderedItemsArray[index][1];
		var item = renderedItemsArray[index][2];
		
		document.getElementById("txt" + id).value = item.name;	//.replace( /'/g, "\\'" );
		document.getElementById("hdn" + id).value = item.value;
		document.getElementById("btn" + id).disabled = false;
		document.getElementById("mnu" + id).close();				
	}
	
	// Renders the menu with the matching items.	
	TTRadioList_QuickFind.prototype.render = function() {
	
		//clear array before adding items
		renderedItemsArray = new Array();
				
		if ( this.itemFormatFunction == null ) {
			this.itemFormatFunction = function( item, re, isDuplicate, html ) {
						
				var id = "mnu" + this.id + "_" + item.value;

				var code = new Array();
				code.push( "<div " );
				
				if(selectedItemId==id)
					code.push( "class=\"Item MouseOver\" id=\"" + id + "\" " );
				else
					code.push( "class=\"Item\" id=\"" + id + "\" " );
			
				code.push( "onclick=\"txt" + this.id + ".value=" );
				code.push( "'" + item.name.replace( /'/g, "\\'" ) + "';" );
				code.push( "hdn" + this.id + ".value='" + item.value + "';" );
				code.push( "btn" + this.id + ".disabled=false;" );
				code.push( "mnu" + this.id + ".close();return false;\" " );
				code.push( "onmouseover=\"TTRadioList.prototype.SetItemHighlight('" + id + "');\" " );
				code.push( "onmouseout=\"document.getElementById('" + id + "').className='Item';selectedIndex=-1;selectedItemId='';\">" );
				code.push( item.name.replace( re, "<b>$1</b>" ) );
				if ( isDuplicate && item.folder != null ) code.push( " (" + item.folder.name + ")" );
				code.push( "</div>" );
				html.push( code.join( "" ) );
				
				renderedItemsArray.push([id, this.id, item]);
								
			}
		}
			
		var menu = this.getMenu();
		var text = this.getTextBox().value;
		var items = this.getRadioList().find( text );
		var re = new RegExp( "\\b(" + text + ")", "gi" );
		
		if ( items.length > 0 ) {
			var html = new Array();
			
			var cnt = 0;
			for ( var i = 0; ( i < items.length && cnt < this.maxFindResults ); i++ ) {
				var item = items[ i ];
				if ( item.isSelected() ) continue;
				cnt++;
				this.itemFormatFunction( item, re, items.duplicates[ item.name ], html );
			}
			
			if ( html.length > 0 ) {
				menu.open();
				menu.innerHTML = html.join( "\n" );
			}
			
			else {
				menu.close();
			}
			
			if ( items.length == 1 ) {
				this.getHiddenField().value = items[ 0 ].value;
				this.getButton().disabled = false;
			}
			
			else {
				this.getHiddenField().value = "";
				this.getButton().disabled = true;
			}
		}
		
		else {
			menu.close();
			this.getButton().disabled = true;
		}
		
		
		//Set first item automatically highlighted when the list appears.
		
		if(selectedIndex<=0 && renderedItemsArray.length >0)
		{
			var firstItemId = renderedItemsArray[0][0];
			TTRadioList.prototype.SetItemHighlight(firstItemId);
			selectedItemId = firstItemId;
			selectedIndex = 0;
		}
		
	}

/******************************************************************************
 * TTRadioList_SelectionValidator
 *****************************************************************************/
 
// This validator checks whether the user did not select more items or folders than allowed.
function TTRadioList_SelectionValidator( errorMessage, maxItems, maxFolders, maxItemsInFolder ) {
	this.errorMessage = errorMessage;
	this.maxItems = maxItems != null ? maxItems : 0;
	this.maxFolders = maxFolders != null ? maxFolders : 0;
	this.maxItemsInFolder = maxItemsInFolder != null ? maxItemsInFolder : 0;
}

TTRadioList_SelectionValidator.prototype.validate = function( RadioList ) {
	var result = true;
	var selection = RadioList.getSelection();
	
	if ( this.maxItems > 0 ) result = result && ( selection.itemCount <= this.maxItems );
	if ( result && this.maxFolders > 0 ) result = result && ( selection.folders.length <= this.maxFolders );
	
	if ( result && this.maxItemsInFolder > 0 ) {
		for ( var i = 0; i < selection.folders.length; i++ ) {
			result = result && ( selection.folders[ i ].items.length <= this.maxItemsInFolder );
			if ( !result ) break;
		}
	}
	
	return result;
}
