Finally after a few months of searching and learning AJAX with pagination PHP and History.pushState() I created AJAX PHP pagination.
1. Ajax(index.php)
$(window).on('load', function () { // makes sure that whole site is loaded
$('#status').fadeOut();
$('#preloader').delay(350).fadeOut('slow');
});
$(document).ready(function(){
var url_string = window.location.href;
var url_parts = url_string.split('/');
var piece = url_parts[url_parts.length -1]; //get last part of url (it should be page=n)
var page;
if(piece.substring(0, 5) === 'page=') {
page = parseInt(piece.substr(5));
} else { //if it's the first time you landed in the page you haven't page=n
page = 1;
}
load_data(page);
// load_data(1);
function load_data(page)
{
$('#load_data').html('<div id="status" style="" ></div>');
$.ajax({
url:"pagination2.php",
method:"POST",
data:{page:page},
success:function(data){
$('#load_data').html(data);
}
});
}
$(document).on('click', '.pagination_link', function(event)
{
event.preventDefault();
var page = $(this).attr("id");
load_data(page);
//Now push PAGE to URL
window.history.pushState({page}, `Selected: ${page}`, `${'page=' + page}`)
//window.history.pushState({page}, `Selected: ${page}`, `./selected=${page}`)
return event.preventDefault();
});
window.addEventListener('popstate', e => {
var page = $(this).attr("id");
load_data(e.state.page);
console.log('popstate error!');
});
});
2. pagination2.php
<?php
$rowperpage = 10;
$page = 1;
if($_POST['page'] > 1)
{
$p = (($_POST['page'] - 1) * $rowperpage);
$page = $_POST['page'];
}
else
{
$p = 0;
}
?>
<?php
$visible = $visible ?? true;
$products_count = count_all_products(['visible' => $visible]);
$sql = "SELECT * FROM products ";
$sql .= "WHERE visible = true ";
$sql .= "ORDER BY position ASC ";
$sql .= "LIMIT ".$p.", ".$rowperpage."";
$product_set = find_by_sql($sql);
$product_count = mysqli_num_rows($product_set);
if($product_count == 0) {
echo "<h1>No products</h1>";
}
while($run_product_set = mysqli_fetch_assoc($product_set)) { ?>
<div style="float: left; margin-left: 20px; margin-top: 10px; class="small">
<a href="<?php echo url_for('/show.php?id=' . htmlspecialchars(urlencode($run_product_set['id']))); ?>">
<img src="staff/images/<?php echo htmlspecialchars($run_product_set['filename']); ?> " width="150">
</a>
<p><?php echo htmlspecialchars($run_product_set['prod_name']); ?></p>
</div>
<?php }
mysqli_free_result($product_set);
?>
<section id="pagination">
<?php
if($_POST['page'] > 1)
{
$page_nb = $_POST['page'];
}
else
{
$page_nb = 1;
}
$check = $p + $rowperpage;
$prev_page = $page_nb - 1;
$limit = $products_count / $rowperpage;
$limit = ceil($limit);
$current_page = $page_nb;
if($page_nb > 1) {
echo "<span class='pagination_link' style='cursor:pointer;' id='".$prev_page."'>Back</span>";
}
if ( $products_count > $check ) {
for ( $i = max( 1, $page_nb - 5 ); $i <= min( $page_nb + 5, $limit ); $i++ ) {
if ( $current_page == $i ) {
echo "<span class=\"selected\">{$i}</span>";
} else {
echo "<span class='pagination_link' style='cursor:pointer;' id='".$i."'>".$i."</span>";
}
}
}
if ($products_count > $check) {
$next_page = $page_nb + 1;
echo "<span class='pagination_link' style='cursor:pointer;' id='".$next_page."'>Next</span>";
}
?>
3. htaccess
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-l
RewriteRule ^(.*)$ index.php?/1ドル [L]
</IfModule>
4. Functions
function url_for($script_path) {
// add the leading '/' if not present
if($script_path[0] != '/') {
$script_path = "/" . $script_path;
}
return WWW_ROOT . $script_path;
}
1 Answer 1
Overall Assessment
It appears to work, although when a user on the demo site (presuming the code is the same) goes to page 10, only the link with text BACK
is present. Should other page numbers exist? I understand why no other numbered links appear after having looked at the PHP logic but my experience with other paging systems makes me question this UI experience.
The practice of sending HTML in an AJAX response and using that to directly set the content of a DOM element is an antiquated practice, and could be an XSS avenue. While the practice of sending data from the API and having the front end code construct the HTML dynamically may have been a new construct eight years ago it is much more common in today’s web.
The indentation is inconsistent - especially in the JavaScript code - which makes it difficult to read.
I see that you have a previous post with identical code, thus this post is a follow-up to that one. That post has an accepted answer, though not all suggestions were taken into account. This is fine but you might want to consider those suggestions.
By the way you might not need jQuery...
Feedback/Suggestions
Javascript
DOM ready callback
If you are using jQuery 3.0 or newer then the format for .ready()
can be simplified to "the recommended syntax"3 :
$(function() { ... })
Instead of:
$(document).ready(function(){
Parsing page parameter
Instead of using window.location.href
the search
property could be used. On that MDN page it states:
Modern browsers provide URLSearchParams and URL.searchParams to make it easy to parse out the parameters from the querystring.
Your code could utilize the URLSearchParams
interface - something along the lines of:
const params = new URLSearchParams(document.location.search.substring(1));
const name = params.get("page") || 1;
jQuery AJAX functions
There is a convenience method .post()
that could be used to simplify the code to run the AJAX request - for example, I haven't tested this but it could be as simple as this:
const container = $(#load_data);
container.html('<div id="status" style="" ></div>');
$.post("pagination2.php", { page }, container.html);
unused variable page
in event listener function
window.addEventListener('popstate', e => { var page = $(this).attr("id"); load_data(e.state.page); console.log('popstate error!'); });
The variable page
isn't used within the callback function. It can be eliminated.
PHP
Constants
Things that shouldn't be changed (e.g. number of rows per page) can be declared as a constant - e.g. with define()
or const
.
const ROWS_PER_PAGE = 10;
One thing that was already mentioned in the answer by @mickmackusa to your previous question is that:
The LIMIT clause string can be written without concatenation:
$sql .= "LIMIT $p, $rowperpage";
I see that code is still unchanged. Perhaps it be helpful to know the reason: Variables in double-quoted (and heredoc syntaxes) are expanded when they occur in double quoted strings.
Closing and re-opening PHP tags
I see this code at the top of pagination2.php:
<?php require_once('../private/initialize.php'); ?>
<?php
$page = '';
There is little need to close and re-open the PHP tags.
<?php
require_once('../private/initialize.php');
$page = '';
-
\$\begingroup\$ Long ago, I chose to always write PHP, with
echo
to get HTML. And never go the other way (HTML with embedded PHP). The code in the Question is hard to read since it does both! \$\endgroup\$Rick James– Rick James2021年03月29日 16:43:20 +00:00Commented Mar 29, 2021 at 16:43
h()
andu()
?!? Please read the previous reviews, upvote the helpful ones, accept answers, adjust your future codes, do not ask reviewers to repeat good advice. In recent history: #1: codereview.stackexchange.com/a/240604/141885 #2: codereview.stackexchange.com/a/242782/141885 \$\endgroup\$closed
' is public visible, I thought it was just for me. I was convinced the old one could be thrown away. I correctedh
+u
and added functionurl_for
. \$\endgroup\$htmlspecialchars(urlencode..)
\$\endgroup\$