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

timvw
DevNet Master
Posts: 4897
Joined: Mon Jan 19, 2004 11:11 pm
Location: Leuven, Belgium

Post by timvw »

(1) I have the feeling that it would be more flexible if we simpy decide what the template/output really needs to recieve of data...

- data rows that are in the page
- current page number we are displaying
- last page number
- first page number?
- number of rows per page?
- total number of available rows?
- baseurl? baseurl for previous? baseurl for next? baseurl for changing rows per page?

So we would end up with something as:

Code: Select all

interface PagedView {
  function render($data, $current_page_number, $last_page_number, ... );
}
(2) Another important question is the following: Should render perform output of generated html (or whatever it generates) or should it return a string?

Once we agree on (1) and (2) the implementation(s) should be pretty straightforwarded...

PS: I use interfaces in my examples because they stress the methods that should be implemented. As already mentionned in this thread i can imagine that it's more practical to simply write an implementation and throw away the interface definition (well, instead of throwing it away, place it somewhere in the documentation)
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

jshpro2 wrote:You can still retain the simplicity of sprintf and avoid the overhead of preg_replace by using something like
Yeah ... I actually used str_replace() which is pretty fast (I even passed arrays to it) so sprintf() may actually be slower because it has to parse the format string (but not slowe than preg_replace() probably)
(#10850)
User avatar
feyd
Neighborhood Spidermoddy
Posts: 31559
Joined: Mon Mar 29, 2004 3:24 pm
Location: Bothell, Washington, USA

Post by feyd »

timvw wrote:I have the feeling that it would be more flexible if we simpy decide what the template/output really needs to recieve of data...

- data rows that are in the page
- current page number we are displaying
- last page number
- first page number?
- number of rows per page?
- total number of available rows?
- baseurl? baseurl for previous? baseurl for next? baseurl for changing rows per page?

So we would end up with something as:

Code: Select all

interface PagedView {
  function render($data, $current_page_number, $last_page_number, ... );
}
I've got to mull this over for a bit.
timvw wrote:Another important question is the following: Should render perform output of generated html (or whatever it generates) or should it return a string?
I think return a string, as the choice to do final echoing should be up to the application controller, not its subordinates. But for now, I think there is just enough separation from logic and display.
timvw wrote:I use interfaces in my examples because they stress the methods that should be implemented. As already mentionned in this thread i can imagine that it's more practical to simply write an implementation and throw away the interface definition (well, instead of throwing it away, place it somewhere in the documentation)
I would personally, like to keep interfaces around, but since compatibility with PHP 4 is an issue in this case, they are pretty much useless other than as a blueprint for someone to follow. So documentation is definitely a place where it should reside if it's not code.
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

timvw wrote:(1) I have the feeling that it would be more flexible if we simpy decide what the template/output really needs to recieve of data...

- data rows that are in the page
- current page number we are displaying
- last page number
- first page number?
- number of rows per page?
- total number of available rows?
- baseurl? baseurl for previous? baseurl for next? baseurl for changing rows per page?
I agree compeletly. Yours is an excellent list. This accessor stuff belongs in the base Pager class. My question is how to implement getting to the data you listed:

1. Should we have method like $pager->getCurrentPage()
2. Treat the pager like a Value Object like $pager->get_current_page
3. Or have it export an array that you can use like $pager_data['get_current_page']
timvw wrote:So we would end up with something as:

Code: Select all

interface PagedView {
  function render($data, $current_page_number, $last_page_number, ... );
}
This would imply that the Pager exported a data array.
timvw wrote:(2) Another important question is the following: Should render perform output of generated html (or whatever it generates) or should it return a string?
This gets back to the requirements and who the intended audience is. But because of the design we can acutally have it both ways. Newbies just want the thing to work -- which means using the Outputter classes to generate nice HTML. More advanced programmers just want access to the data (or just URLs) because, like you say, they they will be using a template system, View class, etc.

There is a tension here between making things standalone (and therefore easily useful) and wanting to do it right and build it as a component of a larger system (feyd's framework! ;)). My hope was that we could get both.
timvw wrote:Once we agree on (1) and (2) the implementation(s) should be pretty straightforwarded...
Do you think it is possible to implement (1) at the Pager class level and (2) with the PagerOutputter classes?
timvw wrote:PS: I use interfaces in my examples because they stress the methods that should be implemented. As already mentionned in this thread i can imagine that it's more practical to simply write an implementation and throw away the interface definition (well, instead of throwing it away, place it somewhere in the documentation)
Agreed.
(#10850)
User avatar
feyd
Neighborhood Spidermoddy
Posts: 31559
Joined: Mon Mar 29, 2004 3:24 pm
Location: Bothell, Washington, USA

Post by feyd »

I think exporting an array of the information would probably be best in the long run. It's nice and fast, doesn't require more objects, and is compartmentalized.
josh
DevNet Master
Posts: 4872
Joined: Wed Feb 11, 2004 3:23 pm
Location: Palm beach, Florida

Post by josh »

Agreed, it will also make it easier for people that have no idea what OOP is to use it, the instructions for use would be as simple as:

Code: Select all

// set the data to work with
$pager = new pager($currentpage, $rowsperpage, $totalrows);
// This function outputs the data, you probably want to do something more useful with this
print_r($pager->page());
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

feyd wrote:I think exporting an array of the information would probably be best in the long run. It's nice and fast, doesn't require more objects, and is compartmentalized.
How about getters for every value and a getValues() method that returns an array like this:

Code: Select all

class Pager {
	var $datasource = null;
	var $page_size = 10;
	var $current_page = 0;
	var $max_page = 0;
	var $max_row = 0;
	var $start_row = 0;
	var $end_row = 0;
	var $page_parameter = 'page';
		
	function Pager(&$datasource) {
		$this->datasource = &$datasource;
	}

	function setPageSize($n) {
		if ($n > 0) {
			$this->page_size = $n;
		}
	}

	function setCurrentPage($n) {
		$this->max_row = $this->datasource->getNumRows();
		if ($this->max_row > 0) {
			$this->max_page = ceil($this->max_row / $this->page_size);
			if ($n < 1) {
				$n = 1;
			}
			if ($n > $this->max_page) {
				$n = $this->max_page;
			}
			$this->current_page = $n;
			$this->start_row = ($n - 1) * $this->page_size + 1;
			$this->end_row = $this->start_row + $this->page_size - 1;
			if ($this->end_row > $this->max_row) {
				$this->end_row = $this->max_row;
			}
		} else {
			$this->max_page = 0;
			$this->current_page = 0;
			$this->start_row = 0;
			$this->end_row = 0;
		}
	}

	function getPageSize() {
		return $this->page_size;
	}

	function getMaxPage() {
		return $this->max_page;
	}

	function getCurrentPage() {
		return $this->current_page;
	}

	function getStartRow() {
		return $this->start_row;
	}

	function getEndRow() {
		return $this->end_row;
	}

	function getMaxRow() {
		return $this->max_row;
	}
	
	function getValues() {
		return array(
			'page_size' => $this->page_size,
			'page_size' => $this->max_page,
			'current_page' => $this->current_page,
			'start_row' => $this->start_row,
			'end_row' => $this->end_row,
			'max_row' => $this->max_row,
			);
	}
}
Note 1 - I am blissfully appriciative that we are not having the usual function/variable naming arguements. I am not wed to any of these names and would like to do a naming pass at the end where we can all vote whether it should be, for example, getValues(), export(), or something else.

Note 2 - that this code does not yet deal with setting the page number from the request -- we'll need to deal with the request side of this thing at some point.
(#10850)
User avatar
feyd
Neighborhood Spidermoddy
Posts: 31559
Joined: Mon Mar 29, 2004 3:24 pm
Location: Bothell, Washington, USA

Post by feyd »

that seems simple and straight forward enough.. I didn't even think of doing both when I thought about it. :lol:
timvw
DevNet Master
Posts: 4897
Joined: Mon Jan 19, 2004 11:11 pm
Location: Leuven, Belgium

Post by timvw »

I wrote this one this afternoon while i was on the bus... (Sorry, no integration with other's code.. Meaby i should do that one of these days..)

Code: Select all

<?php
ini_set('error_reporting', E_ALL);
ini_set('display_errors', TRUE);

class PageableData {
	var $data;

	function PageableData($data) {
		$this->data = $data;
	}

	function getNumRows() { 
		return count($this->data);
	}
	
	function getRows($begin, $end) {
		return array_slice($this->data, $begin, $end - $begin);
       	}
}

class Pager {
	var $pageabledata;
	var $current_page;
	var $rows_per_page;

	function Pager($pageabledata) {
		$this->current_page = 1;
		$this->rows_per_page = 10;
		$this->pageabledata = $pageabledata;
	}

	function setCurrentPage($current_page) {
		$this->current_page = $current_page;
	}

	function getCurrentPage() {
		return $this->current_page;
	}

	function setRowsPerPage($rows_per_page) {
		$this->rows_per_page = $rows_per_page;
	}

	function getRowsPerPage() {
		return $this->rows_per_page;
	}

	function getLastPage() {
		$total = $this->pageabledata->getNumRows();
		$last = ceil($total / $this->rows_per_page);
		return $last;
	}

	function getRows() {
		$begin = ($this->current_page - 1) * $this->rows_per_page;
		$end = $begin + $this->rows_per_page;
		return $this->pageabledata->getRows($begin, $end);
	}
}

class PageWriter {
	var $pager;

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

	function render() {
		$current_page = $this->pager->getCurrentPage();
		$last_page = $this->pager->getLastPage();
		$prev_page = $current_page - 1;
		$next_page = $current_page + 1;
		$rows_per_page = $this->pager->getRowsPerPage();

		// generate links to set the rows per page	
		$output = "<div class='pagenumrows'>";
		$output .= "<a href='?page={$current_page}&rowperpage=10'>show 10</a> |";
		$output .= "<a href='?page={$current_page}&rowsperpage=25'>show 25</a> |";
		$output .= "<a href='?page={$current_page}&rowsperpage=50'>show 50</a> |";
		$output .= "<a href='?page={$current_page}&rowsperpage=100'>show 100</a>";
		$output .= "</div>";

		// generate html table with data
		$output .= "<table class='pagedata'>";
		$rows = $this->pager->getRows();
		foreach($rows as $row) {
			$output .= "<tr>";
			foreach($row as $name => $val) {
				$output .= "<td>" . htmlentities($val, ENT_QUOTES, 'UTF-8') . "</td>";
			}
			$output .= "</tr>";
		}
		$output .= "</table>";
		
		// generate pager bar
		$output .= "<div class='pager'>";

		if ($current_page != 1) {
			// only display link to first and previous page if we aren't there yet
			$output .= "<a href='?rowsperpage={$rows_per_page}&page=1'> <<FIRST </a>";
			$output .= "<a href='?rowsperpage={$rows_per_page}&page={$prev_page}'> <PREV </a>";
		}

		// display numbers of current and total available rows
		$output .= " (Page {$current_page} of {$last_page}) ";

		if ($current_page != $last_page) {
			// only display link to next and last page if we aren't there yet
			$output .= "<a href='?rowsperpage={$rows_per_page}&page={$next_page}'> NEXT> </a>";
			$output .= "<a href='?rowsperpage={$rows_per_page}&page={$last_page}'> LAST>> </a>";
		}

		$output .= "</div>";
		
		return $output;
	}
}

$data = array(
	array('name' => 'Van Wassenhove', 'surname' => 'Tim'),
	array('name' => 'Vandervorst', 'surname' => 'Maggy'),
	array('name' => 'Van Wassenhove', 'surname' => 'Ine'),
	array('name' => 'Van Wassenhove', 'surname' => 'Kato'),
	array('name' => 'Vandervorst', 'surname' => 'Mark'),
	array('name' => 'Vandervorst', 'surname' => 'Hans'),
	array('name' => 'Janssens', 'surname' => 'Luc'),
	array('name' => 'Wild', 'surname' => 'Gina'),
	array('name' => 'Jameson', 'surname' => 'Jenna'),
	array('name' => 'Banks', 'surname' => 'Briana'),
	array('name' => 'Giovanni', 'surname' => 'Aria'),
	array('name' => 'Rush', 'surname' => 'Daniella'),
	array('name' => 'Flowers', 'surname' => 'April'),
	array('name' => 'Meadows', 'surname' => 'July'),
	array('name' => 'Luv', 'surname' => 'Bunny'),
        array('name' => 'Jade', 'surname' => 'Kendra'),
        array('name' => 'Camille', 'surname' => 'Isabella'),
        array('name' => 'Windsor', 'surname' => 'Keri'),
        array('name' => 'Hsu', 'surname' => 'Jade'),
        array('name' => 'St. Claire', 'surname' => 'Jasmine'),
        array('name' => 'Nova', 'surname' => 'Nikki'),
        array('name' => 'Lopez', 'surname' => 'Sky'),
        array('name' => 'Knight', 'surname' => 'Victory'),
        array('name' => 'Kassin', 'surname' => 'Katja'),
        array('name' => 'Blue', 'surname' => 'Betty'),
        array('name' => 'Lynn', 'surname' => 'Jasmine'),
);

$pageabledata = new PageableData($data);
$pager = new Pager($pageabledata);
if (isset($_GET['page']) && is_numeric($_GET['page'])) {
	$pager->setCurrentPage($_GET['page']);
}
if (isset($_GET['rowsperpage']) && is_numeric($_GET['rowsperpage'])){
	$pager->setRowsPerPage($_GET['rowsperpage']);
}
$pagewriter = new PageWriter($pager);
echo $pagewriter->render();
?>
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

timvw wrote:I wrote this one this afternoon while i was on the bus... (Sorry, no integration with other's code.. Meaby i should do that one of these days..)
Great Tim. It gives me a lot to think about.

I will have a go at integrating you code with the stuff I have posted. I like the idea of passing the global settings like "rowsperpage" in the URLs. Very REST. I think that "page", "maxrows" and "rowsperpage" would be the basics, but even "sortcolumn" or "search" could be added.
(#10850)
timvw
DevNet Master
Posts: 4897
Joined: Mon Jan 19, 2004 11:11 pm
Location: Leuven, Belgium

Post by timvw »

I can't catch sleep... So i've decided to add a couple of Pageable data sources (As usual: untested)
(All source is still available at http://timvw.madoka.be/files/pager.txt)

Code: Select all

<?php
class PageableFile {
	var $filename;

	function PageableFile($filename) {
		$this->filename = $filename;
	}

	function getNumRows() {
		$counter = 0;
		$fp = fopen($this->filename, 'r');
		if ($fp) {
			while (!feof($fp)) {
				fgets($fp, 4096);
				++$counter;
			}
			fclose($fp);
		}
		return $counter;
	}

	function getRows($begin, $end) {
		$counter = 0;
		$rows = array();
		$fp = fopen($this->filename, 'r');
		if ($fp) {
			while (!feof($fp) && $counter < $begin) {
				fgets($fp, 4096);
				++$counter;
			}
			while (!feof($fp) && $counter < $end) {
				$rows[] = array('line' => fgets($fp, 4096));
				++$counter;
			}
			fclose($fp);
		}
		return $rows;
	}
}
?>

Code: Select all

<?php
class PageableMySQL {
	var $query;
	var $dblink;

	function PageableMySQL($query, $dblink = null) {
		$this->query = $query;
		$this->dblink = $dblink;
	}

	function getNumRows() {
		$query = preg_replace('#SELECT\s+(.*?)\s+FROM#i', 'SELECT COUNT(*) AS count FROM', $this->query);
		$rs = mysql_query($query, $this->dblink);

		if ($rs && $row = mysql_fetch_assoc($rs)) {
			return $row['count'];
		} else {
			return 0;
		}	
	}

	function getRows($begin, $end) {
		$rowcount = $end - $begin;
		$query = $this->query . " LIMIT {$rowcount} OFFSET {$begin}";
		$rs = mysql_query($query, $this->dblink);

		$rows = array();
		while ($row = mysql_fetch_assoc($rs)) {
			$rows[] = $row;
		}
		return $rows;
	}
}
?>

Code: Select all

<?php
class PageableADODB {
	var $query;
	var $db;

	function PageableADODB($query, $db) {
		$this->query = $query;
		$this->db = $db;
	}

	function getNumRows() {
		$query = preg_replace('#SELECT\s+(.*?)\s+FROM#i', 'SELECT COUNT(*) AS count FROM', $this->query);
		$count = $this->db->GetOne($query);

		if ($count) {
			return $count;
		} else {
			return 0;
		}
	}

	function getRows($begin, $end) {
		$rowcount = $end - $begin;
		$rs =& $this->db->SelectLimit($query, $rowcount, $begin);

		$rows = array();
		while (!$rs->EOF) {
			$rows[] = $rs->fields;
			$rs->MoveNext();
		}
		return $rows;
	}
}
?>
Example usage:

Code: Select all

<?php
// initialize datasource -----------------------------

// array example
$pageabledata = new PageableArray($data);

// mysql example
$dblink = mysql_connect('localhost', 'username', 'password');
mysql_select_db('wordpressdatabase', $dblink);
$pageabledata = new PageableMySQL('SELECT * FROM wp_posts', $dblink);

// adodb example
$db = NewADOConnection('mysql://username:password@localhost/wordpressdatabase');
$pageabledata = new PageableADODB('SELECT * FROM wp_posts', $db);

// file example
$pageabledata = new PageableFile("/home/users/timvw/.bash_history");

// initialize pager ----------------------------------------
$pager = new Pager($pageabledata);
if (isset($_GET['page']) && is_numeric($_GET['page'])) {
	$pager->setCurrentPage($_GET['page']);
}
if (isset($_GET['rowsperpage']) && is_numeric($_GET['rowsperpage'])){
	$pager->setRowsPerPage($_GET['rowsperpage']);
}

// generate output ----------------------------------------
$pagewriter = new PageWriter($pager);
echo $pagewriter->render();
?>
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

Tim, you are incredible! Great code that is really creating a library of Pageable classes for use with Pager classes. I have renamed the original array based pager with your naming:

Code: Select all

class PageableArray {
    var $data;

    function PageableArray($data) {
        $this->data = $data;
    }

    function getNumRows() { 
        return count($this->data);
    }
    
    function getRows($begin, $end) {
        return array_slice($this->data, $begin, $end - $begin);
	}
}
You are my favorite Belgian since Eddy Merckx
(#10850)
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

Ok, here is a more complete set of classes. I proceeded with the design that the Pager would only generate page numbers -- supporting first, prev, next, last, and ranges of page numbers. The PagerHTMLWriter class then supports creating URLs and full links. This code has gotten a lot more complex quickly and I am not sure that the design is still right. It needs a bunch of decisions about what to do on errors and edge conditions, and whether all these getters should be included. A refactor is in order.

Here is the Pager.php file that has the base Pager class and the PagerHTMLWriter class that outputs links:

Code: Select all

class Pager {
	var $datasource = null;				// PagerArrayDatasource object
	var $page_size = 10;				// number of rows of data per page
	var $range_size = 10;				// number of links in pager
	var $current_page = 0;
	var $first_page = 1;
	var $last_page = -1;
	var $first_row = 1;
	var $last_row = 0;
	var $start_row = 0;
	var $end_row = 0;
	var $page_url = '';
	var $page_param = 'page';
	var $last_row_param = 'last_row';
	var $page_size_param = 'page_size';
	var $other_params = array();			// array of parameters that are added to all URLs
		
	function Pager(&$datasource) {
		$this->datasource = &$datasource;
	}

	function processRequest() {
		if (isset($_GET[$this->last_row_param])) {
			$this->current_page = intval($_GET[$this->page_param]);
		} else {
			$this->current_page = $this->first_page;
		}
		
		if (isset($_GET[$this->last_row_param])) {
			$this->last_row = intval($_GET[$this->last_row_param]);
		} else {
			$this->last_row = $this->datasource->getNumRows();
		}
		
		if (isset($_GET[$this->page_size_param])) {
			$this->page_size = intval($_GET[$this->page_size_param]);
		}

		$this->setCurrentPage($this->current_page);
	}

	function setPageSize($n) {
		if ($n > 0) {
			$this->page_size = $n;
		}
	}

	function setRangeSize($n) {
		if ($n > 2) {
			$this->range_size = $n;
		}
	}

	function setCurrentPage($n) {
		if ($this->last_row < 0) {			// do not access datasource if last_rows provided
			$this->last_row = $this->datasource->getNumRows();
		}
		if ($this->last_row > 0) {
			$this->last_page = ceil($this->last_row / $this->page_size);
			if ($n < $this->first_page) {
				$n = $this->first_page;
			}
			if ($n > $this->last_page) {
				$n = $this->last_page;
			}
			$this->current_page = $n;
			$this->start_row = ($n - 1) * $this->page_size + 1;
			$this->end_row = $this->start_row + $this->page_size - 1;
			if ($this->end_row > $this->last_row) {
				$this->end_row = $this->last_row;
			}
		} else {
			$this->last_page = 0;
			$this->current_page = 0;
			$this->start_row = 0;
			$this->end_row = 0;
		}
	}

	function getPageSize() {
		return $this->page_size;
	}

	function getCurrentPage() {
		return $this->current_page;
	}

	function getLastPage() {
		return $this->last_page;
	}

	function getFirstPage() {
		return $this->first_page;
	}

	function getFirstRow() {
		return $this->first_row;
	}
	
	function getLastRow() {
		return $this->last_row;
	}
	
	function getStartRow() {
		return $this->start_row;
	}

	function getEndRow() {
		return $this->end_row;
	}

	function getValues() {
		return array(
			'page_size' => $this->page_size,
			'page_size' => $this->last_page,
			'current_page' => $this->current_page,
			'start_row' => $this->start_row,
			'end_row' => $this->end_row,
			'last_row' => $this->last_row,
			);
	}

	function getRange() {
		// get number of links each side of current page
		$side_size = floor($this->range_size / 2);
		$range_start = $this->current_page - $side_size;
		$range_end = $range_start + $this->range_size - 1;
		// bounds check lower side of range
		if ($range_start < $this->first_page) {
			$range_start = $this->first_page;
		}
		if ($range_end > $this->last_page) {
			$range_end = $this->last_page;
		}
		return range($range_start, $range_end);
	}

}

class PagerHTMLWriter {
	var $pager = null;
	var $no_current_link = true;		// no link for current page

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

	function getPageParams() {
		$params = $this->pager->other_params;
		$params[$this->pager->last_row_param] = $this->pager->last_row;
		$params[$this->pager->page_size_param] = $this->pager->page_size;
		return $params;
	}

	function getPageURL($n) {
		if (($n > 0) && ($n <= $this->pager->last_page)) {
			$params = $this->getPageParams();
			foreach ($params as $name => $value) {
				$param_strs[$name] = $name . '=' . $value;
			}
			$param_strs[$this->pager->page_param] = $this->pager->page_param . '=' . $n;
			$url = $this->pager->page_url . '?' . implode('&', $param_strs);
		} else {
			$url = '';
		}
		return $url;
	}

	function getPageLink($n, $text='', $attrs='') {
		if (($n > 0) && ($n <= $this->pager->last_page)) {
			$str = ($text ? $text : $n);
			if ($this->no_current_link && ($n ==$this->pager->current_page)) {
				$link = $str;
			} else {
				$link = '<a href="' . $this->getPageURL($n) . "\" $attrs>" . $str . '</a>';
			}
		} else {
			$link = '';
		}
		return $link;
	}

	function getPrevURL() {
		if ($this->pager->current_page > $this->pager->first_page) {
			$url = $this->getPageURL($this->pager->current_page - 1);
		} else {
			$url = '';
		}
		return $url;
	}

	function getNextURL() {
		if ($this->pager->current_page < $this->pager->last_page) {
			$url = $this->getPageURL($this->pager->current_page + 1);
		} else {
			$url = '';
		}
		return $url;
	}

	function getFirstURL() {
		return $this->getPageURL($this->pager->first_page);
	}

	function getLastURL() {
		return $this->getPageURL($this->pager->last_page);
	}

	function getRangeURLs() {
		foreach ($this->pager->getRange() as $n) {
			$urls[$n] = $this->getPageURL($n);
		}
		return $urls;
	}

	function getRangeLinks($attrs='') {
		foreach ($this->pager->getRange() as $n) {
			$links[$n] = $this->getPageLink($n, $attrs);
		}
		return $links;
	}

	function getPrevLink($text='Prev', $attrs='') {
		if ($this->pager->current_page > $this->pager->first_page) {
			$url = $this->getPageLink($this->pager->current_page - 1, $text, $attrs);
		} else {
			$url = '';
		}
		return $url;
	}

	function getNextLink($text='Next', $attrs='') {
		if ($this->pager->current_page < $this->pager->last_page) {
			$url = $this->getPageLink($this->pager->current_page + 1, $text, $attrs);
		} else {
			$url = '';
		}
		return $url;
	}

	function getFirstLink($text='First', $attrs='') {
		return $this->getPageLink($this->pager->first_page, $text, $attrs);
	}

	function getLastLink($text='Last', $attrs='') {
		return $this->getPageLink($this->pager->last_page, $text, $attrs);
	}

}

Here is the PagerHTMLWriterJump.php file that creates <select> based paging:

Code: Select all

class PagerHTMLWriterJump extends PagerHTMLWriter {
	var $current_page_template = 'Current Page {current_page}';
	var $page_template = 'Page {current_page} of {last_page}';
	
	function PagerHTMLWriterJump(&$pager) {
		$this->PagerHTMLWriter($pager);
	}

	function setCurrentPageTemplate($template) {
		$this->current_page_template = $template;
	}

	function setPageTemplate($template) {
		$this->page_template = $template;
	}

    function createPagination($form_attr='', $submit_attr='', $option_attr='') {

		$output = "<form action=\"\" method=\"GET\" $form_attr>\n";
		$params = $this->getPageParams();
		foreach ($params as $name => $value) {
			$output .= "<input type=\"hidden\" name=\"$name\" value=\"$value\" />\n";
		} 
		$output .= '<select name="'. $this->pager->page_param . "\" onChange=\"this.form.submit()\" $submit_attr>\n";
		for ($i=1; $i<=$this->pager->last_page; ++$i) {
			if ($i == $this->pager->current_page) {
				$template = str_replace('{current_page}', $i, $this->current_page_template);
				$output .= "<option value=\"$i\" selected=\"selected\"  $option_attr>$template</option>\n";
			} else {
				$template = str_replace(array('{current_page}', '{last_page}'), array($i, $this->pager->last_page), $this->page_template);
				$output .= "<option value=\"$i\" $option_attr>$template</option>\n";
			}
		}
		$output .= "</select>\n</form>\n";
        return $output;
    }

}
Here is example code using the above fiels:

Code: Select all

include 'PageableArray.php';
// initialize an array to testing
for ($i=0; $i<=75; ++$i) {
	$myarray[$i] = 'This is row ' . $i;
}
// create a data object that has the interface needed by the Pager object
$datasource = & new PageableArray($myarray);

include 'Pager.php';
// create pager using values from datasource and request params
$pager = & new Pager($datasource);
$pager->setRangeSize(5);
$pager->processRequest();

$writer =  & new PagerHTMLWriter($pager);
echo $writer->getPrevLink() . ' | ' . implode(' | ', $writer->getRangeLinks()) . ' | ' . $writer->getNextLink() . '<p/>';

include 'PagerHTMLWriterJump.php';
$jump =  & new PagerHTMLWriterJump($pager);
echo $jump->createPagination();
(#10850)
timvw
DevNet Master
Posts: 4897
Joined: Mon Jan 19, 2004 11:11 pm
Location: Leuven, Belgium

Post by timvw »

Hello all,

I've thought a bit about the design and change a couple of things. Currently there are four classes:

- pageable (the actual data)
- pager (the actual pager that can page through the collection)
- pagewriter (is able to generate html for a pager, accepts variables for the page parameters etc)
- paginator (manipulates the pager based on request data, call the pagewriter render process)

I think it's pretty flexible (multiple paginators can work indepent in the same page etc...)

Demo: http://timvw.madoka.be/files/pager.php
Source: http://timvw.madoka.be/files/pager.txt

I've tried all Pageable implementations (array, file, mysql, adodb) and PageWriters (regular and jump) and they all seem to work nicely ;)
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post by Christopher »

Looks great Tim. The one thing your code does that is really excellent is to move the request handling code and the parameter info out of the base Pager class. That is a definite improvement. I am not sure about having a separate Paginator class, I need to think about whether having a PagerRequest class (like your Paginator) makes sense. I will update my code. I think the real differene now probably comes down to whether the code generates the rows or not. I'd rather do as little formatting as possible to give maximum control to the programmer.

Unfortunately we now have two very similar codebases and still no clear requirements -- the PHP way! So much for my group/pair programming plans. ;)
(#10850)
Post Reply