Skip to main content

This site requires you to update your browser. Your browsing experience maybe affected by not having the most up to date version.

We've moved the forum!

Please use forum.silverstripe.org for any new questions (announcement).
The forum archive will stick around, but will be read only.

You can also use our Slack channel or StackOverflow to ask for help.
Check out our community overview for more options to contribute.

Archive /

Our old forums are still available as a read-only archive.

Moderators: martimiz, Sean, Ed, biapar, Willr, Ingo

Pagination - Is it possible to use the page number rather than starting point


Go to End


7 Posts   2944 Views

Avatar
Hammy

Community Member, 49 Posts

2 November 2008 at 6:34pm

I've set up the pagination from the example on the site:
http://doc.silverstripe.com/doku.php?id=private:recipes:pagination

However I was wondering if this could be (or has been) improved a little. Instead of having the url parameter

"?start=<num>"
to indicate where to start the list from, is it possible to use the current page number instead (ie
"?page=<num>"
where page indicates the page number that you are on) and use the page number to figure out where to start the list from?

The reason is that the url parameter "start" is a little ambiguous when you first land on it. What does start really mean? Using page instead is a little more meaning full.

Also, if I wanted to increase or decrease the number of items (say from 15 to 10 listed items), links from external sites will show the updated list incorrectly. By using the page number instead, this will allow for less disruptions with such changes to the site for users coming from outside sources.

Avatar
Hammy

Community Member, 49 Posts

4 November 2008 at 6:54pm

Can anyone help me with this?

I just want to know if this has been (or can be) done? I just want to make sure I'm not wasting time when there is something already out there :)

Avatar
Ingo

Forum Moderator, 801 Posts

12 November 2008 at 11:55am

a "page" parameter can be transferred to a "start" parameter by multiplying it with the pagesize, right? so should be trivial to implement, given that you form the query in custom code (function NewsArticles() in the tutorial)

Avatar
Hammy

Community Member, 49 Posts

15 November 2008 at 10:31am

Hi Ingo,

Thanks for your input. On the same topic, I've created the functionality to reference the page number rather than the starting point but have an SEO issue else where.

When browsing down the relavent page numbers and to head back to the first page, on the list of page number links that I have at the bottom of the list, the link to page 1 (the first page of the list), instead of simply linking to domain.com/products/ it has the url domain.com/products/?page=1. I believe this is duplicating the same page unnessarily and could effect the page within search engine results.

I've made the following updates, but have been unable to figure out how to return the page url without the parameters? (In DataObjectSet.php - the comment "// How is this done?" is where I think this functionality needs to be added, but have been thinking it could be made in the ProductHolder template - all code below).

Any ideas?

In Page.php, i've added:

class Page_Controller extends ContentController {
	...
	function ProductList(){
		$source = 'ProductPage';
		$filter = 'StartDate < NOW() AND StartDate IS NOT NULL AND (EndDate > NOW() OR EndDate IS NULL)';
		$sort= 'StartDate DESC';
		$start;
		$page; // Page Number From URL Parameter
		$num=5;
		
		if(!isset($_GET['page']) || !is_numeric($_GET['page']) || (int)$_GET['page'] < 1) {
			$start = $_GET['page'] = 0;
		} else {
			$page = $_GET['page'];
			$start = ($page - 1)  * $num;
		}
		
		$latestProducts = DataObject::get($source,$filter,$sort,'',$start.','.$num);
		return $latestProducts ? $latestProducts : false;
	}
	...
}

I've also had to update the DataObjectSet.php (tho ideally i need to bring this functionality out of here to avoid issues when upgrading silverstripe)


		
	// Custom Pagination
	
	protected $pageNumGetVar = "page";
	
	public function PageNums($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++){
			if($i == 0) {
				// $link = Should be just the url without parameters. If page is equal to 1 then $link should be with parameters
				// How is this done?
				$link = HTTP::setGetVar($this->pageNumGetVar, $i + 1);
			} else {
				$link = HTTP::setGetVar($this->pageNumGetVar, $i + 1);
			}
			$thePage = new ArrayData(array(
					"PageNum" => $i+1,
					"Link" => $link,
					"CurrentBool" => ($this->CurrentPage() == $i+1)?true:false,
					)
			);
			$ret->push($thePage);
		}
		
		return $ret;
	}
	
	public function PageNextLink() {
		if($this->pageStart + $this->pageLength < $this->totalSize) {
			return HTTP::setGetVar($this->pageNumGetVar, $this->CurrentPage() + 1);
		}
	}
	
	public function PagePrevLink() {
		if($this->pageStart - $this->pageLength >= 0) {
			return HTTP::setGetVar($this->pageNumGetVar, $this->CurrentPage() - 1);
		}
	}
	....
}

And the template ProductHolder.ss contains the following

	<% if ProductList.MoreThanOnePage %>
		<div class="pagination">
			<ul>
				<% if ProductList.PrevLink %>
					<li class="prev"><a href="$ProductList.PagePrevLink">Prev</a></li>
				<% end_if %>
				
				<% control ProductList.PageNums %>
					<% if CurrentBool %>
						<li class="current">$PageNum</li>
					<% else %>
						<li><a href="$Link">$PageNum</a></li>
					<% end_if %>
				<% end_control %>
				
				<% if ProductList.NextLink %>
					<li class="next"><a href="$ProductList.PageNextLink">Next</a></li>
				<% end_if %>
			</ul>
			<p class="pages">Page $ProductList.CurrentPage of $ProductList.TotalPages Pages</p>
		</div>
	<% end_if %>	
