<?php
/************************************************************
* WebWidgets
* Library of classes to produce any HTML(XML) documents,
* Also it can create, validate and return data from web-forms.
* Any tag is an object. Your can build tags tree by inserting one tag
* into another and get its content as HTML.
* Based upon "Composite" OOP patern.
* Library is very flexible, so your may derive your own classes.
* **********************************************************
* Version 0.8 09.10.02
* **********************************************************
* License: GPL - http://www.fsf.org/copyleft/gpl.txt
* **********************************************************
* Contain base classes:
*
* WebWidget - abstract class
* ETag - class for making any "empty" tags
* CData - class for making character data
* Comments - guess :)
* Bin - base class for making tags-containers
* TableRow - for making tables row
* Table - table creator
* Form - for creating and manipulation with forms
* TableForm - form in table
* **********************************************************
* Author: Dmitry Levashov (hide@address.com , hide@address.com)
* *********************************************************/
/**
* Abstract class. Parent for all widgets
* Declare common interface for widgets.
* Set default methods
*/
class WebWidget
{
/**
* Unique ID
* @var int
* @access public
*/
var $Id;
/**
* Tag name
* @var string
* @access public
*/
var $Tag;
/**
* Array of tag's attributes
* @var array
* @access public
*/
var $Attr = array();
/**
* Html output of tag
* @var string
* @access public
*/
var $View;
/**
* Constructor. Set Id, tag and attributes.
* @param string tag name
* @param array attributes
*/
function WebWidget($tag, $attr='')
{
$this->Id = WebWidget::_makeId();
$this->Tag = strtolower($tag);
if ($attr) $this->setAttr($attr);
} // end func constructor
/**
* Set tag attribute(s)
* @access public
* @returns void
*/
function setAttr($attr, $val='')
{
if (!$attr) return;
if (!is_array($attr)) {
$this->Attr[$attr] = $val;
} else {
foreach ($attr as $k=>$v) {
$this->Attr[$k] = $v;
}
}
} // end func setAttr
/**
* Return attribute(s). If $attr param is not set, returns all attributes,
* otherwise retun attrute with $attr name.
* @param $attr string
* @access public
* @return string(array)
*/
function getAttr($attr='')
{
if (!$attr) return $this->Attr;
return $this->Attr[$attr] ? $this->Attr[$attr] : false;
} // end func getAttr
/**
* Insert object into object-container
* Virtual method
* Object-leaf do nothing
* Object-container add $Widget to his array of childs objects
* Return Id of added object on success or false
* @param object widget
* @access public
* @return int
*/
function add(&$Widget) { return false; } // end func add
/**
* Search object with Id in his $Childs array and return it by reference if it exists.
* if $rec set to true, search object in all childs
* @param int object Id
* @param bool recursive search?
* @access public
* @return obj
*/
function &getChild($id, $rec=false) { return false; } // end func &getChild
/**
* Return Html output
* @access public
* @return string
*/
function getView()
{
if (!$this->View) $this->_createView();
return $this->View;
} // end func getView
/**
* show html output
* @access public
* @retrun void
*/
function view() { echo $this->getView(); } // end func view
/**
* Return value, which user input in form field.
* Only form elements return his value.
* Object-container call this method for all his childs
* Object-leaf do nothing and return false.
* If name of field is set as name of array's element
* return value as $array1[array2]...[element] = $value,
* otherwise return value as array($element=>$value)
* @access public
* @return array
*/
function getValue($struct=true) { return false; } // end func getValue
/**
* Return object attributes as string
* @access private
* @return string
*/
function _attr2str()
{
foreach ($this->Attr as $k=>$v) {
$str .= ' ' . $k . '="' . htmlspecialchars($v) . '"';
}
return $str;
} // end func _attr2str
/**
* Produce unique Id, small int. Static method
* @access privare
* @access static
* @return int
*/
function _makeId()
{
static $id = 0;
return ++$id;
} // end func _makeId
/**
* Build Html output
* @access private
* @return void
*/
function _createView() {} // end func _createView
/**
* Used by form elements. Fetch value from _GET or _POST, passed by reference, if form was submitted.
* If element contains incorrect value, put into $err array pair of element label and error message
* @param array _GET or _POST by reference
* @param array array of error messages
* @access private
* @return void
**/
function _complite(&$source, &$err) {} // end func _complite
} // end class WebWidget
/**********************************************************
* Class to build any empty tags
**********************************************************/
class ETag extends WebWidget
{
/**
* Build html output
*/
function _createView()
{
$this->View = '<' . $this->Tag . $this->_attr2str() . "/>\n";
} // end func _createView
} // end class ETag
/*********************************************************
* Class to create any character data
*********************************************************/
class CData extends WebWidget
{
/**
* Constructor
* @param string character data
*/
function CData($str)
{
$this->Id = WebWidget::_makeId();
$this->View = $str;
} // end func constructor
/**
* Add text to his content.
* If $pos = 0 new text replace old content,
* if $pos > 0 text adeed after old content,
* if < 0 - before
* @param string new text
* @param int position where text will added
* @return void
*/
function add($str, $pos=1)
{
if ($pos == 0) $this->View = $str;
$this->View = $pos>0 ? $this->View . $str : $str . $this->View;
} // end func add
} // end class CData
/********************************************************
* Simply html comments
*******************************************************/
class Comments extends WebWidget
{
/**
* @param string comments content
*/
function Comments($str)
{
$this->Id = WebWidget::_makeId();
$this->View .= '<!-- ' . $str . " -->\n";
} // end func constructor
} // end class Comments
/*******************************************************
* Base class for tags - containers
*******************************************************/
class Bin extends WebWidget
{
/**
* @param array Array of childs objects
*/
var $Childs = array();
/**
* Add object to $Childs array
* @param object object to add in container
* @access public
* @return int
*/
function add(&$Widget)
{
if (!is_subclass_of($Widget, 'webwidget'))
return false;
$this->Childs[] = &$Widget;
return $Widget->Id;
} // end func add
/**
* Search object with Id in his $Childs array and return it by reference if it exists.
* if $rec set to true, search object in all childs
* @param int object Id
* @param bool recursive search?
* @access public
* @return object
*/
function &getChild($id, $rec=false)
{
foreach ($this->Childs as $i=>$Child)
{
if ($id == $Child->Id)
return $this->Childs[$i];
if ($rec && ($Child = &$this->Childs[$i]->getChild($id)) != false)
return $Child;
}
return false;
} // end func &getChild
/**
* Collect values from all childs and return its.
* @param array
* @access public
* @return array
*/
function getValue($struct=true)
{
$data = array();
foreach ($this->Childs as $i=>$Child) {
if ( ($child_data = $this->Childs[$i]->getValue($struct)) !== false )
$data = array_merge_recursive($data, $child_data);
}
return $data;
} // end func getValue
/**
* Build html output
* @access private
*/
function _createView()
{
$this->View = '<' . $this->Tag . $this->_attr2str() . ">\n";
foreach ($this->Childs as $i=>$Child) {
$this->View .= $this->Childs[$i]->getView();
}
$this->View .= '</' . $this->Tag . ">\n";
} // end func _createView
/**
* Call _complite method for all childs
* @param array _GET or _POST by reference
* @param array array of error messages
* @access private
*/
function _complite(&$source, &$err)
{
// echo "Im ".$this->Tag.'<br>';
foreach ($this->Childs as $i=>$Child) { //echo get_class($Child).'<br>';
$this->Childs[$i]->_complite($source, $err);
}
} // end func _complite
} // end class Bin
/******************************************************
* produce row of table
******************************************************/
class TableRow extends Bin
{
var $Tag = 'tr';
/**
* Row size. Number of cells in row, unlimited if 0
* @param int
*/
var $Size = 0;
/**
* Empty cells attrubutes
* @param array
*/
var $CellAttr = array();
/**
* Position of next free cell
* @param int
*/
var $_next = 0;
/**
* Constructor
* @param int number of cells in row
* @param array attrubutes
*/
function TableRow($w=0, $attr = null)
{
$this->Id = WebWidget::_makeId();
$this->setAttr($attr);
if ($w > 0) $this->Size = (int)$w;
} // end func constructor
/**
* Set attrubutes for empty cells
* @param mixed attribute name or array of attributes
* @param string attrubute value
*/
function setEmptyCellAttr($attr, $value='')
{
if (!is_array($attr))
$this->CellAttr[$attr] = $value;
else
$this->CellAttr = $attr;
} // end func setEmptyCellAttr
/**
* Add cell to row.
* If $x is set, add cell in position number $x,
* if no - add to next free position
* @param object cell object
* @param int position number
* @return int cell Id
*/
function add(&$Cell, $x=null)
{
$w = ($Cell->Attr['colspan']>1) ? $Cell->Attr['colspan'] : 1;
if (($x = $this->_checkPosition($x, $w-1, is_int($x)?false:true)) === false)
return false;
$this->Childs[$x] = &$Cell;
if ($this->_next <= $x)
$this->_next = $x + $w;
if ($w > 1)
$this->_markSpanned($x+1, $x+$w);
return $Cell->Id;
} // end func add
/**
* Mark spanned positions.
* @param int first spanned position
* @param int last spanned position
* @access private
*/
function _markSpanned($begin, $end)
{
while ($begin < $end) { $this->Childs[$begin++] = new CData(''); }
} // end func _markSpanned
/**
* Check is it free position $x, if $x is set, or
* search next free position
* @param int position number
* @param int number of neede free positions after $x
* @param int do we search free position?
*
*/
function _checkPosition($x='', $w=0, $search=true)
{
if (!is_int($x)) $x = $this->_next;
if ($this->Size > 0 && $x+$w >= $this->Size)
return false;
$pos = array_keys($this->Childs);
sort($pos);
$test = array_intersect($pos, range($x, $x+$w));
if (sizeof($test)) {
if (!$search) {
return false;
}
return $this->_checkPosition(end($test)+1, $w, 1);
}
return $x;
} // end func _checkPosition
/**
* build html output
* @access private
*/
function _createView()
{
ksort($this->Childs);
$prev = -1;
$this->View = '<' . $this->Tag . $this->_attr2str() . ">\n";
foreach ($this->Childs as $i=>$Childs) {
if ($i - $prev > 1) {
$this->View .= $this->_emptyCell($i-$prev-1);
}
$this->View .= $this->Childs[$i]->getView();
$prev = $i;
}
if ($this->Size && $i < $this->Size+1) {
$this->View .= $this->_emptyCell($this->Size - $i -1 );
}
$this->View .= '</' . $this->Tag . ">\n";
} // end func _createView
/**
* Return html string with $num empty cells.
* @param int number of neede empty cells
*/
function _emptyCell($num) {
if ($this->CellAttr) {
foreach ($this->CellAttr as $k=>$v) {
$attr .= ' '. $k . '="'.htmlspecialchars($v).'"';
}
}
while($i++ < $num)
$str .= '<td'.$attr."> </td>\n";
return $str;
} // end func _emptyCell
} // end class TableRow
/**************************************************************
* produce table
* There are two ways to add cells in table:
* one by one - to next free place,
* to set coordinates (column number, row number).
* Your can combine its.
*************************************************************/
class Table extends Bin
{
var $Tag = 'table';
/**
* Table height.
* @param int Number of rows in table
*/
var $Height;
/**
* Table width.
* @param int Number of cells in row
*/
var $Width;
/**
* Default cell attrubutes
* @param array
*/
var $CellAttr = array();
/**
* Position of next free row
* @param integer
*/
var $_next = 0;
/**
* Constructor. To create 'unlimited' table set $w and $h to 0.
* @param int number of cells in row
* @param int number or rows in table
* @param array attrubutes
*/
function Table($w=0, $h=0, $attr = null)
{
$this->Id = WebWidget::_makeId();
$this->setAttr($attr);
$this->Height = (int)$h;
$this->Width = (int)$w;
} // end func constructor
/**
* Set default table cells attrubutes
* @param mixed attribute name or array of attributes
* @param string attrubute value
*/
function setCellAttr($attr, $value='')
{
if (!is_array($attr)) {
$this->CellAttr[$attr] = $value;
} else {
$this->CellAttr = $attr;
}
} // end func setCellAttr
/**
* (Create and) add cell to table.
* If Widget is table cell (Bin object with Tag=td or th) - add it.
* If no - create new cell, put Widget to this new cell and add cell to table.
* (see _makeCell() method)
* @param object cell object or cell content object
* @param int cell number
* @param int row number
* @array array attribute for new cell
* @param string type of creating cell (td || th)
* @access public
* @return int
*/
function add(&$Widget, $x=null, $y=null, $attr=null, $type='td')
{
if ('td' == $Widget->Tag || 'th' == $Widget->Tag) {
return $this->_addCell($Widget, $x, $y);
} else {
return $this->_makeCell($Widget, $x, $y, $attr, strtolower($type));
}
} // add func add
/**
* Add cell to table. This method calls by add() and _makeCell() methods.
* @param object cell object
* @param int cell number
* @param int row number
* @access private
* @retur int
*/
function _addCell(&$Cell, $x, $y)
{
$w = ($Cell->Attr['colspan']>1) ? $Cell->Attr['colspan'] : 1;
$h = ($Cell->Attr['rowspan']>1) ? $Cell->Attr['rowspan'] : 1;
if ($this->Width && $x + $w - 1 >= $this->Width) return false;
if (is_int($y)) {
if (($id = $this->_getRowId($y)) === false) return false;
if (!$this->Childs[$y]->add($Cell, $x)) return false;
} else {
$y = $this->_next;
while ($this->_getRowId($y) && ($id = $this->Childs[$y]->add($Cell, $x))===false) {
$y++;
}
if (!$id) return false;
}
if ($y >= $this->_next) $this->_next = $y;
if ($h > 1) $this->_markSpanned($y+1, $y+$h, (int)$x, $x+$w);
return $Cell->Id;
} // end func _addCell
/**
* Create new cell object and passed it to _addCell method.
* @param object content for new cell
* @param int cell number
* @param int row number
* @param array cell attrubutes
* @param string type of creating cell (td || th)
* @access private
* @return int
*/
function _makeCell(&$Widget, $x,$y, $attr, $type)
{
if (!$attr) $attr = $this->CellAttr;
$Cell = new Bin($type == 'th' ? $type : 'td', $attr);
$Cell->add($Widget);
return $this->_addCell($Cell,$x,$y);
} // end func _createCell
/**
* Mark spanned cells in row objects.
* @param int first row number
* @param int last row number
* @param int first cell number
* @param int last cell number
*/
function _markSpanned($y1, $y2, $x1, $x2)
{
while ($y1 < $y2) {
if (!$this->_getRowId($y1))
return false;
$this->Childs[$y1]->_markSpanned($x1,$x2);
$y1++;
}
} // end func _markSpanned
/**
* Check, is it free position with coord $x $y (if $x & $y is set),
* or search next free position.
* @param int cell number
* @param int row number
* @param int cell width
* @param int cell height
* @access private
* @return bool
*/
function _getFree($x,$y,$w,$h)
{
if (is_int($y)) {
if ($y + $w -1 >= $this->Size) return false;
$id = $this->_getRowId($y);
if (($x = $this->Childs[$id]->_getFree($x, $w)) !== false)
return array($id, $x, $y);
} else {
while ($this->_next + $h - 1 < $this->Size) {
$id = $this->_getRowId($this->_next);
if ( ($x = $this->Childs[$id]->_getFree($x, $w)) !== false )
return array($id, $x, $y);
$this->_next++;
}
}
return false;
} // end func _getFree
/**
* Return Id of row in position $pos.
* If row does not exist - create it.
* @param int row position
* @access private
* @retrun int
*/
function _getRowId($pos)
{
if ($this->Childs[$pos])
return $this->Childs[$pos]->Id;
if ($this->Height > 0 && $pos >= $this->Height)
return false;
$Row = new TableRow($this->Width);
if ($this->CellAttr)
$Row->setEmptyCellAttr($this->CellAttr);
$this->Childs[$pos] = $Row;
return $Row->Id;
} // end func _getRowId
} // end class Table
/********************************************************************
* produce and manipulate form
*
* Synopsis:
*
* $MyForm = new Form('my_form.php', 'POST', array('enctype'=>'multipart/form-data'));
* $MyForm->add(new CData('Your name: '));
* $MyForm->add(new TextField('user_name', '', 'Name'));
* $MyForm->add(new CData('Your email: '));
* $Email = new TextField('email', 'hide@address.com', 'Email');
* $Email->setReg('/\w{2,20}@\w{2,20}\.([a-z]){2,4}/i');
* $MyForm->add($Email);
* $MyForm->add(new Submit());
*
* if (!$MyForm->isSubmit()) {
* $MyForm->view();
* } else {
* if (!$MyForm->valid()) {
* echo $MyForm->getErrMsg();
* $MyForm->view();
* } else {
* $data = $MyForm->getValue();
* echo "Your name is: " . $data['user_name'] . "<br>";
* echo "Your email is: " . $data['email'] . "<br>";
* }
* }
*********************************************************************/
class Form extends Bin
{
var $Tag = 'form';
/**
* Flag indicate that form was sumitted
* @param bool
*/
var $Submit = false;
/**
* Array of error messages. Keys are labels of filelds with incorrect input data,
* values are - error messages
* @param array
*/
var $Err = array();
/**
* Constructor
* @param string "action" attribute of form, url
* @param string "method" attribute
* @param array attributes
*/
function Form($action, $method='POST', $attr='')
{
$this->Id = WebWidget::_makeId();
if ($attr) $this->setAttr($attr);
$this->Attr['action'] = $action;
$this->Attr['method'] = strtoupper($method);
$this->add(new Hidden('_form_'));
} // end func constructor
/**
* Check, is form submit or not.
* If form submit, call _complite method to all childs
* @access public
* @return bool
*/
function isSubmit()
{
if ($this->Submit) return true;
if ('POST' == $this->Attr['method'] && isset($_POST['_form_'])) {
$this->_complite($_POST, $this->Err);
return $this->Submit = true;
}
if ('GET' == $this->Attr['method'] && isset($_GET['_form_'])) {
$this->_complite($_GET, $this->Err);
return $this->Submit = true;
}
return false;
} // end func isSubmit
/**
* Validate form fields
* @access public
* @return bool
*/
function valid()
{
return (sizeof($this->Err)) ? false : true;
} // end func valid
function getValue()
{
$value = parent::getValue();
unset($value['_form_']);
return $value;
}
/**
* return error messages as string
* @access public
* @return string
*/
function getErrMsg()
{
foreach ($this->Err as $k=>$v) {
$str .= $k . ': ' . $v . '<br>';
}
return $str;
} // end func getErrMsg
} // end class Form
/***************************************************************************
* Produce form in table
*
* Synopsys:
*
* $Form = new TableForm($PHP_SELF);
* $Form->setTableAttr(array('border'=>1, 'cellpadding'=>4, 'cellspacing'=>0));
* $Form->setTableSize(2);
* $Form->add(new CData('For header'), '', '', 2,'', array('align'=>'center'));
* $Field = new TextField('field_name', 'Default value', 'Field label');
* $Form->addFieldWithLabel($Field);
* $Form->add(new Submit(), '','',2,'',array('align'=>'center'));
*
* if (!$Form->isSubmit()) {
* $Form->view();
* } else {
* if (!$Form->valid()) {
* echo $Form->getErrMsg();
* $Form->view();
* } else {
* $data = $Form->getValue();
* }
* }
*******************************************************************************/
class TableForm extends Form
{
/**
* Table, where placed input fields
* @param object
*/
var $Table;
/**
* Default cell attrubutes
* @param array
*/
var $CellAttr = array();
/**
* Constructor
* @param string "action" attribute of form, url
* @param string "method" attribute
* @param array attributes
*/
function TableForm($action, $method='POST', $attr='')
{
parent::Form($action, $method, $attr);
$this->Table = new Table(2);
$this->Childs[] =&$this->Table;
} // end func constructor
/**
* Set Table width and height
* @param int table width
* @param int table height
*/
function setTableSize($w=0, $h=0)
{
$this->Table->Width = (int)$w;
$this->Table->Height = (int)$h;
} // end func setTableSize
/**
* Set Table attributes
* @param mixed attribute name or array of attributes
* @param string attribute value
*/
function setTableAttr($attr, $value='')
{
$this->Table->setAttr($attr, $value);
} // end func setTableAttr
/**
* Set default table cells attrubutes
* @param mixed attribute name or array of attributes
* @param string attrubute value
*/
function setCellAttr($attr, $value='')
{
if (!is_array($attr)) {
$this->CellAttr[$attr] = $value;
} else {
foreach ($attr as $k=>$v)
$this->CellAttr[$k] = $v;
}
} // end func setCellAttr
/**
* Add Widget th $Childs array.
* @param object widget
* @param int cell number
* @param int row number
* @param int cell colspan attribute
* @param int cell rowspan attribute
* @param array cell attrutes
* @return int widget id
*/
function add(&$Widget, $x='', $y='', $colspan='', $rowspan='', $attr='')
{
if (!is_subclass_of($Widget, 'webwidget'))
return false;
if ('hidden' == get_class($Widget)) {
$this->Childs[] = &$Widget;
} else {
if (!$attr)
$attr = $this->CellAttr;
if ($colspan > 1)
$attr['colspan'] = (int)$colspan;
if ($rowspan > 1)
$attr['rowspan'] = (int)$rowspan;
$this->Table->add($Widget, $x, $y, $attr);
}
return $Widget->Id;
} // end func add
/**
* Add 2 cells to table.
* First contain Widget->Label, second - Widget
* @param object widget
* @param int label cell number
* @param int row number
* @param int label's cell colspan attribute
* @param int label's cell rowspan attribute
* @param array label's cell attrutes
* @param int widget's cell colspan attribute
* @param int widget's cell rowspan attribute
* @param array widget's cell attrutes
* @return int widget id
*/
function addFieldWithLabel(&$Widget, $x='', $y='', $l_cspan='', $l_rspan='', $l_attr='',
$f_cspan='', $f_rspan='', $f_attr='')
{
$this->add(new CData($Widget->Label), $x, $y, $l_cspan, $l_rspan, $l_attr);
$this->add($Widget,'','', $f_cspan, $f_rspan, $f_attr);
return $Widget->Id;
} // end func addFieldWithLabel
} // end class TableForm
?>