How to Create Load content on page scroll using AJAX in CodeIgniter 4

Infinite scroll is a type of pagination where data load automatically while the user scrolling the page.
MySQL database data is loaded using AJAX.
The process will continue until all data is not displayed.
In this tutorial, I show how you can load content on page scroll using jQuery AJAX in CodeIgniter 4.
1. Database configuration
- Open
.envfile which is available at the project root.
NOTE – If dot (.) not added at the start then rename the file to .env.
- Remove # from start of
database.default.hostname,database.default.database,database.default.username,database.default.password, anddatabase.default.DBDriver. - Update the configuration and save it.
database.default.hostname = 127.0.0.1 database.default.database = testdb database.default.username = root database.default.password = database.default.DBDriver = MySQLi
2. Enable CSRF
- Again open
.envfile. - Remove # from the start of the
security.tokenName,security.headerName,security.cookieName,security.expires,security.regenerate, andsecurity.samesite. - I update the
security.tokenNamevalue with'csrf_hash_name'. With this name read CSRF hash. You can update it with any other value. - If you don’t want to regenerate CSRF hash after each AJAX request then set
security.regenerate = false.
security.tokenName = 'csrf_hash_name' security.headerName = 'X-CSRF-TOKEN' security.cookieName = 'csrf_cookie_name' security.expires = 7200 security.regenerate = true security.redirect = true security.samesite = 'Lax'
- Open
app/Config/Filters.phpfile. - Uncomment
'csrf'in'before'if commented.
// Always applied before every request
public $globals = [
'before' => [
//'honeypot'
'csrf',
],
'after' => [
'toolbar',
//'honeypot'
],
];3. Create Table
- Create a new table
postsusing migration.
php spark migrate:create create_posts_table
- Now, navigate to
app/Database/Migrations/folder from the project root. - Find a PHP file that ends with
create_posts_tableand open it. - Define the table structure in the
up()method. - Using the
down()method deletepoststable which calls when undoing migration.
<?php namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
class CreatePostsTable extends Migration
{
public function up() {
$this->forge->addField([
'id' => [
'type' => 'INT',
'constraint' => 5,
'unsigned' => true,
'auto_increment' => true,
],
'title' => [
'type' => 'VARCHAR',
'constraint' => '100',
],
'description' => [
'type' => 'TEXT',
'null' => true,
],
'link' => [
'type' => 'VARCHAR',
'constraint' => '255',
],
]);
$this->forge->addKey('id', true);
$this->forge->createTable('posts');
}
//--------------------------------------------------------------------
public function down() {
$this->forge->dropTable('posts');
}
}- Run the migration –
php spark migrate
4. Model
- Create
PostsModel –
php spark make:model Posts
- Open
app/Models/Posts.phpfile. - In
$allowedFieldsArray specify field names –['title','description','link']that can be set during insert and update.
Completed Code
<?php
namespace App\Models;
use CodeIgniter\Model;
class Posts extends Model
{
protected $DBGroup = 'default';
protected $table = 'posts';
protected $primaryKey = 'id';
protected $useAutoIncrement = true;
protected $insertID = 0;
protected $returnType = 'array';
protected $useSoftDeletes = false;
protected $protectFields = true;
protected $allowedFields = ['title','description','link'];
// Dates
protected $useTimestamps = false;
protected $dateFormat = 'datetime';
protected $createdField = 'created_at';
protected $updatedField = 'updated_at';
protected $deletedField = 'deleted_at';
// Validation
protected $validationRules = [];
protected $validationMessages = [];
protected $skipValidation = false;
protected $cleanValidationRules = true;
// Callbacks
protected $allowCallbacks = true;
protected $beforeInsert = [];
protected $afterInsert = [];
protected $beforeUpdate = [];
protected $afterUpdate = [];
protected $beforeFind = [];
protected $afterFind = [];
protected $beforeDelete = [];
protected $afterDelete = [];
}5. Route
- Open
app/Config/Routes.phpfile. - Define 2 routes –
- / – Load initial page content.
- getPosts – To handle page scroll AJAX request.
Completed Code
$routes->get('/', 'PostsController::index');
$routes->post('getPosts', 'PostsController::getPosts');6. Controller
- Create
PostsControllerController –
php spark make:controller PostsController
- Open
app/Controllers/PostsController.phpfile. - Import
PostsModel. - I defined a class variable –
- rowperpage – To store the number of records fetch at a time. I set it to 4. Adjust its value according to your requirement.
- Create 2 methods –
- index() – Assign
$this->rowperpageto$data['rowperpage'], total number of record to$data['totalrecords']and fetch records frompoststable.
- index() – Assign
Load index view and pass $data.
- getPosts() – This method is use to handle page scroll AJAX requests.
Read POST values and assign to $postData variable. Assign $postData['start'] to $start.
Fetch records from posts table according to $start and $this->rowperpage value. Loop on the fetched records and create HTML layout and assign to $html variable.
Assign new CSRF token hash to $data['token'] and $html to $data['html'].
Return $data Array in JSON format.
Completed Code
<?php
namespace App\Controllers;
use App\Controllers\BaseController;
use App\Models\Posts;
class PostsController extends BaseController {
public $rowperpage = 4; // Number of rowsperpage
public function index() {
$posts = new Posts();
// Number of rowsperpage
$data['rowperpage'] = $this->rowperpage;
// Total number of records
$data['totalrecords'] = $posts->select('id')->countAllResults();
// Fetch 4 records
$data['posts'] = $posts->select('*')
->findAll($this->rowperpage, 0);
return view('index',$data);
}
public function getPosts(){
$request = service('request');
$postData = $request->getPost();
$start = $postData['start'];
// Fetch records
$posts = new Posts();
$records = $posts->select('*')
->findAll($this->rowperpage, $start);
$html = "";
foreach($records as $record){
$id = $record['id'];
$title = $record['title'];
$description = $record['description'];
$link = $record['link'];
$html .= '<div class="card w-75 post">
<div class="card-body">
<h5 class="card-title">'.$title.'</h5>
<p class="card-text">'.$description.'</p>
<a href="'.$link.'" target="_blank" class="btn btn-primary">Read More</a>
</div>
</div>';
}
// New CSRF token
$data['token'] = csrf_hash();
// Fetch data
$data['html'] = $html;
return $this->response->setJSON($data);
}
}7. View
Create index.php file in app/Views/.
I included Bootstrap CSS only for design.
Create a hidden element to store CSRF token name specified in .env file in the name attribute and store CSRF hash in the value attribute.
<input type="hidden" class="txt_csrfname" name="<?= csrf_token() ?>" value="<?= csrf_hash() ?>" />
Loop on $posts and create layout. I added 'post' class on the <div > to find last content while appending new content.
Created 3 hidden fields –
- Store current row position.
- Store the number of rows fetch at a time.
- Store the total number of records.
Script –
Create 2 functions –
- checkWindowSize() – Using this function to check if the screen is too large or the page has not have enough content to scroll. If the condition is true then fetch new records by calling
fetchData()function. - fetchData() – Using this function fetch new records.
Get values from hidden fields and store them in variables. Add rowperpage with start. If start value is <= allcount then update #start value.
Read CSRF token name and hash and assign to variables.
Send AJAX POST request to site_url('getPosts') where pass {[csrfName]: csrfHash,start:start} as data, set dataType: 'json'.
On successful callback append new fetched data response.html after last <div class='post '>. Also, update CSRF token.
Call checkWindowSize() function to again check if the page is scrollable or not.
Define $(window).scroll event, using this check if scroll position reaches end then fetch new records by calling fetchData() function.
Similarly, define touchmove event for mobile, using this check if scroll position reaches end then fetch new records by calling fetchData() function.
Completed Code
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Load content on page scroll using AJAX in CodeIgniter 4</title>
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/css/bootstrap.min.css">
<style type="text/css">
.card{
margin: 0 auto;
margin-top: 35px;
}
</style>
</head>
<body>
<div class='container'>
<!-- CSRF Token -->
<input type="hidden" class="txt_csrfname" name="<?= csrf_token() ?>" value="<?= csrf_hash() ?>" />
<?php
foreach($posts as $post){
$id = $post['id'];
$title = $post['title'];
$description = $post['description'];
$link = $post['link'];
?>
<div class="card w-75 post">
<div class="card-body">
<h5 class="card-title"><?= $title ?></h5>
<p class="card-text"><?= $description ?></p>
<a href="<?= $link ?>" target="_blank" class="btn btn-primary">Read More</a>
</div>
</div>
<?php
}
?>
<input type="hidden" id="start" value="0">
<input type="hidden" id="rowperpage" value="<?= $rowperpage ?>">
<input type="hidden" id="totalrecords" value="<?= $totalrecords; ?>">
</div>
<!-- Script -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script type="text/javascript">
checkWindowSize();
// Check if the page has enough content or not. If not then fetch records
function checkWindowSize(){
if($(window).height() >= $(document).height()){
// Fetch records
fetchData();
}
}
// Fetch records
function fetchData(){
var start = Number($('#start').val());
var allcount = Number($('#totalrecords').val());
var rowperpage = Number($('#rowperpage').val());
start = start + rowperpage;
if(start <= allcount){
$('#start').val(start);
// CSRF Hash
var csrfName = $('.txt_csrfname').attr('name');
// CSRF Token name
var csrfHash = $('.txt_csrfname').val(); // CSRF hash
$.ajax({
url:"<?=site_url('getPosts')?>",
type: 'post',
data: {[csrfName]: csrfHash,start:start},
dataType: 'json',
success: function(response){
// Add
$(".post:last").after(response.html).show().fadeIn("slow");
// Update token
$('.txt_csrfname').val(response.token);
// Check if the page has enough content or not. If not then fetch records
checkWindowSize();
}
});
}
}
$(document).on('touchmove', onScroll); // for mobile
function onScroll(){
if($(window).scrollTop() > $(document).height() - $(window).height()-100) {
fetchData();
}
}
$(window).scroll(function(){
var position = $(window).scrollTop();
var bottom = $(document).height() - $(window).height();
if( position == bottom ){
fetchData();
}
});
</script>
</body>
</html>8. Run
- Navigate to the project using Command Prompt if you are on Windows or terminal if you are on Mac or Linux, and
- Execute “php spark serve” command.
php spark serve
- Run
http://localhost:8080in the web browser.
9. Demo
10. Conclusion
You can use it as a replacement of traditional pagination.
New data load while scrolling the page, it works even if there is not limited content for scrolling.