/* ***************************************************************
 *
 * Copyright (c) TimeFrame, Inc. 2005, 2007
 * 
 * File: jswl-core.js
 * Created: January 05, 2006 (20060501)
 * Description:
 *
 * JavaScript Widget Library widget definitions.
 * 
 * ***************************************************************
 * CHANGE HISTORY
 * ***************************************************************
 * DATE     USER       RECORD       DESCRIPTION
 * ---------------------------------------------------------------
 * 20060105 dewittsc   ------       Initial version.
 * 
 * *************************************************************** */
 
var SUBMIT_BUTTON_TYPE 			= 0x01;
var RESET_BUTTON_TYPE 			= 0x02;
var STANDARD_BUTTON_TYPE 		= 0x04;	
var CHECKBOX_BUTTON_TYPE		= 0x08;
var RADIO_BUTTON_TYPE 			= 0x10;
var IMAGE_BUTTON_TYPE 			= 0x20;
var PASSWORD_TEXT_FIELD_TYPE	= 0x01;
var FILE_TEXT_FIELD_TYPE		= 0X02;
var STANDARD_TEXT_FIELD_TYPE	= 0x04;

/**
 * Constructor for JSWL widgets which correspond to HTML form INPUT
 * elements.
 *
 * @param wid            a unique widget id.
 * @param name           the name for the INPUT element.
 * @param initialValue   the initial value for the INPUT element.
 * @jswl-public name                      the name for the INPUT element.
 * @jswl-public value                     the value for the INPUT element.
 * @jswl.public styleClass                the CSS classname for the HTML element.
 * @jswl.public inputReady                the input ready flag.
 * @jswl.public enableVisualCues          the visual cue flag.
 * @jswl.public.enableAnimation           the animation flag.
 * @jswl-public addInputChangeListener    add an input change listener.
 * @jswl-public getInputChangeListeners   return all input change listeners.
 */
 
function JsInput( wid, name, initialValue )
{
	this.base = JsWidget;
	this.base(wid);
	this.layoutType = INLINE_BOX_TYPE;
	this.name = name || wid;
	this.value = initialValue || "";
	this.inputReady = false;
	this.enableVisualCues = false;
	this.enableAnimation = false;	
}

JsInput.prototype = new JsWidget;
	
JsInput.prototype.setEnabled =
	function ( enabled )
	{
		this.enabled = enabled;
		if ( this.peer )
		{
			this.peer.disabled = !this.enabled;
		}
	};
	
/**
 * Constructor for JSWL widgets which correspond to HTML form text
 * input elements (text fields and text areas).
 *
 * @param wid            a unique widget id.
 * @param name           the name for the INPUT element.
 * @param initialValue   the initial value for the INPUT element.
 */

function JsTextInput( wid, name, initialValue )
{
	this.base = JsInput;
	this.base( wid, name, initialValue );
	this.actionListeners = new Array();
	this.inputChangeListeners = new Array();
}
 	
JsTextInput.prototype = new JsInput;

JsTextInput.prototype.setText =
	function ( text )
	{
		this.value = text;
		if ( this.peer )
		{
			this.peer.value = text;
			this.fireInputChange( createEvent( 'change', true ) );
		}
	};
	
JsTextInput.prototype.link = 
	function ()
	{
		if ( this.acceptInput )
		{
			this.inputReady = this.acceptInput();
		}
			
		if ( this.peer )
		{
			if ( this.inputReady && this.enableVisualCues )
			{
				this.peer.style.backgroundColor = "#C0FFC0";
				this.peer.style.borderColor = "#40FF40";
			}
		}
	};
	
/* **********************************************************************
 * Implement EventListener
 * ********************************************************************** */
 
JsTextInput.prototype.handleEvent =
	function ( event, type )
	{
		var rc = DO_DEFAULT_ACTION;
		
		if ( type == 'onkeyup' )
		{
			this.value = this.peer.value;
			this.fireInputChange( event );
			
			/*if ( isEnterKey( getKey( event ) ) )
			{
				rc = this.fireAction( event );
			}*/ 
		}
		
		return rc;
	};
	
/* **********************************************************************
 * Implement InputChangeListener
 * ********************************************************************** */
 
