Pagination Goes away when using search query

Pagination works when there is no search query, but as soon as I search the pages do not filter through the search results.

<!DOCTYPE html>
<html lang="en">
<head>
  <style>

.btn-text-right{
	text-align: right;
}

  </style>
    <meta charset="utf-8">
    <link   href="css/bootstrap.min.css" rel="stylesheet">
    <script src="js/bootstrap.min.js"></script>
    <link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" />
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-tagsinput/0.8.0/bootstrap-tagsinput.css" crossorigin="anonymous">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-tagsinput/0.8.0/bootstrap-tagsinput-typeahead.css" />
  <script src="https://code.jquery.com/jquery-2.2.4.min.js" integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-tagsinput/0.8.0/bootstrap-tagsinput.min.js" crossorigin="anonymous"></script>
  <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/tagmanager/3.0.2/tagmanager.min.css">
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/tagmanager/3.0.2/tagmanager.min.js"></script>
</head>
<body>
    <div class="container">
    		<div class="row">
                <div class="span12">
                <h2 style="text-align:center"><strong>Explore Tracking Requests</strong></h2>
<div class="btn-text-right">
   <form class="form-search pull-right" action="<?php echo $_SERVER['PHP_SELF'];?>" method="get">
                        <input type="text" name="query" class="input-medium search-query" value="<?php echo isset($_GET['query'])?$_GET['query']:'';?>">
                        <button type="submit" class="btn">Search</button>
                        <a href="create2.php" class="btn btn-success" >New</a>
                        <div class="span6">
      
</div>
    </div>
    		</div>
			<div class="row">
                    </form>
                </div>
                <div class="span12">
				<table class="table table-striped table-bordered">
		              <thead>
		                <tr>
                <th>ID</th>
                <th>Title</th>
                <th>Analyst</th>
                <th>Agency</th>
                <th>Date</th>
                <th>Requester</th>
                <th>Firearm</th>
                <th>Packets</th>
                <th>Packets1</th>
                <th>Summary</th>
                <th>Action</th>
		                </tr>
		              </thead>
		              <tbody>
		              <?php 
                       include 'paginator.php';
                       include 'database.php';
                       $pdo = Database::connect();

                       $paginator = new Paginator();
                       $sql = "SELECT count(*) FROM tracking ";
                       $paginator->paginate($pdo->query($sql)->fetchColumn());

                       $sql = "SELECT * FROM tracking ";

                       $query = isset($_GET['query'])?('%'.$_GET['query'].'%'):'%';
                       $sql .= "WHERE Title LIKE :query OR Analyst LIKE :query OR Agency LIKE :query OR Date LIKE :query OR Requester LIKE :query OR Summary LIKE :query OR ID LIKE :query ";

                       $start = (($paginator->getCurrentPage()-1)*$paginator->itemsPerPage);
                       $length = ($paginator->itemsPerPage);
                       $sql .= "ORDER BY Date DESC limit :start, :length ";

                       $sth = $pdo->prepare($sql);
                       $sth->bindParam(':start',$start,PDO::PARAM_INT);
                       $sth->bindParam(':length',$length,PDO::PARAM_INT);
                       $sth->bindParam(':query',$query,PDO::PARAM_STR);
                       $sth->execute();

	 				   foreach ($sth->fetchAll(PDO::FETCH_ASSOC) as $row) {
              echo '<tr>';
              echo '<td>'. $row['ID'] . '</td>';
              echo '<td>'. $row['Title'] . '</td>';
              echo '<td>'. $row['Analyst'] . '</td>';
              echo '<td>'. $row['Agency'] . '</td>';
              echo '<td>'. $row['Date'] . '</td>';
              echo '<td>'. $row['Requester'] . '</td>';
              echo '<td>'. $row['Firearm'] . '</td>';
              echo '<td>'. $row['Packets'] . '</td>';
              echo '<td>'. $row['Packets1'] . '</td>';
              echo '<td>'. $row['Summary'] . '</td>';
              echo '<td width=60>';
              echo '<a class="btn btn-success" target=”_blank” href="update2.php?ID='.$row['ID'].'">Update</a>';
              echo '</td>';
              echo '</tr>';
					   }
					   Database::disconnect();
					  ?>
				      </tbody>
	            </table>
              <?php
                echo $paginator->pageNav();
                ?>
                </div>
    	    </div>
    </div> <!-- /container -->
  </body>
