/* 
 * Copyright Notice. 
 * This code was originally created by yCode: http://www.yCode.com  
 * Any modifications to this code can only be made upon purchase from
 * yCode provided that this notice remains in the source code. 
 *
 */

//every spread sheet has to react to windows resize message; store spread
//sheets in this array
var spreadSheetHandlesArray = new Array();

var ss_bEventsInitialized	= false;
var ss_iColResized			= -1;
var ss_oBeingDragged		= null;		// Element to drag.
var ss_oTableResized		= null;		// Table being resized
var ss_oColSeparator		= null;
var ss_asColumnHeaderTags = new Array( 
	"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z");	

var ss_sTableFontFace			= "Arial";
var ss_iTableFontSize			= 14;

/*==========================================================
  Spread Sheet constructor.
  pass the id of the spread sheet and the name of the 
  variable used for the spreadsheet to set up several call 
  back functions.
---------------------------------------------------------- */
function yCodeSpreadSheet(strSpreadSheetID, iBorderWidth, sBorderColor, iCellSpacing )
{
	// Initialize BODY events
	if ( !ss_bEventsInitialized ) {
		initEventHandlers();
		ss_bEventsInitialized = true;
	}


	// Create column separator - one for document
	var strInputCell;
	
	if ( ss_oColSeparator == null ) {
		strInputCell = "<DIV ID='col_separator'></DIV>";

		ss_oColSeparator = document.createElement(strInputCell);
		if ( ss_oColSeparator != null ) {
			ss_oColSeparator.style.position = "absolute";
			ss_oColSeparator.style.backgroundColor = "slategray";
			ss_oColSeparator.style.display = "none";
		
			document.body.insertBefore(ss_oColSeparator);
		}
	}
	this._mySpreadSheet	= document.all[strSpreadSheetID];	//the grid control itself

	doWait( this._mySpreadSheet, true );

	// Member variables
	
	this._fActivationCallback		= null;		// store the callbacks for single and double clicks
	this._oActiveCell				= null;		// currently active cell
	this._sFunctionName				= "";		// name of function applied to numeric data
	this._iFixedCellWidth			= -1;		// fixed cell width
	this._iLayoutWidth				= -1;	    // spreadsheet width in relation to the browser
	this._bAutoLayout				= false;	// automatic layout

	this._mySpreadSheet.bColumnResizeAllowed = false;

	this._bHeaderRow				= false;
	this._bHeaderColumn				= false;

	// Event handlers

	this._mySpreadSheet.onclick		= doClick;
	//this._mySpreadSheet.onresize	= doResize;
	this._mySpreadSheet.onmousedown	= doMouseDown;
	
	this.deactivateCell				= deactivateCell;
	this.doKeyDown					= doKeyDown;


	// Exposed functions

	this.setActivationEvent			= setActivationEvent;
	this.getActivationEvent			= getActivationEvent;
	this.getNumberRows				= getNumberRows;
	this.setNumberRows				= setNumberRows;
	this.getNumberColumns			= getNumberColumns;
	this.setNumberColumns			= setNumberColumns;
	this.setFunctionName			= setFunctionName;
	this.getFunctionName			= getFunctionName;
	this.setHeader					= setHeader;
	this.getHeader					= getHeader;
	this.setColumnResize			= setColumnResize;
	this.getColumnResize			= getColumnResize;
	this.setCellValue				= setCellValue;
	this.getCellValue				= getCellValue;
	this.setLayout					= setLayout;
	this.getLayout					= getLayout;
	this.getFixedCellWidth			= getFixedCellWidth;
	this.getLayoutResize			= getLayoutResize;
	

	// Internal functions
	this._resizeColumn				= _resizeColumn;
	this._checkArgument				= _checkArgument;
	this._applyColumnFunction		= _applyColumnFunction;
	this._insertHeaderRow			= _insertHeaderRow;
	this._insertHeaderColumn		= _insertHeaderColumn;
	this._insertFunctionRow			= _insertFunctionRow;
	this._insertAllColumns			= _insertAllColumns;
	this._insertFirstRow			= _insertFirstRow;
	this._getActualNumberOfColumns	= _getActualNumberOfColumns;
	this._createColGroup			= _createColGroup;

	this._updateTotal				= _updateTotal;
	this._updateAverage				= _updateAverage;
	this._updateResult				= _updateResult;
	this._updateAllResults			= _updateAllResults;

	this._getTextWidth				= _getTextWidth;
	this._setAutoLayout				= _setAutoLayout;
	this._setAutoColumnWidth		= _setAutoColumnWidth;
	
	this._iCurrentSS = spreadSheetHandlesArray.length;
	spreadSheetHandlesArray[this._iCurrentSS] = this;

	// Create edit cell - one for each spreadsheet object
	var strInputCell = "<INPUT id='edit_cell' type='text' " + 
					   "onblur='spreadSheetHandlesArray[" + this._iCurrentSS + "].deactivateCell()' " +
					   "onkeydown='spreadSheetHandlesArray[" + this._iCurrentSS + "].doKeyDown()' >";


	this._myEditCell = document.createElement(strInputCell);

	this._mySpreadSheet.style.fontFamily = ss_sTableFontFace;
	this._mySpreadSheet.style.fontSize = ss_iTableFontSize;


	if ( this._myEditCell != null ) {
		this._myEditCell.style.display = "none";
		this._myEditCell.value = "";
		this._myEditCell.style.position = "absolute";

		this._myEditCell.style.fontFamily = ss_sTableFontFace;
		this._myEditCell.style.fontSize = ss_iTableFontSize;
		document.body.insertBefore(this._myEditCell);
	}


	// Create helper cell to calculate automatic text layout - one for each spreadsheet object
	strInputCell = "<INPUT type='text'>";
	this._myAutoCell = document.createElement(strInputCell);
	if ( this._myAutoCell != null ) {
		this._myAutoCell.style.display = "none";
		this._myAutoCell.value = "";
		this._myAutoCell.style.position = "absolute";

		this._myAutoCell.style.fontFamily = ss_sTableFontFace;
		this._myAutoCell.style.fontSize = ss_iTableFontSize;
		document.body.insertBefore(this._myAutoCell);
	}

	// Initialize table defaults
	this._mySpreadSheet.style.tableLayout = "fixed"; 
	if ( iBorderWidth != null )
		this._mySpreadSheet.style.borderColor = sBorderColor;
	else {
		this._mySpreadSheet.style.borderColor = "black";
	}
	if ( sBorderColor != null ) {
		this._mySpreadSheet.style.borderWidth = iBorderWidth + "px";
	}
	else {
		this._mySpreadSheet.style.borderWidth = "1px";
	}
	this._mySpreadSheet.style.borderStyle = "solid";
	if ( iCellSpacing != null ) {
		this._mySpreadSheet.cellSpacing = iCellSpacing;
		this._mySpreadSheet.border = iCellSpacing;
	}
	else {
		this._mySpreadSheet.cellSpacing = 2;
		this._mySpreadSheet.border = 2;
	}

	// COLGROUP object must be present; if it is not present,
	// create it and insert COL objects for all existing columns
	this._myColGroup = _findColGroup(this._mySpreadSheet);
	if ( (this._myColGroup == null) && !this._createColGroup() ) {
		doWait( this._mySpreadSheet, false );
		alert( "Error creating spreadsheet" );
		return;
	}
	this._mySpreadSheet.cols = this._myColGroup.children.length;		

	var iRows, iColInd, iColumns = this._mySpreadSheet.cols;
	var oNextRow;
	var bTableEmpty;
	
	iRows = this._mySpreadSheet.rows.length;
	bTableEmpty = ( iRows == 0 );

	if ( !bTableEmpty )	
	{
		for ( iInd = 0; iInd < iRows; iInd++ ) {
			oNextRow = this._mySpreadSheet.rows[ iInd ];
			oNextRow.height = 20;
		}
	}

	if ( !this._insertAllColumns() ) {
		doWait( this._mySpreadSheet, false );
		alert( "Error creating spreadsheet" );
		return;
	}
	if ( !this._insertFunctionRow() ) {
		doWait( this._mySpreadSheet, false );
		alert( "Error creating spreadsheet" );
		return;
	}
	if ( !this._insertHeaderRow() ) {
		doWait( this._mySpreadSheet, false );
		alert( "Error creating header" );
		return;
	}
	if ( !this._insertHeaderColumn() ) {
		doWait( this._mySpreadSheet, false );
		alert( "Error creating header" );
		return;
	}

	this.setActivationEvent( true );
	this.setFunctionName( "" );
	this.setHeader( 0, true );
	this.setColumnResize( true );
	this.setLayout( false, 1, 50 );

	if ( bTableEmpty ) {
		this.setNumberRows( 5 );
		this.setNumberColumns( 5 );
	}
	doWait( this._mySpreadSheet, false );
}