JsTextInput.prototype.handleInputChange =
	function ( event )
	{
		if ( this.acceptInput )
		{
			var acceptable = this.acceptInput();
			
			if ( this.enableVisualCues )
			{
				if ( !this.inputReady && acceptable )
				{
					if ( this.enableAnimation )
					{
						fade( this.peer, 0, "FFFFCC", "C0FFC0", 10, 30 );
						fade( this.peer, 1, "808080", "40FF40", 10, 30 );
					}
					else
					{
						this.peer.style.backgroundColor = "#C0FFC0";
						this.peer.style.borderColor = "#40FF40";
					}
				}
				else if ( this.inputReady && !acceptable )
				{
					if ( this.enableAnimation )
					{
						fade( this.peer, 0, "C0FFC0", "FFFFCC", 10, 30 );
						fade( this.peer, 1, "40FF40", "808080", 10, 30 );
					}
					else
					{
						this.peer.style.backgroundColor = "#FFFFCC";
						this.peer.style.borderColor = "808080";					
					}
				}	
			}
			
			this.inputReady = acceptable;
		}	
		
		return DO_DEFAULT_ACTION;
	};
	
/* **********************************************************************
 * Implement InputChangeProducer
 * ********************************************************************** */
	
JsTextInput.prototype.addInputChangeListener =
	function ( listener )
	{
		var count = this.inputChangeListeners.length;
		this.inputChangeListeners[count] = listener;
	};

JsTextInput.prototype.getInputChangeListeners =
	function ()
	{
		return this.inputChangeListeners;
	};
	
JsTextInput.prototype.removeInputChangeListener =
	function ( listener )
	{
		var index = this.inputChangeListeners.indexOf( listener );
		if ( index != -1 )
		{
			this.inputChangeListeners.splice( index, 1 );
		} 
	};
	
JsTextInput.prototype.fireInputChange =	
	function ( event )
	{
		var listeners = this.getInputChangeListeners();
		var rc = DO_DEFAULT_ACTION;
			
		if ( listeners && listeners.length > 0 )
		{
			for ( var i = 0; i < listeners.length; i++ )
			{
				var listener = listeners[i];
				if ( listener.handleInputChange )
				{
					rc = listener.handleInputChange( event );
				}
				else if ( listener )
				{
					rc = listener( event, this );
				}
			}
		}	
		
		return rc;
	};

/* **********************************************************************
 * Implement ActionProducer
 * ********************************************************************** */
	
JsTextInput.prototype.addActionListener =
	function ( listener )
	{
		var count = this.actionListeners.length;
		this.actionListeners[count] = listener;
	};

JsTextInput.prototype.getActionListeners =
	function ()
	{
		return this.actionListeners;
	};
	
JsTextInput.prototype.removeActionListener =
	function ( listener )
	{
		var index = this.actionListeners.indexOf( listener );
		if ( index != -1 )
		{
			this.actionListeners.splice( index, 1 );
		} 
	};
	
JsTextInput.prototype.fireAction =	
	function ( event )
	{
		var listeners = this.getActionListeners();
		var rc = DO_DEFAULT_ACTION;
			
		if ( listeners && listeners.length > 0 )
		{
			for ( var i = 0; i < listeners.length; i++ )
			{
				var listener = listeners[i];
				if ( listener.handleAction )
				{
					rc = listener.handleAction( event );
				}
				else if ( listener )
				{
					rc = listener( event, this );
				}
			}
		}	
		
		return rc;
	};
	
/**
 * Constructor for JSWL widgets which correspond to HTML form INPUT
 * elements of type <code>"text"</code>.
 *
 * @param wid            a unique widget id.
 * @param name           the name for the INPUT element.
 * @param initialValue   the initial value for the INPUT element.
 * @jswl-public size        the size for the text field.
 * @jswl-public maxLength   the maximum accepted length for the text field.
 * @jswl.public styleClass  the CSS classname for the HTML element.
 * @jswl-public render      render the markup for this widget. 
 */
 
function JsTextField( wid, name, initialValue, fieldType )
{
	this.base = JsTextInput;
	this.base( wid, name, initialValue );
	this.fieldType = ( fieldType || STANDARD_TEXT_FIELD_TYPE );	
	this.size = 32;
	this.maxLength = 32;
	this.styleClass = 'jsTextField';
	this.addEventListener( 'onkeyup', this );	
	this.addInputChangeListener( this );
} 

JsTextField.prototype = new JsTextInput;
	
/* **********************************************************************
 * Implement Renderable
 * ********************************************************************** */
	
