15 1 0 4000 1 https://codeblock.co.za 300 true 0
How to Create Ajax Pagination and Filters With PHP

How to Create Ajax Pagination and Filters With PHP

0 Comments

Taking what we learnt in a previous post, “How To Create Pagination With PHP“, we’ll revisit the pagination tutorial and throw in some extra filters. Thanks to Ajax and JQuery, we’ll do all of this without having to reload the page. 

Prerequisites

  • A basic understanding of PHP.
  • A very basic understanding of JQuery and how Ajax works.
  • JQuery installed/included.

Before we begin, check out a working version of this tutorial here. You can also download the code from GitHub.

For this tutorial, I’ve created five files, however, only the first two are required:

  • products-array.php: The PHP file with the products array. This file is only for the purpose of this tutorial. Typically, you’d be requesting this data from your database, which will most likely be presented in an associative array like this.
  • index.php: The main page template.
  • products.php: The products template. This must be kept separate. We’ll use Ajax to request this file and dynamically load the content into the appropriate container. I’ve included the pagination template in this file using PHP’s require() function.
  • pagination.php: The pagination template. This doesn’t have to be separate. If you prefer, include this in the products template file.
  • pagination.js: The Javascript file to take care of the Ajax. This is a small amount of code and doesn’t need to be in it’s own file. You can include this in your website or apps primary .js file.

To keep this tutorial simple, I won’t discuss the layout. I’m also going to exclude the typical HTML (ie. html, body, header, footer, etc) from the code.

Let’s begin.

The Products Array That We’ll Filter With Ajax

This is an multi-dimensional array of products which we can pretend either comes from the database, or from an API call. When we call $products, it will be this array by default, however we’ll override this later when we filter and paginate. This file is products-array.php.

<?php
$products = array(
    array(
    	'id' => '1',
    	'title' => 'Blue Shirt',
    	'sku' => 'code-1',
    	'price' => '95.00',
    	'category' => 'Shirts',
    	'image' => 'https://via.placeholder.com/500x300.png'
    ),
    array(
    	'id' => '2',
    	'title' => 'Green Shirt',
    	'sku' => 'code-2',
    	'price' => '145.00',
    	'category' => 'Pants',
    	'image' => 'https://via.placeholder.com/500x300.png'
    ),
    array(
    	'id' => '3',
    	'title' => 'Yellow Shirt',
    	'sku' => 'code-3',
    	'price' => '895.00',
    	'category' => 'Shirts',
    	'image' => 'https://via.placeholder.com/500x300.png'
    ),
    array(
    	'id' => '4',
    	'title' => 'Cargo Pants',
    	'sku' => 'code-4',
    	'price' => '295.00',
    	'category' => 'Pants',
    	'image' => 'https://via.placeholder.com/500x300.png'
    ),
    array(
    	'id' => '5',
    	'title' => 'Product 5',
    	'sku' => 'code-5',
    	'price' => '215.00',
    	'category' => 'Caps',
    	'image' => 'https://via.placeholder.com/500x300.png'
    ),
    array(
    	'id' => '6',
    	'title' => 'Formal Shirt',
    	'sku' => 'code-6',
    	'price' => '365.00',
    	'category' => 'Shirts',
    	'image' => 'https://via.placeholder.com/500x300.png'
    ),
    array(
    	'id' => '7',
    	'title' => 'Peak Cap',
    	'sku' => 'code-7',
    	'price' => '95.00',
    	'category' => 'Caps',
    	'image' => 'https://via.placeholder.com/500x300.png'
    ),
    array(
    	'id' => '8',
    	'title' => 'Beret',
    	'sku' => 'code-8',
    	'price' => '495.00',
    	'category' => 'Caps',
    	'image' => 'https://via.placeholder.com/500x300.png'
    ),
    array(
    	'id' => '9',
    	'title' => 'Sleeveless Jacket',
    	'sku' => 'code-9',
    	'price' => '95.00',
    	'category' => 'Jackets',
    	'image' => 'https://via.placeholder.com/500x300.png'
    ),
    array(
    	'id' => '10',
    	'title' => 'Windbreaker',
    	'sku' => 'code-109',
    	'price' => '295.00',
    	'category' => 'Jackets',
    	'image' => 'https://via.placeholder.com/500x300.png'
    ),
    array(
    	'id' => '11',
    	'title' => 'Trousers',
    	'sku' => 'code-11',
    	'price' => '395.00',
    	'category' => 'Pants',
    	'image' => 'https://via.placeholder.com/500x300.png'
    ),
    array(
    	'id' => '12',
    	'title' => 'Bomber Jacket',
    	'sku' => 'code-12',
    	'price' => '75.00',
    	'category' => 'Jackets',
    	'image' => 'https://via.placeholder.com/500x300.png'
    ),
    array(
    	'id' => '13',
    	'title' => 'Bomber Jacket Orange',
    	'sku' => 'code-13',
    	'price' => '175.00',
    	'category' => 'Jackets',
    	'image' => 'https://via.placeholder.com/500x300.png'
    ),
    array(
    	'id' => '14',
    	'title' => 'Black T-Shirt',
    	'sku' => 'code-14',
    	'price' => '75.00',
    	'category' => 'Shirts',
    	'image' => 'https://via.placeholder.com/500x300.png'
    ),
    array(
    	'id' => '15',
    	'title' => 'Beanie',
    	'sku' => 'code-15',
    	'price' => '25.00',
    	'category' => 'Caps',
    	'image' => 'https://via.placeholder.com/500x300.png'
    )
);

