From 3fbb29a6c5ad48e1f7cff74c40cbf9f9d312b089 Mon Sep 17 00:00:00 2001 From: ajshort Date: Thu, 31 Mar 2011 12:01:04 +1100 Subject: [PATCH] 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. --- core/PaginatedList.php | 416 ++++++++++++++++++++ core/model/DataList.php | 1 - docs/en/howto/pagination.md | 66 ++++ docs/en/reference/built-in-page-controls.md | 4 +- docs/en/reference/searchcontext.md | 20 +- filesystem/File.php | 1 - model/DataObjectSet.php | 295 +------------- tests/PaginatedListTest.php | 247 ++++++++++++ tests/model/DataObjectSetTest.php | 46 --- 9 files changed, 745 insertions(+), 351 deletions(-) create mode 100644 core/PaginatedList.php create mode 100644 docs/en/howto/pagination.md create mode 100644 tests/PaginatedListTest.php diff --git a/core/PaginatedList.php b/core/PaginatedList.php new file mode 100644 index 000000000..11227495d --- /dev/null +++ b/core/PaginatedList.php @@ -0,0 +1,416 @@ +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: + * + * <% if MyPages.MoreThanOnePage %> + * <% if MyPages.NotFirstPage %> + * + * <% end_if %> + * <% control MyPages.PaginationSummary(4) %> + * <% if CurrentBool %> + * $PageNum + * <% else %> + * <% if Link %> + * $PageNum + * <% else %> + * ... + * <% end_if %> + * <% end_if %> + * <% end_control %> + * <% if MyPages.NotLastPage %> + * + * <% end_if %> + * <% end_if %> + * + * + * @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); + } + +} \ No newline at end of file diff --git a/core/model/DataList.php b/core/model/DataList.php index 6f69b1a54..950e6fe2c 100644 --- a/core/model/DataList.php +++ b/core/model/DataList.php @@ -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) { diff --git a/docs/en/howto/pagination.md b/docs/en/howto/pagination.md new file mode 100644 index 000000000..195b6adee --- /dev/null +++ b/docs/en/howto/pagination.md @@ -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 + + +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 %> + + <% end_if %> + <% control PaginatedPages.Pages %> + <% if CurrentBool %> + $PageNum + <% else %> + <% if Link %> + $PageNum + <% else %> + ... + <% end_if %> + <% end_if %> + <% end_control %> + <% if PaginatedPages.NotLastPage %> + + <% 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]`. \ No newline at end of file diff --git a/docs/en/reference/built-in-page-controls.md b/docs/en/reference/built-in-page-controls.md index 7f03dcb5b..e41e17d5d 100644 --- a/docs/en/reference/built-in-page-controls.md +++ b/docs/en/reference/built-in-page-controls.md @@ -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 diff --git a/docs/en/reference/searchcontext.md b/docs/en/reference/searchcontext.md index 8f4f88130..e005a68ed 100644 --- a/docs/en/reference/searchcontext.md +++ b/docs/en/reference/searchcontext.md @@ -89,23 +89,27 @@ 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 ��� 558** :::ss diff --git a/filesystem/File.php b/filesystem/File.php index 34640ff88..c07a267b3 100755 --- a/filesystem/File.php +++ b/filesystem/File.php @@ -714,7 +714,6 @@ class File extends DataObject { $records = $query->execute(); $ret = $this->buildDataObjectSet($records, $containerClass); - if($ret) $ret->parseQueryLimit($query); return $ret; } diff --git a/model/DataObjectSet.php b/model/DataObjectSet.php index 854947c57..eb70f796c 100644 --- a/model/DataObjectSet.php +++ b/model/DataObjectSet.php @@ -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) @@ -188,272 +164,6 @@ class DataObjectSet extends ViewableData implements IteratorAggregate, Countable public function toDropDownMap($index = 'ID', $titleField = 'Title', $emptyString = null, $sort = false) { 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: - * - * <% if MyPages.MoreThanOnePage %> - * <% if MyPages.NotFirstPage %> - * - * <% end_if %> - * <% control MyPages.PaginationSummary(4) %> - * <% if CurrentBool %> - * $PageNum - * <% else %> - * <% if Link %> - * $PageNum - * <% else %> - * ... - * <% end_if %> - * <% end_if %> - * <% end_control %> - * <% if MyPages.NotLastPage %> - * - * <% end_if %> - * <% end_if %> - * - * - * @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. @@ -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(); } /** diff --git a/tests/PaginatedListTest.php b/tests/PaginatedListTest.php new file mode 100644 index 000000000..e6384c8bf --- /dev/null +++ b/tests/PaginatedListTest.php @@ -0,0 +1,247 @@ +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()); + } + +} \ No newline at end of file diff --git a/tests/model/DataObjectSetTest.php b/tests/model/DataObjectSetTest.php index 54d6c687c..c5d73c325 100644 --- a/tests/model/DataObjectSetTest.php +++ b/tests/model/DataObjectSetTest.php @@ -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()} */