/* ***************************************************************
 *
 * Copyright (c) TimeFrame, Inc. 2005, 2007
 * 
 * File: jwt.js
 * Created: Saturday, July 29, 2006 (20060729)
 * Description:
 *
 * JavaScript Widget Toolkit definitions.
 * 
 * ***************************************************************
 * CHANGE HISTORY
 * ***************************************************************
 * DATE     USER       RECORD       DESCRIPTION
 * ---------------------------------------------------------------
 * 20060729 dewittsc   ------       Initial version.
 * 
 * *************************************************************** */

/* **********************************************************************

 Implied IDL Models for interfaces referenced in this file.
 
 interface WindowDecoration {
 	void				renderStartTag( in Writer out, in Window window );
 	void				renderEndTag( in Writer out, in Window window );
 }
 
 interface TableModelChangeListener {
 	boolean				handleModelChange( in Event event );
 }
 	
 interface TableModelChangeProducer {
 	void               						addTableModelChangeListener( in string type, in TableModelChangeListener listener );
 	sequence<TableModelChangeListener>   	getTableModelChangeListeners( in string type );
 	void               						removeTableModelChangeListener( in string type, in TableModelChangeListener listener );
 	void               						fireModelChange( in Event event );
 }
 
 interface TableModel:TableModelChangeProducer {
 	sequence<any>				getColumnModel( in long index );
 	long						getColumnCount();
 	long						indexOfColumn( in string id );
 	long						getRowCount();
 	any							getElementAt( in long column, in long row );
 	void						setElementAt( in any element );
 }
 
 interface TableColumnModel {
 	attribute string			id;
 	attribute string			title;
 	attribute string			width;
 	attribute string			height;
 	attribute long				alignment;
 	attribute string			styleClass;
 	attribute void *			renderer( in Writer out, in Widget widget, in any element );
 }
 
  In addition, the following function pointer types are inferred.
 
 renderProc * void ( in Writer out, in Widget widget, in any element );
 rowDecoratorProc * void ( in Writer out,  in Widget widget, in long index );
 
 * ********************************************************************** */

/* JWT Style Bits */

var 		JWT_NONE			= 0x0000;
var			JWT_TOP				= 0x0001;
var			JWT_RIGHT			= 0x0002;
var			JWT_BOTTOM			= 0x0004;
var 		JWT_LEFT			= 0x0008;
var			JWT_HALIGN_LEFT		= 0x0010;
var			JWT_HALIGN_CENTER	= 0x0020;
var			JWT_HALIGN_RIGHT	= 0x0040;
var			JWT_HALIGN_JUSTIFY	= 0x0080;
var			JWT_VALIGN_TOP		= 0x0100;
var			JWT_VALIGN_MIDDLE	= 0x0200;
var			JWT_VALIGN_BOTTOM	= 0x0400;

/* Dialog Return Values */

var			JWT_CANCEL			= 0;
var			JWT_CONFIRM			= 1;
var			JWT_HELP			= 2;

/* Model Change Types */

var			JWT_INSERT	= 0;
var			JWT_DELETE	= 1;
var			JWT_UPDATE	= 2;

/* **********************************************************************
 * ***************************** JwtLink ********************************
 * ********************************************************************** */

/**
 * The JwtLink gadget is a composite control that can be used to display a hot
 * link area.  The gadget can be is made up of an image, simple text markup, or
 * both and provides support for rollover image, style, and text effects as well
 * as mouse click effects.  If both text and an image are specified, use the <code>style</code> 
 * attribute to specify that the text widget should be to the top, right, bottom, or left
 * of the image using the appropriate JWT_* values.
 *
 * @param wid           the widget identifier.
 * @param action        the script action.
 * @param text          the text.
 * @param imageUri      the image URI.
 * @param imageWidth    the image width.
 * @param imageHeight   the image height.
 * @param style         the style bits.
 */
 
function JwtLink( wid, text, linkUri, imageUri, imageWidth, imageHeight, style, target )
{
	this.base = JsContainer;
	this.base(wid);
	
	this.text = text;
	this.textOver = null;
	this.textClick = null;
	this.textDisabled = null;
	this.imageUri = imageUri;
	this.imageUriOver = null;
	this.imageUriClick = null;
	this.imageUriDisabled = null;
	this.styleClass = 'jwtLink';
	this.styleClassOver = 'jwtLinkOver';
	this.styleClassClick = 'jwtLinkClick';	
	this.styleClassDiabled = 'jwtLinkDisabled';
	
	if ( this.imageUri != null )
	{
		this.wImage = new JsImage( wid + '.image', this.imageUri, imageWidth, imageHeight );
	}	
	
	if ( this.text != null )
	{
		var link = 'javascript:void(0)';
		
		if ( linkUri != null && !( linkUri instanceof Function ) )
		{
			link = linkUri;
		}
		
		this.wLink = new JsLink( wid + '.link', null, link, text, target );
		this.wLink.setStyleClass('');	
	}
 
	var layout;
	
	if ( this.wImage && this.wLink )
	{
		if ( ( style & JWT_LEFT ) > 0 )
		{
			layout = jsHorizontalLayout;
			this.add( this.wLink );
			this.add( this.wImage );
		}
		else if ( ( style & JWT_BOTTOM ) > 0 )
		{
			layout = jsVerticalLayout;
			this.add( this.wImage );
			this.add( this.wLink );
		}
		else if ( ( style & JWT_TOP ) > 0 )
		{
			layout = jsVerticalLayout;
			this.add( this.wLink );
			this.add( this.wImage );
		}
		else
		{
			layout = jsHorizontalLayout;
			this.add( this.wImage );
			this.add( this.wLink );
		}
	}
	else
	{
		layout = jsHorizontalLayout;
		if ( this.wImage )
		{
			this.add( this.wImage );
		}
		else if ( this.wLink )
		{
			this.add( this.wLink );
		}
	}
	
	this.setLayout( layout );
	this.actionListeners = new Array();
	this.addEventListener( 'onkeypress', this );
	this.addEventListener( 'onmouseover', this );
	this.addEventListener( 'onmouseout', this );
	this.addEventListener( 'onmousedown', this );
	this.addEventListener( 'onmouseup', this );
	
	if ( this.wLink )
	{
		/* If the link URI is non-null, we need to make sure that a click on the peer table will invoke the URI
		   just like a click on the link element.  If the link URI is null, we have set the URI automatically to
		   # and we need to make sure this is not processed so we have to stop the even propagation and cancel
		   the default action. Lastly, if the link URI is a function (used to build a dynamic URL), we make sure
		   that it is invoked properly. */
		   
		if ( linkUri != null && ( linkUri instanceof Function || trim(linkUri).length > 0 ) )
		{
			if ( linkUri instanceof Function )
			{
				this.uriResolver = linkUri;
				this.addActionListener( function ( event, widget ) { return widget.openLink( widget.uriResolver( event, widget ) ); } );			
			}
			else
			{
				this.addActionListener( function ( event, widget ) { return widget.openLink( widget.wLink.href ); } );
			}
		}
		//else
		//{
			this.wLink.addActionListener( function ( event, widget ) { stopEventPropagation( event ); return CANCEL_DEFAULT_ACTION; } );
		//}
	}
}

JwtLink.prototype = new JsContainer;
	
