Building Example Pager Class

Not for 'how-to' coding questions but PHP theory instead, this forum is here for those of us who wish to learn about design aspects of programming with PHP.

Moderator: General Moderators

User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

I have not worked on this for a while, but got the ADODB and MySQL Pageable classes working so I thought I would bundle up the code. It is available here:

http://ap3.sourceforge.net/pager-0.2.0.zip

One other thing I wanted to add, because I use it, is to allow the pager to remember where it was and resume. This is very helpful in CRUD type code because the paged list contains a link for each item in the list. When you are done on the sub page for that list item, it is much nicer to return to the list where you left off. Sessions are needed to do that without requiring the sub pages to remember values and pass them back. Unfortunately the logic is messy and the code I wrote just seems terrible to me (and has a terrible name). Maybe someone can find a way to clean up this code:

Code: Select all

class PagerSessionRequest {
	var $pager;
	var $session_name = 'Pager';
	var $page_resume = 'resume';
	var $last_row_recalc = 'recalc';

	function PagerSessionRequest(&$pager) {
		$this->pager = &$pager;
	}

	function process() {
		session_start();
		$pager = &$this->pager;
		$resume = false;

// code to set the page number

// no 'page=' parameter so initialize pager
		if (! isset($_GET[$pager->page_param])) {
			$pager->current_page = $pager->first_page;
			unset($_SESSION[$this->session_name]);		// clear any previous values
			$_SESSION[$this->session_name]['page'] = $pager->current_page;
		} else {
// 'page=resume' and page number in session so resume
			if (($_GET[$pager->page_param] === $this->page_resume) && (isset($_SESSION[$this->session_name]['page']))) {
				$pager->current_page = $_SESSION[$this->session_name]['page'];
				$resume = true;
			} else {
// use 'page=' parameter instead
				$pager->current_page = intval($_GET[$pager->page_param]);
				$_SESSION[$this->session_name]['page'] = $pager->current_page;
			}
		}
		
// code to set the last row value

// no 'last_row=' param and not resuming OR 'last_row=recalc' to force the datasource to recalculate value
		if ((! isset($_GET[$pager->last_row_param]) && (! $resume)) 
					|| ((isset($_GET[$pager->last_row_param]) && ($_GET[$pager->last_row_param] === $this->last_row_recalc)))) {
			$pager->last_row = $pager->datasource->getNumRows();
			$_SESSION[$this->session_name]['last_row'] = $pager->last_row;
		} elseif (isset($_SESSION[$this->session_name]['last_row'])) {
// last_row value is in session so use that
			$pager->last_row = $_SESSION[$this->session_name]['last_row'];
		} else {
// set the value in the session to the value in the pager object
			$_SESSION[$this->session_name]['last_row'] = $pager->last_row;
		}
 
// code to set the page size value

// 'page_size=' parameter passed so save in session
		if (isset($_GET[$pager->page_size_param])) {
			$pager->page_size = intval($_GET[$pager->page_size_param]);
			$_SESSION[$this->session_name]['page_size'] = $pager->page_size;
		} elseif (isset($_SESSION[$this->session_name]['page_size'])) {
// page size value is in session so use that value
			$pager->page_size = $_SESSION[$this->session_name]['page_size'];
		} else {
// set the value in the session to the value in the pager object
			$_SESSION[$this->session_name]['page_size'] = $pager->page_size;
		}

		$pager->setCurrentPage($pager->current_page);

	}

}
(#10850)
timvw
DevNet Master
Posts: 4897
Joined: Mon Jan 19, 2004 11:11 pm
Location: Leuven, Belgium

Post by timvw »

Don't have the time to review all the code, but the following is one that might bite you ;)


I'd change the process function a little to:

Code: Select all

<?php
function process() {
  if (!isset($_SESSION)) session_start();
  ...
}
?>
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

Thanks Tim, I'll change that. Can you think of a better name than "PagerSessionRequest" (ugh!). Maybe PagerRequestWithSession ?

I think the only other thing I want to add is some support for changing the sort order by clicking on links (column heads usually). Then a refactoring pass and clean up my unit tests.
(#10850)
Stripe-man
Forum Newbie
Posts: 1
Joined: Fri Feb 24, 2006 3:52 am

Post by Stripe-man »

Interesting guys...
Im interested in seeing where this might go...
timvw
DevNet Master
Posts: 4897
Joined: Mon Jan 19, 2004 11:11 pm
Location: Leuven, Belgium

Post by timvw »

arborint wrote: I think the only other thing I want to add is some support for changing the sort order by clicking on links (column heads usually). Then a refactoring pass and clean up my unit tests.
Well, the ordering of data is one of the first things that i'd factor out of a Pager..

Sorting usually requires you can do the following on your data:
- void Sort($options);

Most implementation that i know simply have $options defined as following, an array with "column" -> "order" elements... eg:
$options = array( array('col1' => 'desc'), array('col2' => 'asc'), ... );

And now the story for a Sorter starts like the Pager story.. (A pattern in patterns... We're at the next level :p)
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

I was thinking of supporting the URL generation and request processing part to maintain and reverse the current sort direction. That's the useful part as it relates to the Pager.

I don't know the specifics, but assumed that you would initialize sorting by providing some kind of array of the fields you want to be possibly sorted on (like you presented). The database side is easier because "ORDER BY" is standard -- unlike generating record sets.
(#10850)
timvw
DevNet Master
Posts: 4897
Joined: Mon Jan 19, 2004 11:11 pm
Location: Leuven, Belgium

Post by timvw »

I've never said that afterwards they can easily be combined.. When i decided to stop passing arrays with data, but instead "pageable data (whatever that may be) i had the following in mind...

1-) You start with a SortableAndPageableSQLQueryDataSource (atleast it's clear what it is :p)
2-) Order the data by using the methods of the Sortable interface (add an ORDER BY clause (or whatever that is needed))
3-) Page the data by using the methods of the Pageable interface (add a LIMIT clause (or whatever that is needed))
4-) Then you "render/display" the data.. A SortablePageWriter should know how to generate links for sorting and paging.
5-) Time for a couple of beers...
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

