FEATURE: Added PaginatedList, which wraps around a data list or set to provide pagination functionality. This replaces the pagination functionality baked into DataObjectSet.

API CHANGE: Removed pagination related methods from DataObjectSet and implemented them on PaginatedList.
API CHANGE: Removed DataObjectSet::parseQueryLimit(), this is now implemented as PaginatedList::setPaginationFromQuery().
API CHANGE: Deprecated DataObjectSet::TotalItems in favour of Count().
ENHANCEMENT: Added FirstLink and LastLink to PaginatedList.
MINOR: Updated documentation, and added a how-to on paginating items.
This commit is contained in:
ajshort 2011-03-31 12:01:04 +11:00 committed by Sam Minnee
parent 79cde9df25
commit 3fbb29a6c5
9 changed files with 745 additions and 351 deletions

416
core/PaginatedList.php Normal file
View File

@ -0,0 +1,416 @@
<?php
/**
* A decorator that wraps around a data list in order to provide pagination.
*
* @package sapphire
* @subpackage view
*/
class PaginatedList extends ViewableData implements IteratorAggregate {
protected $list;
protected $request;
protected $getVar = 'start';
protected $pageLength = 10;
protected $pageStart;
protected $totalItems;
/**
* Constructs a new paginated list instance around a list.
*
* @param DataObjectSet $list The list to paginate. The getRange method will
* be used to get the subset of objects to show.
* @param array|ArrayAccess Either a map of request parameters or
* request object that the pagination offset is read from.
*/
public function __construct(DataObjectSet $list, $request = array()) {
if (!is_array($request) && !$request instanceof ArrayAccess) {
throw new Exception('The request must be readable as an array.');
}
$this->list = $list;
$this->failover = $list;
$this->request = $request;
}
/**
* @return DataObjectSet
*/
public function getList() {
return $this->list;
}
/**
* Returns the GET var that is used to set the page start. This defaults
* to "start".
*
* If there is more than one paginated list on a page, it is neccesary to
* set a different get var for each using {@link setPaginationGetVar()}.
*
* @return string
*/
public function getPaginationGetVar() {
return $this->getVar;
}
/**
* Sets the GET var used to set the page start.
*
* @param string $var
*/
public function setPaginationGetVar($var) {
$this->getVar = $var;
}
/**
* Returns the number of items displayed per page. This defaults to 10.
*
* @return int.
*/
public function getPageLength() {
return $this->pageLength;
}
/**
* Set the number of items displayed per page.
*
* @param int $length
*/
public function setPageLength($length) {
$this->pageLength = $length;
}
/**
* Sets the current page.
*
* @param int $page
*/
public function setCurrentPage($page) {
$this->pageStart = ($page - 1) * $this->pageLength;
}
/**
* Returns the offset of the item the current page starts at.
*
* @return int
*/
public function getPageStart() {
if ($this->pageStart === null) {
if ($this->request && isset($this->request[$this->getVar])) {
$this->pageStart = (int) $this->request[$this->getVar];
} else {
$this->pageStart = 0;
}
}
return $this->pageStart;
}
/**
* Sets the offset of the item that current page starts at. This should be
* a multiple of the page length.
*
* @param int $start
*/
public function setPageStart($start) {
$this->pageStart = $start;
}
/**
* Returns the total number of items in the unpaginated list.
*
* @return int
*/
public function getTotalItems() {
if ($this->totalItems === null) {
$this->totalItems = count($this->list);
}
return $this->totalItems;
}
/**
* Sets the total number of items in the list. This is useful when doing
* custom pagination.
*
* @param int $items
*/
public function setTotalItems($items) {
$this->totalItems = $items;
}
/**
* Sets the page length, page start and total items from a query object's
* limit, offset and unlimited count. The query MUST have a limit clause.
*
* @param SQLQuery $query
*/
public function setPaginationFromQuery(SQLQuery $query) {
if ($query->limit) {
$this->setPageLength($query->limit['limit']);
$this->setPageStart($query->limit['start']);
$this->setTotalItems($query->unlimitedRowCount());
}
}
/**
* @return IteratorIterator
*/
public function getIterator() {
return new IteratorIterator(
$this->list->getRange($this->getPageStart(), $this->pageLength)
);
}
/**
* Returns a set of links to all the pages in the list. This is useful for
* basic pagination.
*
* By default it returns links to every page, but if you pass the $max
* parameter the number of pages will be limited to that number, centered
* around the current page.
*
* @param int $max
* @return DataObjectSet
*/
public function Pages($max = null) {
$result = new DataObjectSet();
if ($max) {
$start = ($this->CurrentPage() - floor($max / 2)) - 1;
$end = $this->CurrentPage() + floor($max / 2);
if ($start < 0) {
$start = 0;
$end = $max;
}
if ($end > $this->TotalPages()) {
$end = $this->TotalPages();
$start = max(0, $end - $max);
}
} else {
$start = 0;
$end = $this->TotalPages();
}
for ($i = $start; $i < $end; $i++) {
$result->push(new ArrayData(array(
'PageNum' => $i + 1,
'Link' => HTTP::setGetVar($this->getVar, $i * $this->pageLength),
'CurrentBool' => $this->CurrentPage() == ($i + 1)
)));
}
return $result;
}
/**
* Returns a summarised pagination which limits the number of pages shown
* around the current page for visually balanced.
*
* Example: 25 pages total, currently on page 6, context of 4 pages
* [prev] [1] ... [4] [5] [[6]] [7] [8] ... [25] [next]
*
* Example template usage:
* <code>
* <% if MyPages.MoreThanOnePage %>
* <% if MyPages.NotFirstPage %>
* <a class="prev" href="$MyPages.PrevLink">Prev</a>
* <% end_if %>
* <% control MyPages.PaginationSummary(4) %>
* <% if CurrentBool %>
* $PageNum
* <% else %>
* <% if Link %>
* <a href="$Link">$PageNum</a>
* <% else %>
* ...
* <% end_if %>
* <% end_if %>
* <% end_control %>
* <% if MyPages.NotLastPage %>
* <a class="next" href="$MyPages.NextLink">Next</a>
* <% end_if %>
* <% end_if %>
* </code>
*
* @param int $context The number of pages to display around the current
* page. The number should be event, as half the number of each pages
* are displayed on either side of the current one.
* @return DataObjectSet
*/
public function PaginationSummary($context = 4) {
$result = new DataObjectSet();
$current = $this->CurrentPage();
$total = $this->TotalPages();
// Make the number even for offset calculations.
if ($context % 2) {
$context--;
}
// If the first or last page is current, then show all context on one
// side of it - otherwise show half on both sides.
if ($current == 1 || $current == $total) {
$offset = $context;
} else {
$offset = floor($context / 2);
}
$left = max($current - $offset, 1);
$range = range($current - $offset, $current + $offset);
if ($left + $context > $total) {
$left = $total - $context;
}
for ($i = 0; $i < $total; $i++) {
$link = HTTP::setGetVar($this->getVar, $i * $this->pageLength);
$num = $i + 1;
$emptyRange = $num != 1 && $num != $total && (
$num == $left - 1 || $num == $left + $context + 1
);
if ($emptyRange) {
$result->push(new ArrayData(array(
'PageNum' => null,
'Link' => null,
'CurrentBool' => false
)));
} elseif ($num == 1 || $num == $total || in_array($num, $range)) {
$result->push(new ArrayData(array(
'PageNum' => $num,
'Link' => $link,
'CurrentBool' => $current == $num
)));
}
}
return $result;
}
/**
* @return int
*/
public function CurrentPage() {
return floor($this->getPageStart() / $this->pageLength) + 1;
}
/**
* @return int
*/
public function TotalPages() {
return ceil($this->getTotalItems() / $this->pageLength);
}
/**
* @return bool
*/
public function MoreThanOnePage() {
return $this->TotalPages() > 1;
}
/**
* @return bool
*/
public function NotFirstPage() {
return $this->CurrentPage() != 1;
}
/**
* @return bool
*/
public function NotLastPage() {
return $this->CurrentPage() != $this->TotalPages();
}
/**
* Returns the number of the first item being displayed on the current
* page. This is useful for things like "displaying 10-20".
*
* @return int
*/
public function FirstItem() {
return ($start = $this->getPageStart()) ? $start + 1 : 1;
}
/**
* Returns the number of the last item being displayed on this page.
*
* @return int
*/
public function LastItem() {
if ($start = $this->getPageStart()) {
return min($start + $this->pageLength, $this->getTotalItems());
} else {
return min($this->pageLength, $this->getTotalItems());
}
}
/**
* Returns a link to the first page.
*
* @return string
*/
public function FirstLink() {
return HTTP::setGetVar($this->getVar, 0);
}
/**
* Returns a link to the last page.
*
* @return string
*/
public function LastLink() {
return HTTP::setGetVar($this->getVar, ($this->TotalPages() - 1) * $this->pageLength);
}
/**
* Returns a link to the next page, if there is another page after the
* current one.
*
* @return string
*/
public function NextLink() {
if ($this->NotLastPage()) {
return HTTP::setGetVar($this->getVar, $this->getPageStart() + $this->pageLength);
}
}
/**
* Returns a link to the previous page, if the first page is not currently
* active.
*
* @return string
*/
public function PrevLink() {
if ($this->NotFirstPage()) {
return HTTP::setGetVar($this->getVar, $this->getPageStart() - $this->pageLength);
}
}
// DEPRECATED --------------------------------------------------------------
/**
* @deprecated 3.0 Use individual getter methods.
*/
public function getPageLimits() {
return array(
'pageStart' => $this->getPageStart(),
'pageLength' => $this->pageLength,
'totalSize' => $this->getTotalItems(),
);
}
/**
* @deprecated 3.0 Use individual setter methods.
*/
public function setPageLimits($pageStart, $pageLength, $totalSize) {
$this->setPageStart($pageStart);
$this->setPageLength($pageLength);
$this->setTotalSize($totalSize);
}
}