JwtLink.prototype.openLink =
	function ( uri )
	{
		if ( this.wLink.target )
		{
			var winId = this.wLink.target + 'Win';
			var targetWin = window[winId];
			if ( targetWin && !targetWin.closed ) { targetWin.focus(); targetWin.location = uri; }
			else { window[winId] = window.open( uri, winId, 'width=800,height=800,location=yes,directories=yes,scrollbars=yes,resizable=yes,status=yes,menubar=yes,toolbar=yes' ); }
		}
		else
		{
			document.location = uri;
		}
		
		return CANCEL_DEFAULT_ACTION;
	};
	
JwtLink.prototype.setFocus =
	function( focus )
	{
		if ( this.wLink && this.wLink.peer )
		{
			setFocus( this.wLink.peer, focus );
		}	
	};
	
/**
 * Set the enablement state of this gadget.
 *
 * @param enabled   the enablement state.
 */
  
JwtLink.prototype.setEnabled =
	function ( enabled )
	{
		this.enabled = enabled;
		if ( this.peer )
		{
			this.applyEnablementStyle( enabled );
		}
	};
		
/**
 * Set the text of the text widget for this link gadget.  If the gadget does not contain a text widget
 * (no text was specified on creation), do nothing.
 *
 * @param text   the text.
 */
 
JwtLink.prototype.setText =
	function ( text )
	{
		if ( this.wLink != null )
		{
			this.text = text;
			this.wLink.setText( this.text );
		}
	};
	
/**
 * Set the rollover text for the text widget for this link gadget.  If the gadget does not contain a
 * text widget (no text was specified on creation), do nothing.
 *
 * @param text   the text.
 */
 	
JwtLink.prototype.setRolloverText =
	function ( text )
	{
		if ( this.wLink != null )
		{
			this.textOver = text;
		}
	};
	
/**
 * Set the click text for the text widget for this link gadget.  If the gadget does not contain a
 * text widget (no text was specified on creation), do nothing.
 *
 * @param text   the text.
 */
 	
JwtLink.prototype.setClickText =
	function ( text )
	{
		if ( this.wLink != null )
		{
			this.textClick = text;
		}
	};
	
/**
 * Set the disabled text for the text widget for this link gadget.  If the gadget does not contain a
 * text widget (no text was specified on creation), do nothing.
 *
 * @param text   the text.
 */
 	
JwtLink.prototype.setDisabledText =
	function ( text )
	{
		if ( this.wLink != null )
		{
			this.textDisabled = text;
		}
	};
			
/**
 * Set the URI of the image widget for this link gadget.  If the gadget does not contain an image widget
 * (no image was specified on creation), do nothing.  NOTE: The width and height cannot be changed
 * after creation of the gadget.
 *
 * @param uri   the image uri.
 */
 	
JwtLink.prototype.setImage =
	function ( uri )
	{
		if ( this.wImage != null )
		{
			this.imageUri = uri;
			this.wImage.setUri( this.imageUri );
		}
	};

/**
 * Set the URI of the rollover image for the image widget for this link gadget.  If the gadget does not contain an
 * image widget (no image was specified on creation), do nothing.  NOTE: The width and height cannot be changed
 * after creation of the gadget.
 *
 * @param uri   the image uri.
 */
 	
JwtLink.prototype.setRolloverImage =
	function ( uri )
	{
		if ( this.wImage != null )
		{
			this.imageUriOver = uri;
		}
	};	
	
/**
 * Set the URI of the click image for the image widget for this link gadget.  If the gadget does not contain an
 * image widget (no image was specified on creation), do nothing.  NOTE: The width and height cannot be changed
 * after creation of the gadget.
 *
 * @param uri   the image uri.
 */
 	
JwtLink.prototype.setClickImage =
	function ( uri )
	{
		if ( this.wImage != null )
		{
			this.imageUriClick = uri;
		}
	};	

/**
 * Set the URI of the disabled image for the image widget for this link gadget.  If the gadget does not contain an
 * image widget (no image was specified on creation), do nothing.  NOTE: The width and height cannot be changed
 * after creation of the gadget.
 *
 * @param uri   the image uri.
 */
 	
JwtLink.prototype.setDisabledImage =
	function ( uri )
	{
		if ( this.wImage != null )
		{
			this.imageUriDisabled = uri;
		}
	};
	
/**
 * Set the style class for the rollover style for this link gadget.
 *
 * @param styleClass   the CSS style class.
 */
 		
JwtLink.prototype.setRolloverClass =
	function ( styleClass )
	{
		this.styleClassOver = styleClass;
	};

/**
 * Set the style class for the click style for this link gadget.
 *
 * @param styleClass   the CSS style class.
 */
 	
JwtLink.prototype.setClickClass =
	function ( styleClass )
	{
		this.styleClassClick = styleClass;
	};	
	
/**
 * Set the style class for the disabled style for this link gadget.
 *
 * @param styleClass   the CSS style class.
 */
 	
JwtLink.prototype.setDisabledClass =
	function ( styleClass )
	{
		this.styleClassDisabled = styleClass;
	};
				
/**
 * Set the alignment property of the image widget used for this link gadget.  If the gadget does not contain an
 * image widget (no image was specified on creation), do nothing.
 *
 * @param align   the alignment.
 */
 			
JwtLink.prototype.setImageAlignment =
	function ( align )
	{
		if ( this.wImage != null )
		{
			this.wImage.setAlignment( align );
		}
	};
	
/**
 * Set the border property of the image widget used for this link gadget.  If the gadget does not contain an
 * image widget (no image was specified on creation), do nothing.
 *
 * @param border   the border width.
 */
 			
JwtLink.prototype.setImageBorder =
	function ( border )
	{
		if ( this.wImage != null )
		{
			this.wImage.setBorder( border );
		}
	};

JwtLink.prototype.applyInitialStyle =
	function ()
	{
		if ( this.styleClass && this.peer ) 
		{ 
			this.peer.className = this.styleClass;
			if ( this.wImage && this.imageUri != null ) { this.wImage.setUri( this.imageUri ); }
			if ( this.wLink && this.text != null ) { this.wLink.setText( this.text ); }
		}
	};
	
JwtLink.prototype.applyOverStyle =
	function ()
	{
		if ( this.styleClassOver && this.peer ) 
		{ 
			this.peer.className = this.styleClassOver;
			if ( this.wImage && this.imageUriOver != null ) { this.wImage.setUri( this.imageUriOver ); }
			if ( this.wLink && this.textOver != null ) { this.wLink.setText( this.textOver ); }
		}			
	};
	
JwtLink.prototype.applyClickStyle =
	function ()
	{
		if ( this.styleClassClick && this.peer ) 
		{ 
			this.peer.className = this.styleClassClick;
			if ( this.wImage && this.imageUriClick != null ) { this.wImage.setUri( this.imageUriClick ); }
			if ( this.wLink && this.textClick!= null ) { this.wLink.setText( this.textClick ); }
		}
	};
	
JwtLink.prototype.applyEnablementStyle =
	function ( enabled )
	{
		if ( enabled )
		{
			this.applyInitialStyle();
		}
		else
		{
			if ( this.styleClassDisabled && this.peer ) 
			{ 
				this.peer.className = this.styleClassDisabled;
				if ( this.wImage && this.imageUriDisabled != null ) { this.wImage.setUri( this.imageUriDisabled ); }
				if ( this.wLink && this.textDisabled != null ) { this.wLink.setText( this.textDisabled ); }
			}
		}	
	};
					
/**
 * Simulate a click element on this link.
 */
 
