andytwoods

senior engineer psychologist
The views expressed in this site are my own and do not reflect the opinions or positions of my employer.

django htmx modal popup loveliness

Oct. 19, 2021

bootstrap5 django htmx
screenshot.gif

I thought to share a pattern I've been using recently, for providing modal popups, via HTMX. I'm using Bootstrap5 but this is straightforward to implement in whichever framework. As ever, HTMX has a wonderful example of how to do this in the frontend (as well any many other examples!). Here though I show how I'm doing things both front and backend, and specifically for Django. I'm doing things a little differently than the official example, cutting out some lines of code. You can find the repo (including sqlite db -- admin username and password is 'me') here: https://github.com/andytwoods/htmxDjangoModalPopup

Above is our project layout. I like to hold all my htmx files in a partials folder within my templates for a given app.

from django.db import models


class EightiesKidsTVShows(models.Model):
    name = models.CharField(max_length=64)
    decade = models.IntegerField(default=1980)
    blurb = models.TextField()
    url = models.URLField()
    image_url = models.URLField()

Setting the scene, we have a minimal model, containing information about kids shows from the 80s (the best era for kids shows -- my own kids would roll their eyes).

<div class="modal-body"></div>
...

{% for show in shows %}
    <tr>
        <td>
            <a class="btn btn-primary" hx-post="{{ request.path }}?id={{ show.id }}" hx-target=".modal-body">
                {{ show.name }}</a>
        </td>
        <td>{{ show.decade }}</td>
    </tr>
{% endfor %}

Within our template, we iterate over our shows, and use htmx to post an id back to our view. Purists may argue that I should not be attaching the id by means of a url parameter, and instead use the htmx 'include-vals' extension. I feel though that my approach achieves it's goal in maybe 20 characters, whist 'include-vals' is a lot more wordy (requiring additional html nodes to store the parameters). Intercooler, the predecessor to htmx, has the 'ic-include' tool right out of the box (you added your variables in json format ic-include="{'a':'scoobydoo', 'b': 'scrappy'}"), which I find myself wishing perhaps was not removed in htmx.

Note that when someone clicks on a show.name, htmx POSTS the server with the show.id, and the server returns rendered content. That content replaces the contents of the target 'modal-body'.

from django.shortcuts import render

from popup.models import EightiesKidsTVShows


def popup(request):

    if request.htmx:
        id = request.GET.get('id')
        context = {'show': EightiesKidsTVShows.objects.get(id=id)}
        return render(request, 'popup/partials/htmx_show_popup.html', context=context)

    context = {'shows': EightiesKidsTVShows.objects.all()}
    return render(request, 'popup/popup.html', context=context)

Our view detects if htmx is POSTing (using Adam Johnson's django-htmx). If so, we retrieve the desired information from the database and render content to display in the modal on the front end.

<div class="modal-body">
    <h1>{{ show.name }}</h1>
<img src="{{ show.image_url }}" alt="show image">
    <div>{{ show.blurb }}</div>
<script>
    var myModalEl = document.getElementById('myModal');
    var modal = bootstrap.Modal.getInstance(myModalEl);
    modal.toggle();
</script>
</div>

Our partial, our small template we render upon POST request, contains a script that runs when the content replaces 'modal-body'. Note that we wrap our content in a div with the 'modal-body' class so we can replace this content in the future.

Return to blog