View File

@ -122,7 +122,6 @@ class DataList extends DataObjectSet {
*/
protected function generateItems() {
$query = $this->dataQuery->query();
$this->parseQueryLimit($query);
$rows = $query->execute();
$results = array();
foreach($rows as $row) {

View File

@ -0,0 +1,66 @@
# Paginating A List
Adding pagination to a `[api:DataList]` or `[DataObjectSet]` is quite simple. All
you need to do is wrap the object in a `[api:PaginatedList]` decorator, which takes
care of fetching a sub-set of the total list and presenting it to the template.
In order to create a paginated list, you can create a method on your controller
that first creates a `DataList` that will return all pages, and then wraps it
in a `[api:PaginatedSet]` object. The `PaginatedList` object is also passed the
HTTP request object so it can read the current page information from the
"?start=" GET var.
The paginator will automatically set up query limits and read the request for
information.
:::php
/**
* Returns a paginated list of all pages in the site.
*/
public function PaginatedPages() {
$pages = DataList::create('Page');
return new PaginatedList($pages, $this->request);
}
## Setting Up The Template
Now all that remains is to render this list into a template, along with pagination
controls. There are two ways to generate pagination controls:
`[api:PaginatedSet->Pages()]` and `[api:PaginatedSet->PaginationSummary()]`. In
this example we will use `PaginationSummary()`.
The first step is to simply list the objects in the template:
:::ss
<ul>
<% control PaginatedPages %>
<li><a href="$Link">$Title</a></li>
<% end_control %>
</ul>
By default this will display 10 pages at a time. The next step is to add pagination
controls below this so the user can switch between pages:
:::ss
<% if PaginatedPages.MoreThanOnePage %>
<% if PaginatedPages.NotFirstPage %>
<a class="prev" href="$PaginatedPages.PrevLink">Prev</a>
<% end_if %>
<% control PaginatedPages.Pages %>
<% if CurrentBool %>
$PageNum
<% else %>
<% if Link %>
<a href="$Link">$PageNum</a>
<% else %>
...
<% end_if %>
<% end_if %>
<% end_control %>
<% if PaginatedPages.NotLastPage %>
<a class="next" href="$PaginatedPages.NextLink">Next</a>
<% end_if %>
<% end_if %>
If there is more than one page, this block will render a set of pagination
controls in the form `[1] ... [3] [4] [[5]] [6] [7] ... [10]`.

View File

@ -319,7 +319,7 @@ a quick reference (not all of them are described above):
$NexPageLink, $Link, $RelativeLink, $ChildrenOf, $Page, $Level, $Menu, $Section2, $LoginForm, $SilverStripeNavigator,
$PageComments, $Now, $LinkTo, $AbsoluteLink, $CurrentMember, $PastVisitor, $PastMember, $XML_val, $RAW_val, $SQL_val,
$JS_val, $ATT_val, $First, $Last, $FirstLast, $MiddleString, $Middle, $Even, $Odd, $EvenOdd, $Pos, $TotalItems,
$BaseHref, $Debug, $CurrentPage, $Top
$BaseHref, $Debug, $Top
### All fields available in Page_Controller
@ -334,7 +334,7 @@ $LinkToID, $VersionID, $CopyContentFromID, $RecordClassName
$Link, $LinkOrCurrent, $LinkOrSection, $LinkingMode, $ElementName, $InSection, $Comments, $Breadcrumbs, $NestedTitle,
$MetaTags, $ContentSource, $MultipleParents, $TreeTitle, $CMSTreeClasses, $Now, $LinkTo, $AbsoluteLink, $CurrentMember,
$PastVisitor, $PastMember, $XML_val, $RAW_val, $SQL_val, $JS_val, $ATT_val, $First, $Last, $FirstLast, $MiddleString,
$Middle, $Even, $Odd, $EvenOdd, $Pos, $TotalItems, $BaseHref, $CurrentPage, $Top
$Middle, $Even, $Odd, $EvenOdd, $Pos, $TotalItems, $BaseHref, $Top
### All fields available in Page

View File

@ -89,21 +89,25 @@ method, we're building our own `getCustomSearchContext()` variant.
### Pagination
For paginating records on multiple pages, you need to get the generated `SQLQuery` before firing off the actual
search. This way we can set the "page limits" on the result through `setPageLimits()`, and only retrieve a fraction of
the whole result set.
For pagination records on multiple pages, you need to wrap the results in a
`PaginatedList` object. This object is also passed the generated `SQLQuery`
in order to read page limit information. It is also passed the current
`SS_HTTPRequest` object so it can read the current page from a GET var.
:::php
function getResults($searchCriteria = array()) {
public function getResults($searchCriteria = array()) {
$start = ($this->request->getVar('start')) ? (int)$this->request->getVar('start') : 0;
$limit = 10;
$context = singleton('MyDataObject')->getCustomSearchContext();
$query = $context->getQuery($searchCriteria, null, array('start'=>$start,'limit'=>$limit));
$records = $context->getResults($searchCriteria, null, array('start'=>$start,'limit'=>$limit));
if($records) {
$records->setPageLimits($start, $limit, $query->unlimitedRowCount());
$records = new PaginatedList($records, $this->request);
$records->setPageStart($start);
$records->setPageSize($limit);
$records->setTotalSize($query->unlimitedRowCount());
}
return $records;
@ -135,7 +139,7 @@ For more information on how to paginate your results within the template, see [T
to show the results of your custom search you need at least this content in your template, notice that
Results.PaginationSummary(4) defines how many pages the search will show in the search results. something like:
**Next 1 2 *3* 4 5 558**
**Next 1 2 *3* 4 5 <EFBFBD><EFBFBD><EFBFBD> 558**
:::ss

View File

@ -714,7 +714,6 @@ class File extends DataObject {
$records = $query->execute();
$ret = $this->buildDataObjectSet($records, $containerClass);
if($ret) $ret->parseQueryLimit($query);
return $ret;
}

View File

@ -32,30 +32,6 @@ class DataObjectSet extends ViewableData implements IteratorAggregate, Countable
*/
protected $current = null;
/**
* The number object the current page starts at.
* @var int
*/
protected $pageStart;
/**
* The number of objects per page.
* @var int
*/
protected $pageLength;
/**
* Total number of DataObjects in this set.
* @var int
*/
protected $totalSize;
/**
* The pagination GET variable that controls the start of this set.
* @var string
*/
protected $paginationGetVar = "start";
/**
* Create a new DataObjectSet. If you pass one or more arguments, it will try to convert them into {@link ArrayData} objects.
* @todo Does NOT automatically convert objects with complex datatypes (e.g. converting arrays within an objects to its own DataObjectSet)
@ -189,272 +165,6 @@ class DataObjectSet extends ViewableData implements IteratorAggregate, Countable
return $this->map($index, $titleField, $emptyString, $sort);
}
/**
* Set number of objects on each page.
* @param int $length Number of objects per page
*/
public function setPageLength($length) {
$this->pageLength = $length;
}
/**
* Set the page limits.
* @param int $pageStart The start of this page.
* @param int $pageLength Number of objects per page
* @param int $totalSize Total number of objects.
*/
public function setPageLimits($pageStart, $pageLength, $totalSize) {
$this->pageStart = $pageStart;
$this->pageLength = $pageLength;
$this->totalSize = $totalSize;
}
/**
* Get the page limits
* @return array
*/
public function getPageLimits() {
return array(
'pageStart' => $this->pageStart,
'pageLength' => $this->pageLength,
'totalSize' => $this->totalSize,
);
}
/**
* Use the limit from the given query to add prev/next buttons to this DataObjectSet.
* @param SQLQuery $query The query used to generate this DataObjectSet
*/
public function parseQueryLimit(SQLQuery $query) {
if($query->limit) {
if(is_array($query->limit)) {
$length = $query->limit['limit'];
$start = $query->limit['start'];
} else if(stripos($query->limit, 'OFFSET')) {
list($length, $start) = preg_split("/ +OFFSET +/i", trim($query->limit));
} else {
$result = preg_split("/ *, */", trim($query->limit));
$start = $result[0];
$length = isset($result[1]) ? $result[1] : null;
}
if(!$length) {
$length = $start;
$start = 0;
}
$this->setPageLimits($start, $length, $query->unlimitedRowCount());
}
}
/**
* Returns the number of the current page.
* @return int
*/
public function CurrentPage() {
return floor($this->pageStart / $this->pageLength) + 1;
}
/**
* Returns the total number of pages.
* @return int
*/
public function TotalPages() {
if($this->totalSize == 0) {
$this->totalSize = $this->Count();
}
if($this->pageLength == 0) {
$this->pageLength = 10;
}
return ceil($this->totalSize / $this->pageLength);
}
/**
* Return a datafeed of page-links, good for use in search results, etc.
* $maxPages will put an upper limit on the number of pages to return. It will
* show the pages surrounding the current page, so you can still get to the deeper pages.
* @param int $maxPages The maximum number of pages to return
* @return DataObjectSet
*/
public function Pages($maxPages = 0){
$ret = new DataObjectSet();
if($maxPages) {
$startPage = ($this->CurrentPage() - floor($maxPages / 2)) - 1;
$endPage = $this->CurrentPage() + floor($maxPages / 2);
if($startPage < 0) {
$startPage = 0;
$endPage = $maxPages;
}
if($endPage > $this->TotalPages()) {
$endPage = $this->TotalPages();
$startPage = max(0, $endPage - $maxPages);
}
} else {
$startPage = 0;
$endPage = $this->TotalPages();
}
for($i=$startPage; $i < $endPage; $i++){
$link = HTTP::setGetVar($this->paginationGetVar, $i*$this->pageLength);
$thePage = new ArrayData(array(
"PageNum" => $i+1,
"Link" => $link,
"CurrentBool" => ($this->CurrentPage() == $i+1)?true:false,
)
);
$ret->push($thePage);
}
return $ret;
}
/*
* Display a summarized pagination which limits the number of pages shown
* "around" the currently active page for visual balance.
* In case more paginated pages have to be displayed, only
*
* Example: 25 pages total, currently on page 6, context of 4 pages
* [prev] [1] ... [4] [5] [[6]] [7] [8] ... [25] [next]
*
* Example template usage:
* <code>
* <% if MyPages.MoreThanOnePage %>
* <% if MyPages.NotFirstPage %>
* <a class="prev" href="$MyPages.PrevLink">Prev</a>
* <% end_if %>
* <% control MyPages.PaginationSummary(4) %>
* <% if CurrentBool %>
* $PageNum
* <% else %>
* <% if Link %>
* <a href="$Link">$PageNum</a>
* <% else %>
* ...
* <% end_if %>
* <% end_if %>
* <% end_control %>
* <% if MyPages.NotLastPage %>
* <a class="next" href="$MyPages.NextLink">Next</a>
* <% end_if %>
* <% end_if %>
* </code>
*
* @param integer $context Number of pages to display "around" the current page. Number should be even,
* because its halved to either side of the current page.
* @return DataObjectSet
*/
public function PaginationSummary($context = 4) {
$ret = new DataObjectSet();
// convert number of pages to even number for offset calculation
if($context % 2) $context--;
// find out the offset
$current = $this->CurrentPage();
$totalPages = $this->TotalPages();
// if the first or last page is shown, use all content on one side (either left or right of current page)
// otherwise half the number for usage "around" the current page
$offset = ($current == 1 || $current == $totalPages) ? $context : floor($context/2);
$leftOffset = $current - ($offset);
if($leftOffset < 1) $leftOffset = 1;
if($leftOffset + $context > $totalPages) $leftOffset = $totalPages - $context;
for($i=0; $i < $totalPages; $i++) {
$link = HTTP::setGetVar($this->paginationGetVar, $i*$this->pageLength);
$num = $i+1;
$currentBool = ($current == $i+1) ? true:false;
if(
($num == $leftOffset-1 && $num != 1 && $num != $totalPages)
|| ($num == $leftOffset+$context+1 && $num != 1 && $num != $totalPages)
) {
$ret->push(new ArrayData(array(
"PageNum" => null,
"Link" => null,
"CurrentBool" => $currentBool,
)
));
} else if($num == 1 || $num == $totalPages || in_array($num, range($current-$offset,$current+$offset))) {
$ret->push(new ArrayData(array(
"PageNum" => $num,
"Link" => $link,
"CurrentBool" => $currentBool,
)
));
}
}
return $ret;
}
/**
* Returns true if the current page is not the first page.
* @return boolean
*/
public function NotFirstPage(){
return $this->CurrentPage() != 1;
}
/**
* Returns true if the current page is not the last page.
* @return boolean
*/
public function NotLastPage(){
return $this->CurrentPage() != $this->TotalPages();
}
/**
* Returns true if there is more than one page.
* @return boolean
*/
public function MoreThanOnePage(){
return $this->TotalPages() > 1;
}
function FirstItem() {
return isset($this->pageStart) ? $this->pageStart + 1 : 1;
}
function LastItem() {
if(isset($this->pageStart)) {
return min($this->pageStart + $this->pageLength, $this->totalSize);
} else {
return min($this->pageLength, $this->totalSize);
}
}
/**
* Returns the URL of the previous page.
* @return string
*/
public function PrevLink() {
if($this->pageStart - $this->pageLength >= 0) {
return HTTP::setGetVar($this->paginationGetVar, $this->pageStart - $this->pageLength);
}
}
/**
* Returns the URL of the next page.
* @return string
*/
public function NextLink() {
if($this->pageStart + $this->pageLength < $this->totalSize) {
return HTTP::setGetVar($this->paginationGetVar, $this->pageStart + $this->pageLength);
}
}
/**
* Allows us to use multiple pagination GET variables on the same page (eg. if you have search results and page comments on a single page)
*
* @param string $var The variable to go in the GET string (Defaults to 'start')
*/
public function setPaginationGetVar($var) {
$this->paginationGetVar = $var;
}
/**
* Add an item to the DataObject Set.
* @param DataObject $item Item to add.
@ -608,11 +318,10 @@ class DataObjectSet extends ViewableData implements IteratorAggregate, Countable
}
/**
* Return the total number of items in this dataset.
* @return int
* @deprecated 3.0 Use {@link DataObjectSet::Count()}.
*/
public function TotalItems() {
return $this->totalSize ? $this->totalSize : $this->Count();
return $this->Count();
}
/**

247
tests/PaginatedListTest.php Normal file
View File

@ -0,0 +1,247 @@
<?php
/**
* Tests for the {@link PaginatedList} class.
*
* @package sapphire
* @subpackage tests
*/
class PaginatedListTest extends SapphireTest {
public function testPageStart() {
$list = new PaginatedList(new DataObjectSet());
$this->assertEquals(0, $list->getPageStart(), 'The start defaults to 0.');
$list->setPageStart(10);
$this->assertEquals(10, $list->getPageStart(), 'You can set the page start.');
$list = new PaginatedList(new DataObjectSet(), array('start' => 50));
$this->assertEquals(50, $list->getPageStart(), 'The page start can be read from the request.');
}
public function testGetTotalItems() {
$list = new PaginatedList(new DataObjectSet());
$this->assertEquals(0, $list->getTotalItems());
$list->setTotalItems(10);
$this->assertEquals(10, $list->getTotalItems());
$list = new PaginatedList(new DataObjectSet(
new ArrayData(array()),
new ArrayData(array())
));
$this->assertEquals(2, $list->getTotalItems());
}
public function testSetPaginationFromQuery() {
$query = $this->getMock('SQLQuery');
$query->limit = array('limit' => 15, 'start' => 30);
$query->expects($this->once())
->method('unlimitedRowCount')
->will($this->returnValue(100));
$list = new PaginatedList(new DataObjectSet());
$list->setPaginationFromQuery($query);
$this->assertEquals(15, $list->getPageLength());
$this->assertEquals(30, $list->getPageStart());
$this->assertEquals(100, $list->getTotalItems());
}
public function testSetCurrentPage() {
$list = new PaginatedList(new DataObjectSet());
$list->setPageLength(10);
$list->setCurrentPage(10);
$this->assertEquals(10, $list->CurrentPage());
$this->assertEquals(90, $list->getPageStart());
}
public function testGetIterator() {
$list = new PaginatedList(new DataObjectSet(array(
new DataObject(array('Num' => 1)),
new DataObject(array('Num' => 2)),
new DataObject(array('Num' => 3)),
new DataObject(array('Num' => 4)),
new DataObject(array('Num' => 5)),
)));
$list->setPageLength(2);
$this->assertDOSEquals(
array(array('Num' => 1), array('Num' => 2)), $list->getIterator()
);
$list->setCurrentPage(2);
$this->assertDOSEquals(
array(array('Num' => 3), array('Num' => 4)), $list->getIterator()
);
$list->setCurrentPage(3);
$this->assertDOSEquals(
array(array('Num' => 5)), $list->getIterator()
);
$list->setCurrentPage(999);
$this->assertDOSEquals(array(), $list->getIterator());
}
public function testPages() {
$list = new PaginatedList(new DataObjectSet());
$list->setPageLength(10);
$list->setTotalItems(50);
$this->assertEquals(5, count($list->Pages()));
$this->assertEquals(3, count($list->Pages(3)));
$this->assertEquals(5, count($list->Pages(15)));
$list->setCurrentPage(3);
$expectAll = array(
array('PageNum' => 1),
array('PageNum' => 2),
array('PageNum' => 3, 'CurrentBool' => true),
array('PageNum' => 4),
array('PageNum' => 5),
);
$this->assertDOSEquals($expectAll, $list->Pages());
$expectLimited = array(
array('PageNum' => 2),
array('PageNum' => 3, 'CurrentBool' => true),
array('PageNum' => 4),
);
$this->assertDOSEquals($expectLimited, $list->Pages(3));
}
public function testPaginationSummary() {
$list = new PaginatedList(new DataObjectSet());
$list->setPageLength(10);
$list->setTotalItems(250);
$list->setCurrentPage(6);
$expect = array(
array('PageNum' => 1),
array('PageNum' => null),
array('PageNum' => 4),
array('PageNum' => 5),
array('PageNum' => 6, 'CurrentBool' => true),
array('PageNum' => 7),
array('PageNum' => 8),
array('PageNum' => null),
array('PageNum' => 25),
);
$this->assertDOSEquals($expect, $list->PaginationSummary(4));
}
public function testCurrentPage() {
$list = new PaginatedList(new DataObjectSet());
$list->setTotalItems(50);
$this->assertEquals(1, $list->CurrentPage());
$list->setPageStart(10);
$this->assertEquals(2, $list->CurrentPage());
$list->setPageStart(40);
$this->assertEquals(5, $list->CurrentPage());
}
public function testTotalPages() {
$list = new PaginatedList(new DataObjectSet());
$list->setPageLength(1);
$this->assertEquals(0, $list->TotalPages());
$list->setTotalItems(1);
$this->assertEquals(1, $list->TotalPages());
$list->setTotalItems(5);
$this->assertEquals(5, $list->TotalPages());
}
public function testMoreThanOnePage() {
$list = new PaginatedList(new DataObjectSet());
$list->setPageLength(1);
$list->setTotalItems(1);
$this->assertFalse($list->MoreThanOnePage());
$list->setTotalItems(2);
$this->assertTrue($list->MoreThanOnePage());
}
public function testNotFirstPage() {
$list = new PaginatedList(new DataObjectSet());
$this->assertFalse($list->NotFirstPage());
$list->setCurrentPage(2);
$this->assertTrue($list->NotFirstPage());
}
public function testNotLastPage() {
$list = new PaginatedList(new DataObjectSet());
$list->setTotalItems(50);
$this->assertTrue($list->NotLastPage());
$list->setCurrentPage(5);
$this->assertFalse($list->NotLastPage());
}
public function testFirstItem() {
$list = new PaginatedList(new DataObjectSet());
$this->assertEquals(1, $list->FirstItem());
$list->setPageStart(10);
$this->assertEquals(11, $list->FirstItem());
}
public function testLastItem() {
$list = new PaginatedList(new DataObjectSet());
$list->setPageLength(10);
$list->setTotalItems(25);
$list->setCurrentPage(1);
$this->assertEquals(10, $list->LastItem());
$list->setCurrentPage(2);
$this->assertEquals(20, $list->LastItem());
$list->setCurrentPage(3);
$this->assertEquals(25, $list->LastItem());
}
public function testFirstLink() {
$list = new PaginatedList(new DataObjectSet());
$this->assertContains('start=0', $list->FirstLink());
}
public function testLastLink() {
$list = new PaginatedList(new DataObjectSet());
$list->setPageLength(10);
$list->setTotalItems(100);
$this->assertContains('start=90', $list->LastLink());
}
public function testNextLink() {
$list = new PaginatedList(new DataObjectSet());
$list->setTotalItems(50);
$this->assertContains('start=10', $list->NextLink());
$list->setCurrentPage(2);
$this->assertContains('start=20', $list->NextLink());
$list->setCurrentPage(3);
$this->assertContains('start=30', $list->NextLink());
$list->setCurrentPage(4);
$this->assertContains('start=40', $list->NextLink());
$list->setCurrentPage(5);
$this->assertNull($list->NextLink());
}
public function testPrevLink() {
$list = new PaginatedList(new DataObjectSet());
$list->setTotalItems(50);
$this->assertNull($list->PrevLink());
$list->setCurrentPage(2);
$this->assertContains('start=0', $list->PrevLink());
$list->setCurrentPage(3);
$this->assertContains('start=10', $list->PrevLink());
$list->setCurrentPage(5);
$this->assertContains('start=30', $list->PrevLink());
}
}

View File

@ -245,52 +245,6 @@ class DataObjectSetTest extends SapphireTest {
$this->assertEquals($mixedSet->Count(), 2, 'There are 3 unique data objects in a very mixed set');
}
/**
* Test {@link DataObjectSet->parseQueryLimit()}
*/
function testParseQueryLimit() {
// Create empty objects, because they don't need to have contents
$sql = new SQLQuery('*', '"Member"');
$max = $sql->unlimitedRowCount();
$set = new DataObjectSet();
// Test handling an array
$set->parseQueryLimit($sql->limit(array('limit'=>5, 'start'=>2)));
$expected = array(
'pageStart' => 2,
'pageLength' => 5,
'totalSize' => $max,
);
$this->assertEquals($expected, $set->getPageLimits(), 'The page limits match expected values.');
// Test handling OFFSET string
// uppercase
$set->parseQueryLimit($sql->limit('3 OFFSET 1'));
$expected = array(
'pageStart' => 1,
'pageLength' => 3,
'totalSize' => $max,
);
$this->assertEquals($expected, $set->getPageLimits(), 'The page limits match expected values.');
// and lowercase
$set->parseQueryLimit($sql->limit('32 offset 3'));
$expected = array(
'pageStart' => 3,
'pageLength' => 32,
'totalSize' => $max,
);
$this->assertEquals($expected, $set->getPageLimits(), 'The page limits match expected values.');
// Finally check MySQL LIMIT syntax
$set->parseQueryLimit($sql->limit('7, 7'));
$expected = array(
'pageStart' => 7,
'pageLength' => 7,
'totalSize' => $max,
);
$this->assertEquals($expected, $set->getPageLimits(), 'The page limits match expected values.');
}
/**
* Test {@link DataObjectSet->insertFirst()}
*/