This tutorial will teach you how to implement AJAX pagination to build infinite scroll functionality on the blog home page.
Infinite scroll is achieved by loading the next page automatically when the user scrolls to the bottom of the page.
Step 1. Add Post model
Suppose we have the Post
model.
class Post(models.Model):
title = models.CharField(max_length=256)
slug = models.SlugField(unique=True)
text = models.TextField()
image = models.ImageField(upload_to='blog/%Y-%m/')
is_active = models.BooleanField(default=True)
created_dt = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title
The model has a field created_dt
, we need this field to sort posts in descending order by created time. So visitors will get fresher posts first.
Step 2. Add view to serve our home page
Open up views.py
and add post_list
view as below:
views.py
from django.shortcuts import render
from django.core import paginator
from django.template.loader import render_to_string
from . import models
NEWS_COUNT_PER_PAGE = 9
def post_list(request):
page = int(request.GET.get('page', 1))
posts = models.Post.objects.filter(is_active=True).order_by('-created_dt')
p = paginator.Paginator(posts,
NEWS_COUNT_PER_PAGE)
try:
post_page = p.page(page)
except paginator.EmptyPage:
post_page = paginator.Page([], page, p)
if not request.is_ajax():
context = {
'posts': post_page,
}
return render(request,
'blog/posts.html',
context)
else:
content = ''
for post in post_page:
content += render_to_string('blog/post-item.html',
{'post': post},
request=request)
return JsonResponse({
"content": content,
"end_pagination": True if page >= p.num_pages else False,
})
The same view will handle the initial page load and the following AJAX requests. When the user initially loads the post list page, we will display the first page of posts. When the user scrolls to the bottom of the page an AJAX request will be sent to load the next page. Each page will contain 9 posts.
In this view, we create a QuerySet to return all posts from the database. Then, we build a Paginator object to paginate the results, retrieving nine posts per page. We get an EmptyPage
exception if the requested page is out of range. In that case, we will create an empty page instance of the class django.core.paginator.Page
with an empty list: post_page = paginator.Page([], page, p)
.
We will need to create templates posts.html
and post-item.html
to render posts.
- The template
posts.html
will extendbase.html
and it will render posts usingpost-item.html
to avoid repeating code. - Template
post-item.html
will contain an HTML template for displaying each post. This template will be used in two places:
- Included in
posts.html
- Used in our view function.
We will use render_to_string
function to render a post item with the given context and append the rendered HTML to the content
variable.
Finally, the view post_list
will return JSON response with payload: content
and end_pagination
. The variable end_pagination
holds a boolean value, which shows whether it's the last page or not.
blog/post-item.html
{% load thumbnail %}
<div class="col-md-3 mb-4">
<div class="card">
<img src="{% thumbnail post.image '370x250' crop='center' upscale=True %}" class="card-img-top" alt="...">
<div class="card-body">
<h5 class="card-title">{{ post.title }}</h5>
<p class="card-text">
{{ post.text|striptags|slice:'0:120' }}...
</p>
<a href="#" class="btn btn-primary">Read More</a>
</div>
</div>
</div>
Step 3. Write client logic using the jquery library for lazy loading
The template posts.html
will contain JavaScript code for loading additional pages when scrolling to the bottom.
blog/posts.html
{% extends 'base.html' %}
{% load static %}
{% block title %} Posts {% endblock %}
{% block content %}
<div class="mt-3">
<div class="container">
<div class="row news-list">
{% for post in posts %}
{% include 'blog/post-item.html' %}
{% endfor %}
</div>
</div>
</div>
<script>
$(document).ready(function () {
window.news_index = '{% url 'blog:posts' %}';
var page = 1;
var block_request = false;
var end_pagination = false;
$(window).scroll(function () {
var margin = $(document).height() - $(window).height() - 200;
if ($(window).scrollTop() > margin && end_pagination === false && block_request === false) {
block_request = true;
page += 1;
$.ajax({
type: 'GET',
url: window.news_index,
data: {
"page": page
},
success: function (data) {
if (data.end_pagination === true) {
end_pagination = true;
} else {
block_request = false;
}
$('.news-list').append(data.content);
}
})
}
});
})
</script>
{% endblock %}
In JavaScript code we define the following variables:
page
: Stores the current page number. Initially, it equals 1.block_request
: Prevents us from sending additional requests while an AJAX request is in progress.end_pagination
: Allows us to know whether the user is on the last page. If we getdata["end_pagination"]
astrue
in the AJAX response, we change this variable's value totrue
. And it will stop sending additional AJAX requests because we will assume that there are no more results.
We will use the event $(window).scroll()
and define the handler function for it.
We send AJAX request only if:
- The user is closer than 200 pixels to the bottom of the page
- There is no other AJAX request is being sent (
block_request
isfalse
) - We did not reach the last page (
end_pagination
isfalse
).
Then we increment the page count (page += 1
) and set block_request
to true
.
When we get the response from the server there are two scenarios:
- We loaded the last page (
data.end_pagination
istrue
). Then we will set the variableend_pagination
totrue
to prevent the following AJAX request from being sent. data.end_pagination
isfalse
. Then we will setblock_request
tofalse
.
After that, we will append response content to the end of the container with the class .news-list
.
$('.news-list').append(data.content);
Conclusion
In this tutorial, we showed how to implement endless scroll functionality with Django's awesome built-in paginator and jquery AJAX requests. As you can see it is simple.
Thanks for reading!