</html>


The pagination links must be built with any existing $_GET parameters in them, which is where the search term is at. The correct way of doing this is to get a copy of the existing $_GET parameters (you want to leave the original $_GET parameters as is), set the pagination get parameter for the current link being built, use php’s http_build_query() to build the query string part of the link, then output the link on the page.

Also, the query to get the count of matching rows must have the same WHERE clause in it as the main data retrieval query, so that the number of pagination links is correct.

You have also not turned off emulated prepared queries, something that was mentioned in the previous thread, because you cannot use the same named place-holder more than once in a true prepared query. You must want to revisit the points made in the previous thread as they result in the least amount of code, not the most that you have now.

I see what you mean here. Thanks for responding. I am going to look into the http_build_query(). Since my project can go without page numbers I may leave them out and try to get the PDO working correctly for both of these. Seems like less code can do the trick here if done properly. Thanks again.

Here’s a general example on the steps to do pagination:

if (isset($_GET['page']) && !empty($_GET['page'])) {

$current_page = urldecode($_GET['page']);

} else {

$current_page = 1;

}

$per_page = 1; // Total number of records to be displayed:

/* Total number of records that NEEDS to be displayed */

$total_count = totalRecords($pdo, 'cms');

/* calculate the offset */

$offset = $per_page * ($current_page - 1);

/* calculate total pages to be displayed */

$total_pages = ceil($total_count / $per_page);

/* Figure out the Pagination Links */

$links = links_function('index.php', $current_page, $total_pages);

/* Finally, call for the data from the database table to display */

$cms = readData($pdo, 'cms', 'blog', $per_page, $offset);

The function to read in the data using PDO:

/*
 * Pagination Format
 * Read all the data from the database table in an array format
 */
function readData($pdo, $table, $page, $perPage, $offset) {
    $sql = 'SELECT * FROM ' . $table . ' WHERE page=:page ORDER BY date_added DESC LIMIT :perPage OFFSET :blogOffset';
    $stmt = $pdo->prepare($sql); // Prepare the query:
    $stmt->execute(['perPage' => $perPage, 'blogOffset' => $offset, 'page' => $page]); // Execute the query with the supplied data:
    return $stmt->fetchAll(PDO::FETCH_ASSOC);
}

the rest can be found at my GitHub repository - https://github.com/Strider64/Miniature01282021 or
https://github.com/Strider64/phototechguru (my current website) though this one uses some OOP.

This might help you?

1 Like

See the following example showing the points that have been given -

<?php
// pagination with search/filter

// pagination involves two queries. one to get the number of matching rows and the second to get a logical page of data
// both of these queries must have the same FROM, JOINs, WHERE, GROUP BY, and HAVING clauses, i.e. the same parts in the following SELECT query definition -
/*
    [FROM table_references
    [WHERE where_condition]
    [GROUP BY {col_name | expr | position}
      [ASC | DESC], ... [WITH ROLLUP]]
    [HAVING where_condition]
*/

// to do this, build that part of the query in a php variable, with any prepared query inputs in an array, then use these when building and executing both queries


// initialization

// number of items per page
$items_per_page = 10;

// number of +/- links around the current page number
$range = 3;

// make database connection here...


// get method business logic - get/produce data needed to display the page

// determine search part of query
$search = isset($_GET['query']) ? trim($_GET['query']) : '';

$search_params = []; // prepared query search inputs
// if there's no search input, match everything, i.e. leave the search part of the WHERE clause out of the query
if($search)
{
	// array to hold each where term
	$search_terms = [];
	$search_fields = ['Title', 'Analyst', 'Agency', 'Date', 'Requester', 'Summary', 'ID'];
	foreach($search_fields as $sf)
	{
		$search_terms[] = "$sf LIKE ?";
		$search_params[] = "%$search%";
	}
}

$where = ''; // default to an empty where clause
if(!empty($search_terms))
{
	$where = "WHERE ".implode(' OR ',$search_terms);
}