The Main PHP File

This file contains the filter form and the div that will hold the products. The code below is included in index.php, except I’ve excluded the basic HTML.

<div class="filters row">
    <div class="col-md-12">
        <form id="filter-form" class="form" action='' method="GET">
            <span>Filter Products</span>
            
            <select name="cats" class="form-control form-control-sm">
                <option value="">Filter by Category</option>
                <option value="Caps">Caps</options>
                <option value="Jackets">Jackets</options>
                <option value="Pants">Pants</options>
                <option value="Shirts">Shirts</options>
            </select>
            <input type="text" name="search" placeholder="Search" class="form-control form-control-sm" title="Search by product name or SKU" />
            
            <span> Show </span>
            <select name="per-page" class="form-control form-control-sm">
                <option value="3">3</options>
                <option value="6">6</options>
                <option value="9">9</options>
                <option value="12">12</options>
            </select>
            
            <input type="submit" value="Filter" class="btn btn-sm btn-secondary"/>
        </form>
    </div>
</div>

<!-- The filtered and paginated content will be dynamically loaded into the #all-products div -->
<div id="all-products" class="row all-products">
   
   <?php 
        // include the products template
        require('resources/products.php');
    ?>
    
</div>

The Products File Template

The file will serve two purposes: to create a template for the individual products and to do the logic that will dictate which products should be shown, based on the filters and pagination. This file is products.php.

<?php 
// Bring in the array
require('products-array.php');

// Filter query
$cat_filter = isset($_GET['cats']) ? $_GET['cats'] : '';
$search_filter = isset($_GET['search']) ? $_GET['search'] : '';

// Default limit
$limit = isset($_GET['per-page']) ? $_GET['per-page'] : 3;

// Default offset
$offset = 0;
$current_page = 1;
if(isset($_GET['page-number'])) {
    $current_page = (int)$_GET['page-number'];
    $offset = ($current_page * $limit) - $limit;
}

// filter products array based on query
if(!empty($cat_filter) || !empty($search_filter)) {
    $filtered_products = array();
    foreach($products as $product) {
        if( !empty($cat_filter) && !empty($search_filter) ) {
            
            if( ( strpos($product['title'], $search_filter) !== false || $product['sku'] == $search_filter ) && $product['category'] == $cat_filter ) {
                $filtered_products[] = $product;
            }

        }
        else if(!empty($cat_filter) && $product['category'] == $cat_filter) {

            $filtered_products[] = $product;
        }
        else if(!empty($search_filter) && ( strpos($product['title'], $search_filter) !== false || $product['sku'] == $search_filter) ) {
            
            $filtered_products[] = $product;
        }
    }
    
    $products = $filtered_products;
}

// Alter the array
$paged_products = array_slice($products, $offset, $limit);

// Define total products
$total_products = count($products);

// Get the total pages rounded up the nearest whole number
$total_pages = ceil( $total_products / $limit );

// Determine whether or not pagination should be made available.
$paged = $total_products > count($paged_products) ? true : false;

