<?php
/**
* TDataBoundControl class file
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.pradosoft.com/
* @copyright Copyright © 2005 PradoSoft
* @license http://www.pradosoft.com/license/
* @version $Id$
* @package System.Web.UI.WebControls
*/
Prado::using('System.Web.UI.WebControls.TDataSourceControl');
Prado::using('System.Web.UI.WebControls.TDataSourceView');
Prado::using('System.Collections.TPagedDataSource');
/**
* TDataBoundControl class.
*
* TDataBoundControl is the based class for controls that need to populate
* data from data sources. It provides basic properties and methods that allow
* the derived controls to associate with data sources and retrieve data from them.
*
* TBC....
*
* TDataBoundControl is equipped with paging capabilities. By setting
* {@link setAllowPaging AllowPaging} to true, the input data will be paged
* and only one page of data is actually populated into the data-bound control.
* This saves a lot of memory when dealing with larget datasets.
*
* To specify the number of data items displayed on each page, set
* the {@link setPageSize PageSize} property, and to specify which
* page of data to be displayed, set {@link setCurrentPageIndex CurrentPageIndex}.
*
* When the size of the original data is too big to be loaded all in the memory,
* one can enable custom paging. In custom paging, the total number of data items
* is specified manually via {@link setVirtualItemCount VirtualItemCount},
* and the data source only needs to contain the current page of data. To enable
* custom paging, set {@link setAllowCustomPaging AllowCustomPaging} to true.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @version $Id$
* @package System.Web.UI.WebControls
* @since 3.0
*/
abstract class TDataBoundControl extends TWebControl
{
private $_initialized=false;
private $_dataSource=null;
private $_requiresBindToNull=false;
private $_requiresDataBinding=false;
private $_prerendered=false;
private $_currentView=null;
private $_currentDataSource=null;
private $_currentViewValid=false;
private $_currentDataSourceValid=false;
private $_currentViewIsFromDataSourceID=false;
private $_parameters=null;
private $_isDataBound=false;
/**
* @return Traversable data source object, defaults to null.
*/
public function getDataSource()
{
return $this->_dataSource;
}
/**
* Sets the data source object associated with the databound control.
* The data source must implement Traversable interface.
* If an array is given, it will be converted to xxx.
* If a string is given, it will be converted to xxx.
* @param Traversable|array|string data source object
*/
public function setDataSource($value)
{
$this->_dataSource=$this->validateDataSource($value);
$this->onDataSourceChanged();
}
/**
* @return string ID path to the data source control. Defaults to empty.
*/
public function getDataSourceID()
{
return $this->getViewState('DataSourceID','');
}
/**
* @param string ID path to the data source control. The data source
* control must be locatable via {@link TControl::findControl} call.
*/
public function setDataSourceID($value)
{
$dsid=$this->getViewState('DataSourceID','');
if($dsid!=='' && $value==='')
$this->_requiresBindToNull=true;
$this->setViewState('DataSourceID',$value,'');
$this->onDataSourceChanged();
}
/**
* @return boolean if the databound control uses the data source specified
* by {@link setDataSourceID}, or it uses the data source object specified
* by {@link setDataSource}.
*/
protected function getUsingDataSourceID()
{
return $this->getDataSourceID()!=='';
}
/**
* Sets {@link setRequiresDataBinding RequiresDataBinding} as true if the control is initialized.
* This method is invoked when either {@link setDataSource} or {@link setDataSourceID} is changed.
*/
public function onDataSourceChanged()
{
$this->_currentViewValid=false;
$this->_currentDataSourceValid=false;
if($this->getInitialized())
$this->setRequiresDataBinding(true);
}
/**
* @return boolean whether the databound control has been initialized.
* By default, the control is initialized after its viewstate has been restored.
*/
protected function getInitialized()
{
return $this->_initialized;
}
/**
* Sets a value indicating whether the databound control is initialized.
* If initialized, any modification to {@link setDataSource DataSource} or
* {@link setDataSourceID DataSourceID} will set {@link setRequiresDataBinding RequiresDataBinding}
* as true.
* @param boolean a value indicating whether the databound control is initialized.
*/
protected function setInitialized($value)
{
$this->_initialized=TPropertyValue::ensureBoolean($value);
}
/**
* @return boolean whether databind has been invoked in the previous page request
*/
protected function getIsDataBound()
{
return $this->_isDataBound;
}
/**
* @param boolean if databind has been invoked in this page request
*/
protected function setIsDataBound($value)
{
$this->_isDataBound=$value;
}
/**
* @return boolean whether a databind call is required (by the data bound control)
*/
protected function getRequiresDataBinding()
{
return $this->_requiresDataBinding;
}
/**
* @return boolean whether paging is enabled. Defaults to false.
*/
public function getAllowPaging()
{
return $this->getViewState('AllowPaging',false);
}
/**
* @param boolean whether paging is enabled
*/
public function setAllowPaging($value)
{
$this->setViewState('AllowPaging',TPropertyValue::ensureBoolean($value),false);
}
/**
* @return boolean whether the custom paging is enabled. Defaults to false.
*/
public function getAllowCustomPaging()
{
return $this->getViewState('AllowCustomPaging',false);
}
/**
* Sets a value indicating whether the custom paging should be enabled.
* When the pager is in custom paging mode, the {@link setVirtualItemCount VirtualItemCount}
* property is used to determine the paging, and the data items in the
* {@link setDataSource DataSource} are considered to be in the current page.
* @param boolean whether the custom paging is enabled
*/
public function setAllowCustomPaging($value)
{
$this->setViewState('AllowCustomPaging',TPropertyValue::ensureBoolean($value),false);
}
/**
* @return integer the zero-based index of the current page. Defaults to 0.
*/
public function getCurrentPageIndex()
{
return $this->getViewState('CurrentPageIndex',0);
}
/**
* @param integer the zero-based index of the current page
* @throws TInvalidDataValueException if the value is less than 0
*/
public function setCurrentPageIndex($value)
{
if(($value=TPropertyValue::ensureInteger($value))<0)
$value=0;
$this->setViewState('CurrentPageIndex',$value,0);
}
/**
* @return integer the number of data items on each page. Defaults to 10.
*/
public function getPageSize()
{
return $this->getViewState('PageSize',10);
}
/**
* @param integer the number of data items on each page.
* @throws TInvalidDataValueException if the value is less than 1
*/
public function setPageSize($value)
{
if(($value=TPropertyValue::ensureInteger($value))<1)
throw new TInvalidDataValueException('databoundcontrol_pagesize_invalid',get_class($this));
$this->setViewState('PageSize',TPropertyValue::ensureInteger($value),10);
}
/**
* @return integer number of pages of data items available
*/
public function getPageCount()
{
return $this->getViewState('PageCount',1);
}
/**
* @return integer virtual number of data items in the data source. Defaults to 0.
* @see setAllowCustomPaging
*/
public function getVirtualItemCount()
{
return $this->getViewState('VirtualItemCount',0);
}
/**
* @param integer virtual number of data items in the data source.
* @throws TInvalidDataValueException if the value is less than 0
* @see setAllowCustomPaging
*/
public function setVirtualItemCount($value)
{
if(($value=TPropertyValue::ensureInteger($value))<0)
throw new TInvalidDataValueException('databoundcontrol_virtualitemcount_invalid',get_class($this));
$this->setViewState('VirtualItemCount',$value,0);
}
/**
* Sets a value indicating whether a databind call is required by the data bound control.
* If true and the control has been prerendered while it uses the data source
* specified by {@link setDataSourceID}, a databind call will be called by this method.
* @param boolean whether a databind call is required.
*/
protected function setRequiresDataBinding($value)
{
$value=TPropertyValue::ensureBoolean($value);
if($value && $this->_prerendered)
{
$this->_requiresDataBinding=true;
$this->ensureDataBound();
}
else
$this->_requiresDataBinding=$value;
}
/**
* Ensures any pending {@link dataBind} is called.
* This method calls {@link dataBind} if the data source is specified
* by {@link setDataSourceID} or if {@link getRequiresDataBinding RequiresDataBinding}
* is true.
*/
protected function ensureDataBound()
{
if($this->_requiresDataBinding && ($this->getUsingDataSourceID() || $this->_requiresBindToNull))
{
$this->dataBind();
$this->_requiresBindToNull=false;
}
}
/**
* @return TPagedDataSource creates a paged data source
*/
protected function createPagedDataSource()
{
$ds=new TPagedDataSource;
$ds->setCurrentPageIndex($this->getCurrentPageIndex());
$ds->setPageSize($this->getPageSize());
$ds->setAllowPaging($this->getAllowPaging());
$ds->setAllowCustomPaging($this->getAllowCustomPaging());
$ds->setVirtualItemCount($this->getVirtualItemCount());
return $ds;
}
/**
* Performs databinding.
* This method overrides the parent implementation by calling
* {@link performSelect} which fetches data from data source and does
* the actual binding work.
*/
public function dataBind()
{
$this->setRequiresDataBinding(false);
$this->dataBindProperties();
$this->onDataBinding(null);
if(($view=$this->getDataSourceView())!==null)
$data=$view->select($this->getSelectParameters());
else
$data=null;
if($data instanceof Traversable)
{
if($this->getAllowPaging())
{
$ds=$this->createPagedDataSource();
$ds->setDataSource($data);
$this->setViewState('PageCount',$ds->getPageCount());
if($ds->getCurrentPageIndex()>=$ds->getPageCount())
{
$ds->setCurrentPageIndex($ds->getPageCount()-1);
$this->setCurrentPageIndex($ds->getCurrentPageIndex());
}
$this->performDataBinding($ds);
}
else
{
$this->clearViewState('PageCount');
$this->performDataBinding($data);
}
}
$this->setIsDataBound(true);
$this->onDataBound(null);
}
public function dataSourceViewChanged($sender,$param)
{
if(!$this->_ignoreDataSourceViewChanged)
$this->setRequiresDataBinding(true);
}
protected function getDataSourceView()
{
if(!$this->_currentViewValid)
{
if($this->_currentView && $this->_currentViewIsFromDataSourceID)
$this->_currentView->detachEventHandler('DataSourceViewChanged',array($this,'dataSourceViewChanged'));
if(($dataSource=$this->determineDataSource())!==null)
{
if(($view=$dataSource->getView($this->getDataMember()))===null)
throw new TInvalidDataValueException('databoundcontrol_datamember_invalid',$this->getDataMember());
if($this->_currentViewIsFromDataSourceID=$this->getUsingDataSourceID())
$view->attachEventHandler('OnDataSourceViewChanged',array($this,'dataSourceViewChanged'));
$this->_currentView=$view;
}
else
$this->_currentView=null;
$this->_currentViewValid=true;
}
return $this->_currentView;
}
protected function determineDataSource()
{
if(!$this->_currentDataSourceValid)
{
if(($dsid=$this->getDataSourceID())!=='')
{
if(($dataSource=$this->getNamingContainer()->findControl($dsid))===null)
throw new TInvalidDataValueException('databoundcontrol_datasourceid_inexistent',$dsid);
else if(!($dataSource instanceof IDataSource))
throw new TInvalidDataValueException('databoundcontrol_datasourceid_invalid',$dsid);
else
$this->_currentDataSource=$dataSource;
}
else if(($dataSource=$this->getDataSource())!==null)
$this->_currentDataSource=new TReadOnlyDataSource($dataSource,$this->getDataMember());
else
$this->_currentDataSource=null;
$this->_currentDataSourceValid=true;
}
return $this->_currentDataSource;
}
abstract protected function performDataBinding($data);
/**
* Raises <b>OnDataBound</b> event.
* This method should be invoked after a databind is performed.
* It is mainly used by framework and component developers.
*/
public function onDataBound($param)
{
$this->raiseEvent('OnDataBound',$this,$param);
}
/**
* Sets page's <b>OnPreLoad</b> event handler as {@link pagePreLoad}.
* If viewstate is disabled and the current request is a postback,
* {@link setRequiresDataBinding RequiresDataBinding} will be set true.
* This method overrides the parent implementation.
* @param TEventParameter event parameter
*/
public function onInit($param)
{
parent::onInit($param);
$page=$this->getPage();
$page->attachEventHandler('OnPreLoad',array($this,'pagePreLoad'));
}
/**
* Sets {@link getInitialized} as true.
* This method is invoked when page raises <b>PreLoad</b> event.
* @param mixed event sender
* @param TEventParameter event parameter
*/
public function pagePreLoad($sender,$param)
{
$this->_initialized=true;
$isPostBack=$this->getPage()->getIsPostBack();
if(!$isPostBack || ($isPostBack && (!$this->getEnableViewState(true) || !$this->getIsDataBound())))
$this->setRequiresDataBinding(true);
}
/**
* Ensures any pending databind is performed.
* This method overrides the parent implementation.
* @param TEventParameter event parameter
*/
public function onPreRender($param)
{
$this->_prerendered=true;
$this->ensureDataBound();
parent::onPreRender($param);
}
/**
* Validates if the parameter is a valid data source.
* If it is a string or an array, it will be converted as a TList object.
* @param Traversable|array|string data source to be validated
* @return Traversable the data that is traversable
* @throws TInvalidDataTypeException if the data is neither null nor Traversable
*/
protected function validateDataSource($value)
{
if(is_string($value))
{
$list=new TList;
foreach(TPropertyValue::ensureArray($value) as $key=>$value)
{
if(is_array($value))
$list->add($value);
else
$list->add(array($value,is_string($key)?$key:$value));
}
return $list;
}
else if(is_array($value))
return new TMap($value);
else if(($value instanceof Traversable) || $value===null)
return $value;
else
throw new TInvalidDataTypeException('databoundcontrol_datasource_invalid',get_class($this));
}
public function getDataMember()
{
return $this->getViewState('DataMember','');
}
public function setDataMember($value)
{
$this->setViewState('DataMember',$value,'');
}
public function getSelectParameters()
{
if(!$this->_parameters)
$this->_parameters=new TDataSourceSelectParameters;
return $this->_parameters;
}
}
/**
* TListItemType class.
* TListItemType defines the enumerable type for the possible types
* that databound list items could take.
*
* The following enumerable values are defined:
* - Header: header item
* - Footer: footer item
* - Item: content item (neither header nor footer)
* - Separator: separator between items
* - AlternatingItem: alternating content item
* - EditItem: content item in edit mode
* - SelectedItem: selected content item
* - Pager: pager
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @version $Id$
* @package System.Web.UI.WebControls
* @since 3.0.4
*/
class TListItemType extends TEnumerable
{
const Header='Header';
const Footer='Footer';
const Item='Item';
const Separator='Separator';
const AlternatingItem='AlternatingItem';
const EditItem='EditItem';
const SelectedItem='SelectedItem';
const Pager='Pager';
}
/**
* IItemDataRenderer interface.
*
* IItemDataRenderer defines the interface that an item renderer
* needs to implement. Besides the {@link getData Data} property, a list item
* renderer also needs to provide {@link getItemIndex ItemIndex} and
* {@link getItemType ItemType} property.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @version $Id$
* @package System.Web.UI.WebControls
* @since 3.1.0
*/
interface IItemDataRenderer extends IDataRenderer
{
/**
* Returns a value indicating the zero-based index of the item in the corresponding data control's item collection.
* If the item is not in the collection (e.g. it is a header item), it returns -1.
* @return integer zero-based index of the item.
*/
public function getItemIndex();
/**
* Sets the zero-based index for the item.
* If the item is not in the item collection (e.g. it is a header item), -1 should be used.
* @param integer zero-based index of the item.
*/
public function setItemIndex($value);
/**
* @return TListItemType the item type.
*/
public function getItemType();
/**
* @param TListItemType the item type.
*/
public function setItemType($value);
}
?>