JsTextField.prototype.renderTo =
	function ( out )
	{	
		var html;
		
		html = '<input ';
		
		if ( this.fieldType == PASSWORD_TEXT_FIELD_TYPE ) { html += 'type="password" '; }
		else if ( this.fieldType == FILE_TEXT_FIELD_TYPE ) { html += 'type="file" '; }
		else { html += 'type="text" '; }	
			
		html += 'id="' + this.wid + '" ';
		html += 'name="' + this.name + '" ' ;
		html += 'value="' + this.value + '" ';
		
		if ( this.size ) { html += 'size="' + this.size + '" '; }
		if ( this.maxLength ) { html += 'maxlength="' + this.maxLength + '" '; }
		if ( this.styleClass ) { html += 'class="' + this.styleClass + '" '; }
		if ( !this.enabled ) { html += 'disabled="disabled" '; }
		
		html += 'style="';
		if ( !this.isVisible ) { html += 'display: none;' }
		html += '" ';
				
		html += '></input>';
		
		out.write(html); 
	};

/**
 * Constructor for JSWL widgets which correspond to HTML form INPUT
 * elements of type <code>"password"</code>.
 *
 * @param wid            a unique widget id.
 * @param name           the name for the INPUT element.
 * @param initialValue   the initial value for the INPUT element.
 * @jswl-public size        the size for the text field.
 * @jswl-public maxLength   the maximum accepted length for the text field.
 * @jswl.public styleClass  the CSS classname for the HTML element.
 * @jswl-public render      render the markup for this widget. 
 */
 
function JsPasswordField( wid, name )
{
	this.base = JsTextField;
	this.base( wid, name, '', PASSWORD_TEXT_FIELD_TYPE );
	this.styleClass = 'jsPasswordField';
} 

JsPasswordField.prototype = new JsTextField;
	
JsPasswordField.prototype.setText =
	function ( text )
	{
		throw 'Text cannot be set programatically on a password text field.';
	};
		
/**
 * Constructor for JSWL widgets which correspond to HTML form INPUT
 * elements of type <code>"file"</code>.
 *
 * @param wid            a unique widget id.
 * @param name           the name for the INPUT element.
 * @param initialValue   the initial value for the INPUT element.
 * @jswl-public size        the size for the text field.
 * @jswl-public maxLength   the maximum accepted length for the text field.
 * @jswl.public styleClass  the CSS classname for the HTML element.
 * @jswl-public render      render the markup for this widget. 
 */
 
function JsFileField( wid, name )
{
	this.base = JsTextField;
	this.base( wid, name, '', FILE_TEXT_FIELD_TYPE );
	this.styleClass = 'jsFileField';
} 

JsFileField.prototype = new JsTextField;
	
JsFileField.prototype.setText =
	function ( text )
	{
		throw 'Text cannot be set programatically on a file text field.';
	};
	
/**
 * Constructor for JSWL widgets which correspond to HTML form TEXTAREA
 * elements.
 *
 * @param wid            a unique widget id.
 * @param name           the name for the INPUT element.
 * @param initialValue   the initial value for the INPUT element.
 * @jswl-public rows        the size for the text field.
 * @jswl-public cols        the maximum accepted length for the text field.
 * @jswl-public editable    the editable flag.
 * @jswl.public styleClass  the CSS classname for the HTML element.
 * @jswl-public render      render the markup for this widget. 
 */
 
function JsTextArea( wid, name, initialValue )
{
	this.base = JsTextInput;
	this.base( wid, name, initialValue );
	this.editable = true;
	this.rows = 10;
	this.cols = 20;
	this.styleClass = 'jsTextArea';
	this.addEventListener( 'onkeyup', this );	
	this.addInputChangeListener( this );
}
	
JsTextArea.prototype = new JsTextInput;

JsTextArea.prototype.setEditable =
	function ( editable )
	{
		this.editable = editable;
		if ( this.peer )
		{
			this.peer.readOnly = this.editable;
		}
	};
	
/* **********************************************************************
 * Implement Renderable
 * ********************************************************************** */
	
JsTextArea.prototype.renderTo =
	function ( out )
	{		
		var html;
		
		html = '<textarea ';
		html += 'id="' + this.wid + '" ';
		html += 'name="' + this.name + '" ' ;
		
		if ( this.rows ) { html += 'rows="' + this.rows + '" '; }
		if ( this.cols ) { html += 'cols="' + this.cols + '" '; }
		if ( this.styleClass ) { html += 'class="' + this.styleClass + '" '; }
		if ( !this.enabled ) { html += 'disabled="disabled" '; }
		
		html += 'style="';
		if ( !this.isVisible ) { html += 'display: none;' }
		html += '" ';
				
		if ( !this.editable ) { html += 'readonly="readonly" '; }
		
		html += '>';
		html += this.value;
		html += '</textarea>';
		
		out.write(html);
	};
	