JwtLink.prototype.click =
	function ()
	{
		return this.fireAction( createEvent( "mouseup", true ) );
	};

/* **********************************************************************
 * Implement EventListener
 * ********************************************************************** */
	
JwtLink.prototype.handleEvent =
	function ( event, type )
	{
		var rc = DO_DEFAULT_ACTION;
		
		if ( this.enabled )
		{
			if ( type == 'onmouseover' )
			{
				this.applyOverStyle();
				rc = CANCEL_DEFAULT_ACTION;
			}
			else if ( type == 'onmousedown' )
			{
				this.applyClickStyle();
				rc = CANCEL_DEFAULT_ACTION;
			}
			else if ( type == 'onmouseout' || type == 'onmouseup' )
			{		
				this.applyInitialStyle();		
				if ( type == 'onmouseup' )
				{
					rc = this.fireAction( event );
				}
				else
				{
					rc = CANCEL_DEFAULT_ACTION;
				}
			}
			else if ( type == 'onkeypress' )
			{
				if ( isEnterKey( getKey( event ) ) )
				{
					rc = this.fireAction( event );
				}
			}
		}
		
		return rc;
	};
	
/* **********************************************************************
 * Implement ActionProducer
 * ********************************************************************** */
	
JwtLink.prototype.addActionListener =
	function ( listener )
	{
		var count = this.actionListeners.length;
		this.actionListeners[count] = listener;
	};

JwtLink.prototype.getActionListeners =
	function ()
	{
		return this.actionListeners;
	};
	
JwtLink.prototype.removeActionListener =
	function ( listener )
	{
		var index = this.actionListeners.indexOf( listener );
		if ( index != -1 )
		{
			this.actionListeners.splice( index, 1 );
		} 
	};
	
JwtLink.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;
	};	
		
/* **********************************************************************
 * ************************* JwtDateChooser *****************************
 * ********************************************************************** */
 
/**
 * The JwtDateChooser gadget is a composite control that can be used to display a 
 * <em>gregorian</em> calendar for selecting dates.
 *
 * @param wid           the widget identifier.
 * @param style         the style bits.
 */
 
function JwtDateChooser( wid, initialDate, style )
{
	this.base = JsWidget;
	this.base(wid);	
	this.styleClass = 'jwtDateChooser';
	this.selectedDate = initialDate;
	this.allowPastSelection = false;
	this.closeCallback = null;
	this.date = new Date( this.selectedDate.getFullYear(), this.selectedDate.getMonth(), this.selectedDate.getDate() );
	this.selectionChangeListeners = new Array();
	this.superclass = new JsWidget();
}

JwtDateChooser.prototype = new JsWidget;
		
JwtDateChooser.prototype.setVisible =
	function ( visibility, transition, updateFocus )
	{
		this.superclass.setVisible.call( this, visibility, transition );
		if ( updateFocus )
		{
			if ( this.isVisible )
			{
				this.grabFocus();
			}
			else
			{
				this.releaseFocus();
			}
		}
	};
	
JwtDateChooser.prototype.setAllowPastSelection =
	function ( allow )
	{
		this.allowPastSelection = allow;
		// TODO if visible, regenerate the table.
	};
	
JwtDateChooser.prototype.setCloseCallback =
	function ( callback )
	{
		this.closeCallback = callback;
	};
		
JwtDateChooser.prototype.isLeapYear =
	function( year )
	{
		return ( ( year % 4 == 0 ) && !( year % 100 == 0 ) || ( ( year % 100 == 0 ) && ( year % 400 ) == 0 ) );
	};
	
JwtDateChooser.prototype.minusMonths =
	function ( x, months )
	{
		var lessYears = Math.round(months / 12);
		var lessMonths = months % 12;
		var newYear = x.getFullYear() - lessYears;  // not "BC" compatible - ok for now
		var newMonth = x.getMonth() - lessMonths;
		var newDay = x.getDate();
		
		if ( newMonth < 0 ) { newMonth += 12; }
		if ( newDay > GREGORIAN_DAYS_OF_MONTH[newMonth] ) { newDay = GREGORIAN_DAYS_OF_MONTH[newMonth]; }
		
		return new Date( newYear, newMonth, newDay );
	};
	
JwtDateChooser.prototype.dateEquals =
	function( x, y )
	{
		return ( x.getDate() == y.getDate() &&
				 x.getMonth() == y.getMonth() &&
				 x.getFullYear() == y.getFullYear() );
	};
	
JwtDateChooser.prototype.dateLessThan =
	function ( x, y )
	{
		var isLess = false;
		
		if ( x.getFullYear() < y.getFullYear() )
		{	
			isLess = true;
		}
		else if ( x.getFullYear() == y.getFullYear() )
		{
			if ( x.getMonth() < y.getMonth() )
			{
				isLess = true;
			}
			else if ( x.getMonth() == y.getMonth() )
			{
				isLess = ( x.getDate() < y.getDate() );
			}
		}
		
		return isLess;
	};
	
JwtDateChooser.prototype.generateCaption =
	function()
	{
		var		month = this.date.getMonth();
		var		year = this.date.getFullYear();
		
		/* <NON-NLS> */
		return ( GREGORIAN_MONTH_NAMES_EN[month] + '&nbsp;' + year );
		/* </NON-NLS> */
	};
	
JwtDateChooser.prototype.generateCell =
	function ( tmpDate, day )
	{
		var isSelected = false;
		var html = '';
		var now = new Date();
		
		tmpDate.setDate( day );
		isSelected = ( this.dateEquals( this.selectedDate, tmpDate ) );
		isPast = ( this.dateLessThan( tmpDate, now ) );
		
		if ( isSelected )
		{
			html += '<td id="' + this.wid + '.selectedDay" class="jwtDateChooserDaySelected">' + day + '</td>';
		}
		else if ( isPast )
		{
			html += '<td class="jwtDateChooserDay" style="cursor:default">' + day + '</td>';
		}
		else
		{
			html += '<td class="jwtDateChooserDay" onmouseover="setStyleClass(this,\'jwtDateChooserDayOver\'); return false;" onmouseout="setStyleClass(this,\'jwtDateChooserDay\'); return false;" onmousedown="setStyleClass(this,\'jwtDateChooserDayClick\'); return false;" onclick="return invokeWidgetMethod(\'' + this.wid + '\', \'handleSelection\', [ event, ' + day + ' ] );"><a href="#" onclick="invokeWidgetMethod(\'' + this.wid + '\', \'handleSelection\', [ event, ' + day + ' ] );stopEventPropagation(event); return CANCEL_DEFAULT_ACTION;" style="text-decoration: none;">' + day + '</a></td>';
			// <hack-for-ns-8.1>
			html += '<span style="display:none;position:absolute;" id="' + this.wid + '.dummy">&nbsp;</span>';
			// </hack-for-ns-8.1>
		}
		
		return html;
	};