...

Avatar
Hammy

Community Member, 49 Posts

15 November 2008 at 10:36am

Sorry (I always manage to copy and paste something incorrectly) - the DataObjectSet.php update is as follows:

class DataObjectSet extends ViewableData implements IteratorAggregate {
...
	// Custom Pagination
	
	protected $pageNumGetVar = "page";
	
	public function PageNums($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++){
			if($i == 0) {
			// $link = Should be just the url without parameters. 
            // How is this done? 
				$link = HTTP::setGetVar($this->pageNumGetVar, $i + 1);
			} else {
				$link = HTTP::setGetVar($this->pageNumGetVar, $i + 1);
			}
			$thePage = new ArrayData(array(
					"PageNum" => $i+1,
					"Link" => $link,
					"CurrentBool" => ($this->CurrentPage() == $i+1)?true:false,
					)
			);
			$ret->push($thePage);
		}
		
		return $ret;
	}
	
	public function PageNextLink() {
		if($this->pageStart + $this->pageLength < $this->totalSize) {
			return HTTP::setGetVar($this->pageNumGetVar, $this->CurrentPage() + 1);
		}
	}
	
	public function PagePrevLink() {
		if($this->pageStart - $this->pageLength >= 0) {
			return HTTP::setGetVar($this->pageNumGetVar, $this->CurrentPage() - 1);
		}
	}
	
}

Avatar
Ingo

Forum Moderator, 801 Posts

15 November 2008 at 3:30pm

Cool! I don't think its appropriate to merge into DataObjectSet at the current stage, because its going to be confusing with two very similiar implementations side-by-side. Perhaps we can move this functionality into a "Paginator" interface with different implementations?
For now, I'd suggest you put your code into a DataObjectDecorator, and access the DataObjectSet methods and properties through $this->owner instead of $this. Here's something to get you started:

http://pastie.org/315397

You can attach the decorator to each DataObjectSet with the following code in your _config.php:

Object::add_extension('DataObjectSet', 'PaginatedByPage');

Avatar
Hammy

Community Member, 49 Posts

16 November 2008 at 2:23pm

Hi Ingo,

Awesome! Was trying to figure out how to separate my custom pagination from the DataObjectSet.

For some reason the conditional statements aren't working as expected in the methods PageNextLink and PagePrevLink, however if you remove the IF condition and use NextLink and PrevLink in the ss Templates, this works fine. From a developers perspective - is this ok? For the moment it works, so I'm happy... :)

Just one issue tho, I'm still not sure how to grab just the url without the parameters for links to the first page (ie instead of domain.com/products/?page=1, use domain.com/products/ - see comment in /mysite/code/PaginatedByPage.php code below "// $link = Should be just the url without parameters." where I think this needs to be added).

Updates are as follows:

/mysite/_config.php

<?php
...
	// Custom Pagination by page number reference
	Object::add_extension('DataObjectSet', 'PaginatedByPage');
...
?>

/mysite/code/PaginatedByPage.php

<?php
class PaginatedByPage extends Extension { 

	protected $pageNumGetVar = "page"; 
	
	public function PageNums($maxPages = 0){ 
		$ret = new DataObjectSet();
		if($maxPages) { 
			$startPage = ($this->owner->CurrentPage() - floor($maxPages / 2)) - 1; 
			$endPage = $this->owner->CurrentPage() + floor($maxPages / 2);

			if($startPage < 0) { 
				$startPage = 0; 
				$endPage = $maxPages; 
			} 
			if($endPage > $this->owner->TotalPages()) { 
				$endPage = $this->owner->TotalPages(); 
				$startPage = max(0, $endPage - $maxPages); 
			}

		} else { 
			$startPage = 0; 
			$endPage = $this->owner->TotalPages(); 
		}

		for($i=$startPage; $i < $endPage; $i++){ 
			if($i == 0) { 
			// $link = Should be just the url without parameters. 
		 	// How is this done? Change $link below
				$link = HTTP::setGetVar($this->pageNumGetVar, $i + 1); 
			} else { 
				$link = HTTP::setGetVar($this->pageNumGetVar, $i + 1); 
			} 
			$thePage = new ArrayData(array( 
					"PageNum" => $i+1, 
					"Link" => $link, 
					"CurrentBool" => ($this->owner->CurrentPage() == $i+1)?true:false, 
					) 
			);
			$ret->push($thePage); 
		}
		return $ret; 
	} 

	public function PageNextLink() { 		
		return HTTP::setGetVar($this->pageNumGetVar, $this->owner->CurrentPage() + 1);
	}

	public function PagePrevLink() { 
		return HTTP::setGetVar($this->pageNumGetVar, $this->owner->CurrentPage() - 1);
	}
}
?>

ProductHolder.ss

...
	<% if ProductList.MoreThanOnePage %>
		<div class="pagination">
			<ul>
				<% if ProductList.PrevLink %>
					<li class="prev"><a href="$ProductList.PagePrevLink">Prev</a></li>
				<% end_if %>
				
				<% control ProductList.PageNums %>
					<% if CurrentBool %>
						<li class="current">$PageNum</li>
					<% else %>
						<li><a href="$Link">$PageNum</a></li>
					<% end_if %>
				<% end_control %>
				
				<% if ProductList.NextLink %>
					<li class="next"><a href="$ProductList.PageNextLink">Next</a></li>
				<% end_if %>
			</ul>
			<p class="pages">Page $ProductList.CurrentPage of $ProductList.TotalPages Pages</p>
		</div>
	<% end_if %>	
...