2011-12-06 01:56:24 +01:00
|
|
|
<?php
|
2016-06-15 06:03:16 +02:00
|
|
|
|
2016-08-19 00:51:35 +02:00
|
|
|
namespace SilverStripe\Forms\GridField;
|
|
|
|
|
|
|
|
use SilverStripe\Core\Config\Configurable;
|
2016-09-09 08:43:05 +02:00
|
|
|
use SilverStripe\ORM\Limitable;
|
2016-06-15 06:03:16 +02:00
|
|
|
use SilverStripe\ORM\SS_List;
|
|
|
|
use SilverStripe\ORM\UnsavedRelationList;
|
2016-08-19 00:51:35 +02:00
|
|
|
use SilverStripe\View\ArrayData;
|
|
|
|
use SilverStripe\View\SSViewer;
|
|
|
|
use LogicException;
|
|
|
|
|
2011-12-06 01:56:24 +01:00
|
|
|
/**
|
2014-08-15 08:53:05 +02:00
|
|
|
* GridFieldPaginator paginates the {@link GridField} list and adds controls
|
2013-05-20 12:18:07 +02:00
|
|
|
* to the bottom of the {@link GridField}.
|
2011-12-06 01:56:24 +01:00
|
|
|
*/
|
2022-02-01 23:14:33 +01:00
|
|
|
class GridFieldPaginator extends AbstractGridFieldComponent implements GridField_HTMLProvider, GridField_DataManipulator, GridField_ActionProvider, GridField_StateProvider
|
2016-11-29 00:31:16 +01:00
|
|
|
{
|
|
|
|
use Configurable;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Specifies default items per page
|
|
|
|
*
|
|
|
|
* @config
|
|
|
|
* @var int
|
|
|
|
*/
|
|
|
|
private static $default_items_per_page = 15;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var int
|
|
|
|
*/
|
|
|
|
protected $itemsPerPage;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* See {@link setThrowExceptionOnBadDataType()}
|
|
|
|
*/
|
|
|
|
protected $throwExceptionOnBadDataType = true;
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @param int $itemsPerPage - How many items should be displayed per page
|
|
|
|
*/
|
|
|
|
public function __construct($itemsPerPage = null)
|
|
|
|
{
|
2017-02-22 04:14:53 +01:00
|
|
|
$this->itemsPerPage = $itemsPerPage
|
|
|
|
?: GridFieldPaginator::config()->uninherited('default_items_per_page');
|
2016-11-29 00:31:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Determine what happens when this component is used with a list that isn't {@link SS_Filterable}.
|
|
|
|
*
|
|
|
|
* - true: An exception is thrown
|
|
|
|
* - false: This component will be ignored - it won't make any changes to the GridField.
|
|
|
|
*
|
|
|
|
* By default, this is set to true so that it's clearer what's happening, but the predefined
|
|
|
|
* {@link GridFieldConfig} subclasses set this to false for flexibility.
|
|
|
|
*
|
|
|
|
* @param bool $throwExceptionOnBadDataType
|
|
|
|
* @return $this
|
|
|
|
*/
|
|
|
|
public function setThrowExceptionOnBadDataType($throwExceptionOnBadDataType)
|
|
|
|
{
|
|
|
|
$this->throwExceptionOnBadDataType = $throwExceptionOnBadDataType;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* See {@link setThrowExceptionOnBadDataType()}
|
|
|
|
*
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function getThrowExceptionOnBadDataType()
|
|
|
|
{
|
|
|
|
return $this->throwExceptionOnBadDataType;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check that this dataList is of the right data type.
|
|
|
|
* Returns false if it's a bad data type, and if appropriate, throws an exception.
|
|
|
|
*
|
|
|
|
* @param SS_List $dataList
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
protected function checkDataType($dataList)
|
|
|
|
{
|
|
|
|
if ($dataList instanceof Limitable) {
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
if ($this->throwExceptionOnBadDataType) {
|
|
|
|
throw new LogicException(
|
2017-05-17 07:40:13 +02:00
|
|
|
static::class . " expects an SS_Limitable list to be passed to the GridField."
|
2016-11-29 00:31:16 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @param GridField $gridField
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function getActions($gridField)
|
|
|
|
{
|
|
|
|
if (!$this->checkDataType($gridField->getList())) {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
2020-04-20 19:58:09 +02:00
|
|
|
return ['paginate'];
|
2016-11-29 00:31:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @param GridField $gridField
|
|
|
|
* @param string $actionName
|
|
|
|
* @param string $arguments
|
|
|
|
* @param array $data
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function handleAction(GridField $gridField, $actionName, $arguments, $data)
|
|
|
|
{
|
|
|
|
if (!$this->checkDataType($gridField->getList())) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($actionName !== 'paginate') {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
$state = $this->getGridPagerState($gridField);
|
|
|
|
$state->currentPage = (int)$arguments;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected $totalItems = 0;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Retrieves/Sets up the state object used to store and retrieve information
|
|
|
|
* about the current paging details of this GridField
|
|
|
|
* @param GridField $gridField
|
|
|
|
* @return GridState_Data
|
|
|
|
*/
|
|
|
|
protected function getGridPagerState(GridField $gridField)
|
|
|
|
{
|
2019-08-23 07:15:29 +02:00
|
|
|
return $gridField->State->GridFieldPaginator;
|
|
|
|
}
|
2016-11-29 00:31:16 +01:00
|
|
|
|
2019-08-23 07:15:29 +02:00
|
|
|
public function initDefaultState(GridState_Data $data): void
|
|
|
|
{
|
|
|
|
$data->GridFieldPaginator->initDefaults([
|
|
|
|
'currentPage' => 1,
|
|
|
|
'itemsPerPage' => $this->getItemsPerPage()
|
|
|
|
]);
|
2016-11-29 00:31:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @param GridField $gridField
|
|
|
|
* @param SS_List $dataList
|
|
|
|
* @return SS_List
|
|
|
|
*/
|
|
|
|
public function getManipulatedData(GridField $gridField, SS_List $dataList)
|
|
|
|
{
|
|
|
|
if (!$this->checkDataType($dataList)) {
|
|
|
|
return $dataList;
|
|
|
|
}
|
|
|
|
|
|
|
|
$state = $this->getGridPagerState($gridField);
|
|
|
|
|
|
|
|
// Update item count prior to filter. GridFieldPageCount will rely on this value
|
|
|
|
$this->totalItems = $dataList->count();
|
|
|
|
|
|
|
|
$startRow = $this->itemsPerPage * ($state->currentPage - 1);
|
|
|
|
|
|
|
|
// Prevent visiting a page with an offset higher than the total number of items
|
|
|
|
if ($startRow >= $this->totalItems) {
|
|
|
|
$state->currentPage = 1;
|
|
|
|
$startRow = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!($dataList instanceof Limitable) || ($dataList instanceof UnsavedRelationList)) {
|
|
|
|
return $dataList;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $dataList->limit((int)$this->itemsPerPage, (int)$startRow);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Determines arguments to be passed to the template for building this field
|
|
|
|
*
|
|
|
|
* @param GridField $gridField
|
|
|
|
* @return ArrayData If paging is available this will be an ArrayData
|
|
|
|
* object of paging details with these parameters:
|
|
|
|
* <ul>
|
|
|
|
* <li>OnlyOnePage: boolean - Is there only one page?</li>
|
|
|
|
* <li>FirstShownRecord: integer - Number of the first record displayed</li>
|
|
|
|
* <li>LastShownRecord: integer - Number of the last record displayed</li>
|
|
|
|
* <li>NumRecords: integer - Total number of records</li>
|
|
|
|
* <li>NumPages: integer - The number of pages</li>
|
|
|
|
* <li>CurrentPageNum (optional): integer - If OnlyOnePage is false, the number of the current page</li>
|
|
|
|
* <li>FirstPage (optional): GridField_FormAction - Button to go to the first page</li>
|
|
|
|
* <li>PreviousPage (optional): GridField_FormAction - Button to go to the previous page</li>
|
|
|
|
* <li>NextPage (optional): GridField_FormAction - Button to go to the next page</li>
|
|
|
|
* <li>LastPage (optional): GridField_FormAction - Button to go to last page</li>
|
|
|
|
* </ul>
|
|
|
|
*/
|
|
|
|
public function getTemplateParameters(GridField $gridField)
|
|
|
|
{
|
|
|
|
if (!$this->checkDataType($gridField->getList())) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
$state = $this->getGridPagerState($gridField);
|
|
|
|
|
|
|
|
// Figure out which page and record range we're on
|
|
|
|
$totalRows = $this->totalItems;
|
|
|
|
if (!$totalRows) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2017-01-10 14:08:47 +01:00
|
|
|
$totalPages = 1;
|
|
|
|
$firstShownRecord = 1;
|
|
|
|
$lastShownRecord = $totalRows;
|
|
|
|
if ($itemsPerPage = $this->getItemsPerPage()) {
|
|
|
|
$totalPages = (int)ceil($totalRows / $itemsPerPage);
|
|
|
|
if ($totalPages == 0) {
|
|
|
|
$totalPages = 1;
|
|
|
|
}
|
|
|
|
$firstShownRecord = ($state->currentPage - 1) * $itemsPerPage + 1;
|
|
|
|
if ($firstShownRecord > $totalRows) {
|
|
|
|
$firstShownRecord = $totalRows;
|
|
|
|
}
|
|
|
|
$lastShownRecord = $state->currentPage * $itemsPerPage;
|
|
|
|
if ($lastShownRecord > $totalRows) {
|
|
|
|
$lastShownRecord = $totalRows;
|
|
|
|
}
|
2016-11-29 00:31:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// If there is only 1 page for all the records in list, we don't need to go further
|
|
|
|
// to sort out those first page, last page, pre and next pages, etc
|
|
|
|
// we are not render those in to the paginator.
|
|
|
|
if ($totalPages === 1) {
|
2020-04-20 19:58:09 +02:00
|
|
|
return new ArrayData([
|
2016-11-29 00:31:16 +01:00
|
|
|
'OnlyOnePage' => true,
|
|
|
|
'FirstShownRecord' => $firstShownRecord,
|
|
|
|
'LastShownRecord' => $lastShownRecord,
|
|
|
|
'NumRecords' => $totalRows,
|
|
|
|
'NumPages' => $totalPages
|
2020-04-20 19:58:09 +02:00
|
|
|
]);
|
2016-11-29 00:31:16 +01:00
|
|
|
} else {
|
|
|
|
// First page button
|
|
|
|
$firstPage = new GridField_FormAction($gridField, 'pagination_first', 'First', 'paginate', 1);
|
2018-08-12 22:40:52 +02:00
|
|
|
$firstPage->addExtraClass('btn btn-secondary btn--hide-text btn-sm font-icon-angle-double-left ss-gridfield-pagination-action ss-gridfield-firstpage');
|
2016-11-29 00:31:16 +01:00
|
|
|
if ($state->currentPage == 1) {
|
|
|
|
$firstPage = $firstPage->performDisabledTransformation();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Previous page button
|
|
|
|
$previousPageNum = $state->currentPage <= 1 ? 1 : $state->currentPage - 1;
|
|
|
|
$previousPage = new GridField_FormAction(
|
|
|
|
$gridField,
|
|
|
|
'pagination_prev',
|
|
|
|
'Previous',
|
|
|
|
'paginate',
|
|
|
|
$previousPageNum
|
|
|
|
);
|
2018-08-12 22:40:52 +02:00
|
|
|
$previousPage->addExtraClass('btn btn-secondary btn--hide-text btn-sm font-icon-angle-left ss-gridfield-pagination-action ss-gridfield-previouspage');
|
2016-11-29 00:31:16 +01:00
|
|
|
if ($state->currentPage == 1) {
|
|
|
|
$previousPage = $previousPage->performDisabledTransformation();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Next page button
|
|
|
|
$nextPageNum = $state->currentPage >= $totalPages ? $totalPages : $state->currentPage + 1;
|
|
|
|
$nextPage = new GridField_FormAction(
|
|
|
|
$gridField,
|
|
|
|
'pagination_next',
|
|
|
|
'Next',
|
|
|
|
'paginate',
|
|
|
|
$nextPageNum
|
|
|
|
);
|
2018-08-12 22:40:52 +02:00
|
|
|
$nextPage->addExtraClass('btn btn-secondary btn--hide-text btn-sm font-icon-angle-right ss-gridfield-pagination-action ss-gridfield-nextpage');
|
2016-11-29 00:31:16 +01:00
|
|
|
if ($state->currentPage == $totalPages) {
|
|
|
|
$nextPage = $nextPage->performDisabledTransformation();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Last page button
|
|
|
|
$lastPage = new GridField_FormAction($gridField, 'pagination_last', 'Last', 'paginate', $totalPages);
|
2018-08-12 22:40:52 +02:00
|
|
|
$lastPage->addExtraClass('btn btn-secondary btn--hide-text btn-sm font-icon-angle-double-right ss-gridfield-pagination-action ss-gridfield-lastpage');
|
2016-11-29 00:31:16 +01:00
|
|
|
if ($state->currentPage == $totalPages) {
|
|
|
|
$lastPage = $lastPage->performDisabledTransformation();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Render in template
|
2020-04-20 19:58:09 +02:00
|
|
|
return new ArrayData([
|
2016-11-29 00:31:16 +01:00
|
|
|
'OnlyOnePage' => false,
|
|
|
|
'FirstPage' => $firstPage,
|
|
|
|
'PreviousPage' => $previousPage,
|
|
|
|
'CurrentPageNum' => $state->currentPage,
|
|
|
|
'NumPages' => $totalPages,
|
|
|
|
'NextPage' => $nextPage,
|
|
|
|
'LastPage' => $lastPage,
|
|
|
|
'FirstShownRecord' => $firstShownRecord,
|
|
|
|
'LastShownRecord' => $lastShownRecord,
|
|
|
|
'NumRecords' => $totalRows
|
2020-04-20 19:58:09 +02:00
|
|
|
]);
|
2016-11-29 00:31:16 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @param GridField $gridField
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function getHTMLFragments($gridField)
|
|
|
|
{
|
|
|
|
$forTemplate = $this->getTemplateParameters($gridField);
|
|
|
|
if (!$forTemplate) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
$template = SSViewer::get_templates_by_class($this, '_Row', __CLASS__);
|
2020-04-20 19:58:09 +02:00
|
|
|
return [
|
2016-11-29 00:31:16 +01:00
|
|
|
'footer' => $forTemplate->renderWith(
|
|
|
|
$template,
|
2020-04-20 19:58:09 +02:00
|
|
|
['Colspan' => count($gridField->getColumns())]
|
2016-11-29 00:31:16 +01:00
|
|
|
)
|
2020-04-20 19:58:09 +02:00
|
|
|
];
|
2016-11-29 00:31:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param int $num
|
|
|
|
* @return $this
|
|
|
|
*/
|
|
|
|
public function setItemsPerPage($num)
|
|
|
|
{
|
|
|
|
$this->itemsPerPage = $num;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return Int
|
|
|
|
*/
|
|
|
|
public function getItemsPerPage()
|
|
|
|
{
|
|
|
|
return $this->itemsPerPage;
|
|
|
|
}
|
2011-12-06 01:56:24 +01:00
|
|
|
}
|