Location: PHPKode > scripts > Whisper Table > whisper-table/wsp_table.php
<?php
// -------------------------------------------------------------------------------------------------------------------------
//	generate a div-based table-like creature, allowing cells to be accessed by index as desired.
//
//	by F Phil DeGeorge
//	Computer Whisperer, hide@address.com
// -------------------------------------------------------------------------------------------------------------------------
//	Tested against IE 7, FireFox 3.0.5, Safari 3.2, Chrome 1.0154	Opera 9.63
//	Tested against PHP5, PHP4
// -------------------------------------------------------------------------------------------------------------------------


//	class functions
//		WhisperTable($id='WsprTbl')					-- constructor
//		TableID($id)										-- should you need to change the ID from what was assigned in the constructor
//		ShowHeaders($rHdr=false, $cHdr=false)		-- column header (row 0) and row header (column 0), on or off
//		TableAlign($align)								-- left, center, right

//		GetTheTable()										-- output the HTML for the table, without style statements
//		GetStyles()											-- output the style statements
//		WriteStyles($file)								-- output the style statements as a .css

//		CellColSpan($row, $col, $span)				-- column span for a given cell
//		CellRowSpan($row, $col, $span)				-- row span for a given cell, can be set but is NOT supported at this time
//		CellValue($row, $col, $value='')				-- the content of a given cell

//		ColWidth()
//		RowHeight($row, $height=0);					-- height for the given row (0 == default height)

//		TableStyle($style='')							-- the style for the table
//		RowStyle($row, $style='')						-- the style for a row
//		OnRowStyle($style='')							-- if set, trumps RowStyle, styling for rows 1, 3, 5, etc
//		OffRowStyle($style='')							-- if set, trumps RowStyle, styling for rows 2, 4, 6, etc
//		CellStyle($row, $col, $style='')				-- the style for a cell
//		ColStyle($col, $style='')						-- the style for a column
//		AddStyle($style)									-- simply add another style to the style's output statement; a passthrough function, really

//		SetRowHighlight($style='')						-- set a mouse hover highlight of whatever row the mouse is currently over
//		SetCellHighlight($style='')					-- set a mouse hover highlight of whatever cell the mouse is currently over

//	internal helper functions
//		Expand($row, $col)								-- internal helper function
//		SpeakError($msg, $stop=true)					-- display internal error messages
//		SetDebug($mode=false, $border=false)		-- set debug mode on / off, second parameter is whether to place a border around all table elements:table, rows, cells or not
//



//		How the table data/specs are stored:
//
//	$TheTable['rows']										-- holds the array of rows, row[0] being the column header row
//
//	$TheTable['rows'][y]									-- the data rows, 0 == column headers, 1 - $NumRows == the data rows
//	$TheTable['rows'][y]['style'] 					-- the style for the given row
//	$TheTable['rows'][y]['height']					-- height for the row, in pixels -- optional, defaults to 20px
//	$TheTable['rows'][y]['cols']						-- an array of the cells making up the row
//
//	$TheTable['rows'][y]['cols'][x]['style']		-- cell style -- over=rides all other styling
//	$TheTable['rows'][y]['cols'][x]['colspan']	// yet to be implemented... can be set, but is ignored at this time
//	$TheTable['rows'][y]['cols'][x]['rowspan']	// yet to be implemented... can be set, but is ignored at this time

//	$TheTable['rows'][y]['cols'][x]['onclick']	// yet to be implemented... but the implications are legion :O

//	$TheTable['rows'][y]['cols'][x]['value']		-- the contents for the cell: can be anything

//	example (internal) access:
//		$ARow = $this->TheTable['rows'][y];
//		$ACell = $this->TheTable['rows'][y]['cols'][x]