// build common part of query - FROM, JOINs, WHERE, GROUP BY, HAVING
$common_sql = "FROM tracking $where";
$common_params = $search_params; // use array_merge() here if there are place-holders in multiple sections of the query

// query to get the total number of matching rows
$sql = "SELECT COUNT(*) $common_sql";
$stmt = $pdo->prepare($sql);
$stmt->execute($common_params);
$total_rows = $stmt->fetchColumn();

// calculate total number of pages
$total_pages = ceil($total_rows / $items_per_page);

// get current page, default to page 1
$page = $_GET['page'] ?? 1; 

// limit page to be between 1 and $total_pages
$page = max(1,min($total_pages,$page));

// query to get a logical page of data
$start = ($page-1) * $items_per_page;

// note: you should list out the columns you are selecting
$sql = "SELECT ID, Title, Analyst, Agency, Date, Requester, Firearm, Packets, Packets1, Summary
	$common_sql
	ORDER BY Date DESC
	LIMIT ?,?";
$params = array_merge($common_params,[$start,$items_per_page]);
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
$result_data = $stmt->fetchAll();


// pagination links
// produce array of pagination numbers: 1, range around current page, total_pages, without duplicates, between 1 and total_pages
$links = array_filter(array_unique(array_merge([1],range($page-$range, $page+$range),[$total_pages])),
function ($val) use ($total_pages) { return $val >= 1 && $val <= $total_pages; });

// build pagination links
$pagination_links = '';

// get a copy of any existing get parameters
$get = $_GET;

// produce previous
$get['page'] = $page - 1;
$qs = http_build_query($get,'', '&amp;');
$pagination_links .= $page == 1 ? 'prev ' : "<a href='?$qs'>prev</a> ";

// produce numerical links
foreach($links as $link)
{
	$get['page'] = $link;
	$qs = http_build_query($get,'', '&amp;');
	$pagination_links .= $link == $page ? "$link " : "<a href='?$qs'>$link</a> ";
}

// produce next
$get['page'] = $page + 1;
$qs = http_build_query($get,'', '&amp;');
$pagination_links .= $page == $total_pages ? 'next' : "<a href='?$qs'>next</a>";

// html document starts here...
?>
<!DOCTYPE html>
<html lang="en">
<head>
  <style>
.btn-text-right{
	text-align: right;
}
  </style>
    <meta charset="utf-8">
    <link   href="css/bootstrap.min.css" rel="stylesheet">
    <script src="js/bootstrap.min.js"></script>
    <link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" />
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-tagsinput/0.8.0/bootstrap-tagsinput.css" crossorigin="anonymous">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-tagsinput/0.8.0/bootstrap-tagsinput-typeahead.css" />
  <script src="https://code.jquery.com/jquery-2.2.4.min.js" integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-tagsinput/0.8.0/bootstrap-tagsinput.min.js" crossorigin="anonymous"></script>
  <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/tagmanager/3.0.2/tagmanager.min.css">
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/tagmanager/3.0.2/tagmanager.min.js"></script>
</head>
<body>
    <div class="container">
		<div class="row">
			<div class="span12">
                <h2 style="text-align:center"><strong>Explore Tracking Requests</strong></h2>
				<div class="btn-text-right">
					<form class="form-search pull-right" method="get">
                        <input type="text" name="query" class="input-medium search-query" value="<?php echo $search; ?>">
                        <button type="submit" class="btn">Search</button>
                        <a href="create2.php" class="btn btn-success" >New</a>
							<div class="span6">
							</div>
				</div>
			</div>
			<div class="row">
				</form>
			</div>
			<div class="span12">
			<?php
			// note: there may not be any data to display
			if(empty($result_data))
			{
				echo "<p>Search didn't match any data.</p>";
			}
			else
			{
				// there is data to display
			?>
				<table class="table table-striped table-bordered">
					<thead>
						<tr>
						<?php
						// since you are displaying all the columns, produce the output dynamically
						// note: the following line has been corrected from what was originally posted
						foreach(array_keys($result_data[0]) as $col)
						{
							echo "<th>$col</th>";
						}
						?>
						<th>Action</th>
						</tr>
					</thead>
					<tbody>
						<?php
						foreach($result_data as $row)
						{
							echo '<tr>';
							foreach($row as $value)
							{
								echo "<td>$value</td>";
							}
							echo '<td width=60>';
							echo '<a class="btn btn-success" target=”_blank” href="update2.php?ID='.$row['ID'].'">Update</a>';
							echo '</td>';
							echo '</tr>';
						}
				}
				?>
					</tbody>
				</table>
				<div>
				<?php
				// output pagination links
				echo $pagination_links;
				?>
				</div>
			</div>
		</div>
	</div> <!-- /container -->