/*
 * Function:			initEventHandlers
 * Called by:			Spreadsheet constructor	
 * Action:				initialize event handlers for BODY element
 */
function initEventHandlers() 
{
  document.onmousemove = doMouseMove;
  document.onmouseover = doMouseOver;
  document.onmouseup = doMouseUp;
  document.ondragstart = doSelectTest;
  document.onselectstart = doSelectTest;
}


/////////////////////////////////////////////////////////////////////////
//////////////////Event handlers/////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////

/*
 * Function:			doClick
 * Event:				Spreadsheet activation event (currently supported are single or double click)	
 * Action:				activates a cell
 */
function doClick()
{
	//alert( "here" );
	var oCell = event.srcElement;
	var oRow = oCell.parentElement;

	// Find table containing the cell being activated
	var oTable = _getCellTable( oCell );
	if ( oTable == null ) {			// should never happen; sanity check
		return;
	}
	// Find spreadsheet object containing the cell being activated
	var oCurrent = _getSpreadSheetObject( oTable.id );
	if ( oCurrent == null ) {		// should never happen; sanity check
		return;
	}

	if (oCell.tagName != "TD")
	{
		oCurrent._oActiveCell = null;
		return;
	}
	if ( oRow.className == "result" ) {
		oCurrent._oActiveCell = null;
		return;
	}
	
	if (oRow.parentElement.tagName == "THEAD")
	{
		oCurrent._oActiveCell = null;
		return;
	}
	if (oCell.className == "columnHeader")
	{
		oCurrent._oActiveCell = null;
		return;
	}

	oCurrent._oActiveCell = oCell;

	if (oCell.offsetWidth == "")
	{
		oCurrent._oActiveCell.offsetWidth=100;
	}

	// TB: Don't activate "function" row
	//multiply top and left margins by 1 to convert from string to a number
	oCurrent._myEditCell.style.posLeft=calculate_absolute_X(oCell); 
	oCurrent._myEditCell.style.posTop=calculate_absolute_Y(oCell);
	oCurrent._myEditCell.style.posWidth=oCell.offsetWidth;
	oCurrent._myEditCell.style.posHeight=oCell.offsetHeight;


	oCurrent._myEditCell.value = oCell.innerText;
	oCurrent._myEditCell.style.display="";
	oCurrent._myEditCell.focus();
}

/*
 * Function:			handle_data
 * Event:				ONBLUR of edit box	
 * Action:				updates value of a spreadsheet cell
 */
function deactivateCell()
{
	if (this._myEditCell.value != "")
	{
		this._oActiveCell.innerText = this._myEditCell.value;
	}
	else
	{
		this._oActiveCell.innerHTML = "&nbsp;";
	}
	
	this._myEditCell.style.display = "none";

	this._updateResult( this._oActiveCell.cellIndex );		// this will update column width if needed

	// IE has problems displaying table data after deleteRow.
	// This seems to fix this problem.
	this._oActiveCell.style.display = "none";
	this._oActiveCell.style.display = "";
}

/*
 * Function:			doMouseMove
 * Event:				ONMOUSEMOVE	
 * Action:				resizes a column
 */
function doMouseMove() {
    // Check whether mouse button is down and whether an element is being dragged.
    if ( (1 == event.button) && (ss_oBeingDragged != null) ) {
       // Move the element.
       // Save mouse's position in the document
       var intTop = event.clientY + document.body.scrollTop;
       var intLeft = event.clientX + document.body.scrollLeft;
       // Determine what element the mouse is really over.
       var intLessTop  = 0;
       var intLessLeft = 0;
       var elCurrent = ss_oBeingDragged.offsetParent;
       while (elCurrent.offsetParent != null) {
          intLessTop += elCurrent.offsetTop;
          intLessLeft += elCurrent.offsetLeft;
          elCurrent = elCurrent.offsetParent;
       }

       ss_oBeingDragged.style.pixelLeft = intLeft - intLessLeft  - ss_oBeingDragged.x;
	   
	   event.returnValue = false;
    
	}
}

/*
 * Function:			doMouseOver
 * Event:				ONMOUSEOVER	
 * Action:				changes a cursor depending on the context
 */