JwtDateChooser.prototype.generateTable =
	function()
	{
		var		currMonth = this.date.getMonth();
		var		currYear = this.date.getFullYear();
		var		firstOfMonth = new Date( currYear, currMonth, 1 );
		var		dow = firstOfMonth.getDay();
		var		daysInMonth = GREGORIAN_DAYS_OF_MONTH[currMonth];
		var		tmpDate = new Date( this.date.getFullYear(), this.date.getMonth(), this.date.getDate() );
		var		html = '';
		
		if ( currMonth == 1 && this.isLeapYear( currYear ) )
		{
			daysInMonth++;
		}
			
		html += '<table border="0" cellspacing="0" cellpadding="0">';
		html += '<tbody>';
		html += '<tr><th>S</th><th>M</th><th>T</th><th>W</th><th>R</th><th>F</th><th>S</th></tr>';
		
		var		day = 1;
		var		currDow = dow;
		
		html += '<tr>';
		
		for ( var i = 0; i < currDow; i++ )
		{
			html += '<td>&nbsp;</td>';
		}
		
		for ( var i = currDow; i < 7; i++, day++ )
		{
			html += this.generateCell( tmpDate, day );
		}
		
		html += '</tr>';
		
		while ( day <= daysInMonth )
		{
			html += '<tr>';
			var i;
			
			for ( i = 0; i < 7 && day <= daysInMonth; i++, day++ )
			{
				html += this.generateCell( tmpDate, day );				
			}
			
			if ( i < 7 )
			{
				for ( ; i < 7; i++ )
				{
					html += '<td>&nbsp;</td>';
				}
			}
			
			html += '</tr>';
		}
		
		html += '</tbody>';
		html += '</table>';
		
		return html;	
	};
	
JwtDateChooser.prototype.showMonthBack =
	function ()
	{
		var showBack = true;
		
		if ( !this.allowPastSelection )
		{
			var now = new Date();
			var then = new Date( this.date.getFullYear(), this.date.getMonth(), 1 );
			showBack = !( this.dateLessThan( then, now ) );
		}
		
		return showBack;
	};
		
JwtDateChooser.prototype.updateControls =
	function ()
	{		
		var	backMonthCtl = getElement( this.wid + '.backMonthCtl' );
			
		if ( this.showMonthBack() )
		{
			if ( backMonthCtl ) { backMonthCtl.style.visibility = 'visible'; }
		}
		else
		{
			if ( backMonthCtl ) { backMonthCtl.style.visibility = 'hidden'; }
		}			
	};
	
JwtDateChooser.prototype.handleMonthBack =
	function( event )
	{
		var currMonth = this.date.getMonth();
		var currYear = this.date.getFullYear();
		
		currMonth = ( currMonth == 0 ? 11 : currMonth - 1 );
		if ( currMonth == 11 )
		{
			currYear -= 1;
		}
		
		this.date.setMonth( currMonth );
		this.date.setFullYear( currYear );
		
		var caption = getElement( this.wid + '.caption' );
		if ( caption != null )
		{
			caption.innerHTML = this.generateCaption();
		}
		
		var grid = getElement( this.wid + '.grid' );
		if ( grid != null )
		{
			grid.innerHTML = this.generateTable();
		}
		
		this.updateControls();		
		
		return CANCEL_DEFAULT_ACTION;
	};

JwtDateChooser.prototype.handleMonthForward =
	function( event )
	{
		var currMonth = this.date.getMonth();
		var currYear = this.date.getFullYear();
		
		currMonth = ( currMonth + 1 ) % 12;
		if ( currMonth == 0 )
		{
			currYear += 1;
		}
		
		this.date.setDate(1);
		this.date.setMonth( currMonth );
		this.date.setFullYear( currYear );
		
		var caption = getElement( this.wid + '.caption' );
		if ( caption != null )
		{
			caption.innerHTML = this.generateCaption();
		}
				
		var grid = getElement( this.wid + '.grid' );
		if ( grid != null )
		{
			grid.innerHTML = this.generateTable();
		}
		
		this.updateControls();
		
		return CANCEL_DEFAULT_ACTION;
	};
	
JwtDateChooser.prototype.handleSelection =
	function( event, day )
	{
		var	evt = getEvent( event );
		var currSelected = getElement( this.wid + '.selectedDay' );
		var	target = getEventTarget( event );
		
		if ( target && target.nodeName && target.nodeName.toLowerCase() == 'a' )
		{
			target = target.parentNode;
		}
				
		if ( target != null )
		{		
			// <hack-for-ns-8.1>
			if ( currSelected == null )
			{
				currSelected = getElement( this.wid + '.dummy' );
			}
			// </hack-for-ns-8.1>
			
			currSelected.onmouseover = target.onmouseover;
			currSelected.onmouseout = target.onmouseout;
			currSelected.onmousedown = target.onmousedown;
			currSelected.onclick = target.onclick;
			currSelected.id = '';
			setStyleClass( currSelected, 'jwtDateChooserDay' );
			
			target.onmouseover = null;
			target.onmouseout = null;
			target.onmousedown = null;
			target.onclick = null;
			target.id = this.wid + '.selectedDay';
			setStyleClass( target, 'jwtDateChooserDaySelected' );
		}
		
		this.selectedDate.setFullYear( this.date.getFullYear() );
		this.selectedDate.setMonth( this.date.getMonth() );
		this.selectedDate.setDate( day );
		this.fireSelectionChange( event );
		
		return CANCEL_DEFAULT_ACTION;
	};
	
JwtDateChooser.prototype.handleClose =
	function ( event )
	{
		if ( this.closeCallback ) { this.closeCallback( event ); }
	};
	
JwtDateChooser.prototype.getSelectedDate =
	function()
	{
		return this.selectedDate;
	};
	
JwtDateChooser.prototype.getSelectedDateString =
	function( shortForm )
	{
		return dateToString( this.selectedDate, shortForm );
	};
	
JwtDateChooser.prototype.getSelectedTime =
	function ()
	{
		return this.selectedDate.getTime();
	}
		
JwtDateChooser.prototype.setSelectedDate =
	function( date )
	{
		this.selectedDate.setFullYear( date.getFullYear() );
		this.selectedDate.setMonth( date.getMonth() );
		this.selectedDate.setDate( date.getDate() );
		this.fireSelectionChange( createEvent( "change", true ) );
	};
	
JwtDateChooser.prototype.setSelectedTime =
	function( time )
	{
		this.selectedDate.setTime( time );
		this.fireSelectionChange( createEvent( "change", true ) );
	};
	
JwtDateChooser.prototype.link =
	function()
	{
		var	backCtl = getElement( this.wid + '.backMonthCtl' );
		var fwdCtl = getElement( this.wid + '.fwdMonthCtl' );

		jsAddEventListener( backCtl, 'onclick', this );
		jsAddEventListener( fwdCtl, 'onclick', this );
	};
	
JwtDateChooser.prototype.unlink =
	function()
	{
		var	backCtl = getElement( this.wid + '.backMonthCtl' );
		var fwdCtl = getElement( this.wid + '.fwdMonthCtl' );
		
		jsRemoveEventListener( backCtl, 'onclick', this );
		jsRemoveEventListener( fwdCtl, 'onclick', this );
	};

JwtDateChooser.prototype.grabFocus =
	function ()
	{
		if ( this.peer )
		{
			var links = this.peer.getElementsByTagName('a');
			
			for ( var i = 0; i < links.length; i++ )
			{
				var a = links[i];
				a.tabIndex = 0;
			}
		}	
	};
		
JwtDateChooser.prototype.releaseFocus =
	function ()
	{
		if ( this.peer )
		{
			var links = this.peer.getElementsByTagName('a');
			
			for ( var i = 0; i < links.length; i++ )
			{
				var a = links[i];
				a.tabIndex = -1;
				if ( a.blur ) { a.blur(); }
			}
		}
	};
	
/* **********************************************************************
 * Implement Renderable
 * ********************************************************************** */

