PageView

Subscribe to our YouTube Channel!
[Jul 12, 2021] New Video: How to Use Django Rest Framework Permissions (DRF Tutorial - Part 7)


How to Create Infinite Scroll With Django

How to Create Infinite Scroll With Django (Picture: https://www.pexels.com/photo/spiral-stairs-railing-steps-24498/)

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:

Infinite Scroll Loading


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