/**
 * Constructor for JSWL widgets which correspond to HTML form INPUT
 * elements of type <code>"button"</code>.
 *
 * @param wid            a unique widget id.
 * @param name           the name for the INPUT element.
 * @param initialValue   the initial value for the INPUT element.
 * @jswl.public buttonType  the type of this button widget.
 * @jswl.public styleClass  the CSS classname for the HTML element.
 * @jswl-public render      render the markup for this widget. 
 */
 
function JsButton( wid, name, initialValue, buttonType )
{
	this.base = JsInput;
	this.base( wid, name, initialValue );	
	this.buttonType = ( buttonType || STANDARD_BUTTON_TYPE );
	this.styleClass = 'jsButton';
	this.actionListeners = new Array();
	this.addEventListener( 'onclick', this );
}

JsButton.prototype = new JsInput;

JsButton.prototype.setText =
	function( text )
	{
		this.value = text;
		if ( this.peer )
		{
			this.peer.value = this.value;
		}
	};
	
/* **********************************************************************
 * Implement Renderable
 * ********************************************************************** */

JsButton.prototype.renderTo =
	function ( out )
	{	
		var html;
		
		html = '<input ';
		
		if ( this.buttonType == SUBMIT_BUTTON_TYPE ) { html += 'type="submit" '; }
		else if ( this.buttonType == RESET_BUTTON_TYPE ) { html += 'type="reset" '; }
		else if ( this.buttonType == CHECKBOX_BUTTON_TYPE ) { html += 'type="checkbox" '; }
		else if ( this.buttonType == RADIO_BUTTON_TYPE ) { html += 'type="radio" '; }
		else if ( this.buttonType == IMAGE_BUTTON_TYPE ) { html += 'type="image" '; }
		else { html += 'type="button" '; }
		
		html += 'id="' + this.wid + '" ';
		html += 'name="' + this.name + '" ' ;
		html += 'value="' + this.value + '" ';
		if ( ( this.buttonType == CHECKBOX_BUTTON_TYPE || this.buttonType == RADIO_BUTTON_TYPE ) && this.selected ) { html += 'checked="checked" '; } 
		if ( ( this.buttonType == IMAGE_BUTTON_TYPE ) && this.src ) { html += 'src="' + this.src + '" '; }
		
		if ( this.styleClass ) { html += 'class="' + this.styleClass + '" '; }
		if ( !this.enabled ) { html += 'disabled="disabled" '; }
		
		html += 'style="';
		if ( !this.isVisible ) { html += 'display: none;' }
		html += '" ';
				
		html += '>';
		if ( ( this.buttonType == CHECKBOX_BUTTON_TYPE || this.buttonType == RADIO_BUTTON_TYPE ) && this.label ) { html += this.label; }
		html += '</input>';
		
		out.write(html);
	};

/* **********************************************************************
 * Implement EventListener
 * ********************************************************************** */
 
JsButton.prototype.handleEvent =
	function ( event, type )
	{
		var rc = DO_DEFAULT_ACTION;
		
		if ( type == 'onclick' )
		{
			if ( ( this.buttonType == CHECKBOX_BUTTON_TYPE || this.buttonType == RADIO_BUTTON_TYPE ) && this.syncSelectionWithPeer ) { this.syncSelectionWithPeer(); }
			rc = this.fireAction( event );
		}		
		
		return rc;
	};	
	
/* **********************************************************************
 * Implement ActionProducer
 * ********************************************************************** */
	
JsButton.prototype.addActionListener =
	function ( listener )
	{
		var count = this.actionListeners.length;
		this.actionListeners[count] = listener;
	};

JsButton.prototype.getActionListeners =
	function ()
	{
		return this.actionListeners;
	};
	
JsButton.prototype.removeActionListener =
	function ( listener )
	{
		var index = this.actionListeners.indexOf( listener );
		if ( index != -1 )
		{
			this.actionListeners.splice( index, 1 );
		} 
	};
	
JsButton.prototype.fireAction =	
	function ( event )
	{
		var listeners = this.getActionListeners();
		var rc = DO_DEFAULT_ACTION;
			
		if ( listeners && listeners.length > 0 )
		{
			for ( var i = 0; i < listeners.length; i++ )
			{
				var listener = listeners[i];
				if ( listener.handleAction )
				{
					rc = listener.handleAction( event );
				}
				else if ( listener )
				{
					rc = listener( event, this );
				}
			}
		}	
		
		return rc;
	};

/**
 * Constructor for JSWL widgets which correspond to HTML form image
 * input elements.
 *
 * @param wid            a unique widget id.
 * @param name           the name for the INPUT element.
 * @param initialValue   the initial value for the INPUT element.
 */
 
