Hello EZ,
I have recently with the new Alpha project development come to realize that I need a better way to do searching, ordering, pagination and feedback. Alone and in combination with each other. It gets especially tricky when you need to combine them.
The problems are:
- URL building with many GET/POST variables. (Without having a gazillion isset() and is_string()'s etc)
- Doing clean pagination code for multiple pages and scenarios.
- A good way to set and display user feedback.
All of the problems above are solved in the following code package. They are solved individually so you can easily only solve two of them without needing any additional/unnecessary code.
I don't KNOW if this is the best way to do it. I am sure there are plenty of other ways to do it, but after doing searching, ordering, pagination and feedbacks for some years I think this is the best solution to a server-side only approach I have come across so far. If anyone knows of better ways to do this, please do share!
The code package consists of 4 files:
- index.php
- database.php
- feedback.php
- pagination.php
I am using PDO in this code, but it should be easily adaptable to any sort of database framework/class.
The only thing you need is a database and a table. The table's name is "test", and it's fields are: ID, name, email.
Download ZIP:
http://upload.evilzone.org?page=download&file=fLtZcU3DDWERO1CokxbQDAhWNTjGdQOKvbRPVyZltsQttD8PgQLive DEMO:
http://upload.evilzone.org/ande/searchsortpagination/index.phpSimple feedback usage:
<?php
// Simple feedback
Feedback::setFeedback('Results: '.$totalNumRes);
?>
// Then place this somewhere in your HTML:
<?php Feedback::printFeedback(); ?>
Simple pagination usage:
<?php
// Pagination example
$myPagination = new Pagination($totalNumRes);
$myPagination->currentPage = $startPage;
?>
// Then place this somewhere in your HTML:
<?php $myPagination->printPagination(); ?>
How I solved searching and sorting is in index.php
In-forum code:
index.php
<?php
/*
Coded by ande@evilzone.org
Use as you wish.
*/
// Includes
include('database.php');
include('pagination.php');
include('feedback.php');
// Few basic functions
// Validates a GET parameter as string. Has a default value if error.
function getGetString($name, $default) {
if(isset($_GET[$name]) && is_string($_GET[$name]))
return $_GET[$name];
return $default;
}
// Validates a GET paramenter as int. Has a default value if error.
function getGetInt($name, $default) {
if(isset($_GET[$name]) && validateInt($_GET[$name]))
return intval($_GET[$name]);
return $default;
}
// Validates that a numeric number is within the int scope.
function validateInt($i) {
if(is_numeric($i) && (int)$i==$i)
return TRUE;
return FALSE;
}
/////////////////////////////////////////////////////////////////////////////////////////////
// Set up the database interface
$DBInterface = DB::getInstance();
// Grab the various inputs
$searchPattern = getGetString('q', NULL);
$startPage = getGetInt ('start', 0);
$sort = getGetString('sort', 'id');
$sortDir = getGetString('dir', 'asc');
// Not part of URL, used as part of the limiting, also used in pagination
$numRows = 5;
// Sanetising $sort and $sortDir
if(!in_array($sort, array('id','name','email')))
$sort = 'id';
if(!in_array($sortDir, array('asc','desc')))
$sortDir = 'asc';
/////// Searching and ordering
// First we see how many results we got without any ordering and limiting. This is needed for pagination later.
if($searchPattern != NULL) {
$statement = $DBInterface->prepare('SELECT COUNT(*) FROM test WHERE name LIKE :searchPattern OR email LIKE :searchPattern');
$searchPatternTmp = '%'.$searchPattern.'%';
$statement->bindParam(':searchPattern', $searchPatternTmp, PDO::PARAM_STR);
} else {
$statement = $DBInterface->prepare('SELECT COUNT(*) FROM test');
}
$statement->execute();
$totalNumRes = $statement->fetchColumn();
// Then we do the same query with ordering and limiting
if($searchPattern != NULL) {
$statement = $DBInterface->prepare('SELECT * FROM test WHERE name LIKE :searchPattern OR email LIKE :searchPattern ORDER BY '.$sort.' '.$sortDir.' LIMIT :limitStart, :numRes');
$searchPatternTmp = '%'.$searchPattern.'%';
$statement->bindParam(':searchPattern', $searchPatternTmp, PDO::PARAM_STR);
} else {
$statement = $DBInterface->prepare('SELECT * FROM test ORDER BY '.$sort.' '.$sortDir.' LIMIT :limitStart, :numRes');
}
$startPageTmp = $startPage*$numRows;
$statement->bindParam(':limitStart', $startPageTmp, PDO::PARAM_INT);
$statement->bindParam(':numRes', $numRows, PDO::PARAM_INT);
$statement->execute();
// Fetch the results
$results = '';
while($result = $statement->fetch()) {
$results .= '<tr><td>'.$result['id'].'</td><td>'.htmlentities($result['name'], ENT_QUOTES).'</td><td>'.htmlentities($result['email'], ENT_QUOTES).'</td></tr>';
}
/////// Feedback
// Simple feedback
Feedback::setFeedback('Results: '.$totalNumRes.'<br />Listing: '.$startPageTmp.'-'.$numRows);
/////// Pagination
// Pagination example
$myPagination = new Pagination($totalNumRes);
$myPagination->pageMultiplier = $numRows;
$myPagination->currentPage = $startPage;
// Insert additional unknown variables to the suffix to preserve search query and ordering
$urlData = array('q' => $searchPattern, 'sort' => $sort, 'dir' => $sortDir);
$myPagination->URLSuffix = '&'.http_build_query($urlData);
// Used for table sort links
$urlData = array('q' => $searchPattern, 'dir' => (($sortDir=='asc')?'desc':'asc'), 'start' => $startPage);
$existingURL = http_build_query($urlData);
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Seaching, sorting, pagination and user feedback demo</title>
<style type="text/css">
#searchDiv, #feedbackDiv, #resultsDiv, #paginationDiv {
margin: 10px;
padding: 10px;
border: solid #505050 1px;
}
#resultsDiv table {
width: 100%;
}
#resultsDiv table tr:nth-child(odd) td {
background-color: #ffffff;
}
#resultsDiv table tr:nth-child(even) td {
background-color: #eeeeee;
}
#resultsDiv table tr:first-child td {
background-color: #dddddd;
}
#resultsDiv table tr td:nth-child(1) { /* ID */
width: 30px;
}
</style>
</head>
<body>
<div id="searchDiv">
<form method="get" action="index.php">
<input type="text" name="q" placeholder="Search" value="<?php if(isset($searchPattern)) echo(htmlentities($searchPattern, ENT_QUOTES)); ?>" />
<input type="submit" value="Search" />
</form>
</div>
<?php Feedback::printFeedback(); ?>
<div id="resultsDiv">
<table>
<tr>
<td><a href="index.php?sort=id&<?php print $existingURL; ?>">ID</a></td>
<td><a href="index.php?sort=name&<?php print $existingURL; ?>">Name</a></td>
<td><a href="index.php?sort=email&<?php print $existingURL; ?>">Email</a></td>
</tr>
<?php print $results; ?>
</table>
</div>
<?php $myPagination->printPagination(); ?>
</body>
</html>
database.php
<?php
/*
Coded by ande@evilzone.org
Use as you wish.
*/
// DB structure:
// Table: test
// ID | name | email
abstract class DB
{
public static function getInstance($newInstance = false)
{
static $connection = NULL;
if($connection == NULL || $newInstance === true)
{
try {
$connection = new PDO('mysql:host=localhost;dbname=searchsortpagination', 'root', '');
} catch(Exception $e) {
die('Could not connect to the database. Forgot to initialize?');
}
}
return $connection;
}
}
?>
feedback.php
<?php
/*
Coded by ande@evilzone.org
Use as you wish.
*/
abstract class Feedback {
private static $feedback = '';
public static function computeFeedback() {
if(self::$feedback != '')
return '<div id="feedbackDiv">'.self::$feedback.'</div>';
return '';
}
public static function printFeedback() {
echo(self::computeFeedback());
}
public static function setFeedback($feedback) {
self::$feedback = $feedback;
}
}
?>
pagination.php
<?php
/*
Coded by ande@evilzone.org
Use as you wish.
*/
class Pagination {
public $displayOuterArrows = TRUE; // Show the [<<] and [>>] buttons?
public $displayInnerArrows = TRUE; // Show the [<] and [>] buttons?
public $numButtons = 5; // Number of buttons to display. [1] [2] [3] [4] [5]
public $numItems = 0; // Total number of items being browser (MAX)
public $pageMultiplier = 5; // How many items per page do you want to display?
public $currentPage = 0; // What page are we currently on?
public $URLPrefix = 'index.php'; // URL start (part Typically index.php)
public $URLInfix = '?start='; // URL middle part (Pagination part ?start=)
public $URLSuffix = ''; // URL end part (Got additional data? &herp=derp&derp=herp)
// Class constructor
function __construct($numItems, $numButtons=5, $pageMultiplier=5, $currentPage=0) {
$this->numItems = $numItems;
$this->numButtons = $numButtons;
$this->pageMultiplier = $pageMultiplier;
$this->currentPage = $currentPage;
}
// Return output of pagination
public function computePagination() {
// Are there even enough data for pagination?
if($this->numItems < $this->pageMultiplier)
return '';
// Init data
$startPrintPage = max(0, $this->currentPage - floor($this->numButtons / 2));
$maxPages = ceil($this->numItems / $this->pageMultiplier);
$numPrintedButtons = 0;
$currentPage = $this->currentPage;
$URLPrefix = $this->URLPrefix;
$URLInfix = $this->URLInfix;
$URLSuffix = $this->URLSuffix;
// This does so that when you come to the end of the page display, it will still display $this->numButtons buttons
while(($startPrintPage + floor($this->numButtons / 2)*2 >= $maxPages) && $startPrintPage > 0)
$startPrintPage--;
// Outer pagination div
$output = '<div id="paginationDiv">';
// [<<] and [<] buttons
if($this->displayOuterArrows && $currentPage > 0) {
$output .= '<a href="'.$URLPrefix.$URLInfix.'0'.$URLSuffix.'"><<</a>';
}
if($this->displayInnerArrows && $currentPage > 0) {
$output .= ' <a href="'.$URLPrefix.$URLInfix.($currentPage-1).$URLSuffix.'"><</a>';
}
// [1] [2] [3] [4] [5] buttons
$currentPrintPage = $startPrintPage;
while($currentPrintPage < $maxPages && $numPrintedButtons < $this->numButtons) {
if($currentPrintPage == $currentPage)
$output .= ' <a href="'.$URLPrefix.$URLInfix.($currentPrintPage).$URLSuffix.'">[_'.($currentPrintPage+1).'_]</a>';
else
$output .= ' <a href="'.$URLPrefix.$URLInfix.($currentPrintPage).$URLSuffix.'">['.($currentPrintPage+1).']</a>';
$currentPrintPage++;
$numPrintedButtons++;
}
// [>] and [>>] buttons
if($this->displayInnerArrows && $currentPage < $maxPages-1) {
$output .= ' <a href="'.$URLPrefix.$URLInfix.($currentPage+1).$URLSuffix.'">></a>';
}
if($this->displayOuterArrows && $currentPage < $maxPages-1) {
$output .= ' <a href="'.$URLPrefix.$URLInfix.($maxPages-1).$URLSuffix.'">>></a>';
}
// End of outer pagination div
$output .= '</div>';
return $output;
}
// Print output of pagination
public function printPagination() {
echo $this->computePagination();
}
}
?>