function doMouseOver()
{
	var oTable = event.srcElement;
	var bDraggable = true;

	if ( ss_oBeingDragged != null ) {		// Keep "resize" cursor if dragging border
		oTable.style.cursor = "move";
		return;
	}
	if ( oTable.tagName != "TABLE" ) {		// Not a spreadsheet, regular cursor
		oTable.style.cursor = "auto";
		return;
	}
	if ( !oTable.bColumnResizeAllowed ) {	// Non-resizable columns
		oTable.style.cursor = "auto";
		return;
	}

	// Determine border of which cell was clicked
	var oCell = document.elementFromPoint( event.clientX - 1*oTable.border, event.clientY );
	if ( ( oCell == null ) || ( oCell.tagName != "TD" ) ) {
		bDraggable = false;
	}
	else {
		var oRow = oCell.parentElement;
		if ( ( oRow == null ) || ( oRow.parentElement == null ) || ( oRow.parentElement.tagName != "THEAD" ) ) {
			bDraggable = false;
		}
	}
	oTable.style.cursor = bDraggable ? "move" : "auto";
}

/*
 * Function:			doMouseDown
 * Event:				ONMOUSEDOWN	
 * Action:				determines which column to resize
 */
function doMouseDown() 
{
	var oTable = event.srcElement;

	if ( !oTable.bColumnResizeAllowed ) {
		return;
	}
	ss_iColResized = -1;


	// Determine border of which cell was clicked
	var oCell = document.elementFromPoint(event.clientX - 1*oTable.border, event.clientY);
	if ( ( oCell == null ) || ( oCell.tagName != "TD" ) ) {
		return;
	}

	var oRow = oCell.parentElement;
	if ( ( oRow == null ) || ( oRow.parentElement.tagName != "THEAD" ) ) {
		return;				// if cell is not located in a header row, it cannot be dragged
	}

	ss_iColResized = oCell.cellIndex;
	ss_oBeingDragged = ss_oColSeparator;
	ss_oTableResized = oTable;

	// show separator in a new position
	ss_oBeingDragged.style.posLeft = calculate_absolute_X( oCell ) + oCell.offsetWidth;  		
	ss_oBeingDragged.style.posTop = calculate_absolute_Y( oTable );  		
	ss_oBeingDragged.style.posHeight = oTable.offsetHeight;
	ss_oBeingDragged.style.posWidth = 1*oTable.border + 1;
	ss_oBeingDragged.style.display = "";

	// Not interested in location of a mouse in the separator
	ss_oBeingDragged.x = 0;
	ss_oBeingDragged.y = 0;
}

/*
 * Function:			doMouseUp
 * Event:				ONMOUSEUP	
 * Action:				completes column resize
 */
function doMouseUp()
{
	if ( ( ss_oBeingDragged == null) || ( ss_iColResized < 0 ) || ( ss_oTableResized == null ) )
	{
		return;
	}

	// Find which spreadsheet object is being resized
	var oCurrent = _getSpreadSheetObject( ss_oTableResized.id );
	if ( oCurrent == null ) {		// should never happen; sanity check
		alert( "Table being resized not found" );
		return;
	}

	var iWidth = calculate_absolute_X(ss_oBeingDragged);
	var oRows = ss_oTableResized.rows;

	var oCell = oRows[0].cells[ ss_iColResized ];
	var iX = calculate_absolute_X(oCell);

	oCurrent._resizeColumn( true, ss_iColResized, iWidth - iX );
	ss_oBeingDragged.style.posHeight = oCell.offsetHeight;

	ss_oBeingDragged.style.display = "none";			// hide separator
	ss_oBeingDragged = null;
	ss_oTableResized = null;

	event.srcElement.style.cursor = "default";
}

/*
 * Function:			doSelectTest
 * Event:				ONSELECTTEXT	
 * Action:				cancels selection depending on the contex
 */
function doSelectTest() {
    // Don't start text selections in dragged elements.
	event.returnValue = (ss_oBeingDragged==null) && ( event.srcElement.className != "columnHeader");
 }
 
/*
 * Function:			doKeyDown
 * Event:				ONKEYDOWN	
 * Action:				processes TAB character
 */
function doKeyDown() {
	var oCurrentCell = this._oActiveCell;
	if ( oCurrentCell == null ) {
		return;
	}
	// We are only interested in TAB key when CTRL is not pressed
	if ( ( event.keyCode != 9 ) || ( event.ctrlKey == true ) || ( event.ctrlLeft == true ) ) {
		return;
	}
	
	var oCurrentRow = oCurrentCell.parentElement;
	if ( oCurrentRow == null ) {		// sanity check
		alert( "Incorrect table structure" );
		return;
	}
	
	var iNextCellInd, iNextRowInd;
	var iInd, iRows, iCellsInRow;
	var iFirstRow = -1;		
	var iLastRow = -1;		
	var iFirstColumn = 1;		// excludes header column
	
	iRows = this._mySpreadSheet.rows.length;
	iCellsInRow = this._mySpreadSheet.cols + 1;
	
	iFirstRow = 1;			// identifies where data rows begin (excludes the header row)
	iLastRow = iRows - 2;	// identifies where data rows end (excludes the function row)

	if ( iFirstRow >= iLastRow ) {
		return;			// no data rows
	}
	
	// Shift + TAB is backward tab
	var bBackwards = ( event.shiftKey == true ) || ( event.shiftLeft == true );
	
	if ( bBackwards ) {								// Backward TAB
		if ( oCurrentCell.cellIndex > iFirstColumn ) {
			iNextCellInd = oCurrentCell.cellIndex - 1;
			iNextRowInd = oCurrentRow.rowIndex;
		}
		else if ( oCurrentRow.rowIndex > iFirstRow ) {	// first cell of a row
			iNextCellInd = iCellsInRow - 1;
			iNextRowInd = oCurrentRow.rowIndex - 1;
		}
		else {											// first cell of first row
			iNextCellInd = iCellsInRow - 1;
			iNextRowInd = iLastRow;
		}
	}
	else {												// Forward TAB
		if ( oCurrentCell.cellIndex < iCellsInRow - 1 ) {
			iNextCellInd = oCurrentCell.cellIndex + 1;
			iNextRowInd = oCurrentRow.rowIndex;
		}
		else if ( oCurrentRow.rowIndex < iLastRow ) {	// last cell of a row
			iNextCellInd = iFirstColumn;
			iNextRowInd = oCurrentRow.rowIndex + 1;
		}
		else {											// last cell of last row
			iNextCellInd = iFirstColumn;
			iNextRowInd = iFirstRow;
		}
	}
	
	// Save text in active cell
	if (this._myEditCell.value != "")
	{
		this._oActiveCell.innerText = this._myEditCell.value;
	}
	else
	{
		this._oActiveCell.innerHTML = "&nbsp;"; 
	}
	
	// Update function result
	this._updateResult( this._oActiveCell.cellIndex );		// this will update column width if needed

	// Reposition edit control
	var oNextCell = this._mySpreadSheet.rows[ iNextRowInd ].cells[ iNextCellInd ];

	this._myEditCell.style.posLeft=calculate_absolute_X( oNextCell ); 
	this._myEditCell.style.posTop=calculate_absolute_Y( oNextCell );
	this._myEditCell.style.posWidth=oNextCell.offsetWidth;
	this._myEditCell.style.posHeight=oNextCell.offsetHeight;

	this._oActiveCell = oNextCell;
	this._myEditCell.value = ( oNextCell.innerHTML == "&nbsp;" ) ? "" : oNextCell.innerText;
	this._myEditCell.focus();

	event.returnValue = false;
}