/*********************
    The Template
********************/
if (count($paged_products)) {
    foreach ($paged_products as $product) { ?>
       
       <div class="col-md-4 product-wrapper">
           <div class="product">
               <div class="product-image">
                   <img src="<?php echo $product['image']; ?>" />
                </div>
                <div class="product-title">
                    <h3><?php echo $product['title']; ?></h3>
                </div>
                <div class="product-info">
                    <p class="product-sku"><?php echo $product['sku']; ?></p>
                    <p class="product-price">R <?php echo $product['price']; ?></p>
                    <p class="product-category">Listed in <?php echo $product['category']; ?></p>
                </div>
           </div>
           
       </div>
           
     <?php }
}

else {
    echo '<p class="alert alert-warning" >No results found.</p>';
}
 
if ($paged) {
    require('pagination.php');
}

Looking at the above code, we can see a few things happening here. First we include the products.

// Bring in the array
require('products-array.php');

Then, we check if there are any queries set and assign them to a variable. I’m using a ternary if statement and if there aren’t any $_GET parameters, those variables will be empty.

// Filter query
// Category filter
$cat_filter = isset($_GET['cats']) ? $_GET['cats'] : '';
// Search filter
$search_filter = isset($_GET['search']) ? $_GET['search'] : '';

Next, we set our default limit, offset and current page. Your default offset should always be 0 to make sure that, when no queries are set, the products show from the first one. Your current page, by default, should be 1.

The if statement checks if a page-number query is set and if it is, $current_page will then become that value instead of 1, while $offset will calculate what the new offset should be.

// Default limit
$limit = isset($_GET['per-page']) ? $_GET['per-page'] : 3;

// Default offset
$offset = 0;
$current_page = 1;
if(isset($_GET['page-number'])) {
    $current_page = (int)$_GET['page-number'];
    $offset = ($current_page * $limit) - $limit;
}

Now we have to check which products are being requested. In the code below, we check if either the category filter ($cat_filter aka $_GET[‘cats’]) or the search filter ($search_filter aka $_GET[‘search’]) have values. If they aren’t empty, we’ll proceed with the filters. In the foreach loop, when the filter matches the query, it will be pushed into the $filtered_products array.

Filter Products Based on User Query With Conditional Statements

The first part of the conditional ( if ( !empty($cat_filter) && !empty($search_filter) ) ) checks if both category and search have values and will look for matches that include both terms. For the text search it will check if either the title contains the query or if the SKU is the search term.

If the category and search query don’t both have values, the second part of the conditional ( else if(!empty($cat_filter) && $product[‘category’] == $cat_filter) ) will take effect. This checks only for product categories.

The last part of the conditional statement will use the search query ( else if(!empty($search_filter) && ( strpos($product[‘title’], $search_filter) !== false || $product[‘sku’] == $search_filter) ) ). This is similar to the first condition, except it excludes the categories and will only take effect if $cat_filter is empty.

Finally, when the loop completes we will redefine $products by making it a copy of $filtered_products. If $products is an empty array, we let the user know that no products match the query.

// Filter products array based on query
if(!empty($cat_filter) || !empty($search_filter)) {
   // Define empty array to store query matches
    $filtered_products = array();

   // Loop through array and store matches in $filtered_products[]
    foreach($products as $product) {
        if( !empty($cat_filter) && !empty($search_filter) ) {
            
            if( ( strpos($product['title'], $search_filter) !== false || $product['sku'] == $search_filter ) && $product['category'] == $cat_filter ) {
                $filtered_products[] = $product;
            }

        }
        else if(!empty($cat_filter) && $product['category'] == $cat_filter) {

            $filtered_products[] = $product;
        }
        else if(!empty($search_filter) && ( strpos($product['title'], $search_filter) !== false || $product['sku'] == $search_filter) ) {
            
            $filtered_products[] = $product;
        }
    }
    
    $products = $filtered_products;
}

Now, one of three outcomes will be true:

  • Either there aren’t any queries in place, in which case, none of the previous code in the if statement will run and our $products variable will still be the original one in products_array.php,
  • Filters are in place and products were found, redefining $products with the values of $filtered_products,
  • Or filters are in place and no products were found, redefining $products with the empty $filtered_products array.

Now we will use PHP’s array_slice() to limit and offset the $products array so we can add pagination. This sliced array is stored in $paged_products which you can think of as a segment of the $products array that will be shown when pagination is in effect. $limit and $offset were defined earlier.

// Alter the array
$paged_products = array_slice($products, $offset, $limit);

The following code is self-explanatory and just defines some variables we’ll use to make things a bit easier to read. In the last part, we check our total products compared to the amount currently shown. Logically, we only need pagination if the total is more than what’s currently on the page.


// Define total products
$total_products = count($products);

