In this tutorial I will show you how to implement a very simple infinite scrolling with Django. Basically we will take advantage of Django’s pagination API and a jQuery plug-in. You will find examples using both function-based views and class-based views.
Dependencies
We don’t need anything other than Django installed in the back-end. For this example you will need jQuery and Waypoints.
After downloading the dependencies, include the following scripts in your template:
base.html
<script src="{% static 'js/jquery-3.1.1.min.js' %}"></script>
<script src="{% static 'js/jquery.waypoints.min.js' %}"></script>
<script src="{% static 'js/infinite.min.js' %}"></script>
Basic Example
This example uses function-based view and I’m simply paginating a list of numbers, which I generate on the fly.
urls.py
from django.conf.urls import url
from mysite.blog import views
urlpatterns = [
url(r'^$', views.home, name='home'),
]
views.py
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.shortcuts import render
def home(request):
numbers_list = range(1, 1000)
page = request.GET.get('page', 1)
paginator = Paginator(numbers_list, 20)
try:
numbers = paginator.page(page)
except PageNotAnInteger:
numbers = paginator.page(1)
except EmptyPage:
numbers = paginator.page(paginator.num_pages)
return render(request, 'blog/home.html', {'numbers': numbers})
Here in this view, numbers_list
represents a list of 1000 numbers, which I’m breaking down into blocks of 20 numbers
per page. After paginating it, I return the numbers
object to the template, which is a block of 20 numbers.
I won’t get into details about Django pagination because I have an article talking exclusively about it, so if you want to learn more, check this post: How to Paginate with Django.
Now here is where the magic happens:
blog/home.html
{% extends 'base.html' %}
{% block content %}
<div class="infinite-container">
{% for number in numbers %}
<div class="infinite-item">{{ number }}</div>
{% endfor %}
</div>
{% if numbers.has_next %}
<a class="infinite-more-link" href="?page={{ numbers.next_page_number }}">More</a>
{% endif %}
<script>
var infinite = new Waypoint.Infinite({
element: $('.infinite-container')[0]
});
</script>
{% endblock %}
The element identified by the class .infinite-container
is the container where the plug-in will load more items. This
action will occur every time the element .infinite-more-link
appears in the screen. When that happens, it will
trigger an asynchronous request (AJAX) loading the content from the URL specified in the href
of the
.infinite-more-link
.
The page will keep loading new items until the .infinite-more-link
is no longer shown. It will happen when there is
no more items to be loaded. That is, the paginator reached the last page.
That’s why we have the conditional if numbers.has_next
.
Adding Loading State
We can improve the basic example by adding a loading state, while the page is grabbing more data.
blog/home.html
{% extends 'base.html' %}
{% block content %}
<div class="infinite-container">
{% for number in numbers %}
<div class="infinite-item">{{ number }}</div>
{% endfor %}
</div>
{% if numbers.has_next %}
<a class="infinite-more-link" href="?page={{ numbers.next_page_number }}">More</a>
{% endif %}
<div class="loading" style="display: none;">
Loading...
</div>
<script>
var infinite = new Waypoint.Infinite({
element: $('.infinite-container')[0],
onBeforePageLoad: function () {
$('.loading').show();
},
onAfterPageLoad: function ($items) {
$('.loading').hide();
}
});
</script>
{% endblock %}
Our loading block will be shown while the AJAX is running in the background:
Class-Based View Example With Models
The idea remain the same. Actually, the example will become even cleaner. Consider the following model:
models.py
from django.db import models
class Article(models.Model):
title = models.CharField(max_length=30)
body = models.TextField(max_length=2000)
date = models.DateTimeField()
author = models.CharField(max_length=30)
views.py
from django.views.generic.list import ListView
from .models import Article
class ArticlesView(ListView):
model = Article
paginate_by = 5
context_object_name = 'articles'
template_name = 'blog/articles.html'
blog/articles.html
{% extends 'base.html' %}
{% block content %}
<div class="infinite-container">
{% for article in articles %}
<div class="infinite-item">
<h3>{{ article.title }}</h3>
<p>
<small>{{ article.author }} / {{ article.date }}</small>
</p>
<p>{{ article.body|truncatechars:100 }}</p>
</div>
{% endfor %}
</div>
<div class="loading" style="display: none;">
Loading...
</div>
{% if page_obj.has_next %}
<a class="infinite-more-link" href="?page={{ articles.next_page_number }}">More</a>
{% endif %}
<script>
var infinite = new Waypoint.Infinite({
element: $('.infinite-container')[0],
onBeforePageLoad: function () {
$('.loading').show();
},
onAfterPageLoad: function ($items) {
$('.loading').hide();
}
});
</script>
{% endblock %}
Just a small difference here, that since I’m using a ListView
, the page object is available in the page_obj
object
in the template.
Conclusions
That’s pretty much it! If you want to explore the example, the code is available on GitHub: github.com/sibtc/simple-infinite-scroll