Server-Side Table Pagination using PHP, AngularJS, and Bootstrap 5 Tutorial

In this tutorial, you will learn how to Create a Server-Side Table Pagination using PHP, MySQL Database, AngularJS, and Bootstrap 5 Framework. The main purpose of this tutorial is to provide the IT/CS students and new programmers with a reference for implementing a Server-Side Pagination. Here, snippets are provided and a sample web application source code is also provided and is free to download.

What is Server-Side Table Pagination?

Server-Side Table Pagination is one of the common features of web applications. This feature helps to optimize the speed of the page load. It is a process of dividing the data into discrete pages. It also prevents exhausting the database of retrieving data.

How to Create a Server-Side Table Pagination in PHP, JavaScript, and AngularJS?

To create a Server-Side Table pagination using AngularJS, we will use $scope, $http, and $compile API. Using JavaScript and Bootstrap 5 Framework, we can create a script that generates the pagination buttons dynamically. And on the server side, using PHP and MySQL Database, we can query the limited data using LIMIT and OFFSET clauses of MySQL. Check out the snippets below that I prepared for this tutorial to have some idea of how to achieve this tutorial's objective.

Sample Web Application Snippets

The following snippets of the simple web application demonstrate the Server-Side Table Pagination.

Technologies

Here are the following technologies used to develop the snippets below.

  • HTML
  • CSS
  • JavaScript
  • PHP
  • MySQL Database
  • AngularJS
  • Bootstrap 5 Framework

Database