/*
 * Function:			doResize
 * Event:				ONRESIZE of a spreadsheet
 * Action:				deactivate cell (hide edit box)
 */
function doResize()
{
	var oTable = event.srcElement;
	if ( oTable == null ) {
		return;
	}

	// Find which spreadsheet object is being clicked
	var oCurrent = _getSpreadSheetObject( oTable.id );
	if ( oCurrent == null ) {		// should never happen; sanity check
		return;
	}
	oCurrent._myEditCell.style.display = "none";
}


/*
 * Function:			handleWindowResize
 * Event:				ONRESIZE of a window
 * Action:				deactivate cell in all spreadsheets (if there is more then one)
 */
function handleWindowResize()
{
	var iIndex;
	for (iIndex=0; iIndex < spreadSheetHandlesArray.length; iIndex++)
	{
		spreadSheetHandlesArray[iIndex]._myEditCell.style.display = "none";
	}
}

/////////////////////////////////////////////////////////////////////////
///////////////Exposed functions/////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////

/*
 * Function:			setActivationEvent
 * Parameters:			bSingle: true if activation event is single-click; false if double-click
 * Return value:		none
 */
function setActivationEvent(bSingle)
{
	if (bSingle && (this._mySpreadSheet.ondblclick != null) )
	{
		this._fActivationCallback = this._mySpreadSheet.ondblclick;
		this._mySpreadSheet.ondblclick=null;
		this._mySpreadSheet.onclick = this._fActivationCallback;
	}
	else
	if ( this._mySpreadSheet.onclick != null )
	{
		this._fActivationCallback = this._mySpreadSheet.onclick;
		this._mySpreadSheet.onclick=null;
		this._mySpreadSheet.ondblclick = this._fActivationCallback;
	}
}

/*
 * Function:			getActivationEvent
 * Parameters:			none
 * Return value:		1 if ctivation event is single-click; 2 if double-click
 */
function getActivationEvent()
{
	return ( this._mySpreadSheet.onclick != null ) ? 1 : 2;
}


/*
 * Function:			setLayout
 * Parameters:			bAutomatic true if layout is automatic; false otherwise
 *						1 if cells have fixed width
 *						2 if spreadsheet resizes with the browser
 *						-1 for user-defined layout
 * Return value:		none
 */
function setLayout( bAutomatic, iResizeOption, iResizeValue ) 
{
	var iInd, iLen = this._getActualNumberOfColumns();

	if ( bAutomatic ) {
		this._mySpreadSheet.width = "";
		this._setAutoLayout();
		this._iFixedCellWidth = -1;
		this._iLayoutWidth = -1;
		this._bAutoLayout = true;
	}
	else if ( iResizeOption < 0 ) {
		this._iFixedCellWidth = -1;
		this._iLayoutWidth = -1;
		this._bAutoLayout = false;
	}
	else {
		this._bAutoLayout = false;
		var iWidth = _checkArgument(iResizeValue);

		if ( iResizeOption == 1 ) {								// fixed cell width
			this._iFixedCellWidth = iWidth;
			this._iLayoutWidth = -1;
			
			for ( iInd = 0; iInd < iLen; iInd++ ) {
				this._myColGroup.children[ iInd ].width = iWidth;
			}
		}
		else {													// resize with the browser
			this._iLayoutWidth = iWidth;
			this._iFixedCellWidth = -1;

			for ( iInd = 0; iInd < iLen; iInd++ ) {
				this._myColGroup.children[ iInd ].width = "";
			}
			this._mySpreadSheet.width = (iWidth + "%");
		}
	}
}

/*
 * Function:			getLayout
 * Parameters:			none
 * Return value:		0 if spreadsheet layout is automatic (columns resize to accomodate the data)
 *						1 if cells have fixed width
 *						2 if spreadsheet resizes with the browser
 *						-1 if spreadsheet has user-defined layout (columns have been resized by user)
 */
function getLayout() 
{
	if ( this._bAutoLayout ) {
		return 0;
	}
	else if ( this._iFixedCellWidth != -1 ) {
		return 1;
	}
	else if ( this._iLayoutWidth != -1 ) {
		return 2;
	}
	return -1;
}

/*
 * Function:			getLayoutResize
 * Parameters:			none
 * Return value:		resize value if spreadsheet resizes with the browser; null otherwise
 */
function getLayoutResize()
{
	return this._iLayoutWidth;
}

/*
 * Function:			getFixedCellWidth
 * Parameters:			iColumn: index of a column in question; null for all columns
 * Return value:		width of a specified column, or fixed width of a cell in a spreasheet; null if cells don't have fixed width
 */
function getFixedCellWidth( iColumn )
{
	if ( iColumn == null ) {
		return this._iFixedCellWidth;
	}
	return this._myColGroup.children(iColumn).width;
}


/*
 * Function:			setHeader
 * Parameters:			iHeaderInd - 0 for column header, 1 for row header, 2 for both
 *						bOn - true if header should be present
 * Return value:		none
 */