function JsImageButton( wid, name, uri, width, height )
{
	this.base = JsButton;
	this.base( wid, name, '', IMAGE_BUTTON_TYPE );
	this.image = ((width && height) ? new Image( width, height ) : new Image());
	this.image.src = uri;
	this.image.align = 'absmiddle';
	this.image.border = 0;	
}

JsImageButton.prototype = new JsButton;
	
JsImageButton.prototype.setUri =
	function ( uri )
	{
		this.image.src = uri;
		if ( this.peer )
		{
			this.peer.src = this.image.src;
		}
	};
	
/**
 * Constructor for JSWL widgets which correspond to HTML form submit
 * input elements.
 *
 * @param wid            a unique widget id.
 * @param name           the name for the INPUT element.
 * @param initialValue   the initial value for the INPUT element.
 */
 
function JsSubmitButton( wid, name, initialValue )
{
	this.base = JsButton;
	this.base( wid, name, (initialValue || 'Submit' ), SUBMIT_BUTTON_TYPE );
}

JsSubmitButton.prototype = new JsButton;

/**
 * Constructor for JSWL widgets which correspond to HTML form reset
 * input elements.
 *
 * @param wid            a unique widget id.
 * @param name           the name for the INPUT element.
 * @param initialValue   the initial value for the INPUT element.
 */
 
function JsResetButton( wid, name, initialValue )
{
	this.base = JsButton;
	this.base( wid, name, (initialValue || 'Reset' ), RESET_BUTTON_TYPE );
}

JsResetButton.prototype = new JsButton;

/**
 * Constructor for JSWL widgets which correspond to HTML form checkbox
 * input elements.
 *
 * @param wid            a unique widget id.
 * @param name           the name for the INPUT element.
 * @param initialValue   the initial value for the INPUT element.
 */

function JsToggleButton( wid, name, initialValue, type, label, selected )
{
	this.base = JsButton;
	this.base( wid, name, initialValue, type );
	this.label = label;
	this.selected = selected;
}
 	
JsToggleButton.prototype = new JsButton;
	
JsToggleButton.prototype.setText =
	function( text )
	{
		this.label = text;
		if ( this.peer )
		{
			this.peer.innerHTML = this.label;
		}
	};
	
JsToggleButton.prototype.setSelected =
	function( selected )
	{
		this.selected = selected;
		if ( this.peer )
		{
			this.peer.checked = selected;
			this.fireAction( createEvent( 'change', true ) );
		}
	};
	
JsToggleButton.prototype.syncSelectionWithPeer =
	function ()
	{
		if ( this.peer )
		{
			this.selected = this.peer.checked;
		}	
	};
		
JsToggleButton.prototype.link = 
	function ()
	{
		if ( this.peer )
		{
			this.syncSelectionWithPeer();
			
			if ( this.acceptInput )
			{
				this.inputReady = this.acceptInput();
			}		
				
			if ( this.inputReady && this.enableVisualCues )
			{
				this.peer.style.backgroundColor = "#C0FFC0";
				this.peer.style.borderColor = "#40FF40";
			}
		}
	};

/**
 * Constructor for JSWL widgets which correspond to HTML form checkbox
 * input elements.
 *
 * @param wid            a unique widget id.
 * @param name           the name for the INPUT element.
 * @param initialValue   the initial value for the INPUT element.
 */

function JsCheckbox( wid, name, initialValue, label, selected )
{
	this.base = JsToggleButton;
	this.base( wid, name, initialValue, CHECKBOX_BUTTON_TYPE, label, selected );
}

JsCheckbox.prototype = new JsToggleButton;

/**
 * Constructor for JSWL widgets which correspond to HTML form radio button
 * input elements.
 *
 * @param wid            a unique widget id.
 * @param name           the name for the INPUT element.
 * @param initialValue   the initial value for the INPUT element.
 */

function JsRadioButton( wid, name, initialValue, label, selected )
{
	this.base = JsToggleButton;
	this.base( wid, name, initialValue, RADIO_BUTTON_TYPE, label, selected );
}

JsRadioButton.prototype = new JsToggleButton;
			
/**
 * Constructor for JSWL widgets which correspond to HTML form list
 * input elements (SELECT).
 * <p>
 * <strong>NOTE:</strong> The <code>value</code> for a {@link JsList}
 * object is an _array_ that contains the indices of all selected items.
 * </p>
 *
 * @param wid            a unique widget id.
 * @param name           the name for the INPUT element.
 */	