// Get the total pages rounded up the nearest whole number
$total_pages = ceil( $total_products / $limit );

// Determine whether or not pagination should be made available.
$paged = $total_products > count($paged_products) ? true : false;

If pagination is required, we call the pagination template.

if ($paged) {
    require('pagination.php');
}

The Pagination Template

The pagination template also includes some required logic along with the HTML.

<?php
    $current_query = isset($_GET) ? 'data-query="' . http_build_query($_GET) . '"' : '';
?>
<nav class="col-md-12" >
  <ul class="pagination">
      
        <li class="page-item <?php echo $current_page === 1 ? 'disabled' : ''; ?>">
          <a class="page-link" href="#" data-page-number="<?php echo $current_page - 1; ?>" <?php echo $current_query; ?> >
            <span>«</span>
          </a>
        </li>
    
    <?php
    
    for($page_number = 1; $page_number <= $total_pages; $page_number ++) { ?>
             <li class="page-item <?php echo isset($_GET['page-number']) && $_GET['page-number'] == $page_number ? 'active' : ''; ?>"><a class="page-link" href="#" data-page-number="<?php echo $page_number; ?>" <?php echo $current_query; ?>><?php echo $page_number; ?></a></li>
    
    <?php } ?>
    
        <li class="page-item <?php echo $current_page === (int)$total_pages ? 'disabled' : ''; ?>">
          <a class="page-link" href="#" data-page-number="<?php echo $current_page + 1; ?>" <?php echo $current_query; ?>>
            <span>»</span>
          </a>
        </li>
  </ul>
</nav>

In the following code, we use PHP’s http_build_query() function to generate a query we’ll send through ajax when we page. This will ensure we keep the filters, since with Ajax, we can’t grab them from the URL. We’re going to use this as an HTML data attribute (data-query) we can access in JQuery along with the additional data-page-number attribute .

$current_query = isset($_GET) ? 'data-query="' . http_build_query($_GET) . '"' : '';

For an explanation of the for loop and pagination, please see “How To Create Pagination With PHP“, which has a thorough explanation of how we can determine which page we’re on to build a working pagination template.

JQuery and Ajax Pagination

Finally, in our Javascript we add the following code:

$(document).on('click', '.page-link', function(e){
    e.preventDefault();
    
    var page_number = $(this).data('page-number');
    var current_query;
    
    if( $(this).data('query') ) {
        current_query = '?' + $(this).data('query');
    }
    else {
        current_query = '';
    }
    
    $.get('content/resources/products.php' + current_query, {'page-number' : page_number}, function(data){
        $('#all-products').html(data);
    })
    /** Shorthand for
    * $.ajax({
    * url: 'content/resources/products.php' + current_query,
    * data: {'page-number' : page_number},
    * success: function(data){
    *   $('#all-products').html(data);
    * },
    * dataType: 'html'
    * });
    */
})

$(document).on('submit', '#filter-form', function(e){
    e.preventDefault();
    
    var form = $(this);
    
    $.get('content/resources/products.php', $(form).serialize(), function(data){
        $('#all-products').html(data);
    })
    
})

In the above code, the first function will be in charge of determining what happens when we click any of the pagination links. The page_number variable grabs the data we set in the data-page-number attribute while current_query uses the value set in data-query we built with the http_build_query() PHP function.

This will send a request to products.php, appending the current filter query (if there is one set) and additionally send the requested page number (ie. offset). The server will then send back the data from the PHP file with newly queries items.

JQuery’s html() function will load that HTML into the div with the id of all-products.

 $.get('content/resources/products.php' + current_query, {'page-number' : page_number}, function(data){
        $('#all-products').html(data);
    })

The second function is similar, but is only responsible for filtering, not pagination. It serialises all the inputs from the form with JQuery’s serialize() helper function when the user submits the form. The products.php file then does what it needs to do and sends back the data based on the request.

As you can see, creating a paged list of products or posts with filters and search features without the need to reload is not difficult at all. In this tutorial we used built in functions for both PHP and JQuery, and didn’t even need to write our own functions to make it work!

Download the files used in this project.

Is this still valid in 2021? Please let me know in the comments below.

Was This Helpful?

How to Access a Controller Method From Another Controller in Laravel
Previous Post
How to Access a Controller Method From Another Controller in Laravel
How to Easily Install a WordPress Plugin
Next Post
How to Install a WordPress Plugin Easily

0 Comments

Leave a Reply