function setHeader( iHeaderInd, bOn ) 
{
	var oHeader, oRow;
	var oCell;
	var oFirstCol = this._myColGroup.children[0];

	if ( oFirstCol == null ) {
		return;
	}

	doWait( this._mySpreadSheet, true );

	var bSetRowHeader = ( iHeaderInd > 0 );
	var bSetColumnHeader = ( ( iHeaderInd == 0 ) || ( iHeaderInd == 2 ) );

	if ( bOn ) {
		if ( bSetColumnHeader && !this._bColumnHeader ) {
			oHeader = this._mySpreadSheet.tHead;
			if ( oHeader != null ) {
				oHeader.style.display = "";
			}
			this._bColumnHeader = true;
		}
	
		if ( bSetRowHeader && !this._bRowHeader ) {
			oFirstCol.style.display = "";
			this._bRowHeader = true;
		}
	}
	else {		// off
		if ( bSetColumnHeader && this._bColumnHeader ) {
			oHeader = this._mySpreadSheet.tHead;
			if ( oHeader != null ) {
				oHeader.style.display = "none";
			}
			this._bColumnHeader = false;
		}
		if ( bSetRowHeader && this._bRowHeader ) {
			oFirstCol.style.display = "none";
			this._bRowHeader = false;
		}
	}
	doWait( this._mySpreadSheet, false );

	// recalculate layout
	var iLayout = this.getLayout();
	if ( iLayout == 0 ) {					// automatic
		this.setLayout( true );
	}
	else if  ( iLayout == 1 ) {				// column fixed size
		this.setLayout( false, 1, this._iFixedCellWidth );
	}
	else if ( iLayout == 2 ) {			// resize with the browser
		this.setLayout( false, 2, this._iLayoutWidth );		// resize again
	}
}

/*
 * Function:			getHeader
 * Parameters:			none
 * Return value:		-1 - no headers
 *						0 - column header
 *						1 - row header
 *						2 - both headers
 */
function getHeader()
{
	if ( !this._bRowHeader && !this._bColumnHeader ) {
		return -1;
	}
	if ( this._bColumnHeader && !this._bRowHeader ) {
		return 0;
	}
	if ( !this._bColumnHeader && this._bRowHeader ) {
		return 1;
	}
	return 2;
}

/*
 * Function:			setColumnResize
 * Parameters:			bResizeAllowed: true if user should be allowed to resize columns by dragging border; false otherwise
 * Return value:		width of a specified column, or fixed width of a cell in a spreasheet; null if cells don't have fixed width
 */
function setColumnResize( bResizeAllowed )
{
	this._mySpreadSheet.bColumnResizeAllowed = bResizeAllowed;
}

/*
 * Function:			getColumnResize
 * Parameters:			none
 * Return value:		true if user is allowed to resize columns by dragging border; false otherwise
 */
function getColumnResize()
{
	return this._mySpreadSheet.bColumnResizeAllowed;
}


/*
 * Function:			setFunctionName
 * Parameters:			name of function to apply( currently supported values are "sum", "average", and "" for none)
 * Return value:		none
 */
function setFunctionName(functionName)
{
	var myFunction = functionName.toLowerCase();

	if ((myFunction != "") && (myFunction != "sum") && (myFunction != "average"))
	{
		throw("'" + functionName + "' is not a valid function name");
		return;
	}

	this._sFunctionName = myFunction;
	this._applyColumnFunction();
}

/*
 * Function:			getFunctionName
 * Parameters:			none
 * Return value:		name of function applied to numeric data
 */
function getFunctionName()
{
	return this._sFunctionName;
}


/*
 * Function:			setNumberColumns
 * Parameters:			iNewNumberColumns: new number of columns
 * Return value:		none
 */
function setNumberColumns(iNewNumberColumns)
{
	iNewNumberColumns = this._checkArgument(iNewNumberColumns, 0);

	var iIndex;
	var nCurrentNumberColumns = this._mySpreadSheet.cols;

	if (nCurrentNumberColumns == iNewNumberColumns)
	{
		return;
	}

	var nMaxNumberOfColumns = ss_asColumnHeaderTags.length - 1;
	if ( iNewNumberColumns > nMaxNumberOfColumns ) {
		return;
	}

	var currentRow;
	var oCOL, oEl; //used to format table width
	
	var nCurrentNumberRows = this._mySpreadSheet.rows.length;
	var j;

	if ( nCurrentNumberColumns < iNewNumberColumns ) {
		for ( iIndex = nCurrentNumberColumns + 1; iIndex <= iNewNumberColumns; iIndex++ )
		{	
			this._myColGroup.children[ iIndex ].style.display = "";
		}
	}
	else
	{
		doWait( this._mySpreadSheet, true );
		
		var iRowCount = this.getNumberRows();	// data rows + result row
		var oNextRow, oNextCell;
		var iRowInd;

		for (iIndex = nCurrentNumberColumns; iIndex > iNewNumberColumns; iIndex--)
		{
			// erase text
			for ( iRowInd = 1; iRowInd <= iRowCount; iRowInd++ ) {
				oNextRow = this._mySpreadSheet.rows[ iRowInd ];
				oNextCell = oNextRow.cells[ iIndex ];
				oNextCell.innerText = " ";
			}
			// result row
			oNextRow = this._mySpreadSheet.rows[ iRowCount + 1 ];
			oNextRow.cells[ iIndex ].innerText = "0";

			this._myColGroup.children[ iIndex ].style.display = "none";
		}
		doWait( this._mySpreadSheet, false );
	}
	this._mySpreadSheet.cols = iNewNumberColumns;

	// recalculate layout
	var iLayout = this.getLayout();
	if ( iLayout == 0 ) {					// automatic
		this.setLayout( true );
	}
	else if  ( iLayout == 1 ) {				// column fixed size
		this.setLayout( false, 1, this._iFixedCellWidth );
	}
	else if ( iLayout == 2 ) {			// resize with the browser
		this.setLayout( false, 2, this._iLayoutWidth );		// resize again
	}
}

/*
 * Function:			getNumberColumns
 * Parameters:			none
 * Return value:		number of columns, excluding header column
 */
function getNumberColumns()
{
	return this._mySpreadSheet.cols;
}

/*
 * Function:			setNumberRows
 * Parameters:			iNewNumberRows: new number of rows
 * Return value:		none
 */
