* @link http://www.pradosoft.com/
* @copyright Copyright © 2005 PradoSoft
* @license http://www.pradosoft.com/license/
* @version $Revision: $ $Date: $
* @package System.Web.UI.WebControls
*/
Prado::using('System.Web.UI.WebControls.TDataBoundControl');
Prado::using('System.Web.UI.WebControls.TPanelStyle');
Prado::using('System.Collections.TPagedDataSource');
Prado::using('System.Collections.TDummyDataSource');
/**
* TPager class.
*
* TPager creates a pager that controls the paging of the data populated
* to a data-bound control specified by {@link setControlToPaginate ControlToPaginate}.
* 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.
*
* TPager can be in one of three {@link setMode Mode}:
* - NextPrev: a next page and a previous page button are rendered.
* - Numeric: a list of page index buttons are rendered.
* - List: a dropdown list of page indices are rendered.
*
* TPager raises an {@link onPageIndexChanged OnPageIndexChanged} event when
* the end-user interacts with it and specifies a new page (e.g. clicking
* on a page button that leads to a new page.) The new page index may be obtained
* from the event parameter's property {@link TPagerPageChangedEventParameter::getNewPageIndex NewPageIndex}.
*
* When multiple pagers are associated with the same data-bound control,
* these pagers will do synchronization among each other so that the interaction
* with one pager will automatically update the UI of the other relevant pagers.
*
* The following example shows a typical usage of TPager:
*
* $pager->ControlToPaginate="Path.To.Control";
* $pager->DataSource=$data;
* $pager->dataBind();
*
* Note, the data is assigned to the pager and dataBind() is invoked against the pager.
* Without the pager, one has to set datasource for the target control and call
* its dataBind() directly.
*
* @author Qiang Xue
* @version $Revision: $ $Date: $
* @package System.Web.UI.WebControls
* @since 3.0.2
*/
class TPager extends TDataBoundControl implements INamingContainer
{
/**
* Command name that TPager understands.
*/
const CMD_PAGE='Page';
const CMD_PAGE_NEXT='Next';
const CMD_PAGE_PREV='Previous';
const CMD_PAGE_FIRST='First';
const CMD_PAGE_LAST='Last';
/**
* @var array list of all pagers, used to synchronize their appearance
*/
static private $_pagers=array();
/**
* Registers the pager itself to a global list.
* This method overrides the parent implementation and is invoked during
* OnInit control lifecycle.
* @param mixed event parameter
*/
public function onInit($param)
{
parent::onInit($param);
self::$_pagers[]=$this;
}
/**
* Unregisters the pager from a global list.
* This method overrides the parent implementation and is invoked during
* OnUnload control lifecycle.
* @param mixed event parameter
*/
public function onUnload($param)
{
parent::onUnload($param);
if(($index=array_search($this,self::$_pagers,true))!==false)
unset(self::$_pagers[$index]);
}
/**
* Restores the pager state.
* This method overrides the parent implementation and is invoked when
* the control is loading persistent state.
*/
public function loadState()
{
parent::loadState();
if(!$this->getEnableViewState(true))
return;
if(!$this->getIsDataBound())
$this->restoreFromViewState();
}
/**
* @return string the ID path of the control whose content would be paginated.
*/
public function getControlToPaginate()
{
return $this->getViewState('ControlToPaginate','');
}
/**
* Sets the ID path of the control whose content would be paginated.
* The ID path is the dot-connected IDs of the controls reaching from
* the pager's naming container to the target control.
* @param string the ID path
*/
public function setControlToPaginate($value)
{
$this->setViewState('ControlToPaginate',$value,'');
}
/**
* @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)
throw new TInvalidDataValueException('pager_currentpageindex_invalid');
$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('pager_pagesize_invalid');
$this->setViewState('PageSize',TPropertyValue::ensureInteger($value),10);
}
/**
* @return integer number of pages
*/
public function getPageCount()
{
if(($count=$this->getItemCount())<1)
return 1;
else
{
$pageSize=$this->getPageSize();
return (int)(($count+$pageSize-1)/$pageSize);
}
}
/**
* @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 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('pager_virtualitemcount_invalid');
$this->setViewState('VirtualItemCount',$value,0);
}
/**
* @return integer total number of items in the datasource.
*/
public function getItemCount()
{
return $this->getViewState('ItemCount',0);
}
/**
* @return string pager mode. Defaults to 'NextPrev'.
*/
public function getMode()
{
return $this->getViewState('Mode','NextPrev');
}
/**
* @param string pager mode. Valid values include 'NextPrev', 'Numeric' and 'List'.
*/
public function setMode($value)
{
$this->setViewState('Mode',TPropertyValue::ensureEnum($value,'NextPrev','Numeric','List'),'NextPrev');
}
/**
* @return string the type of command button for paging. Defaults to 'LinkButton'.
*/
public function getButtonType()
{
return $this->getViewState('ButtonType','LinkButton');
}
/**
* @param string the type of command button for paging. Valid values include 'LinkButton' and 'PushButton'.
*/
public function setButtonType($value)
{
$this->setViewState('ButtonType',TPropertyValue::ensureEnum($value,'LinkButton','PushButton'));
}
/**
* @return string text for the next page button. Defaults to '>'.
*/
public function getNextPageText()
{
return $this->getViewState('NextPageText','>');
}
/**
* @param string text for the next page button.
*/
public function setNextPageText($value)
{
$this->setViewState('NextPageText',$value,'>');
}
/**
* @return string text for the previous page button. Defaults to '<'.
*/
public function getPrevPageText()
{
return $this->getViewState('PrevPageText','<');
}
/**
* @param string text for the next page button.
*/
public function setPrevPageText($value)
{
$this->setViewState('PrevPageText',$value,'<');
}
/**
* @return string text for the first page button. Defaults to '<<'.
*/
public function getFirstPageText()
{
return $this->getViewState('FirstPageText','<<');
}
/**
* @param string text for the first page button. If empty, the first page button will not be rendered.
*/
public function setFirstPageText($value)
{
$this->setViewState('FirstPageText',$value,'<<');
}
/**
* @return string text for the last page button. Defaults to '>>'.
*/
public function getLastPageText()
{
return $this->getViewState('LastPageText','>>');
}
/**
* @param string text for the last page button. If empty, the last page button will not be rendered.
*/
public function setLastPageText($value)
{
$this->setViewState('LastPageText',$value,'>>');
}
/**
* @return integer maximum number of pager buttons to be displayed. Defaults to 10.
*/
public function getPageButtonCount()
{
return $this->getViewState('PageButtonCount',10);
}
/**
* @param integer maximum number of pager buttons to be displayed
* @throws TInvalidDataValueException if the value is less than 1.
*/
public function setPageButtonCount($value)
{
if(($value=TPropertyValue::ensureInteger($value))<1)
throw new TInvalidDataValueException('pager_pagebuttoncount_invalid');
$this->setViewState('PageButtonCount',$value,10);
}
/**
* @return TPagedDataSource creates a paged data source
*/
private function createPagedDataSource()
{
$ds=new TPagedDataSource;
$ds->setAllowPaging(true);
$customPaging=$this->getAllowCustomPaging();
$ds->setAllowCustomPaging($customPaging);
$ds->setCurrentPageIndex($this->getCurrentPageIndex());
$ds->setPageSize($this->getPageSize());
if($customPaging)
$ds->setVirtualItemCount($this->getVirtualItemCount());
return $ds;
}
/**
* Removes the existing child controls.
*/
protected function reset()
{
$this->getControls()->clear();
}
/**
* Restores the pager from viewstate.
*/
protected function restoreFromViewState()
{
$this->reset();
$ds=$this->createPagedDataSource();
$ds->setDataSource(new TDummyDataSource($this->getItemCount()));
$this->buildPager($ds);
}
/**
* Performs databinding to populate data items from data source.
* This method is invoked by {@link dataBind()}.
* You may override this function to provide your own way of data population.
* @param Traversable the bound data
*/
protected function performDataBinding($data)
{
$this->reset();
$controlID=$this->getControlToPaginate();
if(($targetControl=$this->getNamingContainer()->findControl($controlID))===null || !($targetControl instanceof TDataBoundControl))
throw new TConfigurationException('pager_controltopaginate_invalid',$controlID);
$ds=$this->createPagedDataSource();
$ds->setDataSource($this->getDataSource());
$this->setViewState('ItemCount',$ds->getDataSourceCount());
$this->buildPager($ds);
$this->synchronizePagers($targetControl,$ds);
$targetControl->setDataSource($ds);
$targetControl->dataBind();
}
/**
* Synchronizes the state of all pagers who have the same {@link getControlToPaginate ControlToPaginate}.
* @param TDataBoundControl the control whose content is to be paginated
* @param TPagedDataSource the paged data source associated with the pager
*/
protected function synchronizePagers($targetControl,$dataSource)
{
foreach(self::$_pagers as $pager)
{
if($pager!==$this && $pager->getNamingContainer()->findControl($pager->getControlToPaginate())===$targetControl)
{
$pager->reset();
$pager->setCurrentPageIndex($dataSource->getCurrentPageIndex());
$customPaging=$dataSource->getAllowCustomPaging();
$pager->setAllowCustomPaging($customPaging);
$pager->setViewState('ItemCount',$dataSource->getDataSourceCount());
if($customPaging)
$pager->setVirtualItemCount($dataSource->getVirtualItemCount());
$pager->buildPager($dataSource);
}
}
}
/**
* Builds the pager content based on the pager mode.
* Current implementation includes building 'NextPrev', 'Numeric' and 'List' pagers.
* Derived classes may override this method to provide additional pagers.
* @param TPagedDataSource data source bound to the target control
*/
protected function buildPager($dataSource)
{
switch($this->getMode())
{
case 'NextPrev':
$this->buildNextPrevPager($dataSource);
break;
case 'Numeric':
$this->buildNumericPager($dataSource);
break;
case 'List':
$this->buildListPager($dataSource);
break;
}
}
/**
* Creates a pager button.
* Depending on the button type, a TLinkButton or a TButton may be created.
* If it is enabled (clickable), its command name and parameter will also be set.
* Derived classes may override this method to create additional types of buttons, such as TImageButton.
* @param string button type, either LinkButton or PushButton
* @param boolean whether the button should be enabled
* @param string caption of the button
* @param string CommandName corresponding to the OnCommand event of the button
* @param string CommandParameter corresponding to the OnCommand event of the button
* @return mixed the button instance
*/
protected function createPagerButton($buttonType,$enabled,$text,$commandName,$commandParameter)
{
if($buttonType==='LinkButton')
{
if($enabled)
$button=new TLinkButton;
else
{
$button=new TLabel;
$button->setText($text);
return $button;
}
}
else
{
$button=new TButton;
if(!$enabled)
$button->setEnabled(false);
}
$button->setText($text);
$button->setCommandName($commandName);
$button->setCommandParameter($commandParameter);
$button->setCausesValidation(false);
return $button;
}
/**
* Builds a next-prev pager
* @param TPagedDataSource data source bound to the pager
*/
protected function buildNextPrevPager($dataSource)
{
$buttonType=$this->getButtonType();
$controls=$this->getControls();
if($dataSource->getIsFirstPage())
{
if(($text=$this->getFirstPageText())!=='')
{
$label=$this->createPagerButton($buttonType,false,$text,'','');
$controls->add($label);
$controls->add("\n");
}
$label=$this->createPagerButton($buttonType,false,$this->getPrevPageText(),'','');
$controls->add($label);
}
else
{
if(($text=$this->getFirstPageText())!=='')
{
$button=$this->createPagerButton($buttonType,true,$text,self::CMD_PAGE_FIRST,'');
$controls->add($button);
$controls->add("\n");
}
$button=$this->createPagerButton($buttonType,true,$this->getPrevPageText(),self::CMD_PAGE_PREV,'');
$controls->add($button);
}
$controls->add("\n");
if($dataSource->getIsLastPage())
{
$label=$this->createPagerButton($buttonType,false,$this->getNextPageText(),'','');
$controls->add($label);
if(($text=$this->getLastPageText())!=='')
{
$controls->add("\n");
$label=$this->createPagerButton($buttonType,false,$text,'','');
$controls->add($label);
}
}
else
{
$button=$this->createPagerButton($buttonType,true,$this->getNextPageText(),self::CMD_PAGE_NEXT,'');
$controls->add($button);
if(($text=$this->getLastPageText())!=='')
{
$controls->add("\n");
$button=$this->createPagerButton($buttonType,true,$text,self::CMD_PAGE_LAST,'');
$controls->add($button);
}
}
}
/**
* Builds a numeric pager
* @param TPagedDataSource data source bound to the pager
*/
protected function buildNumericPager($dataSource)
{
$buttonType=$this->getButtonType();
$controls=$this->getControls();
$pageCount=$dataSource->getPageCount();
$pageIndex=$dataSource->getCurrentPageIndex()+1;
$maxButtonCount=$this->getPageButtonCount();
$buttonCount=$maxButtonCount>$pageCount?$pageCount:$maxButtonCount;
$startPageIndex=1;
$endPageIndex=$buttonCount;
if($pageIndex>$endPageIndex)
{
$startPageIndex=((int)(($pageIndex-1)/$maxButtonCount))*$maxButtonCount+1;
if(($endPageIndex=$startPageIndex+$maxButtonCount-1)>$pageCount)
$endPageIndex=$pageCount;
if($endPageIndex-$startPageIndex+1<$maxButtonCount)
{
if(($startPageIndex=$endPageIndex-$maxButtonCount+1)<1)
$startPageIndex=1;
}
}
if($startPageIndex>1)
{
if(($text=$this->getFirstPageText())!=='')
{
$button=$this->createPagerButton($buttonType,true,$text,self::CMD_PAGE_FIRST,'');
$controls->add($button);
$controls->add("\n");
}
$prevPageIndex=$startPageIndex-1;
$button=$this->createPagerButton($buttonType,true,$this->getPrevPageText(),self::CMD_PAGE,"$prevPageIndex");
$controls->add($button);
$controls->add("\n");
}
for($i=$startPageIndex;$i<=$endPageIndex;++$i)
{
if($i===$pageIndex)
{
$label=$this->createPagerButton($buttonType,false,"$i",'','');
$controls->add($label);
}
else
{
$button=$this->createPagerButton($buttonType,true,"$i",self::CMD_PAGE,"$i");
$controls->add($button);
}
if($i<$endPageIndex)
$controls->add("\n");
}
if($pageCount>$endPageIndex)
{
$controls->add("\n");
$nextPageIndex=$endPageIndex+1;
$button=$this->createPagerButton($buttonType,true,$this->getNextPageText(),self::CMD_PAGE,"$nextPageIndex");
$controls->add($button);
if(($text=$this->getLastPageText())!=='')
{
$controls->add("\n");
$button=$this->createPagerButton($buttonType,true,$text,self::CMD_PAGE_LAST,'');
$controls->add($button);
}
}
}
/**
* Builds a dropdown list pager
* @param TPagedDataSource data source bound to the pager
*/
protected function buildListPager($dataSource)
{
$list=new TDropDownList;
$this->getControls()->add($list);
$list->setDataSource(range(1,$dataSource->getPageCount()));
$list->dataBind();
$list->setSelectedIndex($dataSource->getCurrentPageIndex());
$list->setAutoPostBack(true);
$list->attachEventHandler('OnSelectedIndexChanged',array($this,'listIndexChanged'));
}
/**
* Event handler to the OnSelectedIndexChanged event of the dropdown list.
* This handler will raise {@link onPageIndexChanged OnPageIndexChanged} event.
* @param TDropDownList the dropdown list control raising the event
* @param TEventParameter event parameter
*/
public function listIndexChanged($sender,$param)
{
$pageIndex=$sender->getSelectedIndex();
$this->onPageIndexChanged(new TPagerPageChangedEventParameter($sender,$pageIndex));
}
/**
* This event is raised when page index is changed due to a page button click.
* @param TPagerPageChangedEventParameter event parameter
*/
public function onPageIndexChanged($param)
{
$this->raiseEvent('OnPageIndexChanged',$this,$param);
}
/**
* Processes a bubbled event.
* This method overrides parent's implementation by wrapping event parameter
* for OnCommand event with item information.
* @param TControl the sender of the event
* @param TEventParameter event parameter
* @return boolean whether the event bubbling should stop here.
*/
public function bubbleEvent($sender,$param)
{
if($param instanceof TCommandEventParameter)
{
$command=$param->getCommandName();
if(strcasecmp($command,self::CMD_PAGE)===0)
{
$pageIndex=TPropertyValue::ensureInteger($param->getCommandParameter())-1;
$this->onPageIndexChanged(new TPagerPageChangedEventParameter($sender,$pageIndex));
return true;
}
else if(strcasecmp($command,self::CMD_PAGE_NEXT)===0)
{
$pageIndex=$this->getCurrentPageIndex()+1;
$this->onPageIndexChanged(new TPagerPageChangedEventParameter($sender,$pageIndex));
return true;
}
else if(strcasecmp($command,self::CMD_PAGE_PREV)===0)
{
$pageIndex=$this->getCurrentPageIndex()-1;
$this->onPageIndexChanged(new TPagerPageChangedEventParameter($sender,$pageIndex));
return true;
}
else if(strcasecmp($command,self::CMD_PAGE_FIRST)===0)
{
$this->onPageIndexChanged(new TPagerPageChangedEventParameter($sender,0));
return true;
}
else if(strcasecmp($command,self::CMD_PAGE_LAST)===0)
{
$this->onPageIndexChanged(new TPagerPageChangedEventParameter($sender,$this->getPageCount()-1));
return true;
}
return false;
}
else
return false;
}
}
/**
* TPagerPageChangedEventParameter class
*
* TPagerPageChangedEventParameter encapsulates the parameter data for
* {@link TPager::onPageIndexChanged PageIndexChanged} event of {@link TPager} controls.
*
* The {@link getCommandSource CommandSource} property refers to the control
* that originally raises the OnCommand event, while {@link getNewPageIndex NewPageIndex}
* returns the new page index carried with the page command.
*
* @author Qiang Xue
* @version $Revision: $ $Date: $
* @package System.Web.UI.WebControls
* @since 3.0.2
*/
class TPagerPageChangedEventParameter extends TEventParameter
{
/**
* @var integer new page index
*/
private $_newIndex;
/**
* @var TControl original event sender
*/
private $_source=null;
/**
* Constructor.
* @param TControl the control originally raises the OnCommand event.
* @param integer new page index
*/
public function __construct($source,$newPageIndex)
{
$this->_source=$source;
$this->_newIndex=$newPageIndex;
}
/**
* @return TControl the control originally raises the OnCommand event.
*/
public function getCommandSource()
{
return $this->_source;
}
/**
* @return integer new page index
*/
public function getNewPageIndex()
{
return $this->_newIndex;
}
}
?>