JwtDateChooser.prototype.renderTo =
	function ( out )
	{
		html = '<div ';
		html += 'id="' + this.wid + '" ';
		html += 'name="' + this.wid + '" ' ;
		
		if ( this.styleClass ) { html += 'class="' + this.styleClass + '" '; }
		
		html += 'style="';
		if ( !this.isVisible ) { html += 'display: none;' }
		html += '" ';
		
		html += '>';
		
		html += '<table border="0" ';
		html += 'cellspacing="0" cellpadding="0" ';
		html += '><tbody>';	
		
		html += '<tr><td align="center">';
		html += '<table border="0" cellspacing="0" cellpadding="0" width="100%">';
		html += '<tbody><tr>';
		html += '<td id="' + this.wid + '.backMonthCtl" style="padding-right:5px;font-weight:bold;cursor:pointer;visibility:' + ( this.showMonthBack() ? 'visible' : 'hidden' ) + '">&lt;</td><td id="' + this.wid + '.caption" style="font-weight:bold;" align="center">' + this.generateCaption() + '</td><td id="' + this.wid + '.fwdMonthCtl" style="padding-left:5px;font-weight:bold;cursor:pointer;">&gt;</td>';
		html += '</tr></tbody>';
		html += '</table>';
		html += '<tr><td id="' + this.wid + '.grid">';
		html += this.generateTable();
		html += '</td></tr>';
		html += '<tr><td colspan="3" align="right" id="' + this.wid + '.closeCtl" class="jwtCloseCtl"><a href="#" onclick="invokeWidgetMethod(\'' + this.wid + '\', \'handleClose\', [ event ] );stopEventPropagation(event);return CANCEL_DEFAULT_ACTION;">Close</a></td>';
		html += '</tr>';
		
		html += '</tbody>';
		html += '</table>';
		
		html += '</div>';
		
		out.write( html );
	};

/* **********************************************************************
 * Implement EventListener
 * ********************************************************************** */
	
JwtDateChooser.prototype.handleEvent =
	function ( event, type )
	{
		var	target = getEventTarget( event );
		var rc = CANCEL_DEFAULT_ACTION;
		
		if ( target.id )
		{
			var	wid = target.id;
			
			if ( wid == ( this.wid + '.backMonthCtl' ) )
			{
				rc = this.handleMonthBack();
			}
			else if ( wid == ( this.wid + '.fwdMonthCtl' ) )
			{
				rc = this.handleMonthForward();
			}
		}
		
		return rc;
	};
	
/* **********************************************************************
 * Implement SelectionChangeProducer
 * ********************************************************************** */
	
JwtDateChooser.prototype.addSelectionChangeListener =
	function ( listener )
	{
		var count = this.selectionChangeListeners.length;
		this.selectionChangeListeners[count] = listener;
	};

JwtDateChooser.prototype.getSelectionChangeListeners =
	function ()
	{
		return this.selectionChangeListeners;
	};
	
JwtDateChooser.prototype.removeSelectionChangeListener =
	function ( listener )
	{
		var index = this.selectionChangeListeners.indexOf( listener );
		if ( index != -1 )
		{
			this.selectionChangeListeners.splice( index, 1 );
		} 
	};
	
JwtDateChooser.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;
	};
	
/* **********************************************************************
 * ***************************** JwtDialog ******************************
 * ********************************************************************** */

/**
 * The JWT dialog gadget provides a "psuedo"-dialog using Javascript and CSS positioning
 * and visibility rules to provide the appearance of a popup window.
 *
 * @param wid   the widget identifier.
 */	
	
function JwtDialog( wid, style, windowProc )
{
	this.base = JsWidget;
	this.base(wid);
	this.styleClass = 'jwtDialog';
	this.acceptInput = null;
	this.contentPane = new JsContainer( wid + '.contentPane', DIV_CONTAINER_TYPE );
	this.contentPane.setLayout( jsDivLayout );
	this.buttonBar = new JsContainer( wid + '.buttonBar', DIV_CONTAINER_TYPE );
	this.buttonBar.setLayout( this.layoutButtonBar );
	
	this.windowProc = ( windowProc || new DefaultWindowProc() );
	this.confirmLabel = 'OK';
	this.cancelLabel = 'Cancel';
	
	this.confirmButton = new JwtLink( wid + '.confirmButton', this.confirmLabel, null, -1, -1, JWT_NONE  );
	this.confirmButton.addActionListener( { dialog: this, handleAction: function ( event ) { return this.dialog.handleConfirm( event ); } } );
	this.confirmButton.setStyleClass( 'jwtLink jwtButtonMedium' );
	this.confirmButton.setRolloverClass( 'jwtLinkOver jwtButtonMediumOver' );
	this.confirmButton.setClickClass( 'jwtLinkClick jwtButtonMediumClick' );
	this.confirmButton.setDisabledClass( 'jwtLinkDisabled jwtButtonMediumDisabled' );
	this.cancelButton = new JwtLink( wid + '.cancelButton', this.cancelLabel, null, -1, -1, JWT_NONE  );	
	this.cancelButton.addActionListener( { dialog: this, handleAction: function ( event ) { return this.dialog.handleCancel( event ); } } );	
	this.cancelButton.setStyleClass( 'jwtLink jwtButtonMedium' );
	this.cancelButton.setRolloverClass( 'jwtLinkOver jwtButtonMediumOver' );
	this.cancelButton.setClickClass( 'jwtLinkClick jwtButtonMediumClick' );
	this.cancelButton.setDisabledClass( 'jwtLinkDisabled jwtButtonMediumDisabled' );			
	this.buttonBar.add( this.confirmButton );
	this.buttonBar.add( this.cancelButton );
	
	this.actionListeners = new Array();
}

JwtDialog.prototype = new JsWidget;

JwtDialog.prototype.updateControls =
	function ()
	{
		if ( this.acceptInput && this.acceptInput() )
		{
			this.confirmButton.setEnabled( true );
		}
		else
		{
			this.confirmButton.setEnabled( false );
		}
	};
	
JwtDialog.prototype.layoutButtonBar =
	function ( out, container )
	{
		var html;
		
		html = '<table ';
		html += 'id="' + container.wid + '" ';
		html += 'name="' + container.wid + '" ' ;
		
		if ( container.styleClass ) { html += 'class="' + container.styleClass + '" '; }
		
		html += 'cellspacing="0" cellpadding="0" ';
		html += 'style="';
		if ( !container.isVisible ) { html += 'display: none;' }
		html += '" ';
		
		html += '><tbody>';	
		
		out.write(html);
		
		out.write('<tr>');
		for ( var i = 0; i < container.children.length; i++ )
		{
			out.write('<td align="' + jsResolveAlignment(container.children[i].alignment) + '" valign="' + jsResolveVerticalAlignment(container.children[i].alignment) + '">');
			if ( container.children[i].renderTo ) { container.children[i].renderTo( out ); }
			out.write('</td>');
			
			if ( ( i + 1 ) < container.children.length )
			{
				out.write('<td style="padding:0px 3px 0px 3px">&nbsp;</td>');
			}
		}	
		out.write('</tr>');
		
		html = '</tbody></table>';
		
		out.write(html);		
	};
	
JwtDialog.prototype.setConfirmLabel =
	function ( label )
	{
		this.confirmLabel = label;
		if ( this.peer )
		{
		}
	};
	
JwtDialog.prototype.setCancelLabel =
	function ( label )
	{
		this.cancelLabel = label;
		if ( this.peer )
		{
		}
	};
	