/**
*	WhisperTable -- a "table" done with the Smoke-And-Mirrors of DIVs and CSS
*
*	This is an object meant to replace <table> as a method of formatting data in X-by-Y format
*	or tabular format. It uses <div>s to accomplish this, and mostly it works quite well.<br/><br/>
*  (In reality I have found it no more cranky in usage than <table> has been (some of the problems
*	I cite with this class are actually identical to problems I've had when using <table>s
*	for formatting data, such as "width:100%" causing formatting issues, especially in Opera or Safari,
*	and so on)<br/><br/>
*
*	This class has been kept PHP4 compatible.<br/>
*	Tested against IE 7, FireFox 3.0.5, Safari 3.2, Chrome 1.0154	Opera 9.63<br/>
*	Tested against PHP5, PHP4.<br/><br/>
*
*	<b>Random Notes:</b><br/>
* <ul>
*		<li><b>[]</b> RowSpan can be set, but is ignored at this time. (Complicated to implement)</li>
*		<li><b>[]</b> row 0 is the (optionally displayed) column header row</li>
*		<li><b>[]</b> col 0 is the (optionally displayed) row header (ie, row number) column</li>
*		<li><b>[]</b> A cell is creating by referencing it, assigning a style or value to it</li>
*		<li><b>[]</b> If cell at row 10, col 5 is references, all rows and columns up to that point are "created" at that moment, in default state</li></ul>
*
*<br/><br/><b>Concerning Styles within this "Table"</b><br/><br/>
*		The precedence order of styles, lowest to highest<br/>
* <ul>
*			<li><b>[]</b> document body or container/parent object (ie, if no styles are set within the table at all, then it's default according to document or the immediate container)</li>
*			<li><b>[]</b> table style</li>
*			<li><b>[]</b> column style</li>
*			<li><b>[]</b> row style (row style will trump column style, in other words)</li>
*			<li><b>[]</b> onrow / offrow style (styling for alternating rows)</li>
*			<li><b>[]</b> cell style</li></ul>
*<br/>
*		A variety of styles / style-statements are produced to go along with each table. These styles can be obtained as
*		a string for inclusion directly in the document, or written out as a css file, and manually included
*		in the destination file via a standard style link statement. <br/>
*		The names used for the various styles are prefixed with the Table's ID, to ensure that the styling for each
*		table in a particular page applies only to the table it meant for.<br/><br/>
*
* @todo TableAlign parameter should be changed to define's rather than a string value
* @todo would like this to be stable when width's are spec'd at 100%; it currently is not
* @todo need to figure out why the formatting goes to pieces (in some browsers) when borders are set arond the cells
* @todo want to provide support for onclick and other potentially useful events
* @todo want to provide some predifined "skins" or packaged appearances
* @todo want to provide a database link method, when on specifying a table and an optional select statement, it will then display the data as if it was "bound" to the table
* @todo want to provide a built-in paging mechanism for when data rows go beyond a reasonable number (especially with the previous todo)
* @todo want to allow for "data nesting"; that is, child records, or expanding a row by clicking on a [+] or [-] to open or close the 
* @todo want to provide an edit mode where (for example, when displaying table data) the user can edit a value and have it automatically updated back to the data source
*
* @author F Philip DeGeorge Computer Whisperer <hide@address.com>
* @version 0.02
* @copyright Copyright (c) 2009, by F Philip DeGeorge
* @package WhisperTable
* @license http://opensource.org/licenses/gpl-license.php GNU Public License
* @category UI elements
*
*/
class WhisperTable
{
/**
* @var string the ID string to use for the table on next generation {@link GetTable()}
*/
	var $ID;									// id for the table
/**
* @var integer internal, the current number of rows defined
*/
	var $NumRows;							// number of rows
/**
* @var integer internal, the current number of columns defined
*/
	var $NumCols;							// number of cols
/**
* @var array the central block of data holding the majority of the information / data about the table, including cell values
*/
	var $TheTable = array();			// array of rows, including the header row (index 0)

//	formatting
/**
* @var string alignment of the table: left, center, right
*/
	var $TableAlign;						// left, center, right
/**
* @var boolean show the column headers?(row 0)
*/
	var $bColHeaders;						// show column headers? (Row 0)
/**
* @var boolean show the row headers (column 0, row numbers)
*/
	var $bRowHeaders;						// show row numbers? (Col 0)
/**
* @var array for controlling individual column widths: by default column widths are distributed evenly. A percentage value
*/
	var $ColWidths = array();			// for controlling column widths

//	styles
/**
* @var string the style statement for use for the table as a whole
*/
	var $TableStyle;						// default style for table, rows and cells
/**
* @var array optional column styles
*/
	var $ColStyles = array();			// optional column styles
/**
* @var string the style used in alternating row styles for the "on" rows (1, 3, 5...)
*/
	var $onRowStyle;						// for alternating row styling
/**
* @var string the style used in alternating row styles for the "off" rows (2, 4, 6...)
*/
	var $offRowStyle;						// for alternating row styling
/**
* @var string additional styles that can be specified to be included with the default / generated styles
*/
	var $Styles;							// for additional styles being added to this table object

/**
* @var boolean if true, sets debug_mode
*	debug mode:
*		includes longer error messages (with back-trace)
*		includes "pretty print" on the final table output, for easier analysis of results
*		shows row numbers (row header column) by default, again for easier analysis of results
*		optionally also includes a border around all cells, to show how cells are actually distributed
*/
	var $debug_mode;						// set debug mode on or off (off by default)
/**
* @var boolean, if true, and debug_mode is true, then puts borders around the cells: NOTE: this sometimes causes formatting problems
*/
	var $debug_show_border;				// set to true by default when $debug_mode is set. 


/** 
* constructor, sets up a basic, completely blank "grid" 0 x 0
* @param string $id id to use for this of the table (optional)
*/ 
	function WhisperTable($id='WsprTbl')
	{
		$this->NumRows = 0;
		$this->NumCols = 0;
		$this->TableAlign = "center";
		$this->TableStyle = '';
		$this->bColHeaders = false;
		$this->bRowHeaders = false;
		$this->ID = $id;

		$this->TableStyle= '';
		$this->onRowStyle = '';
		$this->offRowStyle = '';
		$this->Styles = '';
		
		$this->debug_mode = false;
		$this->debug_show_border = true;					// only operates if debug_mode is true
	} 

	
/** set the ID to use for the table as a whole (actually, it becomes the ID of the enclosing <DIV>
* @param string $id id to use for this table, useful if you are re-using the table object to generate another table and want a different id for it
*/
	function TableID($id)
	{
		$this->ID = $id;
	}
	
	
/**
* show the headers?
* @param boolean $rHdr show the row headers?
* @param boolean $cHdr show the column headers (row numbers)?
*/
	function ShowHeaders($rHdr=false, $cHdr=false)
	{
		$this->bRowHeaders = $rHdr;
		$this->bColHeaders = $cHdr;
	}
	

/** 
* set the horizontal alignment (location) of the table against the document
* @param string $align the alignment for the table against: valid values are 'center' 'left' or 'right'
*/
	function TableAlign($align)
	{
		if (strtolower($align) != 'center' && strtolower($align) != 'left' && strtolower($align) != 'right')
		{
			SpeakError('WhisperTable::TableAlign() -- invalid alignment value: only left, center and right accepted');
		}
		if (empty($align))
		{
			$this->TableAlign = 'center';
		}
		else
		{
			$this->TableAlign = $align;
		}
	}

	
/** 
* set a column span for the given cell
*	NOT YET IMPLEMENTED: you can set the span here, but it ignored at this time when the table is generated
*	@param integer $row the row of the cell whose col span is being set.
*	@param integer $col the col of the cell whose col span is being set.
*	@param integer $span the number of columns this cell is to span across
*	@todo ColSpan is not yet fully implemented. It can be set but will be ignored during table generation.
*/
	function CellColSpan($row, $col, $span)
	{
		if ($row < 1 || $col < 1){$this->SpeakError("WhisperTable::CellColSpan() -- invalid row / col parameters");}
		$this->Expand($row, $col);
		$cell =& $this->TheTable['rows'][$row]['cols'][$col];
		$cell['colspan'] = $span;		
	}
	
/**
* set a row span for the given cell
* Can be set but is NOT supported at this time...
* (Frankly, I'm not entirely sure how to go about this. I _think_ it can be done with some absolute positioning of the div's affected)
* @param integer $row the row of the cell whose row span is being set.
* @param integer $col the col of the cell whose row span is being set.
* @param integer $span the number of rows this cell is to cover.
* @todo RowSpan is not yet fully implemented. It can be set, but will be ignored during table generation.
*/
	function CellRowSpan($row, $col, $span)
	{
		if ($row < 1 || $col < 1){$this->SpeakError("WhisperTable::CellRowSpan() -- invalid row/col indicated");}
		$this->Expand($row, $col);
		$cell =& $this->TheTable['rows'][$row]['cols'][$col];
		$cell['rowspan'] = $span;		
	}


/** 
* set contents of the given cell
* calling it as "CellValue(x, y)" will clear the contents for that cell
* @param integer $row the row of the cell whose value is being set 
* @param integer $col the col of the cell whose value is being set
* @param string $value the value to set, can be HTML code, anything you might want to place in a "cell"
*/
	function CellValue($row, $col, $value='')
	{
		if ($row < 0|| $col < 0){$this->SpeakError("WhisperTable::CellValue() -- invalid row/col indicated");}
		$this->Expand($row, $col);
		$this->TheTable['rows'][$row]['cols'][$col]['value'] = $value;		
	}

	
/** 
* set the style of the given cell
* calling it as "CellStyle(x, y)" will clear any style statement for that cell
* @param integer $row the row of the cell whose style is being set
* @param integer $col the col of the cell whose style is being set
* @param string $style the style to use for the given cell: over-rides ALL other style settings
*/
	function CellStyle($row, $col, $style='')
	{
		if ($row < 1 || $col < 1){$this->SpeakError("WhisperTable::CellStyle() -- invalid row/col indicated");}
		$this->Expand($row, $col);
		$cell =& $this->TheTable['rows'][$row]['cols'][$col];
		$cell['style'] = $style;		
	}
	
	
/** 
* add a style statement. This is simply added to the styles "output" (GetStyles()), so it really
* is just a sort of pass-through thing. This is useful for things like "onclick()" implementation,
* to set up a style specifically for some action defined by cell content or whatever.
* @param string $style the complete style statment, including (in this one case only) the "style=" and quotes.
*/
	function AddStyle($style)
	{
		if (!empty($style))
		{
			$this->Styles .= PHP_EOL . $style;
		}
	}	
	

/** 
* height for the given row (0 == default height == 22px)
* <code>
* $tbl->RowHeight(5, 32px);		// for, say, setting a larger size font in that row
* ...
* $tbl->RowHeight(5);				// restore the row to default size, when table is next generated
* </code>
* @param integer $row the row whose height is being set
* @param integer $height, the height in pixels for this row. If you do not provide this parameter, the row is set to "default" height (22px)
*/
	function RowHeight($row, $height=0)
	{
		if ($row < 0){$this->SpeakError("WhisperTable::RowStyle() -- invalid row indicated");}
		$this->Expand($row, $this->NumCols);
		$this->TheTable['rows'][$row]['height'] = $height;	
	}


/** 
* set style for the indicated row
* calling it as "RowStyle(y)" will clear any style statement for that row
* This will over-ride any styling set for the table in general, for the indicated row
* @param integer $row the row whose style is being set
* @param string $style the style statement to use for this row (leave off the "style=")
*/
	function RowStyle($row, $style='')
	{
		if ($row < 0){$this->SpeakError("WhisperTable::RowStyle() -- invalid row indicated");}
		$this->Expand($row, $this->NumCols);
		$this->TheTable['rows'][$row]['style'] = $style;
	}
	

/** 
* set style for OnRow -- this is for alternating highlights on a table (a common display format for readability)
* (this will over-ride - trump - any ordinary row styling set)
* If either OnRowStyle or OffRowStyle is set, then any RowStyles that are set will be ignored.
* @param string $style the styling to use for the "on row" (rows 1, 3, 5, ...). Call as OnRowStyle() to clear this setting. Do not include "style="
*/
	function OnRowStyle($style = '')
	{	
		$this->onRowStyle = $style;
	}

/** 
* set style for OffRow -- this is for alternating highlights on a table (a common display format for readability)
* @param string $style the styling to use for the "off row" (rows 2, 4, 6, ...). Call as OffRowStyle() to clear this setting. Do not include "style="
*/
	function OffRowStyle($style = '')
	{
		$this->offRowStyle = $style;
	}


/** 
* set styling for columns (row and cell styling will over-ride - trump - this styling)
* @param integer $col the col whose style is being set
* @param string $style the style to apply to this column. Do not include "style="
*/
	function ColStyle($col, $style='')
	{
		if ($col < 0) {$this->SpeakError("WhisperTable::ColStyle() -- invalid col indicated");}
		$this->Expand($this->NumRows, $col);
		$this->ColStyles[$col] = $style;		
	}
	

/** 
* set column width, in percent only (at this time, at least)
* 0	==	default width, which is the available (unassigned) percentage divided evenly among all unsized columns
* if total specified width goes over 100, all the specified widths are discarded and width is distributed evenly among the columns
* <code>
* $tbl->ColWidth(5, 25);		// sets column 5 to 25% of the total width
* ...
* $tbl->ColWidth(5);			// return column 5 to default width, when table is next generated with {@link GetTable()}
* </code>
* @param integer $col the col whose width is being set
* @param double|integer $prcnt (optional) the percentage of the total for this column. If left off or at zero, width will be assigned during table generation. Can be a floating point value, and some times a value like 99.9% gives the expected results where 100% does not. 
*/
	function ColWidth($col, $prcnt = 0)
	{
		if ($col < 0) {$this->SpeakError("WhisperTable::ColStyle() -- invalid col indicated");}
		$this->Expand($this->NumRows, $col);
		$this->ColWidths[$col] = $prcnt;
	}

	
/** 
* set the table style, over-rides the table's container object's (pagebody, a div, whatever) styling
* <code>
* $tbl->TableStyle('background-color:#FFFFCC;color:#0000FF');
* </code>
* @param string $style the style to use for the table. Do not include "style="
*/
	function TableStyle($style='')
	{
		$this->TableStyle = $style;
	}
	
	
/** 
* A style to use for row hover (CSS answer to onmouseover); if style is not specified, a default style is used.
* This function must be called, even to use default styles, for the dynamic highlighting to get turned on
* <code>
* // Supply only the style, not the style's name.
* $tbl->SetRowHighlight('border:1px #FFFFCC;background-color:#FFFF99');
* </code>
* @param string $style the style to use for onmouseover on a row. Default style is provided if you do not wish to provide one
*/
function SetRowHighlight($style='')
{
	$this->Styles .= PHP_EOL . '.' . $this->ID . 'Row:hover {';
	if (empty($style)){$this->Styles .= 'background-color:#FFBBBB}';}
	else{$this->Styles .= $style . '}';}
	
	$this->Styles .= PHP_EOL . '.' . $this->ID . 'OnRow:hover {';
	if (empty($style)){$this->Styles .= 'background-color:#FFBBBB}';}
	else{$this->Styles .= $style . '}';}

	$this->Styles .= PHP_EOL . '.' . $this->ID . 'OffRow:hover {';
	if (empty($style)){$this->Styles .= 'background-color:#FFBBBB}';}
	else{$this->Styles .= $style . '}';}
}


/** 
* style to use for cell hover (CSS version of onmouseover)	if style is not specified, a default style is used
* Function must be called to activate this dynamic cell highighting
* <code>
* // Supply only the style, not the style's name. Example:
* $tbl->SetCellHighlight('border:1px #E0E0FF;background-color:#FDFDFF');
* </code>
* @param string $style the style to use for onmouseover on a cell. Default style is provided if you do not wish to provide one
*/
function SetCellHighlight($style='')
{
	$this->Styles .= PHP_EOL . '.' . $this->ID . 'Cell:hover {';
	if (empty($style)){$this->Styles .= 'background-color:#FFFF99}';}
	else{$this->Styles .= $style . '}';}
}

	

/** 
* Produce the HTML for the table, as it is currently defined.
* <code>
* <php
* $tbl = new WhisperTable();
* $tbl->CellValue(5,4,'This is the cell at row 5, col 4');
* $tbl->SetRowHighlight();	// turn on dynamic row highlighting
* echo($tbl->GetStyles());	// the styles -- could also be done as a .CSS, and linked in the header section of the output page
* echo($tbl->GetTable());		// the table itself
* ?>
* </code>
* @return string
*/
	function GetTheTable()
	{
		$TheHTML = '';
	
		if ($this->debug_mode)
		{
			$TheHTML .= PHP_EOL;
			$TheHTML .= '<!-- Whisper Table Begins here : Debug Mode On -->';
			$TheHTML .= PHP_EOL;
		}

	//	compute column widths
		$this->ColWidths[0]=4;		// column zero is always 4%
		$assigned = 0;
  		if ($this->bRowHeaders){$assigned += 4;}
		$num_unassigned = 0;
		for ($i = 1; $i <= $this->NumCols; $i++)
		{
			if (!isset($this->ColWidths[$i])) {$this->ColWidths[$i]=0;}
			if ($this->ColWidths[$i] == 0) {$num_unassigned++;}
			$assigned += $this->ColWidths[$i];
		}
		
		if ($assigned > 100 || $assigned == 0)
		{
		//	if it goes over 100%, scrap all assigned values and just do a default even split
	  		$Width = intval(99 / ($this->NumCols));
	  		if ($this->bRowHeaders)
	  		{
	  		//	if showing row headers (row numbers, col 0), we don't need col 0 to have an equal share of the available space
	  		//	Take 4% for column zero, and divide the rest equally
	  			$Width = intval(96 / ($this->NumCols));
	  		}
			for ($i = 1; $i <= $this->NumCols; $i++)
			{
				$this->ColWidths[$i] = $Width;
			}
		}
		else
		{	
			if ($num_unassigned > 0)
			{	
				$un_width = (99 - $assigned) / $num_unassigned;		// 99 because going 100% doesn't work... go figure
				for ($i = 1; $i <= $this->NumCols; $i++)
				{
					if ($this->ColWidths[$i] == 0) {$this->ColWidths[$i] = $un_width;}
				}
			}
		}	
		
	// ------------------------------------------------------------------------------------------------------------------
	//			<<TBD>> THIS NEEDS TO GO AWAY ONCE THE PROBLEM IS REALLY UNDERSTOOD
	//
	//	Short version: adding borders around the cells messes things up BADLY, especially in Opera. This fixes that
	//		in some cases, but not all.
	//
	//	this works for IE, Safari, Chrome and FireFox... but only if the window is wide enough.
	//	Opera behaves the opposite: this works if the window is narrow enough... Gadzooks
	//	I don't really understand what's happening with the divs and why this is even necessary when borders are on.
	//
	// finagle factor, apparently the problem is the borders taking up additional space
		if ($this->debug_mode && $this->debug_show_border)
		{
			for ($i = 1; $i <= $this->NumCols; $i++)
			{
				$this->ColWidths[$i] -= 0.1;
			}
		}
	// ------------------------------------------------------------------------------------------------------------------
		
		
	//	overall-enclosing div
		if ($this->debug_mode) {$TheHTML .= PHP_EOL;}
		$TheHTML .= '<div align="' . $this->TableAlign . '">'; 
		$TheHTML .= '<div class="' . $this->ID . 'Tbl"';
		if (!empty($this->TableStyle)) {$TheHTML .= ' style="' . $this->TableStyle . '"';}
		$TheHTML .= ' id="' . $this->ID . '"';
		$TheHTML .= '>';


	//	for each row
		$rowFlag = false;
		for ($y = 0; $y <= $this->NumRows; $y++)
		{
			$rowFlag = !$rowFlag;
			$TheRow = array();
			if (!empty($this->TheTable['rows'][$y]))
			{
				$TheRow = $this->TheTable['rows'][$y]; 
			}
			
			if (!($y == 0 && $this->bColHeaders==false))						// if header row, don't output if headers are off
			{
				if ($this->debug_mode) {$TheHTML .= PHP_EOL;}

			//	row class: either "normal" or onrow/offrow
				if ($y > 0 && (!empty($this->onRowStyle) || !empty($this->offRowStyle)))
				{
					if ($rowFlag)
					{
						$TheHTML .= '<div class="' . $this->ID . 'OffRow"';
					}
					else
					{
						$TheHTML .= '<div class="' . $this->ID . 'OnRow"';
					}
				}
				else
				{
					$TheHTML .= '<div class="' . $this->ID . 'Row"';
				}
				
				
							
				$stFlag = false;
				if (!empty($TheRow['style']))
				{
				//	"normal" styling (not onrow-offrow)
					$TheHTML .= ' style="' . $TheRow['style'];
					$stFlag=true;
				}
				
			//	height -- part of the style statement currently being built
				if (!empty($TheRow['height']) && $TheRow['height'] > 0)
				{
					if ($stFlag == false)
					{
						$TheHTML .= 'style="height:' . $TheRow['height'] . 'px';
					}
					else
					{
						$TheHTML .= '; height:' . $TheRow['height'] . 'px';
					}
					$stFlag = true;
				}
				if ($stFlag) {$TheHTML .= '"';}									// finishes (closes) style statement for the row
				
				$TheHTML .= '>';														// closes the ROW div

			//	for each column
				for ($x = 0; $x <= $this->NumCols; $x++)
				{
					if (!($x == 0 && !$this->bRowHeaders))						// if column zero, don't output if row numbers are off
					{
						if ($this->debug_mode) {$TheHTML .= PHP_EOL;}
						if ($x == 0 && $this->bRowHeaders)
						{
						//	row number / column 0 has its own class
						
						//	and the 0x0 cell has its own style, too
							if ($y==0)
							{
								$TheHTML .= '<div class="' . $this->ID . 'Crnr"';
								if (!empty($TheRow['cols'][$x]['style']))
								{
									$TheHTML .= 'style="' . $TheRow['cols'][$x]['style'] . '"';
								}
								$TheHTML .= '>';
								if (!empty($TheRow['cols'][$x]['value']))
								{
									$TheHTML .= $TheRow['cols'][$x]['value'];						
								}
								else
								{
									$TheHTML .= '&nbsp;';
								}
							}
							else
							{
								$TheHTML .= '<div class="' . $this->ID . 'Col0" ';
								if (!empty($TheRow['cols'][$x]['style']))
								{
									$TheHTML .= 'style="' . $TheRow['cols'][$x]['style'] . '"';
								}
								else if (!empty($this->ColStyles[$x]) && empty($TheRow['style']))
								{
									$TheHTML .= 'style="' . $this->ColStyles[$x] . '"';
								}
								$TheHTML .= '>';
								$TheHTML .= $y;										// row number
							}											
						}
						else
						{
						//	data cells
							$TheHTML .= '<div class="' . $this->ID . 'Cell" style="width:' . $this->ColWidths[$x] . '%';
							if (!empty($TheRow['cols'][$x]['style']))
							{
								$TheHTML .= '; ' . $TheRow['cols'][$x]['style'];
							}
							else if (!empty($this->ColStyles[$x]) && empty($TheRow['style']))
							{
								$TheHTML .= '; ' . $this->ColStyles[$x];
							}
							$TheHTML .= '">';
							if (!empty($TheRow['cols'][$x]['value']))
							{
								$TheHTML .= $TheRow['cols'][$x]['value'];						
							}
							else
							{
								$TheHTML .= '&nbsp;';
							}
						}
						$TheHTML .= '</div>';
	
					}	// the check for row numbers (col 0)
				} 		// for each column in the row
				$TheHTML .= '</div>';		// end of row

			}			// the check for column header (row 0)
		}				// for each row in the table

		if ($this->debug_mode) {$TheHTML .= PHP_EOL;}
		$TheHTML .= "</div></div>";
		if ($this->debug_mode)
		{
			$TheHTML .= PHP_EOL;
			$TheHTML .= '<!-- Whisper Table Ends here -->';
			$TheHTML .= PHP_EOL;
		}
		return $TheHTML;
	}


/** 
* get the style statements necessary for the table, as it sits. See also WriteStyles()
* @return string
* @see WriteStyles()
*/
	function GetStyles()
	{
		$TheHTML = '';
		$TheHTML .= PHP_EOL . '<style type="text/css">';
		$TheHTML .= PHP_EOL . '.' . $this->ID . 'Tbl {width:auto; text-align:left; background-color:inherit; color:inherit}';
		$TheHTML .= PHP_EOL . '.' . $this->ID . 'OnRow, .' . $this->ID . 'OffRow, .' . $this->ID . 'Row {width:100%; height:22px; margin:0; padding:0}';
		$TheHTML .= PHP_EOL . '.' . $this->ID . 'OnRow {' . $this->onRowStyle . '}';
		$TheHTML .= PHP_EOL . '.' . $this->ID . 'OffRow {' . $this->offRowStyle . '}';
		$TheHTML .= PHP_EOL . '.' . $this->ID . 'Row {background-color:inherit; color:inherit}';

	//	for the row numbers, column 0
		if ($this->debug_mode && $this->debug_show_border)
		{
			$TheHTML .= PHP_EOL . '.' . $this->ID . 'Col0 {float:left; margin:0; padding-right:5px; width:4%; height:22px; text-align:right; background-color:inherit; color:inherit; border:1px blue solid}';
			$TheHTML .= PHP_EOL . '.' . $this->ID . 'Crnr {float:left; margin:0; padding-right:5px; width:4%; height:22px; text-align:right; background-color:white; color:inherit; border:1px blue solid}';
		}
		else
		{
			$TheHTML .= PHP_EOL . '.' . $this->ID . 'Col0 {float:left; margin:0; padding-right:5px; width:4%; height:22px; text-align:right; background-color:inherit; color:inherit}';
			$TheHTML .= PHP_EOL . '.' . $this->ID . 'Crnr {float:left; margin:0; padding-right:5px; width:4%; height:22px; text-align:right; background-color:white; color:inherit}';
		}

		$cellClass = 'float:left; margin:0; padding:0; background-color:inherit; color:inherit; height:100%';
		if ($this->debug_mode && $this->debug_show_border) {$cellClass .= '; border:1px blue solid';}
		$TheHTML .= PHP_EOL . '.' . $this->ID . 'Cell {' . $cellClass . '}';
		
		$TheHTML .= $this->Styles;			// any additional user-supplied styles
		$TheHTML .= PHP_EOL . '</style>';
		return $TheHTML;
	}


/** 
* write the styles as a .CSS file. Just another option.
* If file name is not supplied, it will write out <tableid>.css to the cwd.
* @param $file optional file name to write the styles out to as .css. DO NOT INCLUDE .css extension! If not provided, table ID is used as default file name
* @see GetStyles()
*/ 
	function WriteStyles($file = '')
	{
		$fyl = $file;
		if (empty($fyl)) {$fyl = strtolower($this->ID) . '.css';}
		if (($handle = fopen($fyl, 'w+')))
		{
			$styles = GetStyles();
			fwrite($handle, $styles);
			fclose($handle);
		}
		else
		{
			SpeakError('could not open/create file "' . $fyl . '"');
		}
	}
	
	
// -------------------------------------------------------------------------------------------------------------------------
//	Miscellaneous
//
	
/** 
* expand the rows / columns as necessary according to the latest cell, row or column reference
* also sets default values, styles, etc in unset cells, according to current settings
* Internal, helper function.
* @param integer $row the row being referenced
* @param integer $col the col being referenced
*/
	function Expand($row, $col)
	{
		if ($row >= $this->NumRows || $col >= $this->NumCols)
		{
			if ($row >= $this->NumRows) {$this->NumRows = $row;}
			if ($col >= $this->NumCols) {$this->NumCols = $col;}

		}
	}
	

/** 
* error handling. Called only for critical, unrecoverable errors
* @param string $msg the error message to be presented
* @param boolean $stop (optional) hault processing at this point if true (default is to stop)
*/
	function SpeakError($msg, $stop=true)
	{
		echo('<div style="text-align:center;background-color:#FF9999;color:$FFFF00"><br/>' . $msg . '<br/>');
		if ($this->debug_mode)
		{
		//	<<tbd>> add backtrace
		
		
		}
		echo('</div>');
		if ($stop) {exit();}
	}


/** 
* debug mode on/off. Debug mode is off initially
* @param boolean $mode turns debug mode on (true) or off (false)
* @param boolean $border turns on (true) cell borders. Useful for diagnosing layout difficulties, especially among the different browsers
*/
	function SetDebug($mode=false, $border=false)
	{
		$this->debug_mode = $mode;
		$this->debug_show_border = $border;
	}
	

};	// end of class WhisperTable
// -------------------------------------------------------------------------------------------------------------------------


?>
Return current item: Whisper Table