function setNumberRows(iNewNumberRows)
{
	iNewNumberRows  = _checkArgument(iNewNumberRows, 0);

	var nCurrentNumberRows		= this._mySpreadSheet.rows.length;
	var nCurrentNumberColumns   = this._getActualNumberOfColumns();

	var oCopyRow, oNextRow, oNextCell;
	var iIndex;

	if ( nCurrentNumberRows < 2 ) {
		alert( "Spreadsheet initialization is not finished" );
		return;			// spreadsheet wasn't set up properly; at least function and header rows must be present
	}
	
	var iDataRows = nCurrentNumberRows - 2; // Determine how many rows contain data (exclude header and result row)

	if (iDataRows == iNewNumberRows)	// same number of rows
	{
		return;
	}

	if (iDataRows < iNewNumberRows)							//add new rows
	{
		oCopyRow = this._mySpreadSheet.rows( 0 ); // clone header row first time around;
		
		oNextRow = oCopyRow.cloneNode( true );
		if ( oNextRow == null ) {
			alert( "Error adding row" );
			return;
		}

		doWait( this._mySpreadSheet, true );

		this._mySpreadSheet.tBodies[0].appendChild(oNextRow);

		// Change row properties
		oNextRow.className = "";
		oNextCell = oNextRow.cells( 0 );
		oNextCell.className = "columnHeader";
		oNextCell.innerText = oNextRow.rowIndex - 1;		// function row will need to be moved later

		for (iColInd = 1; iColInd < nCurrentNumberColumns; iColInd++)
		{
			oNextCell = oNextRow.cells( iColInd );
			oNextCell.className = "";
			oNextCell.innerText = " ";
		}
		oCopyRow = oNextRow;			// now, clone this row

		for ( iIndex = iDataRows + 1; iIndex < iNewNumberRows; iIndex++ ) {
			oNextRow = oCopyRow.cloneNode( true );
			if ( oNextRow == null ) {
				doWait( this._mySpreadSheet, false );
				alert( "Error adding row" );
				return;
			}
			this._mySpreadSheet.tBodies[0].appendChild(oNextRow);

			// Change header cell text
			oNextCell = oNextRow.cells( 0 );
			if ( oNextCell != null ) {
				oNextCell.innerText = oNextRow.rowIndex - 1;
			}
		}
		this._mySpreadSheet.moveRow( nCurrentNumberRows - 1, iNewNumberRows + 1 );		// move result row to the end
		
		doWait( this._mySpreadSheet, false );
	}

	else												// delete rows
	{
		doWait( this._mySpreadSheet, true );
		for (iIndex = nCurrentNumberRows - 2; iIndex > iNewNumberRows; iIndex--) {
			this._mySpreadSheet.deleteRow( iIndex );
		}
		doWait( this._mySpreadSheet, false );
	}
	this._updateAllResults();			// update function result; this will update layout
}

/*
 * Function:			getNumberRows
 * Parameters:			none
 * Return value:		number of rows, excluding the header and function row
 */
function getNumberRows()
{
	return this._mySpreadSheet.rows.length - 2;	// don't count function and header row
}

/*
 * Function:			getCellValue
 * Parameters:			iRow, iColumn - cell coordinates
 * Return value:		value of a cell specified
 */
function getCellValue( iRow, iColumn )
{
	var nCurrentNumberColumns = this.getNumberColumns();
	var nCurrentNumberRows = this.getNumberRows();
	
	if ((nCurrentNumberRows == 0) || (nCurrentNumberColumns == 0))
	{
		throw ("The spread sheet has no data." );
	}

		//to convert from a string object if necessary
	iRow = _checkArgument( iRow, 0 ); 
	iColumn = _checkArgument( iColumn, 0 );

	if ( iRow >= nCurrentNumberRows )
	{
		throw ("Valid column numbers are 0 to " + (nCurrentNumberRows - 1) );
	}
	if ( iColumn >= nCurrentNumberColumns )
	{
		throw ("Valid column numbers are 0 to " + (nCurrentNumberColumns - 1) );
	}
	// account for header row and column
	return this._mySpreadSheet.rows( iRow + 1 ).cells( iColumn + 1 ).innerText;
}


/*
 * Function:			setCellValue
 * Parameters:			iRow, iColumn - cell coordinates, sValue - new value of a cell
 * Return value:		none
 */
function setCellValue( iRow, iColumn, sValue )
{
	var nCurrentNumberColumns = this.getNumberColumns();
	var nCurrentNumberRows = this.getNumberRows();

	if ((nCurrentNumberRows == 0) || (nCurrentNumberColumns == 0))
	{
		throw ("The spread sheet has no columns or rows." );
	}

		//to convert from a string object if necessary
	iRow = _checkArgument( iRow, 0 ); 
	if ( iRow >= nCurrentNumberRows )
	{
		throw ("Valid row numbers are 0 to " + (nCurrentNumberRows - 1) );
	}

	iColumn = _checkArgument( iColumn, 0 );
	if ( iColumn >= nCurrentNumberColumns )
	{
		throw ("Valid column numbers are 0 to " + (nCurrentNumberColumns - 1) );
	}

	// account for header row and column
	iRow++;
	iColumn++;

	var oRowCells = this._mySpreadSheet.rows( iRow ).cells;
	if ( sValue != "")
	{
		oRowCells[iColumn].innerText = sValue;
	}
	else
	{
		oRowCells[iColumn].innerHTML = "&nbsp;";
	}

	// Update result
	this._updateResult( iColumn );		// this will update column width if needed
}


//////////////////////////////////////////////////////////////////
//////////////////Internal functions//////////////////////////////
//////////////////////////////////////////////////////////////////

/*==============================================================
 _checkArgument() throws an exception if the input value is not
 a positive number >= 1 or optional input value.  Returns 
 the input parameter converted from a string (if it was passed
 as a string).
-------------------------------------------------------------- */
function _checkArgument(inputArg, lowerBound)
{
	//first handle the case when lowerBound exists and = 0
	if (isNaN(lowerBound))
	{
		lowerBound = 1;
	}

	if (isNaN(inputArg))
	{
		throw("'" + inputArg + "' is Not Numeric");
	}
	else
	{
		inputArg = parseFloat(inputArg);
		if (inputArg < lowerBound)
		{
			throw("'" + inputArg + "' is less than " + lowerBound);
		}
	}

	return inputArg;
}



/*================================================================
_applyColumnFunction() -- set the last cell in a column to be summation,
average, etc of the cells in the same column;  set "nan" to the 
cell's value if there is a non-numberic value.
---------------------------------------------------------------- */
function _applyColumnFunction()
{
	var iCurrentNumberColumns   = this._getActualNumberOfColumns();
	var iTableLen				= this._mySpreadSheet.rows.length;
	var iIndex;

	if ( iTableLen == 0 ) {		// can never happen; sanity check;
		return;
	}

	var oResultRow = this._mySpreadSheet.rows(iTableLen - 1);
	var oCell;

	if (this._sFunctionName == "")  // Hide function row
	{
		oResultRow.style.display = "none";

		if ( this.getLayout() == 0 ) {
			this._setAutoLayout();
		}
		return;
	}

	oResultRow.style.display = "block";

	// Header column
	oCell = oResultRow.cells( 0 );
	if ( this._sFunctionName == "sum" ) {
		oCell.innerText = "Sum";
	}
	else if ( this._sFunctionName == "average" ) {
		oCell.innerText = "Avg.";
	}
	else {
		oCell.innerText = "?";			// sanity check
	}
	this._updateAllResults();
}