function JsChoice( wid, name, selectionType, listModel )
{
	this.base = JsInput;
	this.base( wid, name, new Array() );
	this.size = 1;
	this.selectionType = selectionType;
	this.styleClass = ( this.selectionType == SINGLE_SELECT_TYPE ? 'jsComboBox' : 'jsList' );
	this.listModel = ( listModel || new DefaultListModel() );
	this.selectionChangeListeners = new Array();
	this.addEventListener( 'onchange', this );
	this.addSelectionChangeListener( this );	
	this.listModel.addListModelChangeListener( this );
}

JsChoice.prototype = new JsInput;
	
JsChoice.prototype.getElementAt =
	function ( index )
	{
		return this.listModel.getElementAt( index );
	};
	
JsChoice.prototype.getModel =
	function ()
	{
		return this.listModel;
	};
		
JsChoice.prototype.isSelectionEmpty =
	function ()
	{
		return ( this.value == null || this.value.length == 0 );
	};
	
JsChoice.prototype.isElementSelected =
	function ( element )
	{
		var index = this.listModel.indexOf( element );
		return ( index != -1 && this.isIndexSelected( index ) );
	};
	
JsChoice.prototype.isIndexSelected =
	function ( index )
	{
		return ( this.value.indexOf( index ) != -1 );
	};
	
JsChoice.prototype.setSelectedElement =
	function ( element )
	{
		var index = this.listModel.indexOf( element );
		if ( index != -1 )
		{
			this.setSelectedIndex( index );
		}	
	};
	
JsChoice.prototype.setSelectedIndex =
	function ( index )
	{
		var element = this.listModel.getElementAt( index );
		
		if ( element != null )
		{
			this.value.length = 0;
			this.value[0] = index;
			
			if ( this.peer )
			{
				this.peer.selectedIndex = index;
			}
			
			this.fireSelectionChange( createEvent( "change", true ) );
		}
	};
	
JsChoice.prototype.addSelectedElement =
	function ( element )
	{
		if ( this.selectionType == SINGLE_SELECT_TYPE && this.value.length > 0 )
		{
			throw "Selection type does not allow multiple selections.";
		}
		
		var index = this.listModel.indexOf( element );
		if ( index != -1 )
		{
			this.addSelectedIndex( index );
		}
	};
	
JsChoice.prototype.addSelectedIndex =
	function ( index )
	{
		if ( this.selectionType == SINGLE_SELECT_TYPE && this.value.length > 0 )
		{
			throw "Selection type does not allow multiple selections.";
		}
		
		var element = this.listModel.getElementAt( index );
		
		if ( element != null && ( this.indexOf( index ) != -1 ) )
		{
			var count = this.value.length;
			this.value[count] = index;
			this.value.sort();
			
			if ( this.peer )
			{
				for ( var i = 0; i < this.peer.options.length; i++ )
				{
					var option = this.peer.options[i];
					if ( i == index )
					{
						option.selected = true;
					}
				}
			}
			
			this.fireSelectionChange( createEvent( "change", true ) );
		}	
	};
	
JsChoice.prototype.removeSelectedElement =
	function ( element )
	{
		var index = this.listModel.indexOf( element );
		if ( index != -1 )
		{
			this.removeSelectedIndex( index );
		}
	};
	
JsChoice.prototype.removeSelectedIndex =
	function ( index )
	{
		var element = this.listModel.getElementAt( index );
		
		if ( element != null  )
		{
			var j = this.value.indexOf( index );
			
			if ( j != -1 )
			{
				this.value.splice( j, 1 );
			
				if ( this.peer )
				{
					for ( var i = 0; i < this.peer.options.length; i++ )
					{
						var option = this.peer.options[i];
						if ( i == index )
						{
							option.selected = false;
						}
					}
				}
				
				this.fireSelectionChange( createEvent( 'change', true ) );
			}
		}	
	};
	
JsChoice.prototype.handleListModelChange =
	function ( event )
	{
		if ( this.peer )
		{
			var	changeType = event.modelChangeType;
			var	index = event.modelChangeData;
			
			switch ( changeType )
			{
				case JWT_INSERT:
					var element = this.listModel.getElementAt(index);
					if ( element != null )
					{
						var label = ( this.listModel.getLabelAt ? this.listModel.getLabelAt(index) : element );
						this.peer.options[index] = new Option(label, element);
					}
					break;
					
				case JWT_DELETE:
					this.peer.options[index] = null;
					break;
					
				case JWT_UPDATE:
					// TODO
					break;
			}
		}
		
		return CANCEL_DEFAULT_ACTION;
	};
		
