Server-Side Pagination using HTML, CSS, Javascript and PHP

Server-side pagination tutorial using Javascript, PHP and MySQL

This tutorial explains how to implement server-side pagination using JavaScript and PHP. When dealing with large databases, loading all data at once can slow down a webpage. Pagination helps by loading only a portion of the data (data-on-demand) based on user actions on the client-side.

pagination

We begin by creating a table in MySQL database for the tutorial in . The main focus here is on a server-side implementation using PHP and MySQL in , and client-side implementation using JavaScript in . SQLdump data, sample images, live demos, and codes are provided in

Pre-requisite

HTML, CSS, Javascript, JQuery, PHP, and SQL.

MySQL database creation

We create a DEMO_TABLE in DEMO_DB database. For pagination based data-on-demand, our server-side script will fetch data from this table.


INSERT INTO `photos` (id, title, description, path) VALUES
(1,'Sunset Beach','A photo of the beach during sunset','images/1.jpg'),
(2,'Mountain View','A view of snow-capped mountains','images/2.jpg'),
(3,'City Skyline','Skyline of a city at night','images/3.jpg'),
(4,'Forest Path','Path going through a forest','images/4.jpg'),
(5,'Desert Dunes','Sand dunes under the sun','images/5.jpg'),
(6,'Waterfall','Waterfall in the mountains','images/6.jpg'),
(7,'Countryside','Countryside with green fields','images/7.jpg'),
(8,'Bridge View','Bridge over a river','images/8.jpg'),
(9,'Lake Reflection','Reflection of trees on a lake','images/9.jpg'),
(10,'Starry Night','Stars visible in the night sky','images/10.jpg'),
(11,'City Lights','Bright lights in downtown','images/11.jpg'),
(12,'Autumn Leaves','Fallen leaves in autumn','images/12.jpg'),
(13,'Foggy Morning','A foggy landscape in the morning','images/13.jpg'),
(14,'Ocean Waves','Waves crashing on rocks','images/14.jpg'),
(15,'Snow Forest','Snow covering the forest trees','images/15.jpg'),
(16,'Canyon View','Looking across a canyon','images/16.jpg'),
(17,'Old Town','Historic streets in an old town','images/17.jpg'),
(18,'Flower Field','Wide field full of flowers','images/18.jpg'),
(19,'Boat Ride','A boat sailing on the water','images/19.jpg'),
(20,'Night Bridge','Bridge illuminated at night','images/20.jpg'),
(21,'Rainy Street','A wet street after rain','images/21.jpg'),
(22,'Winter Cabin','Cabin in the snowy woods','images/22.jpg'),
(23,'Golden Gate','Famous red suspension bridge','images/23.jpg'),
(24,'City Park','Green park inside a city','images/24.jpg'),
(25,'Hot Air Balloons','Balloons flying in the sky','images/25.jpg'),
(26,'Harbor Boats','Boats docked at the harbor','images/26.jpg'),
(27,'Desert Road','Straight road through desert','images/27.jpg'),
(28,'Rocky Shore','Shore with large rocks','images/28.jpg'),
(29,'Sunrise Hills','Sun rising over hills','images/29.jpg'),
(30,'Lighthouse','Lighthouse on the coast','images/30.jpg'),
(31,'City Market','Street market with stalls','images/31.jpg'),
(32,'Rural Barn','A barn in the countryside','images/32.jpg'),
(33,'Palm Beach','Palm trees near the ocean','images/33.jpg'),
(34,'Busy Crosswalk','People crossing the street','images/34.jpg'),
(35,'Mountain Lake','Lake surrounded by mountains','images/35.jpg'),
(36,'Green Valley','Valley covered with grass','images/36.jpg'),
(37,'Street Art','Wall covered in graffiti','images/37.jpg'),
(38,'Clock Tower','Clock tower in the square','images/38.jpg'),
(39,'Winter River','Frozen river during winter','images/39.jpg'),
(40,'Train Tracks','Tracks leading to distance','images/40.jpg'),
(41,'Camping Site','Tent set up in woods','images/41.jpg'),
(42,'Sunny Field','Field under bright sun','images/42.jpg'),
(43,'Snowy Peak','Sharp mountain peak covered with snow','images/43.jpg'),
(44,'Bridge Tunnel','View through a tunnel under a bridge','images/44.jpg'),
(45,'Fishing Dock','People fishing off a dock','images/45.jpg'),
(46,'Cloudy Sky','Thick clouds covering sky','images/46.jpg'),
(47,'Ancient Temple','Ruins of an ancient temple','images/47.jpg'),
(48,'Field Road','Small road cutting through fields','images/48.jpg'),
(49,'Island View','Tropical island in the distance','images/49.jpg'),
(50,'Pier Walk','Wooden pier into the water','images/50.jpg');