For this tutorial purposes only, let's create a new database named dummy_db and use the following MySQL Schema and Table Data.

  1.  
  2. CREATE TABLE `employee_list` (
  3. `name` varchar(255) DEFAULT NULL,
  4. `phone` varchar(100) DEFAULT NULL,
  5. `address` varchar(255) DEFAULT NULL
  6.  
  7.  
  8. INSERT INTO `employee_list` (`id`, `name`, `phone`, `address`) VALUES
  9. (1, 'Bradley Charles', '1-387-631-7773', '201-7452 Interdum Rd.'),
  10. (2, 'Lysandra Schwartz', '1-329-483-7978', '9649 Dolor Road'),
  11. (3, 'Whitney Moran', '(392) 138-7649', 'Ap #502-4286 Elementum Av.'),
  12. (4, 'Flavia Adkins', '1-517-765-3895', '563-8222 At Street'),
  13. (5, 'Vladimir Bradley', '1-436-562-8352', 'P.O. Box 170, 9079 Amet, Ave'),
  14. (6, 'Asher Osborn', '(255) 217-5562', '332-4023 Ridiculus Street'),
  15. (7, 'Shaine Mathews', '1-528-220-9818', 'P.O. Box 865, 3851 Dui. Road'),
  16. (8, 'Donovan Todd', '1-615-939-1360', 'P.O. Box 637, 895 Ac, Avenue'),
  17. (9, 'Jackson Ward', '1-305-537-3752', '199-8650 Sapien. Rd.'),
  18. (10, 'Naida Michael', '(157) 545-9897', '502-3887 Ultrices. St.'),
  19. (11, 'Scarlet Willis', '1-761-717-4296', '9935 A, St.'),
  20. (12, 'Colette Carr', '1-586-832-2814', 'Ap #654-7677 Mattis Ave'),
  21. (13, 'Macy Pena', '1-322-550-6531', '6567 Malesuada Rd.'),
  22. (14, 'Rooney Mckinney', '(295) 257-5029', 'P.O. Box 444, 1418 Vivamus Street'),
  23. (15, 'Denton Wilder', '1-464-768-6866', '798 Dictum St.'),
  24. (16, 'Jin Emerson', '(295) 421-5258', '394-5031 Risus. Rd.'),
  25. (17, 'Jack Rose', '(716) 946-1411', 'Ap #511-7704 Odio Rd.'),
  26. (18, 'Eric Serrano', '(911) 717-7798', 'P.O. Box 760, 9708 Penatibus Road'),
  27. (19, 'Forrest Andrews', '1-657-248-8527', '600-6646 Tellus Rd.'),
  28. (20, 'Elijah Gregory', '(252) 814-5344', 'Ap #372-9501 Aliquet Avenue'),
  29. (21, 'Phoebe Moses', '1-332-384-8360', '746-9767 Suspendisse St.'),
  30. (22, 'William Peters', '(850) 847-5722', 'P.O. Box 382, 4880 Neque St.'),
  31. (23, 'Basil Parsons', '(751) 775-5828', 'Ap #497-4995 Per Rd.'),
  32. (24, 'Mufutau Bradley', '(935) 451-8759', '520-315 Congue, Avenue'),
  33. (25, 'Ciaran Bradshaw', '(788) 774-8911', 'Ap #388-1102 Magna Ave');

Database Connection

Next, in our source code folder, let's create a new PHP File and save it as db-connect.php. This file contains the PHP Script that connects our created database to our web application.

db-connect.php

  1. <?php
  2. $host = "localhost";
  3. $username = "root";
  4. $pw = "";
  5. $db_name = "dummy_db";
  6.  
  7. $conn = new mysqli($host, $username, $pw, $db_name);
  8.  
  9. if(!$conn){
  10. die("Database Connection Failed");
  11. }

Page Interface

Next, we'll create the page interface of the application which contains a table for listing the data from the database. The script contains the HTML elements and AngularJS that are needed for the application. Save the snippet below as index.php.

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <meta charset="UTF-8">
  4. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>Angular JS Table Pagination</title>
  7. <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.0/css/all.min.css" integrity="sha512-xh6O/CkQoPOWDdYTDqeRdPCVd1SpvCA9XXcUnZS2FmJNp1coAFzvtCN9BmamE+4aHK8yyUHUSCcJHgXloTyT2A==" crossorigin="anonymous" referrerpolicy="no-referrer" />
  8. <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" integrity="sha384-Zenh87qX5JnK2Jl0vWa8Ck2rdkQ2Bzep5IDxbcnCeuOxjzrPF/et3URy9Bv1WTRi" crossorigin="anonymous">
  9. <script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.0/js/all.min.js" integrity="sha512-naukR7I+Nk6gp7p5TMA4ycgfxaZBJ7MO5iC3Fp6ySQyKFHOGfpkSZkYVWV5R7u7cfAicxanwYQ5D1e17EfJcMA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
  10. <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.8.3/angular.js" integrity="sha512-klc+qN5PPscoGxSzFpetVsCr9sryi2e2vHwZKq43FdFyhSAa7vAqog/Ifl8tzg/8mBZiG2MAKhyjH5oPJp65EA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
  11. <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-OERcA2EqjJCMA+/3y+gxIOqMEjwtxJY7qPCqsdltbNJuaOe923+mo//f6V8Qbsw3" crossorigin="anonymous"></script>
  12. <script src="bootstrap5-custom-pagination.js"></script>
  13. html, body{
  14. min-height:100%;
  15. width:100%;
  16. }
  17. tbody:empty:after{
  18. content:'No records found'
  19. }
  20. </style>
  21. </head>
  22. <nav class="navbar navbar-expand-lg navbar-dark bg-primary bg-gradient">
  23. <div class="container">
  24. <a class="navbar-brand" href="./">Angular JS Table Pagination</a>
  25. <div>
  26. <a href="https://sourcecodester.com" class="text-light fw-bolder h6 text-decoration-none" target="_blank">SourceCodester</a>
  27. </div>
  28. </div>
  29. </nav>
  30. <div class="container-fluid px-5 my-3" ng-app="SampleApp" ng-controller="tblController">
  31. <div class="col-lg-8 col-md-10 col-sm-12 mx-auto">
  32. <div class="card rounded-0 shadow mb-3">
  33. <div class="card-header">
  34. <div class="card-title"><b>Employee List</b></div>
  35. </div>
  36. <div class="card-body">
  37. <div class="container-fluid">
  38. <div class="alert alert-danger mb-3 rounded-0" ng-show="error">{{error_msg}}</div>
  39. <div class="table-responsive">
  40. <table class="table table-bordered table-striped">
  41. <tr class="bg-primary bg-gradient text-light">
  42. <th class="text-center">Employee</th>
  43. <th class="text-center">Contact #</th>
  44. <th class="text-center">Address</th>
  45. </tr>
  46. </thead>
  47. <tr ng-repeat="data in Employees">
  48. <td>{{data.name}}</td>
  49. <td>{{data.phone}}</td>
  50. <td>{{data.address}}</td>
  51. </tr>
  52. </tbody>
  53. </table>
  54. </div>
  55.  
  56. <div class="bootstrap5-custom-pagination"
  57. data-max-length="5"
  58. data-number-of-pages="{{NumberOfPages}}"
  59. data-current-page="{{currentPage}}"
  60. data-btn-callback="paginate"
  61. >
  62. </div>
  63. </div>
  64. </div>
  65. </div>
  66. </div>
  67. </div>
  68. <script src="app.js"></script>
  69. </body>
  70. </html>

Retrieving the Data

Next, we'll create a PHP File that contains the script for fetching the sample data from the database. The script requires POST data of "limit" and currentPage which will be needed for fetching only the data of the table's current page. Save the snippet below as getEmployees.php.

  1. <?php
  2. require_once('db-connect.php');
  3. $limit = isset($_POST['limit']) ? $_POST['limit'] : 5;
  4. $currentPage = $offset = isset($_POST['currentPage']) ? $_POST['currentPage'] : 1;
  5. $offset = $currentPage > 1 ? ceil(($currentPage * $limit) -$limit) : 0;
  6. $sqlTotal = "SELECT id FROM `employee_list`";
  7. $sql = "SELECT * FROM `employee_list` order by `name` asc LIMIT {$limit} OFFSET {$offset}";
  8. $query = $conn->query($sql);
  9. $queryTotal = $conn->query($sqlTotal);
  10. $data['employees'] = $query->fetch_all(MYSQLI_ASSOC);
  11. $data['total'] = $queryTotal->num_rows;
  12.  
  13. echo json_encode($data);
  14. $conn->close();

Pagination Buttons

Next, we'll create a custom JavaScript class that contains the script for generating the pagination buttons including the previous and next buttons. The script generates a button that uses Bootstrap 5 Framework's design. In my case, I named this file bootstrap5-custom-pagination.js, and is loaded at the index.php file.

  1. class bs5cp_paginations_class {
  2. paginate(){
  3. // Get Pagination Node Elements
  4. this.bs5cp_paginations_els = document.querySelectorAll('.bootstrap5-custom-pagination')
  5.  
  6. // Generate Pagination Buttons
  7. this.bs5cp_paginations_els.forEach(function(element, index){
  8. element.innerHTML = "";
  9. var options = {
  10. maxLength: parseInt(element.getAttribute('data-max-length')) || 5,
  11. currentPage: parseInt(element.getAttribute('data-current-page')) || 1,
  12. totalBtns: parseInt(element.getAttribute('data-number-of-pages')) || 1,
  13. maxButtons: parseInt(element.getAttribute('data-buttons-max')) || 5,
  14. btn_callback: element.getAttribute('data-btn-callback') || null,
  15. previousText: element.getAttribute('data-previous-text') || "prev",
  16. nextText: element.getAttribute('data-next-text') || "next"
  17. }
  18.  
  19. // Button Group Parent
  20. var btn_group = document.createElement('div')
  21. btn_group.setAttribute('role','group');
  22. btn_group.classList.add('btn-group');
  23.  
  24.  
  25. //Previous Button
  26. var prev_btn = document.createElement('button')
  27. prev_btn.setAttribute('type','button');
  28. prev_btn.classList.add('btn', 'btn-sm', 'rounded-0', 'border');
  29. var prev_page = parseInt(options.currentPage) - 1;
  30. prev_btn.setAttribute('ng-click',`${options.btn_callback}(${prev_page})`);
  31. if(options.currentPage == 1)
  32. prev_btn.setAttribute('disabled', true)
  33. prev_btn.innerHTML = options.previousText
  34. element.appendChild(prev_btn)
  35.  
  36.  
  37. //First Page Button
  38. var first_btn = document.createElement('button')
  39. first_btn.classList.add('btn', 'btn-sm', 'rounded-0', 'border');
  40. first_btn.setAttribute('type','button');
  41. first_btn.setAttribute('ng-click',`${options.btn_callback}(1)`);
  42. first_btn.innerHTML = 1
  43. if(options.currentPage == 1)
  44. first_btn.classList.add('active', 'btn-primary');
  45. element.appendChild(first_btn)
  46.  
  47. //Ellipsis Button
  48. var ellipsis_btn = document.createElement('button')
  49. ellipsis_btn.classList.add('btn', 'btn-sm', 'rounded-0', 'border');
  50. ellipsis_btn.setAttribute('type','button');
  51. ellipsis_btn.setAttribute('disabled',true);
  52. ellipsis_btn.innerHTML = '...'
  53.  
  54.  
  55. //Dynamic Page Buttons
  56. var dynamic_btn = document.createElement('button')
  57. dynamic_btn.classList.add('btn', 'btn-sm', 'rounded-0', 'border');
  58. dynamic_btn.setAttribute('type','button');
  59. if(options.currentPage > 3 && options.totalBtns > 5)
  60. element.appendChild(ellipsis_btn.cloneNode(true))
  61. if(options.totalBtns > 5){
  62. var iteratePage = parseInt(options.currentPage > 2 ? options.currentPage : 3) ;
  63. for(var $i = iteratePage; $i < (iteratePage + 3); $i++){
  64. var new_page_btn = dynamic_btn.cloneNode(true)
  65. var num = $i -1;
  66. if((iteratePage + 1) == options.totalBtns)
  67. num = $i -2;
  68. if(iteratePage == options.totalBtns)
  69. num = $i -3;
  70. new_page_btn.setAttribute('ng-click',`${options.btn_callback}(${num})`);
  71. new_page_btn.innerHTML = num
  72. if(options.currentPage == num)
  73. new_page_btn.classList.add('active', 'btn-primary');
  74. element.appendChild(new_page_btn)
  75. }
  76. }else{
  77. for(var $i = 2; $i < 5; $i++){
  78. var new_page_btn = dynamic_btn.cloneNode(true)
  79. new_page_btn.setAttribute('ng-click',`${options.btn_callback}(${$i})`);
  80. new_page_btn.innerHTML = $i
  81. if(options.currentPage == $i)
  82. new_page_btn.classList.add('active', 'btn-primary');
  83. element.appendChild(new_page_btn)
  84. }
  85. }
  86. if(options.totalBtns > 5 && ((options.totalBtns - 2) > options.currentPage)){
  87. element.appendChild(ellipsis_btn.cloneNode(true))
  88. }
  89.  
  90.  
  91. //Last Page Button
  92. if(options.totalBtns > 1){
  93. var last_btn = document.createElement('button')
  94. last_btn.classList.add('btn', 'btn-sm', 'rounded-0', 'border');
  95. last_btn.setAttribute('type','button');
  96. last_btn.setAttribute('ng-click',`${options.btn_callback}(${options.totalBtns})`);
  97. last_btn.innerHTML = options.totalBtns
  98. if(options.currentPage == options.totalBtns)
  99. last_btn.classList.add('active', 'btn-primary');
  100. element.appendChild(last_btn)
  101. }
  102.  
  103. //Next Page Button
  104.  
  105. var next_btn = document.createElement('button')
  106. next_btn.setAttribute('type','button');
  107. next_btn.classList.add('btn', 'btn-sm', 'rounded-0', 'border');
  108. var next_page = parseInt(options.currentPage) + 1;
  109. next_btn.setAttribute('ng-click',`${options.btn_callback}(${next_page})`);
  110. if(options.currentPage == options.totalBtns)
  111. next_btn.setAttribute('disabled', true)
  112. next_btn.innerHTML = options.nextText
  113. element.appendChild(next_btn)
  114.  
  115. })
  116. }
  117. }
  118. let bs5cp_paginations = new bs5cp_paginations_class();
  119.  

AngularJS

Lastly, we'll create a JavaScript file that initiates the angular module of the application. Save the snippet below as app.js.

  1. var mymodule = angular.module("SampleApp",[]);
  2.  
  3. /**
  4.   * Angular Controller
  5.   */
  6.  
  7. var Employee_list = mymodule.controller("tblController", function ($compile, $scope, $http){
  8. $scope.NumberOfPages = 0;
  9. $scope.currentPage = 1;
  10. $scope.paginationMaxBtn = 5;
  11. $scope.maxLength = 5;
  12. $scope.Employees={}
  13. $scope.error = false
  14. $scope.error_msg = ''
  15. /*
  16.   * Get Employees
  17.   */
  18.  
  19. $scope.getEmployees = function(){
  20. var fdata = new FormData();
  21. fdata.append('currentPage', $scope.currentPage)
  22. fdata.append('limit', $scope.maxLength)
  23. $http.post('getEmployees.php', new URLSearchParams(fdata).toString() ,{
  24. headers:{
  25. 'Content-Type' : 'application/x-www-form-urlencoded; charset=utf-8'
  26. }
  27. })
  28. .then(
  29. function (response){
  30. if(response.status == 200){
  31. var data = response.data
  32. if(!!data.employees && data.total){
  33. $scope.Employees = data.employees;
  34. $scope.NumberOfPages = Math.ceil(parseInt(data.total) / $scope.maxLength);
  35.  
  36. // Initiate Pagination
  37. bs5cp_paginations.paginate();
  38. document.querySelectorAll('.bootstrap5-custom-pagination').forEach(function(element){
  39. element.childNodes.forEach(function(child_element){
  40. $compile(child_element)($scope)
  41. })
  42. })
  43.  
  44. }else if(!!data.error){
  45. $scope.error_msg = data.error
  46. }else{
  47. console.error(response)
  48. $scope.error_msg = "Fetching Employee List failed due to some reasons."
  49. }
  50. }else{
  51. $scope.error_msg = "Fetching Employee List failed due to some reasons."
  52. console.error(response)
  53. }
  54. },
  55. function (error){
  56. $scope.error_msg = "Fetching Employee List failed due to some reasons."
  57. console.error(error)
  58. }
  59. )
  60. }
  61.  
  62. /**
  63.   * Trigger Pagination
  64.   * @param {int} page
  65.   */
  66. $scope.paginate = function(page){
  67. $scope.currentPage = page;
  68. }
  69.  
  70. /**
  71.   * Listen for Current Page Changes
  72.   */
  73. $scope.$watch('currentPage', function(){
  74. $scope.getEmployees()
  75. })
  76. $scope.getEmployees()
  77.  
  78. })
  79.  

Application Snapshot

Here is the following snapshot of the result of the snippets of the sample web application that demonstrates the Server-Side Table Pagination.

Server-Side Table Pagination

That's it! You can now test the sample application that we created on your end. I have provided also the complete source code zip file that I created for this tutorial. You can download it by clicking the Download Button below this article.

That's the end of this tutorial. I hope this Server-Side Table Pagination Tutorial helps you with what you are looking for and that you'll find it useful for your current and future web application projects.

Happy Coding :)

Add new comment

CAPTCHA
This question is for testing whether or not you are a human visitor and to prevent automated spam submissions.
13 + 2 =
Solve this simple math problem and enter the result. E.g. for 1+3, enter 4.