function _updateTotal( iColumn, bUpdate )
{
	var iTotal = 0;

	var iEnd, iIndex;
	var oCells;

	iEnd = this._mySpreadSheet.rows.length - 1;			// stop at result row

	for (iIndex = 1; iIndex < iEnd; iIndex++)
	{
		oCells = this._mySpreadSheet.rows(iIndex).cells;
		iTotal += oCells[ iColumn ].innerText * 1;
	}

	if ( ( bUpdate == null ) || bUpdate ) {
		var oResultRow = this._mySpreadSheet.rows[ iEnd ];
		oResultRow.cells[ iColumn ].innerText = iTotal + "";
	}
	return iTotal;
}

function _updateAverage( iColumn )
{
	var iTotal = this._updateTotal( iColumn, false );
	var iRows = this._mySpreadSheet.rows.length;		
	var iDataRows = iRows - 2;				// exclude result and header row

	if ( iDataRows == 0 ) {
		return "NaN";
	}
	var iAvg = iTotal / iDataRows;

	var oResultRow = this._mySpreadSheet.rows[ iRows - 1 ];
	oResultRow.cells[ iColumn ].innerText = iAvg + "";
}

function _updateResult( iColumn )
{
	if ( this._sFunctionName == "sum" ) {
		this._updateTotal( iColumn );
	}
	else if ( this._sFunctionName == "average" ) {
		this._updateAverage( iColumn );
	}
	if ( this.getLayout() == 0 ) {
		this._setAutoColumnWidth( iColumn, false );
	}
}

function _updateAllResults()
{
	doWait( this._mySpreadSheet, true );
	
	var iColumns = this._getActualNumberOfColumns();
	var iInd;

	for ( iInd = 1; iInd < iColumns; iInd++ ) {			// skip header column
		if ( this._sFunctionName == "sum" ) {
			this._updateTotal( iInd );
		}
		else if ( this._sFunctionName == "average" ) {
			this._updateAverage( iInd );
		}
	}
	doWait( this._mySpreadSheet, false );

	if ( this.getLayout() == 0 ) {
		this.setLayout( true );
	}
}

/*=============================================================================
_findColGroup() -- helper function to find a colgroup for the table (if exists)
-------------------------------------------------------------------------------*/
function _findColGroup( tab )
{
	var all_els = tab.all;
	var len = all_els.length;
	var index;
	for (index=0; index<len; index++)
	{
		if (all_els[index].tagName == "COLGROUP")
		{
			return (all_els[index]);
		}
	}
	return null;
}

function _createColGroup()
{
	var sColGroup = "<COLGROUP ID=colgroup_" + this._mySpreadSheet.id + "></COLGROUP>";
	
	this._myColGroup = document.createElement(sColGroup);
	if ( this._myColGroup == null ) {
		return false;
	}
	this._mySpreadSheet.insertAdjacentElement("afterBegin", this._myColGroup);

	var bTableEmpty = ( this._mySpreadSheet.rows.length == 0 );
	if ( bTableEmpty ) {
		return true;		// no columns exist
	}

	var iInd, iLen;
	var oCOL, oEl;
	
	iLen = this._mySpreadSheet.rows[0].cells.length;
	for ( iInd = 0; iInd < iLen; iInd++ ) {
		oCOL=document.createElement("COL");
		if ( oCOL == null ) {
			return false;
		}
		oEl = this._myColGroup.appendChild(oCOL);
		if ( oEl == null ) {
			return false;
		}
		oEl.nowrap = true;
		oEl.width = 30;					// default value in case of user-defined layout
	}
	return true;
}


function _resizeColumn( bResize, iColumn, width)
{
	// If column widht will be less then its border width, it will be impossible
	// to change its width by dragging
	var iMinWidth = 1*this._mySpreadSheet.border + 1;
	this._myColGroup.children(iColumn).width = ( width > iMinWidth ) ? width : iMinWidth;

	this.setLayout( false, -1 );
}

function _getTextWidth() 
{
	var oTextRange = this._myAutoCell.createTextRange();
	if ( oTextRange == null ) {
		return 0;
	}
	return oTextRange.boundingWidth + 10;
}

function _setAutoColumnWidth( iColInd, bAllColumns, iMinWidth, iStartRow, iEndRow )
{
	var iRowInd;
	var oNextCell;
	var iColWidth = 0;
	var oRows = this._mySpreadSheet.rows;

	if ( !bAllColumns ) {
		var iRows = oRows.length;
		
		iMinWidth = 1*this._mySpreadSheet.border + 1;
		iEndRow = ( this._sFunctionName == "" ) ? iRows - 2 : iRows - 1;
		iStartRow = ( this._bHeaderRow ) ? 0 : 1;

		this._myAutoCell.style.display = "";
	}

	for ( iRowInd = iStartRow; iRowInd <= iEndRow; iRowInd++ ) {
		oNextCell = oRows[ iRowInd ].cells[ iColInd ];
		this._myAutoCell.value = oNextCell.innerText;
		iColWidth = Math.max( this._getTextWidth(), iColWidth );

		// IE has problems displaying table data after deleteRow.
		// This seems to fix this problem.
		oNextCell.style.display = "none";
		oNextCell.style.display = "";
	}

	if ( !bAllColumns ) {
		this._myAutoCell.style.display = "none";
	}

	this._myColGroup.children( iColInd ).width = Math.max( iColWidth, iMinWidth );
}

function _setAutoLayout()
{
	var iColumns = this._mySpreadSheet.cols + 1;	// include row header    
	var iRows = this._mySpreadSheet.rows.length;
	
	if ( ( iColumns > 30 ) || ( iRows > 30 ) ) {
		throw( "This is a slow function; it is disabled for large tables" );
	}

	doWait( this._mySpreadSheet, true );

	var iColInd;
	var iMinWidth = 1*this._mySpreadSheet.border + 1;
	var iEndRow = ( this._sFunctionName == "" ) ? iRows - 2 : iRows - 1;
	var iStartRow = ( this._bHeaderRow ) ? 0 : 1;

	this._myAutoCell.style.display = "";
	for ( iColInd = 0; iColInd < iColumns; iColInd++ ) {
		this._setAutoColumnWidth( iColInd, true, iMinWidth, iStartRow, iEndRow );
	}
	this._myAutoCell.style.display = "none";

	doWait( this._mySpreadSheet, false );
}