Server-side script for supplying data-on-demand

To implement pagination, the backend must be set up to send specific data to the client. In this tutorial, we use MySQL as our database. In SQL, one needs a start index (offset) and a limit to determine how much data to fetch. Here's a server-side PHP code that sends data-on-demand to the client side.

<?php
/**
 * server.php
 * Pagination API returning JSON (success or error)
 */

declare(strict_types=1);

// Force JSON response for everything
header('Content-Type: application/json; charset=utf-8');

// Database config
const DB_HOST = "localhost";
const DB_USER = "demo_user";
const DB_PASS = "str0ng_passw0rd";
const DB_NAME = "pagination_demo";

// Connect to DB
$conn = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME);
if ($conn->connect_error) {
    http_response_code(500);
    echo json_encode(['error' => 'Database connection failed: ' . $conn->connect_error]);
    exit;
}

// Get request params
$limit = isset($_GET['limit']) && is_numeric($_GET['limit']) ? max(1, intval($_GET['limit'])) : 10;
$page  = isset($_GET['page']) && is_numeric($_GET['page']) ? max(0, intval($_GET['page'])) : 0;
$offset = $page * $limit;

// Count total rows
$result_count = $conn->query("SELECT COUNT(*) AS total_data FROM photos");
if (!$result_count) {
    http_response_code(500);
    echo json_encode(['error' => 'Failed to count photos: ' . $conn->error]);
    $conn->close();
    exit;
}
$total_rows  = (int)$result_count->fetch_assoc()['total_data'];
$total_pages = (int)ceil($total_rows / $limit);

// Fetch paginated rows
$stmt = $conn->prepare("SELECT id, title, description, photo_url FROM photos LIMIT ?, ?");
if (!$stmt) {
    http_response_code(500);
    echo json_encode(['error' => 'Failed to prepare query: ' . $conn->error]);
    $conn->close();
    exit;
}
$stmt->bind_param("ii", $offset, $limit);
$stmt->execute();
$result = $stmt->get_result();

// Build response
$photos = [];
while ($row = $result->fetch_assoc()) {
    $photos[] = [
        'id'          => (int)$row['id'],
        'title'       => $row['title'],
        'description' => $row['description'],
        'url'         => 'https://soln.tech/images/picsum/'.$row['photo_url'],
    ];
}

// Output JSON
echo json_encode([
    'total_pages' => $total_pages,
    'photos'      => $photos
]);

// Cleanup
$stmt->close();
$conn->close();
?>

Client-side pagination using Javascript

JQuery to make AJAX Call to the Server

After setting up the backend for pagination, the next step is to build the client-side using HTML, CSS, and JavaScript. jQuery is used to keep the code simple and readable. An AJAX call is made to fetch data for the selected page from the server. We implement this inside renderData function.

renderData(page) {
        $.ajax({
            url: this.apiUrl,
            type: "GET",
            data: { page: page, limit: this.limit },
            success: (response) => {
                if (response.error) {
                    console.error("Server error:", response.error);
                    this.showError(response.error);
                    return;
                }

                this.totalPages = response.total_pages || 0;
                this.renderCards(response.photos || []);
                this.renderPagination();
            },
            error: (xhr, status, error) => {
                console.error("AJAX error:", error);
                this.showError("Failed to fetch data from server.");
            }
        });
    }

Update data in the HTML container

The renderData sends a GET request to the server.php file on the server, passing the selected page number and limit as a parameter. Once the server responds with the relevant data, the renderCards function updates data inside the HTML element. This allows the webpage to update the displayed content without needing to reload the entire page.

renderCards(photos) {
        if (!photos.length) {
            this.dataContainer.html("<p class='text-muted'>No photos available.</p>");
            return;
        }

        const html = photos.map(photo => `
            <div class="col-lg-4 col-md-6 mb-4">
                <div class="card shadow rounded overflow-hidden">
                    <img class="img-fluid"
                         style="width:100%;height:250px;object-fit:cover;"
                         src="${photo.url}"
                         alt="${photo.title}">
                    <div class="card-body">
                        <h5 class="card-title">${photo.title}</h5>
                        <p class="card-text">${photo.description}</p>
                    </div>
                </div>
            </div>
        `).join("");

        this.dataContainer.html(html);
    }

Pagination buttons

In the previous section, we implemented an AJAX call to the server to fetch data for a selected page and update the HTML container. Now, we add the logic to navigate between pages using JavaScript. This involves the following steps:

renderPagination() {
        if (this.totalPages <= 1) {
            this.paginationTop.empty();
            this.paginationBottom.empty();
            return;
        }

        let html = ``;

        for (let i = 0; i < this.totalPages; i++) {
            const isActive = (i === this.page) ? "btn-dark active" : "btn-outline-dark";
            html += `
                <li class="page-item">
                    <button class="btn ${isActive} page-btn" data-page="${i}">${i + 1}</button>
                </li>`;
        }

        this.paginationTop.html(html);
        this.paginationBottom.html(html);
    }
    bindEvents() {
        $(document).on("click", ".page-btn", (e) => {
            this.page = parseInt($(e.currentTarget).data("page"));
            this.renderData(this.page);
        });
    }

Complete Javascript code for the client-side

We also implement an updateUrl function to add the current page number as a parameter in the url. This facilitates user to save the link to revisit a particular page.

The updateURL(page) function updates the browser's address bar to reflect the current page number without reloading the webpage. It first creates a URL object using the current page URL. Then it uses searchParams.set('page', page) to either add or update the page parameter in the query string. Finally, it calls history.replaceState() to change the URL in the browser’s address bar without triggering a full page reload. This allows users to bookmark or share the exact page they’re viewing in a paginated interface. One should call this function whenever a pagination button is pressed and also the first time the webpage is loaded.


class PhotoGallery {
    constructor(config) {
        this.page = 0;
        this.limit = config.limit || 6;
        this.totalPages = 0;
        this.apiUrl = config.apiUrl;
        this.dataContainer = $(config.dataContainer);
        this.paginationTop = $(config.paginationTop);
        this.paginationBottom = $(config.paginationBottom);

        this.init();
    }

    init() {
        this.bindEvents();
        this.renderData(this.page);
    }

    bindEvents() {
        $(document).on("click", ".page-btn", (e) => {
            this.page = parseInt($(e.currentTarget).data("page"));
            this.renderData(this.page);
        });
    }

    renderData(page) {
        $.ajax({
            url: this.apiUrl,
            type: "GET",
            data: { page: page, limit: this.limit },
            success: (response) => {
                if (response.error) {
                    console.error("Server error:", response.error);
                    this.showError(response.error);
                    return;
                }

                this.totalPages = response.total_pages || 0;
                this.renderCards(response.photos || []);
                this.renderPagination();
            },
            error: (xhr, status, error) => {
                console.error("AJAX error:", error);
                this.showError("Failed to fetch data from server.");
            }
        });
    }

    renderCards(photos) {
        if (!photos.length) {
            this.dataContainer.html("<p class='text-muted'>No photos available.</p>");
            return;
        }

        const html = photos.map(photo => `
            <div class="col-lg-4 col-md-6 mb-4">
                <div class="card shadow rounded overflow-hidden">
                    <img class="img-fluid"
                         style="width:100%;height:250px;object-fit:cover;"
                         src="${photo.url}"
                         alt="${photo.title}">
                    <div class="card-body">
                        <h5 class="card-title">${photo.title}</h5>
                        <p class="card-text">${photo.description}</p>
                    </div>
                </div>
            </div>
        `).join("");

        this.dataContainer.html(html);
    }

    renderPagination() {
        if (this.totalPages <= 1) {
            this.paginationTop.empty();
            this.paginationBottom.empty();
            return;
        }

        let html = ``;

        for (let i = 0; i < this.totalPages; i++) {
            const isActive = (i === this.page) ? "btn-dark active" : "btn-outline-dark";
            html += `
                <li class="page-item">
                    <button class="btn ${isActive} page-btn" data-page="${i}">${i + 1}</button>
                </li>`;
        }

        this.paginationTop.html(html);
        this.paginationBottom.html(html);
    }

    showError(message) {
        this.dataContainer.html(`<p class='text-danger fw-bold'>${message}</p>`);
        this.paginationTop.empty();
        this.paginationBottom.empty();
    }
}

// Initialize when document is ready
$(document).ready(() => {
    new PhotoGallery({
        apiUrl: "server.php",
        dataContainer: "#data",
        paginationTop: "#pagination-top",
        paginationBottom: "#pagination-bottom",
        limit: 6
    });
});

Live demos and codes for pagination

Sample images and sqldump are common to all versions of pagination implementaion.

Version 1

[Demo] [Code]

Version 2

[Demo] [Code]

Version 3

[Demo] [Code]

Version 4

[Demo] [Code]

Author

Anurag Gupta is an M.S. graduate in Electrical and Computer Engineering from Cornell University. He also holds an M.Tech degree in Systems and Control Engineering and a B.Tech degree in Electrical Engineering from the Indian Institute of Technology, Bombay.


Comment

* Required information
1000
Drag & drop images (max 3)
Captcha Image
Powered by Commentics

Past Comments

No comments yet. Be the first!

Similar content