JsChoice.prototype.syncSelectionWithPeer =
	function ()
	{
		if ( this.peer )
		{
			if ( this.selectionType == SINGLE_SELECT_TYPE )
			{
				this.value.length = 0;
				if ( this.peer.selectedIndex >= 0 )
				{
					this.value[0] = this.peer.selectedIndex;
				}
			}
			else
			{
				var count = this.peer.options.length;
				var j = 0;
			
				this.value.length = 0;
				
				for ( var i = 0; i < count; i++ )
				{
					var option = this.peer.options[i];
					if ( option.selected )
					{
						this.value[j++] = i;
					}
				}	
			}
		}	
	};
	
JsChoice.prototype.link = 
	function ()
	{
		if ( this.peer )
		{
			if ( this.isSelectionEmpty() )
			{
				this.syncSelectionWithPeer();
			}
			
			if ( this.acceptInput )
			{
				this.inputReady = this.acceptInput();
			}		
				
			if ( this.inputReady && this.enableVisualCues )
			{
				this.peer.style.backgroundColor = "#C0FFC0";
				this.peer.style.borderColor = "#40FF40";
			}
		}
	};
			
/* **********************************************************************
 * Implement Renderable
 * ********************************************************************** */
 		
JsChoice.prototype.renderTo =
	function ( out )
	{	
		var html;
		
		html = '<select ';
		
		html += 'id="' + this.wid + '" ';
		html += 'name="' + this.name + '" ' ;
		
		if ( this.size ) { html += 'size="' + this.size + '" '; }
		if ( this.selectionType == MULTI_SELECT_TYPE ) { html += 'multiple="multiple" ' };
		if ( this.styleClass ) { html += 'class="' + this.styleClass + '" '; }
		if ( !this.enabled ) { html += 'disabled="disabled" '; }
		
		html += 'style="';
		if ( !this.isVisible ) { html += 'display: none;' }
		html += '" ';
				
		html += '>';
		
		if ( this.listModel )
		{
			var listSize = this.listModel.size();
			
			for ( var i = 0; i < listSize; i++ )
			{
				var element = this.listModel.getElementAt(i);
				var value = element.toString();
				var label;
				
				label = ( this.listModel.getLabelAt ? this.listModel.getLabelAt(i) : value );
					
				html += '<option value="' + value + '" label="' + label + '" ';
				if ( this.isIndexSelected(i) )
				{
					html += 'selected="selected" ';
				}
				html += '>';
				html += label;
				html += '</option>';
			}
		}
		
		html += '</select>';
		
		out.write(html);
	};
	
/* **********************************************************************
 * Implement EventListener
 * ********************************************************************** */
 
JsChoice.prototype.handleEvent =
	function ( event, type )
	{
		var rc = DO_DEFAULT_ACTION;
		
		if ( type == 'onchange' )
		{
			this.syncSelectionWithPeer();				
			rc = this.fireSelectionChange( event );
		}
		
		return rc;
	};	
	
/* **********************************************************************
 * Implement SelectionChangeListener
 * ********************************************************************** */
 
JsChoice.prototype.handleSelectionChange =
	function ( event )
	{
		if ( this.acceptInput )
		{
			var acceptable = this.acceptInput();
			
			if ( this.enableVisualCues )
			{
				if ( !this.inputReady && acceptable )
				{
					if ( this.enableAnimation )
					{
						fade( this.peer, 0, "FFFFCC", "C0FFC0", 10, 30 );
						fade( this.peer, 1, "808080", "40FF40", 10, 30 );
					}
					else
					{
						this.peer.style.backgroundColor = "#C0FFC0";
						this.peer.style.borderColor = "#40FF40";
					}
				}
				else if ( this.inputReady && !acceptable )
				{
					if ( this.enableAnimation )
					{
						fade( this.peer, 0, "C0FFC0", "FFFFCC", 10, 30 );
						fade( this.peer, 1, "40FF40", "808080", 10, 30 );
					}
					else
					{
						this.peer.style.backgroundColor = "#FFFFCC";
						this.peer.style.borderColor = "808080";					
					}
				}	
			}
			
			this.inputReady = acceptable;
		}	
		
		return DO_DEFAULT_ACTION;
	};
		
/* **********************************************************************
 * Implement SelectionChangeProducer
 * ********************************************************************** */
	
JsChoice.prototype.addSelectionChangeListener =
	function ( listener )
	{
		var count = this.selectionChangeListeners.length;
		this.selectionChangeListeners[count] = listener;
	};

JsChoice.prototype.getSelectionChangeListeners =
	function ()
	{
		return this.selectionChangeListeners;
	};
	
