andytwoods

engineer psychologist

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