I was thinking:

1) Time for a couple of beers...
2) Add the ability to register fields to sort on
3) Figure out how to maintain sort direction internally
4) Add a method to create URLs for changing the sort order
5) Add code to get sorting parameters from the request
6) Maybe add an OrderBy() method to the Pagable classes
(#10850)
AlecH
Forum Commoner
Posts: 27
Joined: Fri Feb 24, 2006 4:22 pm
Location: New Hampshire

Post by AlecH »

Wow, this is a very nice class and I do believe that I will be needing this soon. If its allowed to be used as open source code that is.
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

AlecH wrote:If its allowed to be used as open source code that is.
I assume that you are free to use the code posted here. Should it have some sort of license or disclaimer?
(#10850)
matthijs
DevNet Master
Posts: 3360
Joined: Thu Oct 06, 2005 3:57 pm

Post by matthijs »

Maybe a free license in the sense of free beer (for the developers) :) ? They seem very thirsty..
DragonI
Forum Newbie
Posts: 2
Joined: Sun Aug 13, 2006 10:56 pm

Adding onclick?

Post by DragonI »

Hi,

I just came across your great class. I would like to you it for Ajax based pagination. How would I go about adding something like this:

onclick="ajaxPaging($var1, $var2, $page);return false;"

Thanks!
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

That's a great idea. There are probably a couple of ways to do it. The application specific way would be to create one wrapper that accepted calls and another that put the page data into XML/JSON/whatever as a response. But it would be easier to have the library handle that as well because the whole point of a Pager class is to deal with all the annoying details of pagination. It seems like the XML response should contain the page data, plus info for the paging display. Then on the Javascript side we could have code to parse the XML and return the data as an array and set the paging variables at the same time.

What did you see the code looking like? You showed:

Code: Select all

onclick="ajaxPaging($var1, $var2, $page);return false;"
I guess we would need similar calls in Javascript to the current PHP interface, such as ajaxPage, ajaxPageNext, ajaxPagePrev, ajaxFirst, ajaxLast, ajaxRange, etc.

I would be interested to know what you (and others) think the Javascript interfaceshould look like.

FYI - I have continued to develop that code and may have a newer version with more features than the one you are looking at.
(#10850)
DragonI
Forum Newbie
Posts: 2
Joined: Sun Aug 13, 2006 10:56 pm

Post by DragonI »

Hi arborint,

I'm currently using a pagination class called PaginateIt, http://www.bradyvercher.com/, that only produces the pagination links. Therefore the data is displayed independently. However, I had to make some mod's to pass in the javascript function and parms as a string. Not the nicest solution but it works. I'm also using xajax, so I won't require the Json/XML. But I'm just being selfish! People who are using the Pager class may want it

Here's an example of the link would look like:

Code: Select all

<a href="/products/?cat=1&pid=1&page=2" title="goto page 2" onclick="xajax_pageNavigate(1,1,2);return false;">2</a>
I have the href stuff just in case users don't have javascript enabled.

Sorry, it's late - hopefully this makes sense
User avatar
Jenk
DevNet Master
Posts: 3587
Joined: Mon Sep 19, 2005 6:24 am
Location: London

Post by Jenk »

With regards to including functionality such as ordering and so forth, should that not be a seperate entity/class to the pager class?

I can just see this pager class, as awesome as it will be/is, being blob like with lots of functionality that is only rarely used, weighing down the objects. So would it be better to seperate the classes out as per functionality? You could also argue that the ordering should be done by the datasource/dataobject before being passed to the pager?

Likewise for the iteration of pageable data, shouldn't the pager class return the pages data in bulk, and a seperate iterator (be it a template system's iterator such as smarty {section}) handle the.. iteration?
Post Reply