JwtDialog.prototype.centerOnViewport =
	function ()
	{
		if ( this.peer )
		{
			var myBounds = this.getBounds();
			var	viewportBounds = getViewportBounds();
			var top, left;

			top = ( viewportBounds.height - myBounds.height ) / 2;
			left = ( viewportBounds.width - myBounds.width ) / 2;
			this.setLocation( new Point( left, top ) );
		}		
	};
	
JwtDialog.prototype.show =
	function ()
	{
		if ( this.peer )
		{
			this.setVisible( true, null );
			this.centerOnViewport();
			this.peer.style.visibility = 'visible';
			this.updateControls();
		}		
	};
	
JwtDialog.prototype.hide =
	function ()
	{
		if ( this.peer )
		{
			this.peer.style.visibility = 'hidden';
			this.setVisible( false, null );
		}
	};
	
JwtDialog.prototype.renderTo =
	function( out )
	{
		var html;
		
		html = '<div ';
		html += 'id="' + this.wid + '" ';
		html += 'name="' + this.wid + '" ' ;
		
		if ( this.styleClass ) { html += 'class="' + this.styleClass + '" '; }
		
		html += 'style="';
		if ( !this.isVisible ) { html += 'display: none;' }
		html += '" ';
		
		html += '>';	
		
		out.write(html);
		
		if ( this.windowProc )
		{
			this.windowProc.renderStartTag( out, this );
		}			
		
		html = '<table border="0" cellspacing="3" cellpadding="0" width="100%">';
		html += '<tr><td>';
		out.write(html);
		
		this.contentPane.render( out );
		
		html = '</td></tr>';
		html += '<tr><td align="center" valign="middle" style="padding:8px 0px 0px 0px">';
		out.write(html);
		
		this.buttonBar.render( out );
		
		html = '</td></tr>';
		html += '</table>';
		out.write(html);
		
		if ( this.windowProc )
		{
			this.windowProc.renderEndTag( out, this );
		}	
		
		html = '</div>';
		
		out.write(html);	
	};

JwtDialog.prototype.handleConfirm =
	function ( event )
	{
		event.dialogValue = JWT_CONFIRM;
		if ( this.fireAction( event ) )
		{
			this.hide();
		}
		return CANCEL_DEFAULT_ACTION;
	};
	
JwtDialog.prototype.handleCancel =
	function ( event )
	{
		event.dialogValue = JWT_CANCEL;
		if ( this.fireAction( event ) )
		{
			this.hide();
		}
		return CANCEL_DEFAULT_ACTION;
	};
	
/* **********************************************************************
 * Implement ActionProducer
 * ********************************************************************** */
	
JwtDialog.prototype.addActionListener =
	function ( listener )
	{
		var count = this.actionListeners.length;
		this.actionListeners[count] = listener;
	};

JwtDialog.prototype.getActionListeners =
	function ()
	{
		return this.actionListeners;
	};
	
JwtDialog.prototype.removeActionListener =
	function ( listener )
	{
		var index = this.actionListeners.indexOf( listener );
		if ( index != -1 )
		{
			this.actionListeners.splice( index, 1 );
		} 
	};
	
JwtDialog.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;
	};

/* **********************************************************************
 * ************************* DefaultWindowProc **************************
 * ********************************************************************** */
		
/**
 * The DefaultWindowProc displays a simple transparent window sash with the frame
 * title and a close control.
 */
 
function DefaultWindowProc()
{ 
}
 
/* **********************************************************************
 * Implement WindowDecoration
 * ********************************************************************** */

DefaultWindowProc.prototype.renderStartTag =
	function ( out, window )
	{
		var html;
		
		html = '<div ';
		html += 'class="jwtSimpleDialogFrame" ';
		html += '>';
		html += '<table border="0" cellspacing="0" cellpadding="0" width="100%">';
		html += '<tr>';
		html += '<td align="left" class="jwtSimpleDialogTitle">Window Title</td>';
		html += '<td align="right"><a class="jwtSimpleDialogSashLink" href="#" onclick="return invokeWidgetMethod(\'' + window.wid + '\',\'handleCancel\',[event]);">Close</a></td>';
		html += '</tr>';
		html += '<tr><td>&nbsp;</td></tr>';
		html += '</table>';
		
		out.write(html);	
	};
	
DefaultWindowProc.prototype.renderEndTag =
	function ( out, window )
	{
		var html;
		
		html = '</div>';	
		
		out.write(html);	
	};
	
/* **********************************************************************
 * ***************************** JwtTable *******************************
 * ********************************************************************** */
	
/**
 * Jwt Table Gadget
 */
 
function JwtTable( wid, selectionType, style, tableModel, rowDecorator )
{
	this.base = JsWidget;
	this.base( wid );
	this.selectionType = selectionType;
	this.tableModel = ( tableModel || new DefaultTableModel() );
	this.rowDecorator = ( rowDecorator || null );
	this.showHeader = true;
	this.scrollViewport = true;
	this.viewportHeight = '200px';
	this.selectionClass = 'jwtSelectedRow';
	this.selection = new Array();
	this.selectionChangeListeners = new Array();
	this.tableModel.addTableModelChangeListener( this );
}

JwtTable.prototype = new JsWidget;

JwtTable.prototype.setShowHeader =
	function ( showHeader )
	{
		this.showHeader = showHeader;
	};
	
JwtTable.prototype.setScrollViewport =
	function ( scrollViewport )
	{
		this.scrollViewport = scrollViewport;
	};
	
JwtTable.prototype.setViewportHeight =
	function ( height )
	{
		this.viewportHeight = height;
	};
			
JwtTable.prototype.modelIndexToPeerIndex =
	function ( index )
	{
		return ( this.showHeader && !this.scrollViewport ? index + 1 : index );
	};
	
JwtTable.prototype.peerIndexToModelIndex =
	function ( index )
	{
		return ( this.showHeader && !this.scrollViewport ? index - 1 : index );
	};
		
JwtTable.prototype.getPeerTable =
	function ()
	{
		return ( this.scrollViewport ? getElement( this.wid + '.viewportTable' ) : this.peer );
	};
			
JwtTable.prototype.getPeerRow =
	function ( peerIndex )
	{
		var row = null;
		
		if ( this.scrollViewport )
		{
			var viewport = getElement( this.wid + '.viewportTable' );
			row = ( viewport != null ? viewport.rows[peerIndex] : null );
		}
		else
		{
			this.peer.rows[peerIndex];
		}
		
		return row;
	};
			
JwtTable.prototype.selectOnPeer =
	function ( index )
	{
		var peerIndex = this.modelIndexToPeerIndex(index);
		var row = this.getPeerRow(peerIndex);
		
		if ( row != null )
		{
			row.prevClass = row.className;
			row.className = this.selectionClass;
		}
	};

JwtTable.prototype.deselectAllOnPeer =
	function ()
	{
		for ( var i = 0; i < this.selection.length; i++ )
		{
			var peerIndex = this.modelIndexToPeerIndex( this.selection[i] );
			var row = this.getPeerRow(peerIndex);
			
			if ( row != null )
			{
				row.className = ( row.prevClass || '' );
			}
		}
	};
	
JwtTable.prototype.isSelectionEmpty =
	function ()
	{
		return ( this.selection == null || this.selection.length == 0 );
	};
	
