How to implement a countdown timer in Django via HTMX

How to implement a countdown timer in Django via HTMX

June 28 2022

The goal of this article is to show how we can implement a countdown timer in Django via HTMX. You can see an example of the implementation here. I ran into this issue while working on my HTMX/Django course. I wanted to have a countdown timer for when the course would go live.

Something to keep in mind is that the standard implementation of a countdown timer is through Javascript. In a countdown timer, the user DOM is constantly updated with the difference between two dates. So, traditionally a Python implementation without client-side Javascript is impossible.

Thanks to HTMX, a library that allows us to access modern browser features with HTML. We can build a countdown timer without writing a single line of Javascript.

Keep in mind that since we are not going to have client-side Javascript, our code would make repeated server requests. This can be resource-intensive for larger applications. For my use case though, eliminating Javascript made sense.

Here is how we would build a countdown timer with a Javascript implementation.

 <script>

let countDownDate = new Date("Dec 1, 2021 00:00:01").getTime();

// Update the count down every 1 second
const x = setInterval(function() {

  // Get today's date and time
  let now = new Date().getTime();

  // Find the distance between now and the count down date
  const distance = countDownDate - now;

  // Time calculations for days, hours, minutes and seconds
  const days = Math.floor(distance / (1000 * 60 * 60 * 24));
  const hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
  const minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
  const seconds = Math.floor((distance % (1000 * 60)) / 1000);

 
  document.getElementById("days").innerHTML = days 
  document.getElementById("hours").innerHTML = hours 
  document.getElementById("minutes").innerHTML = minutes 
  document.getElementById("seconds").innerHTML = seconds 

  if (distance < 0) {
    clearInterval(x);
    document.getElementById("countdown").innerHTML = "We are live";
  }
}, 1000);

</script>

This tutorial assumes basic knowledge of Django and its development patterns. If you want to try coding along, I have built this minimal boilerplate as a base for quick development.

In a traditional Django application, there are two ways users can make a request to the server. They can make a "GET" request through <a> tag or a "POST" request when they submit a form. After the request is processed, the server returns a new HTML template.

HTMX gives us two superpowers. The first superpower is the response of the server doesn't have to replace the whole page. It can replace just the pieces of the DOM we specify.

The second superpower is that we can make a request to the server with anything in the DOM. Clicking a <div> tag, moving a mouse over something, or typing something in an input field can all trigger requests. We can even make a request every second, without the user doing anything (polling). This is exactly what we will do for our countdown timer.

Here is what we will need for our countdown timer:

  1. The HTMX library & a way to deal with the Django CSRF token for HTMX requests.
  2. A Django URL that we can poll every second for the time difference.
  3. A Django View that calculates the time difference between two dates and returns it.
  4. An HTML template with HTMX attributes that allow for that polling to happen,

The first two parts are easy. For HTMX, we just need a couple of lines in our base.html template.

<!-- HTMX CDN in base.html -->
<script src="https://unpkg.com/htmx.org@1.1.0"></script> 
    <!-- CSRF request in the headers -->
<script>
document.body.addEventListener('htmx:configRequest', (e) => {
  e.detail.headers['X-CSRFToken'] = '{{ csrf_token }}';
  })
</script>
For our path,  a typical URL pattern in Django will do. Something like this.

#urls.py
from django.urls import path
from . import views

urlpatterns = [
    path("time-difference", views.time_difference, name="time_difference"),
]

Here is the code for our Django view:

from datetime import datetime

from django.utils import timezone
from django.shortcuts import render


def time_difference(request):
    #go live date is 12/01/2021
    delta = datetime(2021, 12, 1, tzinfo=timezone.utc) - timezone.now()
    
    return render(
        request,
        "components/time_difference.html",
        {
            "days": delta.days,
            "seconds": delta.seconds % 60,
            "minutes": (delta.seconds // 60) % 60,
            "hours": delta.seconds // 3600,
        },
    )

In that view, we are using the Python datetime module to calculate the time difference between two days. We should note that when we use the Django "timezone" module, our times are "aware". That means we are taking timezones into consideration. As such, we should also include the timezone info with our Python datetime object.

The second issue that we have to deal with is that timedelta objects attributes are in days, seconds, and microseconds only. Every 1,000,000 microseconds overflows to a second, and every 86,400 seconds (60 seconds * 60 minutes * 24 hours) overflows to a day.

So, without manipulation, our seconds are a number between 0 and 86,400 and we don't have minutes or hours. With some math though, we can easily calculate the hours, minutes, and seconds left until our target date. Here is a replit with the step-by-step math.

Lastly, this view returns an HTML component called time_difference.html. It is a not full HTML template. Rather, only the part that contains the countdown section. This is one of the differences between working in Django by itself and using HTMX. Because we update the parts of the DOM we want, we use a component-like structure. Where each HTML page is made up of several HTMX components.

Here is the code for our component.

<div
id= "time" 
class="d-flex justify-content-between"
hx-get="{% url 'time_difference' %}"
hx-trigger="every 1s"
hx-select="#time" 
hx-swap="outerHTML"
>    
            <div>
              <h5>Days</h5>
              <p> {{days}} </p>
            </div>
       
            <div>
              <h5>Hours</h5>
              <p> {{hours}} </p>
            </div>
          
            <div>
              <h5>Minutes</h5>
              <p> {{minutes}} </p>
            </div>
        
            <div>
              <h5>Seconds</h5>
              <p> {{seconds}} </p>
            </div>
</div>

All HTMX attributes start with "hx". Here is what our code does,

  • hx-get: the request will be a GET request to a URL named "time-difference"
  • hx-trigger: the request will happen every second
  • hx-select: the response will affect the DOM element with the id "time"
  • hx-swap: the response will replace the entire DOM element named "time"

So via HTMX, we are requesting a new DOM element every second from the a Django view. On the server, our Django view calculates the difference between the two days and returns an HTML component with the new values. Neat, right?

Now, we can insert that component anywhere we like in our normal HTML templates or even in all our templates via the normal Django template magic.

Here is how we would include it in all our templates,

#base.html
<div class="content-wrapper" id="content">
     {% if messages %} {% include 'components/messages.html' %} {% endif %}
      {% include 'components/navbar.html' %}
      {% block content %} {% endblock content %}
      {% include 'components/time_difference.html' %}
      {% include 'components/footer.html' %}
    </div>

We can even make its rendering conditional. For example, we can write something like this,

{% if timer_is_needed %} {% include 'components/time_difference.html' %} {% endif %}

Combining Django's template tag "include" and its conditional rendering tag is powerful. Adding HTMX to the mix can open up endless new ways to develop with Django as a full-stack modern solution. Who needs Javascript when you can do that?

Using this combination, we can build web applications with the elegance of Python, the batteries that Django gives us, and the friendly user experience of single-page applications.

That's why I am building an interactive course using HTMX/Django. The goal would be to build eight modern Single-Page applications without any Javascript. If you would like to be notified of its progress or when I publish smaller posts like this one, please sign-up here.

I am happy to answer any specific questions about this article or the course. The best way is on Twitter.

Get notified about new HTMX/Django tutorials
You will only get an email whenever a post or a course is published!

Copyright © HTMX-Django 2022.

Built by Jonathan Adly. Contact for opportunities.