</body>
</html>

Thanks for giving me a line by line explanation, I will use this for reference in the future. This looks much cleaner than what I had. The two queries part is what was tripping me up but that makes perfect sense. I have added the pdo connection in the specified location:

// make database connection here...
include 'database.php';           
$pdo = Database::connect();

I am trying to run this and get SQL syntax error.

" Fatal error : Uncaught PDOException: SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ‘‘0’,‘10’’ at line 4 in C:\xampp\htdocs\explore2.php:84 Stack trace: #0 C:\xampp\htdocs\explore2.php(84): PDOStatement->execute(Array) #1 {main} thrown in C:\xampp\htdocs\explore2.php on line 84

I feel like I am missing something small in the code that I was supposed to change, but can’t seem to find it.

You are getting an error at the ->execute() call because you are (still) using emulated prepared queries, and the query is not actually prepared at the ->prepare() call. This is also the reason for the error, because emulated prepared queries treat all data types as strings, by default, which the LIMIT x,y syntax cannot use.

When you make the PDO database connection, you should -

  1. Set the character set to match your database tables.
  2. Set the error mode to use exceptions.
  3. Set emulated prepared queries to false.
  4. Set the default fetch mode to assoc.

Here is typical PDO connection code -

$DB_HOST = ''; // database host name or ip address
$DB_USER = ''; // database username
$DB_PASS = ''; // database password
$DB_NAME = ''; // database name
$DB_ENCODING = 'utf8mb4'; // db character encoding. set to match your database table's character set. note: utf8 is an alias of utf8mb3/utf8mb4

$options = [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // set the error mode to exceptions
			PDO::ATTR_EMULATE_PREPARES => false, // run real prepared queries
			PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, // set default fetch mode to assoc so that you don't need to specify it in each fetch statement
			];

$pdo = new pdo("mysql:host=$DB_HOST;dbname=$DB_NAME;charset=$DB_ENCODING",$DB_USER,$DB_PASS,$options);

Pages work great, I may edit them a bit now that I got the feel for them. The last two things I am looking at are related to the layout of the table. In the for loop is there an easy way for my columns to be labeled? Right now they are 0-9. Secondly, I have tried to make the table responsive so that the last column doesn’t get cut off when the text entered is large. Is there an easy way to do this?

Thanks, you have no idea how much this has help me learn and I’m sure I will be working with this often now.

also I have been browsing the web for the best ways to back up a mysql database. Is there a method that you recommend?

The html table columns should be labeled with the database column names. This is affected by the PDO fetch mode. Did you use the $options values in the connection code that I posted?

A .sql export, from a tool like phpmyadmin, is the best since is carries the database and table definitions too.

Yep I did include the whole code above

$options = [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // set the error mode to exceptions
			PDO::ATTR_EMULATE_PREPARES => false, // run real prepared queries
			PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, // set default fetch mode to assoc so that you don't need to specify it in each fetch statement
			];

$pdo = new pdo("mysql:host=$DB_HOST;dbname=$DB_NAME;charset=$DB_ENCODING",$DB_USER,$DB_PASS, $options);

I filled in my database info before these lines

Ok. I see the problem. Change the following line -

foreach(array_keys($result_data) as $col)

to this -

foreach(array_keys($result_data[0]) as $col)

awesome! that did it, any easy fix for the columns to fit on the page?

Not sure what the result will be in your case, but you can apply bootstrap responsive styling (after loading the bootstrap library files), by adding class="table" to the opening <table class="table"> tag.

Ref: Tables · Bootstrap

Okay thank you. I will look at the references and figure it out a proper display. Thanks for all your help on this.

Mike

I can’t find a solution to fit on one page. I will post a new post.

Sponsor our Newsletter | Privacy Policy | Terms of Service