JwtTable.prototype.isIndexSelected =
	function ( index )
	{
		return ( this.selection.indexOf( index ) != -1 );
	};
		
JwtTable.prototype.getSelectedIndex =
	function ()
	{
		return ( this.isSelectionEmpty() ? -1 : this.selection[0] );
	};
	
JwtTable.prototype.setSelectedIndex =
	function ( index )
	{
		if ( this.peer )
		{
			this.deselectAllOnPeer();
		}
		
		this.selection = [index];
		if ( this.peer )
		{
			this.selectOnPeer( index );
		}
		this.fireSelectionChange( createEvent( 'change', true ) );
	};
	
JwtTable.prototype.addSelectedIndex =
	function ( index )
	{
		if ( this.selection.indexOf( index ) == -1 )
		{
			this.selection[this.selection.length] = index;
			this.fireSelectionChange( createEvent( 'change', true ) ); 
		}
	};

JwtTable.prototype.removeSelectedIndex =
	function ( index )
	{
		var i = this.selection.indexOf( index );
		
		if ( i != -1 )
		{
			this.selection.splice( i, 1 );
			this.fireSelectionChange( createEvent( 'change', true ) );
		}	
	};
	
JwtTable.prototype.adjustSelection =
	function ( index, delta )
	{
		for ( var i = 0; i < this.selection.length; i++ )
		{
			var selIndex = this.selection[i];
			if ( selIndex > index ) { this.selection[i] = selIndex + delta; }
		}
	};
		
JwtTable.prototype.handleSelection =
	function ( event, index )
	{
		if ( this.selectionType == MULTI_SELECT_TYPE )
		{
			this.addSelectedIndex( index );
		}
		else
		{
			this.setSelectedIndex( index );
		}
		
		return CANCEL_DEFAULT_ACTION;
	};
	
JwtTable.prototype.handleTableModelChange =
	function ( event )
	{
		if ( this.peer )
		{
			var	changeType = event.modelChangeType;
			var	index = event.modelChangeData;
			var peerIndex = this.modelIndexToPeerIndex(index);
			var peerTable = this.getPeerTable();
					
			switch ( changeType )
			{
				case JWT_INSERT:
					if ( peerIndex > peerTable.rows.length )
					{
						peerTable.insertRow(-1);
					}
					else
					{
						peerTable.insertRow(peerIndex);
					}
					this.generateRow(index, peerTable.rows[peerIndex]);
					this.linkRow(peerTable.rows[peerIndex]);
					this.adjustSelection( index, 1 );
					break;
					
				case JWT_DELETE:
					if ( this.isIndexSelected( index ) ) { this.removeSelectedIndex( index ); }
					peerTable.deleteRow(peerIndex);
					this.adjustSelection( index, -1 );
					break;
					
				case JWT_UPDATE:
					// TODO
					break;
			}
		}
		
		return CANCEL_DEFAULT_ACTION;
	};
	
JwtTable.prototype.getModel =
	function ()
	{
		return this.tableModel;
	};
	
JwtTable.prototype.link =
	function ()
	{
		if ( this.peer )
		{
			var peerTable = this.getPeerTable();
			var peerStart = this.modelIndexToPeerIndex(0);
			
			for ( var i = peerStart; i < peerTable.rows.length; i++ )
			{
				var row = peerTable.rows[i];
				this.linkRow(row);
			}
			
			if ( this.scrollViewport )
			{
				var viewportTable = getElement( this.wid + '.viewportTable' );
				if ( viewportTable != null ) { viewportTable.jsWidget = this; }
			}
		}
	};

JwtTable.prototype.linkRow =
	function(row)
	{
		if ( this.showHeader && !this.scrollViewport )
		{
			row.onclick = function ( event ) { return getAncestorByName( this, 'table' ).jsWidget.handleSelection( event, this.rowIndex - 1 ); };	
		}
		else
		{
			row.onclick = function ( event ) { return getAncestorByName( this, 'table' ).jsWidget.handleSelection( event, this.rowIndex ); };	
		}		
	};
	
JwtTable.prototype.generateRow =
	function (index, aRow)
	{
		var out = new StringWriter();
		
		if ( !aRow )
		{
			out.write('<tr ');
		}
		
		if ( this.rowDecorator ) { this.rowDecorator( out, this, index, aRow ); }	
		
		if ( !aRow )
		{
			out.write('>');
		}
		
		for ( var j = 0; j < this.tableModel.getColumnCount(); j++ )
		{
			var columnModel = this.tableModel.getColumnModel(j);
			var html;
			
			if ( !aRow )
			{
				html = '<td ';
			
				if ( columnModel.width ) { html += 'width="' + columnModel.width + '" '; }
				if ( columnModel.height ) { html += 'height="' + columnModel.height + '" '; }
				if ( columnModel.alignment ) { html += 'align="' + jsResolveAlignment( columnModel.alignment ) + '" valign="' + jsResolveVerticalAlignment( columnModel.alignment ) + '" '; }
				html += 'class="' + ( columnModel.styleClass ? columnModel.styleClass : '' );
				if ( j == 0 ) { html += ' jwtTableFirstColumn'; }
				if ( j + 1 >= this.tableModel.getColumnCount() ) { html += ' jwtTableLastColumn'; }
				html += '" ';
				
				html += '>';			
				out.write(html);
			}
			else
			{
				aRow.insertCell(-1);
				if ( columnModel.width ) { aRow.cells[j].width = columnModel.width; }
				if ( columnModel.height ) { aRow.cells[j].height = columnModel.height; }
				if ( columnModel.alignment ) { aRow.cells[j].align = jsResolveAlignment( columnModel.alignment ); aRow.cells[j].valign = jsResolveVerticalAlignment( columnModel.alignment ); }
				
				var styleClass = ( columnModel.styleClass ? columnModel.styleClass : '' );
				if ( j == 0 ) { styleClass += ' jwtTableFirstColumn'; }
				if ( j + 1 >= this.tableModel.getColumnCount() ) { styleClass += ' jwtTableLastColumn'; }
				aRow.cells[j].className = styleClass;
				
				out.output = '';
			}
			
			columnModel.renderer( out, this, this.tableModel.getElementAt( index, j ), index, j );
			
			if ( !aRow )
			{
				out.write( '</td>' );
			}
			else
			{
				aRow.cells[j].innerHTML = out.output;
			}
		}	
		
		if ( !aRow )
		{
			out.write('</tr>');
		}
		
		return ( aRow ? aRow : out.output );
	};
	
JwtTable.prototype.generateBody =
	function ()
	{
		var out = new StringWriter();
		
		if ( this.showHeader )
		{
			out.write('<tr>');	
			for ( var i = 0; i < this.tableModel.getColumnCount(); i++ )
			{
				var columnModel = this.tableModel.getColumnModel(i);
				
				out.write('<th width="' + columnModel.width + '" align="center">');
				if ( columnModel.headerRenderer ) { columnModel.headerRenderer( out, this, columnModel ); }
				else { out.write( columnModel.title ); }
				out.write('</th>');
			}
			out.write('</tr>');
		}
		
		if ( this.scrollViewport )
		{
			var viewportId = this.wid + '.viewport';
			var tableId = this.wid + '.viewportTable';
			out.write( '<tr><td colspan="' + this.tableModel.getColumnCount() + '">' );
			out.write( '<div id="' + viewportId + '" name="' + viewportId + '" style="width:100%;height:' + this.viewportHeight + ';overflow:auto;" align="center">' );
			out.write( '<table id="' + tableId + '" name="' + tableId + '" border="0" cellspacing="0" cellpadding="0" width="100%">' );
		}
		
		for ( var i = 0; i < this.tableModel.getRowCount(); i++ )
		{
			out.write(this.generateRow(i));			
		}	
		
		if ( this.scrollViewport )
		{
			out.write( '</table>' );
			out.write( '</div>' );
			out.write( '</td></tr>' );
		}
		
		return out.output;
	};
	