JsChoice.prototype.removeSelectionChangeListener =
	function ( listener )
	{
		var index = this.selectionChangeListeners.indexOf( listener );
		if ( index != -1 )
		{
			this.selectionChangeListeners.splice( index, 1 );
		} 
	};
	
JsChoice.prototype.fireSelectionChange =	
	function ( event )
	{
		var listeners = this.getSelectionChangeListeners();
		var rc = DO_DEFAULT_ACTION;
		
		if ( listeners && listeners.length > 0 )
		{
			for ( var i = 0; i < listeners.length; i++ )
			{
				var listener = listeners[i];
				if ( listener.handleSelectionChange )
				{
					rc = listener.handleSelectionChange( event );
				}
				else if ( listener )
				{
					rc = listener( event, this );
				}
			}
		}	
		
		return rc;
	};

/**
 * Constructor for a JSWL list model.
 */	
 
function DefaultListModel()
{
	this.elements = new Array();
	this.modelChangeListeners = new Array();
}

DefaultListModel.prototype.add =
	function ( element )
	{
		var count = this.elements.length;
		this.elements[count] = element;
		this.fireElementAdded(count);
	};
	
DefaultListModel.prototype.remove =
	function ( element )
	{
		var index = this.elements.indexOf( element );
		if ( index != -1 )
		{
			this.elements.splice( index, 1 );
			this.fireElementRemoved(index);
		}
	};
	
DefaultListModel.prototype.removeAll =
	function ()
	{
		for ( var i = this.elements.length - 1; i >= 0; i-- )
		{
			this.remove(this.elements[0]);
		}
	};
		
DefaultListModel.prototype.fireElementAdded =
	function ( index )
	{
		var event = createEvent( 'change', true );
		event.modelChangeType = JWT_INSERT;
		event.modelChangeData = index;
		this.fireListModelChange( event );
	};
	
DefaultListModel.prototype.fireElementRemoved =
	function ( index )
	{
		var event = createEvent( 'change', true );
		event.modelChangeType = JWT_DELETE;
		event.modelChangeData = index;
		this.fireListModelChange( event );
	};
		
DefaultListModel.prototype.fireElementUpdated =
	function ( index)
	{
		var event = createEvent( 'change', true );
		event.modelChangeType = JWT_UPDATE;
		event.modelChangeData = index;
		this.fireListModelChange( event );
	};
		
/* **********************************************************************
 * Implement ListModelChangeProducer
 * ********************************************************************** */

DefaultListModel.prototype.addListModelChangeListener =
	function ( listener )
	{
		var count = this.modelChangeListeners.length;
		this.modelChangeListeners[count] = listener;
	};
	
DefaultListModel.prototype.getListModelChangeListeners =
	function ()
	{
		return this.modelChangeListeners;
	};
	
DefaultListModel.prototype.removeListModelChangeListener =
	function ( listener )
	{
		var index = this.modelChangeListeners.indexOf( listener );
		if ( index != -1 )
		{
			this.modelChangeListeners.splice( index, 1 );
		} 
	};
		
DefaultListModel.prototype.fireListModelChange =	
	function ( event )
	{
		var listeners = this.getListModelChangeListeners();
		var rc = DO_DEFAULT_ACTION;
		
		if ( listeners && listeners.length > 0 )
		{
			for ( var i = 0; i < listeners.length; i++ )
			{
				var listener = listeners[i];
				if ( listener.handleListModelChange )
				{
					rc = listener.handleListModelChange( event );
				}
				else if ( listener )
				{
					rc = listener( event, this );
				}
			}
		}	
		
		return rc;
	};
		
/* **********************************************************************
 * Implement ListModel
 * ********************************************************************** */
 
DefaultListModel.prototype.getElementAt =
	function ( index )
	{
		return ( index < this.elements.length ? this.elements[index] : null );
	};
	
DefaultListModel.prototype.indexOf =
	function ( element )
	{
		return this.elements.indexOf( element );
	};
	
DefaultListModel.prototype.size =
	function ()
	{
		return this.elements.length;
	};
	
/**
 * Constructor for a JSWL ID/Name list model.
 */	
 
function IdNameListModel()
{
    this.base = DefaultListModel;
    this.base();
}

IdNameListModel.prototype = new DefaultListModel;

IdNameListModel.prototype.getElementAt =
	function ( index )
	{
		return ( index < this.elements.length ? this.elements[index][0] : null );
	};
	
IdNameListModel.prototype.getLabelAt =
	function ( index )
	{
	    return ( index < this.elements.length ? this.elements[index][1] : null );
	};	
 