function _insertFunctionRow()
{
	var oResultRow, oCell;
	var bTableEmpty = ( this._mySpreadSheet.rows.length == 0 );
	var iInd, iLen;

	// if this is the first row in the table, create each cell
	if ( bTableEmpty ) {
		oResultRow = this._insertFirstRow( true );
		if ( oResultRow == null ) {
			return false;
		}
	}
	else {					// if not the first row, clone the existing row (for speed)
		var oCopyRow = this._mySpreadSheet.rows( 0 ); 
		oResultRow = oCopyRow.cloneNode( true );
		if ( oResultRow == null ) {
			return false;
		}
		this._mySpreadSheet.tBodies[0].insertBefore(oResultRow);

		iLen = oResultRow.cells.length;
		for (iInd = 0; iInd < iLen; iInd++)
		{
			oCell = oResultRow.cells[ iInd ];
			if ( oCell == null ) {
				return false;
			}
			oCell.className = "columnHeader";
		}
	}
	oResultRow.className="result";
	return true;
}

function _getSpreadSheetObject( sSpreadSheetId )
{
	var iInd, iLen;
	var oCurrent;

	iLen = spreadSheetHandlesArray.length;
	for ( iInd = 0; iInd < iLen; iInd++ ) {
		oCurrent = spreadSheetHandlesArray[ iInd ];
		if ( oCurrent._mySpreadSheet.id == sSpreadSheetId ) {
			return oCurrent;
		}
	}
	return null;
}

function _getCellTable( oCell )
{
	var iInd, iLen;
	var oCurrent;

	iLen = spreadSheetHandlesArray.length;
	for ( iInd = 0; iInd < iLen; iInd++ ) {
		oCurrent = spreadSheetHandlesArray[ iInd ];
		if ( oCurrent._mySpreadSheet.contains( oCell ) ) {
			return oCurrent._mySpreadSheet;
		}
	}
	return null;
}

function doWait( oTable, bWait )
{
	if ( bWait ) {
		oTable.style.cursor = "wait";
		window.status = "Please wait...";
	}
	else {
		oTable.style.cursor = "auto";
		window.status = "";
	}
}

function _insertHeaderRow() 
{
	var iInd, iColumns;
	var oHeaderRow, oCell;
	
	var oHeader = this._mySpreadSheet.createTHead();
	if ( oHeader == null ) {
		return false;
	}

	// Assume at least one row (function) is already present
	var oCopyRow = this._mySpreadSheet.rows( 0 ); 
	oHeaderRow = oCopyRow.cloneNode( true );
	if ( oHeaderRow == null ) {
		return false;
	}
	oHeader.insertBefore(oHeaderRow);
	oHeaderRow.className = "";

	// assume column header is not present
	iColumns = oHeaderRow.cells.length;
	for ( iInd = 0; iInd < iColumns; iInd++ ) {
		oCell = oHeaderRow.cells[iInd];
		if ( oCell == null ) {
			return false;
		}
		oCell.className = "columnHeader";
		oCell.innerText = ss_asColumnHeaderTags[ iInd ];
	}
	return true;
}

function _insertHeaderColumn() 
{
	var oCOL=document.createElement("COL");
	if ( oCOL == null ) {
		return false;
	}
	var oEl;

	var iColumns = this._getActualNumberOfColumns();
	if ( iColumns == 0 ) {
		oEl = this._myColGroup.insertBefore(oCOL);
	}
	else {
		var oFirstCol = this._myColGroup.children[ 0 ];
		oEl = this._myColGroup.insertBefore(oCOL, oFirstCol);
	}
	if ( oEl == null ) {
		return false;
	}
	oEl.nowrap = true;
	oEl.width = 30;		// default value in case of user-defined layout

	var iInd;
	var iRows = this._mySpreadSheet.rows.length;
	var oRow, oCell;

	for ( iInd = 0; iInd < iRows; iInd++ ) {
		oRow = this._mySpreadSheet.rows[ iInd ];
		oCell = oRow.insertCell( 0 );
		if ( oCell == null ) {
			return false;
		}
		oCell.className = "columnHeader";

		if ( oRow.parentElement.tagName == "THEAD" ) {
			oCell.innerText =  " ";
		}
		else if ( oRow.className == "result" ) {
			if ( this._sFunctionName == "average" ) {
				oCell.innerText = "Avg";
			}
			else {
				oCell.innerText =  this._sFunctionName;
			}
		}
		else {
			oCell.innerText = oRow.rowIndex;  // assume row header is already present
		}
	}
	return true;
}

function _insertAllColumns()
{
	var nMaxNumberOfColumns		= ss_asColumnHeaderTags.length - 1;		// maximum number of columns allowed (excluding header)
	var nCurrentNumberColumns	= this._getActualNumberOfColumns();		// actual number of columns present (excluding header)
	var nCurrentNumberRows		= this._mySpreadSheet.rows.length;		// actual number of rows present (no function row yet)

	var currentRow, oCell;
	var oCOL, oEl; //used to format table width
	var iColInd, iRowInd;

	// create COL elements
	for (iColInd = nCurrentNumberColumns; iColInd < nMaxNumberOfColumns; iColInd++)
	{
		oCOL=document.createElement("COL");
		if ( oCOL == null ) {
			return false;
		}
		oEl = this._myColGroup.appendChild(oCOL);
		if ( oEl == null ) {
			return false;
		}
		oEl.nowrap = true;
		oEl.width = 30;					// default value in case of user-defined layout
		oEl.style.display = "none";		// hide columns initially; show only as many columns as requested
	}

	for (iRowInd = 0; iRowInd < nCurrentNumberRows; iRowInd++)
	{
		currentRow = this._mySpreadSheet.rows(iRowInd);
		
		for (iColInd = nCurrentNumberColumns; iColInd < nMaxNumberOfColumns; iColInd++)
		{
			oCell = currentRow.insertCell();
			if ( oCell == null ) {
				return false;
			}
			oCell.innerText = " ";
		}
	}
	return true;
}

function _getActualNumberOfColumns()
{
	return this._myColGroup.children.length;
}

function _insertFirstRow( bHeader )
{
	var oFirstRow, oCell;

	oFirstRow = this._mySpreadSheet.insertRow();		// Insert a new row for result
	if ( oFirstRow == null ) {
		return null;
	}

	iLen = this._getActualNumberOfColumns();

	for (iInd = 0; iInd < iLen; iInd++)
	{
		oCell = oFirstRow.insertCell();
		if ( oCell == null ) {
			return null;
		}
		if ( ( bHeader != null ) && bHeader ) {
			oCell.className = "columnHeader";
		}
	}
	oFirstRow.height = 20;
	return oFirstRow;
}