/* **********************************************************************
 * Implement SelectionChangeProducer
 * ********************************************************************** */
	
JwtTable.prototype.addSelectionChangeListener =
	function ( listener )
	{
		var count = this.selectionChangeListeners.length;
		this.selectionChangeListeners[count] = listener;
	};

JwtTable.prototype.getSelectionChangeListeners =
	function ()
	{
		return this.selectionChangeListeners;
	};
	
JwtTable.prototype.removeSelectionChangeListener =
	function ( listener )
	{
		var index = this.selectionChangeListeners.indexOf( listener );
		if ( index != -1 )
		{
			this.selectionChangeListeners.splice( index, 1 );
		} 
	};
	
JwtTable.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;
	};
		
/* **********************************************************************
 * Implement Renderable
 * ********************************************************************** */

JwtTable.prototype.renderTo =
	function( out )
	{
		var html;
		
		html = '<table border="0" cellspacing="0" cellpadding="0" width="100%" ';
		html += 'id="' + this.wid + '" ';
		html += 'name="' + this.wid + '" ' ;
		
		if ( this.styleClass ) { html += 'class="' + this.styleClass + '" '; }
		
		html += 'style="';
		if ( !this.isVisible ) { html += 'display: none;' }
		html += '" ';
				
		html += '>';
		html += '<tbody>';
		
		out.write(html);
		
		out.write( this.generateBody() );
		
		html = '</tbody>';
		html += '</table>';		
		
		out.write(html);
	};
	
/* **********************************************************************
 * ************************ TableModelBase ******************************
 * ********************************************************************** */
	
function TableModelBase()
{
	this.cols = new Array();
	this.modelChangeListeners = new Array();
}
	
TableModelBase.prototype.addColumn =
	function ( id, title, width, height, alignment, styleClass, renderer, headerRenderer )
	{
		this.cols[this.cols.length] = { id: id, title: title, width: width, height: height, alignment: alignment, styleClass: styleClass, renderer: ( renderer || jsStringRenderer ), headerRenderer: headerRenderer };
	};
	
TableModelBase.prototype.removeColumn =
	function ( id )
	{
		var index = this.indexOfColumn( id );
		if ( index != -1 )
		{
			this.cols.splice( index, 1 );
		}			
	};
	
TableModelBase.prototype.fireRowAdded =
	function ( index )
	{
		var event = createEvent( 'change', true );
		event.modelChangeType = JWT_INSERT;
		event.modelChangeData = index;
		this.fireTableModelChange( event );
	};
	
TableModelBase.prototype.fireRowRemoved =
	function ( index )
	{
		var event = createEvent( 'change', true );
		event.modelChangeType = JWT_DELETE;
		event.modelChangeData = index;
		this.fireTableModelChange( event );
	};
		
TableModelBase.prototype.fireElementUpdated =
	function ( row, col )
	{
		var event = createEvent( 'change', true );
		event.modelChangeType = JWT_UPDATE;
		event.modelChangeData = [ row, col ];
		this.fireTableModelChange( event );
	};	
		
/* **********************************************************************
 * Implement TableModelChangeProducer
 * ********************************************************************** */

TableModelBase.prototype.addTableModelChangeListener =
	function ( listener )
	{
		var count = this.modelChangeListeners.length;
		this.modelChangeListeners[count] = listener;
	};
	
TableModelBase.prototype.getTableModelChangeListeners =
	function ()
	{
		return this.modelChangeListeners;
	};
	
TableModelBase.prototype.removeTableModelChangeListener =
	function ( listener )
	{
		var index = this.modelChangeListeners.indexOf( listener );
		if ( index != -1 )
		{
			this.modelChangeListeners.splice( index, 1 );
		} 
	};
		
TableModelBase.prototype.fireTableModelChange =	
	function ( event )
	{
		var listeners = this.getTableModelChangeListeners();
		var rc = DO_DEFAULT_ACTION;
		
		if ( listeners && listeners.length > 0 )
		{
			for ( var i = 0; i < listeners.length; i++ )
			{
				var listener = listeners[i];
				if ( listener.handleTableModelChange )
				{
					rc = listener.handleTableModelChange( event );
				}
				else if ( listener )
				{
					rc = listener( event, this );
				}
			}
		}	
		
		return rc;
	};
			
/* **********************************************************************
 * Implement TableModel
 * ********************************************************************** */
			
TableModelBase.prototype.indexOfColumn =
	function ( id )
	{
		var index = -1;
		
		for ( var i = 0; i < this.cols.length; i++ )
		{
			if ( this.cols[i].id == id )
			{
				index = i;
				break;
			}	
		}
		
		return index;
	};
	
TableModelBase.prototype.getColumnModel =
	function ( col )
	{
		return ( col < this.getColumnCount() ? this.cols[col] : null );
	};
	
TableModelBase.prototype.getColumnCount =
	function ()
	{
		return this.cols.length;
	}
				
/* **********************************************************************
 * ************************ DefaultTableModel ***************************
 * ********************************************************************** */
	
/**
 * Default Table Model
 * This table model uses a simple array of arrays as its rows.
 */
 
function DefaultTableModel()
{
	this.base = TableModelBase;
	this.base();
	this.data = new Array();
}
	
DefaultTableModel.prototype = new TableModelBase;

DefaultTableModel.prototype.addRow =
	function ( rowData )
	{
		if ( rowData instanceof Array && rowData.length >= this.cols.length )
		{
			this.data[this.data.length] = rowData;
			this.fireRowAdded( this.data.length - 1 );
		}
	};
	
DefaultTableModel.prototype.insertRow =
	function ( rowData, index )
	{
		if ( index >= this.data.length )
		{
			this.addRow( rowData );
		}
		else if ( index >= 0 && index < this.data.length && 
				  rowData instanceof Array && rowData.length >= this.cols.length )
		{
			this.data.splice( index, 1, rowData );
			this.fireRowAdded( index );
		}
	};
	
DefaultTableModel.prototype.removeRow =
	function ( index )
	{
		if ( index >= 0 && index < this.data.length )
		{
			this.data.splice( index, 1 );
			this.fireRowRemoved( index );
		}
	};

DefaultTableModel.prototype.removeAll =
	function ()
	{
		for ( var i = this.data.length - 1; i >= 0; i-- )
		{
			this.removeRow(0);
		}
	};
	
/* **********************************************************************
 * Implement TableModel
 * ********************************************************************** */
	
DefaultTableModel.prototype.getRowCount =
	function ()
	{
		return this.data.length;
	};
	
DefaultTableModel.prototype.getElementAt =
	function ( row, col )
	{
		return ( row < this.getRowCount() && col < this.getColumnCount() ? this.data[row][col] : null );
	};
	

DefaultTableModel.prototype.setElementAt =
	function ( row, col, element )
	{
		if ( row < this.getRowCount() && col < this.getColumnCount() )
		{
			this.data[row][col] = element;
			this.fireElementUpdated( row, col );
		}
	};