Jekyll2023-11-06T07:38:12+00:00https://blog.benoitblanchon.fr/feed.xmlGood Code SmellTake a deep breath and enjoy the flavor of a well written program :-) Welcome to my blog where I relate my personal experience in programming and electronics.Using the Django messages framework with HTMX’s OOB swaps2023-03-06T00:00:00+00:002023-03-06T00:00:00+00:00https://blog.benoitblanchon.fr/django-htmx-messages-framework-oob<p>In <a href="/django-htmx-messages-framework/">my previous article</a>, I showed you how to integrate the <a href="https://docs.djangoproject.com/en/4.1/ref/contrib/messages/">Django messages framework</a> with <a href="https://htmx.org/">HTMX</a> thanks to the <a href="https://htmx.org/headers/hx-trigger/"><code class="language-plaintext highlighter-rouge">HX-Trigger</code></a> header and some JavaScript code.
Today, we’ll see an alternative (and probably better) way to do this using <a href="https://htmx.org/docs/#oob_swaps">out-of-band swaps</a>.</p>
<p><img src="/images/2023/03/django-messages-framework-htmx-oob.gif" alt="Django messages framework with HTMX's OOB swaps" /></p>
<p>I won’t do a tutorial because it would be too similar to my previous article; instead, I’ll focus on the key elements of this pattern. You can find the corresponding source code in the <a href="https://github.com/bblanchon/django-htmx-messages-framework/tree/oob"><code class="language-plaintext highlighter-rouge">oob</code> branch</a> of <a href="https://github.com/bblanchon/django-htmx-messages-framework">the dedicated GitHub repository</a>.</p>
<p><strong>You can find an <a href="https://youtu.be/dc4fhli61bQ">extended version</a> of this article on YouTube.</strong></p>
<h2 id="overview">Overview</h2>
<p>Here is how the previous technique works:</p>
<ol>
<li>It all starts with a button with the <a href="https://htmx.org/attributes/hx-get/"><code class="language-plaintext highlighter-rouge">hx-get</code></a> attribute.</li>
<li>When the user clicks on this button, HTMX performs a <code class="language-plaintext highlighter-rouge">GET</code> request which triggers the execution of a Django view.</li>
<li>The view creates an HTTP response and publishes one or more messages.</li>
<li>The response is intercepted by our <a href="https://docs.djangoproject.com/en/4.1/topics/http/middleware/">middleware</a>.</li>
<li>The middleware pulls the messages and stores them in the <code class="language-plaintext highlighter-rouge">HX-Trigger</code> header of the response.</li>
<li>The response goes over the wire and is received by HTMX.</li>
<li>HTMX updates the page with the response body.</li>
<li>When it sees the <code class="language-plaintext highlighter-rouge">HX-Trigger</code> header, it calls a JavaScript function.</li>
<li>This function creates the <a href="https://getbootstrap.com/docs/5.3/components/toasts/">toasts</a> and adds them to the <a href="https://developer.mozilla.org/en-US/docs/Glossary/DOM">DOM</a>.</li>
<li>Finally, the browser displays the toasts along with the updated page.</li>
</ol>
<p><img src="/images/2023/03/django-messages-htmx-version-1.png" alt="Django messages framework with HTMX - version 1 with HX-Trigger" /></p>
<p>Now, let’s see how the new version works.</p>
<ol>
<li>Everything before the middleware stays the same.</li>
<li>The middleware still pulls the messages, but instead of storing them in the <code class="language-plaintext highlighter-rouge">HX-Trigger</code> header, it appends the toast divs to the response’s body.</li>
<li>The response goes over the wire and is received by HTMX.</li>
<li>HTMX updates the page and injects the toast divs into the DOM.</li>
<li>Finally, the browser displays the toasts along with the updated page.</li>
</ol>
<p><img src="/images/2023/03/django-messages-htmx-version-2.png" alt="Django messages framework with HTMX - version 2 with OOB swaps" /></p>
<p>As you see, we don’t need much JavaScript with this new version because we piggybacked the toasts to the page. This is what HTMX calls out-of-band swaps. They are “out of band” because they are not part of the primary swap; instead, they target specific elements in the DOM.
This technique is more in line with HTMX’s philosophy of rendering the HTML in the backend.</p>
<p><img src="/images/2023/03/whats-an-out-of-band-swap.png" alt="What is an out-of-band swap?" /></p>
<p>In this graphic, I tried to represent the primary swap and the OOB swap. The primary swap targets the element designated by the <a href="https://htmx.org/attributes/hx-target/"><code class="language-plaintext highlighter-rouge">hx-target</code></a> (or the element that triggered the request if <code class="language-plaintext highlighter-rouge">hx-target</code> is not set).
The OOB swap includes all the elements with the <a href="https://htmx.org/attributes/hx-swap-oob/"><code class="language-plaintext highlighter-rouge">hx-swap-oob</code></a> attribute and targets the elements with the same ids.</p>
<p>In the following sections, we’ll see each piece of the puzzle.</p>
<h2 id="the-page-template">The page template</h2>
<p>The element that triggers the HTMX request must have the <a href="https://htmx.org/attributes/hx-get/"><code class="language-plaintext highlighter-rouge">hx-get</code></a>, <a href="https://htmx.org/attributes/hx-post/"><code class="language-plaintext highlighter-rouge">hx-post</code></a>, <a href="https://htmx.org/attributes/hx-put/"><code class="language-plaintext highlighter-rouge">hx-put</code></a>, <a href="https://htmx.org/attributes/hx-patch/"><code class="language-plaintext highlighter-rouge">hx-patch</code></a>, or <a href="https://htmx.org/attributes/hx-delete/"><code class="language-plaintext highlighter-rouge">hx-delete</code></a> attribute.
By default, the trigger element is also the target of the HTMX swap, but we can override this behavior by adding the <a href="https://htmx.org/attributes/hx-target/"><code class="language-plaintext highlighter-rouge">hx-target</code></a> attribute.</p>
<p>In this example, I used a <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button">button</a> as the trigger and a <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/p">paragraph</a> as the target.</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><button</span> <span class="na">hx-get=</span><span class="s">"/message"</span> <span class="na">hx-target=</span><span class="s">"#main"</span><span class="nt">></span>Emit message<span class="nt"></button></span>
<span class="nt"><p</span> <span class="na">id=</span><span class="s">"main"</span><span class="nt">></span>Lorem ipsum<span class="nt"></p></span>
</code></pre></div></div>
<h2 id="the-django-view">The Django view</h2>
<p>The view pushes a message to the messages framework and returns the content for the paragraph:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">django.shortcuts</span> <span class="kn">import</span> <span class="n">render</span>
<span class="kn">from</span> <span class="nn">django.contrib</span> <span class="kn">import</span> <span class="n">messages</span>
<span class="k">def</span> <span class="nf">message</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
<span class="n">messages</span><span class="p">.</span><span class="n">info</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="s">"Hello World!"</span><span class="p">)</span>
<span class="k">return</span> <span class="n">render</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="s">"lorem.html"</span><span class="p">)</span>
</code></pre></div></div>
<p>Don’t worry about the <code class="language-plaintext highlighter-rouge">lorem.html</code> template; it is just random text.</p>
<h2 id="the-middleware">The Middleware</h2>
<p>The middleware intercepts the responses returned by all Django views, so we first check that the response corresponds to an HTMX request.
We must also check the response status to ensure it’s not a <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections">redirection</a> because HTMX could not intercept the response.
Similarly, we must ignore “soft” redirections triggered by <code class="language-plaintext highlighter-rouge">HX-Redirect</code> because HTMX ignores swaps in this case.</p>
<p>Once these three checks pass, we append the toast to the response body.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">django.contrib.messages</span> <span class="kn">import</span> <span class="n">get_messages</span>
<span class="kn">from</span> <span class="nn">django.template.loader</span> <span class="kn">import</span> <span class="n">render_to_string</span>
<span class="kn">from</span> <span class="nn">django.utils.deprecation</span> <span class="kn">import</span> <span class="n">MiddlewareMixin</span>
<span class="k">class</span> <span class="nc">HtmxMessageMiddleware</span><span class="p">(</span><span class="n">MiddlewareMixin</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">process_response</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">request</span><span class="p">,</span> <span class="n">response</span><span class="p">):</span>
<span class="k">if</span> <span class="p">(</span>
<span class="s">"HX-Request"</span> <span class="ow">in</span> <span class="n">request</span><span class="p">.</span><span class="n">headers</span>
<span class="ow">and</span>
<span class="ow">not</span> <span class="mi">300</span> <span class="o"><=</span> <span class="n">response</span><span class="p">.</span><span class="n">status_code</span> <span class="o"><</span> <span class="mi">400</span>
<span class="ow">and</span>
<span class="s">"HX-Redirect"</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">response</span><span class="p">.</span><span class="n">headers</span>
<span class="p">):</span>
<span class="n">response</span><span class="p">.</span><span class="n">write</span><span class="p">(</span>
<span class="n">render_to_string</span><span class="p">(</span>
<span class="s">"toasts.html"</span><span class="p">,</span>
<span class="p">{</span><span class="s">"messages"</span><span class="p">:</span> <span class="n">get_messages</span><span class="p">(</span><span class="n">request</span><span class="p">)},</span>
<span class="p">)</span>
<span class="p">)</span>
<span class="k">return</span> <span class="n">response</span>
</code></pre></div></div>
<p>As you can see, we render the HTML for the toasts using a regular Django template to which we provide the list of messages.</p>
<h2 id="the-toasts-template">The toasts template</h2>
<p>Here is the toast template:</p>
<div class="language-django highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="nt"><div</span> <span class="na">id=</span><span class="s">"toasts"</span> <span class="na">hx-swap-oob=</span><span class="s">"afterbegin"</span><span class="nt">></span>
<span class="cp">{%</span> <span class="k">for</span> <span class="nv">message</span> <span class="ow">in</span> <span class="nv">messages</span> <span class="cp">%}</span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"toast </span><span class="cp">{{</span> <span class="nv">message.tags</span> <span class="cp">}}</span><span class="s">"</span><span class="nt">></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"toast-body"</span><span class="nt">></span><span class="cp">{{</span> <span class="nv">message.message</span> <span class="cp">}}</span><span class="nt"></div></span>
<span class="nt"></div></span>
<span class="cp">{%</span> <span class="k">endfor</span> <span class="cp">%}</span>
<span class="nt"></div></span>
</code></pre></div></div>
<p>As you can see, it’s the same code you’d use to render <a href="https://getbootstrap.com/docs/5.3/components/toasts/">Bootstrap toasts</a> in a classic Django app except for the <a href="https://htmx.org/attributes/hx-swap-oob/"><code class="language-plaintext highlighter-rouge">hx-swap-oob</code></a> attribute on the container.
This attribute tells HTMX to exclude this element from the primary swap and instead swap it with the element with the id <code class="language-plaintext highlighter-rouge">toasts</code>.
The value <code class="language-plaintext highlighter-rouge">afterbegin</code> tells HTMX to insert this div’s content after the existing div’s opening tag. In other words, HTMX will insert the new toasts before the existing ones. This way, the new toasts will appear at the top. You could use the value <code class="language-plaintext highlighter-rouge">beforeend</code> to put the new toasts at the bottom.</p>
<h2 id="the-javascript-code">The JavaScript code</h2>
<p>Django renders the toasts on the server side, so one might think that we don’t need any JavaScript. Unfortunately, that’s not so simple.</p>
<p><a href="https://getbootstrap.com/docs/5.3/components/toasts/">Bootstrap toasts</a> are initially hidden, and they will only show if we initialize them with JavaScript. For each toast, we must instantiate the <code class="language-plaintext highlighter-rouge">bootstrap.Toast</code> class and call its <code class="language-plaintext highlighter-rouge">show()</code> method.
We must do this initialization step on the initial page load and for subsequent partial loads.</p>
<p>Fortunately, HTMX provides a helper function to do this: we can call <a href="https://htmx.org/api/#onLoad"><code class="language-plaintext highlighter-rouge">htmx.onLoad()</code></a> to register a callback function that will be called on the initial page load and for any later swap (including OOB swaps).</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">htmx</span><span class="p">.</span><span class="nx">onLoad</span><span class="p">(()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">htmx</span><span class="p">.</span><span class="nx">findAll</span><span class="p">(</span><span class="dl">"</span><span class="s2">.toast</span><span class="dl">"</span><span class="p">).</span><span class="nx">forEach</span><span class="p">((</span><span class="nx">element</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">toast</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">bootstrap</span><span class="p">.</span><span class="nx">Toast</span><span class="p">(</span><span class="nx">element</span><span class="p">)</span>
<span class="nx">toast</span><span class="p">.</span><span class="nx">show</span><span class="p">()</span>
<span class="p">})</span>
<span class="p">})</span>
</code></pre></div></div>
<p>That’s a first step, but this code has a problem: not only does it show the newly added toasts, but it also resurrects old toasts!</p>
<p>HTMX passes the newly loaded element to the callback, so theoretically, we should be able to target only the newly loaded toasts.
In practice, however, I could not find an elegant way to do it.
Instead, I call <code class="language-plaintext highlighter-rouge">bootstrap.Toast.getInstance()</code> to get the instance of the <code class="language-plaintext highlighter-rouge">bootstrap.Toast</code> class for the toasts that are already initialized. If no instance exists, I create a new one and call <code class="language-plaintext highlighter-rouge">show()</code>.
As a bonus, I also delete the old hidden toasts.</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">htmx</span><span class="p">.</span><span class="nx">onLoad</span><span class="p">(()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">htmx</span><span class="p">.</span><span class="nx">findAll</span><span class="p">(</span><span class="dl">"</span><span class="s2">.toast</span><span class="dl">"</span><span class="p">).</span><span class="nx">forEach</span><span class="p">((</span><span class="nx">element</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">let</span> <span class="nx">toast</span> <span class="o">=</span> <span class="nx">bootstrap</span><span class="p">.</span><span class="nx">Toast</span><span class="p">.</span><span class="nx">getInstance</span><span class="p">(</span><span class="nx">element</span><span class="p">)</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">toast</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">toast</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">bootstrap</span><span class="p">.</span><span class="nx">Toast</span><span class="p">(</span><span class="nx">element</span><span class="p">)</span>
<span class="nx">toast</span><span class="p">.</span><span class="nx">show</span><span class="p">()</span>
<span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">toast</span><span class="p">.</span><span class="nx">isShown</span><span class="p">())</span> <span class="p">{</span>
<span class="nx">toast</span><span class="p">.</span><span class="nx">dispose</span><span class="p">()</span>
<span class="nx">element</span><span class="p">.</span><span class="nx">remove</span><span class="p">()</span>
<span class="p">}</span>
<span class="p">})</span>
<span class="p">})</span>
</code></pre></div></div>
<h2 id="compatibility-with-the-modal-form-pattern">Compatibility with the modal form pattern</h2>
<p>Three articles ago, I presented a pattern to put <a href="/django-htmx-modal-form/">Django forms in modal dialogs</a> using HTMX.</p>
<p>This pattern uses an empty response (with the status code 204, but 200 works too) as a trigger to hide the dialog. A JavaScript hook listens to the <a href="https://htmx.org/events/#htmx:beforeSwap"><code class="language-plaintext highlighter-rouge">htmx:beforeSwap</code></a> event. The event handler checks if the target is the modal dialog and if the response is empty, in which case, it disables the swap (so the modal remains on screen during the fade-out animation) and hides the modal.</p>
<p>Unfortunately, this “empty response” trick doesn’t work with OOB swaps because the response is not empty anymore: it contains the toasts.</p>
<p>To work around this issue, we need another way to disable the primary swap and hide the modal. We can disable the swap by adding <code class="language-plaintext highlighter-rouge">HX-Reswap: none</code> in the response headers, and we can use <code class="language-plaintext highlighter-rouge">HX-Trigger</code> to raise a “hide modal” event. For example, the “movie edit” view now contains a statement like this:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">return</span> <span class="n">HttpResponse</span><span class="p">(</span>
<span class="n">headers</span><span class="o">=</span><span class="p">{</span>
<span class="s">'HX-Trigger'</span><span class="p">:</span> <span class="s">'{"movieListChanged":true,"hideModal":true}'</span><span class="p">,</span>
<span class="s">'HX-Reswap'</span><span class="p">:</span> <span class="s">"none"</span><span class="p">,</span>
<span class="p">}</span>
<span class="p">)</span>
</code></pre></div></div>
<p>It works but is quite inelegant. Hopefully, I’ll find a better solution someday. Until then, you can check the complete implementation in the branch <a href="https://github.com/bblanchon/django-htmx-modal-form/tree/messages-framework-oob"><code class="language-plaintext highlighter-rouge">messages-framework-oob</code></a> in <a href="https://github.com/bblanchon/django-htmx-modal-form">the dedicated repository</a>.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Honestly, I still haven’t fully made up my mind: is this technique superior to the one using <code class="language-plaintext highlighter-rouge">HX-Trigger</code>?</p>
<p>Sure, it seems better since we removed the clumsy JavaScript and simplified the template. However, we carelessly appended content to the response body, and I wonder if this is always possible.
Moreover, the issue with empty responses bothers me because I really like this trick.
For these reasons, I still use the old technique on my projects and don’t plan to upgrade them yet.</p>
<p>And you? What do you think of this technique?
Please let me know in the comments.</p>In my previous article, I showed you how to integrate the Django messages framework with HTMX thanks to the HX-Trigger header and some JavaScript code. Today, we’ll see an alternative (and probably better) way to do this using out-of-band swaps.Using the Django messages framework with HTMX2022-09-17T00:00:00+00:002022-09-17T00:00:00+00:00https://blog.benoitblanchon.fr/django-htmx-messages-framework<p>This article explains how to create a middleware that extracts the messages from <a href="https://docs.djangoproject.com/en/4.1/ref/contrib/message/">Django’s messages framework</a> to make them available to HTMX. It’s a condensed version of my 30-minute video: <a href="https://youtu.be/I5_g7XYyemQ">Django+HTMX: integration with the messages framework</a> and a follow-up to <a href="/django-htmx-toasts/">my previous article on Django+HTMX</a>, but you can read it independently.</p>
<p><img src="/images/2022/09/django-htmx-messages-framework.gif" alt="Using the Django messages framework with HTMX" /></p>
<p>This article is designed as a tutorial that shows how to recreate the sample project. If this format doesn’t suit you, please watch <a href="https://youtu.be/I5_g7XYyemQ">the video</a> as it takes a radically different angle.</p>
<p>I’m not too fond of long articles, so I’ll go straight to the point, even if I have to simplify some aspects. After reading this article, please look at the complete project in the <a href="https://github.com/bblanchon/django-htmx-messages-framework/">GitHub repository</a>.</p>
<h2 id="overview">Overview</h2>
<p>Starting from scratch, we’ll create a minimalistic Django application with only a home page. This page will contain one button. Clicking on this button will invoke a Django view that adds a message into the messages framework. A custom middleware will extract the messages to put them in the <code class="language-plaintext highlighter-rouge">HX-Trigger</code> header. HTMX recognizes this header and raises a JavaScript event. A custom JavaScript file will listen to this event and create a <a href="https://getbootstrap.com/docs/5.2/components/toasts/">Bootstrap 5 toast</a> for each message.</p>
<p>You can follow the steps of this article in the commits of the <a href="https://github.com/bblanchon/django-htmx-messages-framework/tree/blog"><code class="language-plaintext highlighter-rouge">blog</code> branch of the Git repository</a>.</p>
<h2 id="step-1-create-the-project">Step 1: Create the project</h2>
<p>Let’s start an empty Django project:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>django-admin startproject demoproject <span class="nb">.</span>
<span class="gp">$</span><span class="w"> </span>python manage.py startapp demoapp
</code></pre></div></div>
<p>Edit <code class="language-plaintext highlighter-rouge">demoproject/settings.py</code> to add <code class="language-plaintext highlighter-rouge">demoapp</code> to the <code class="language-plaintext highlighter-rouge">INSTALLED_APPS</code>:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">INSTALLED_APPS</span> <span class="o">=</span> <span class="p">[</span>
<span class="p">...</span>
<span class="s">'demoapp'</span><span class="p">,</span>
<span class="p">]</span>
</code></pre></div></div>
<h2 id="step-2-create-the-home-view">Step 2: Create the home view</h2>
<p>Create <code class="language-plaintext highlighter-rouge">demoapp/templates/home.html</code> from <a href="https://getbootstrap.com/docs/5.2/getting-started/introduction">Bootstrap’s Quick Start example</a>:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><!doctype html></span>
<span class="nt"><html</span> <span class="na">lang=</span><span class="s">"en"</span><span class="nt">></span>
<span class="nt"><head></span>
<span class="nt"><meta</span> <span class="na">charset=</span><span class="s">"utf-8"</span><span class="nt">></span>
<span class="nt"><meta</span> <span class="na">name=</span><span class="s">"viewport"</span> <span class="na">content=</span><span class="s">"width=device-width, initial-scale=1"</span><span class="nt">></span>
<span class="nt"><title></span>Bootstrap demo<span class="nt"></title></span>
<span class="nt"><link</span> <span class="na">href=</span><span class="s">"https://cdn.jsdelivr.net/npm/bootstrap@5.2.1/dist/css/bootstrap.min.css"</span> <span class="na">rel=</span><span class="s">"stylesheet"</span> <span class="na">integrity=</span><span class="s">"sha384-iYQeCzEYFbKjA/T2uDLTpkwGzCiq6soy8tYaI1GyVh/UjpbCx/TYkiZhlZB6+fzT"</span> <span class="na">crossorigin=</span><span class="s">"anonymous"</span><span class="nt">></span>
<span class="nt"></head></span>
<span class="nt"><body></span>
<span class="nt"><h1></span>Hello, world!<span class="nt"></h1></span>
<span class="nt"><script </span><span class="na">src=</span><span class="s">"https://cdn.jsdelivr.net/npm/bootstrap@5.2.1/dist/js/bootstrap.bundle.min.js"</span> <span class="na">integrity=</span><span class="s">"sha384-u1OknCvxWvY5kfmNBILK2hRnQC3Pr17a+RTT6rIHI7NnikvbZlHgTPOOmMi466C8"</span> <span class="na">crossorigin=</span><span class="s">"anonymous"</span><span class="nt">></script></span>
<span class="nt"></body></span>
<span class="nt"></html></span>
</code></pre></div></div>
<p>In <code class="language-plaintext highlighter-rouge">demoapp/views.py</code>, create a view that renders this template:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">home</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
<span class="k">return</span> <span class="n">render</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="s">"home.html"</span><span class="p">)</span>
</code></pre></div></div>
<p>In <code class="language-plaintext highlighter-rouge">demoproject/urls.py</code>, attach this view to the root path:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">demoapp</span> <span class="kn">import</span> <span class="n">views</span>
<span class="n">urlpatterns</span> <span class="o">=</span> <span class="p">[</span>
<span class="n">path</span><span class="p">(</span><span class="s">""</span><span class="p">,</span> <span class="n">views</span><span class="p">.</span><span class="n">home</span><span class="p">),</span>
<span class="p">]</span>
</code></pre></div></div>
<p>Start the development server to make sure everything is correct so far:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>python manage.py runserver
</code></pre></div></div>
<p>You can ignore the warning about the unapplied migrations since we won’t use models in this article.</p>
<p>Open <code class="language-plaintext highlighter-rouge">http://127.0.0.1:8000/</code> on your browser. You should see a blank page with just “Hello, world!”.</p>
<h2 id="step-3-install-htmx">Step 3: Install HTMX</h2>
<p>Copy the <code class="language-plaintext highlighter-rouge"><script></code> tag from <a href="https://htmx.org/">HTMX’s Quick Start</a> and paste it before <code class="language-plaintext highlighter-rouge"></body></code> in <code class="language-plaintext highlighter-rouge">home.html</code></p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><html</span> <span class="na">lang=</span><span class="s">"en"</span><span class="nt">></span>
<span class="nt"><body></span>
...
<span class="nt"><script </span><span class="na">src=</span><span class="s">"https://unpkg.com/htmx.org@1.8.0"</span><span class="nt">></script></span>
<span class="nt"></body></span>
<span class="nt"></html></span>
</code></pre></div></div>
<h2 id="step-4-add-the-button">Step 4: Add the button</h2>
<p>Add <code class="language-plaintext highlighter-rouge">hx-get="/message"</code> so that HTMX will perform a <code class="language-plaintext highlighter-rouge">GET</code> request every time someone clicks on this button:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><button</span> <span class="na">class=</span><span class="s">"btn btn-primary"</span> <span class="na">hx-get=</span><span class="s">"/message"</span><span class="nt">></span>Emit message<span class="nt"></button></span>
</code></pre></div></div>
<p>In an actual project, you should use the <a href="https://docs.djangoproject.com/en/4.1/ref/templates/builtins/#url"><code class="language-plaintext highlighter-rouge">url</code> template tag</a> instead of hard-coding the URL.</p>
<p>Open your browser’s developer console. Click on the button, and you should see an error like this:</p>
<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>GET http://127.0.0.1:8000/message 404 (Not Found)
</code></pre></div></div>
<p>Indeed, we didn’t create the <code class="language-plaintext highlighter-rouge">/message</code> view, so Django returns a 404.</p>
<h2 id="step-5-create-the-message-view">Step 5: Create the <code class="language-plaintext highlighter-rouge">/message</code> view</h2>
<p>Import Django’s messages framework and <code class="language-plaintext highlighter-rouge">HttpResponse</code> in <code class="language-plaintext highlighter-rouge">views.py</code>:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">django.contrib</span> <span class="kn">import</span> <span class="n">messages</span>
<span class="kn">from</span> <span class="nn">django.http.response</span> <span class="kn">import</span> <span class="n">HttpResponse</span>
</code></pre></div></div>
<p>Add the following function in <code class="language-plaintext highlighter-rouge">views.py</code>:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">message</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
<span class="n">messages</span><span class="p">.</span><span class="n">success</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="s">"It works!"</span><span class="p">)</span>
<span class="k">return</span> <span class="n">HttpResponse</span><span class="p">(</span><span class="n">status</span><span class="o">=</span><span class="mi">204</span><span class="p">)</span>
</code></pre></div></div>
<p>As you can see, this view adds a “success” message.
Then, it returns an empty response, but you could return a regular response.</p>
<p>Add the following route to <code class="language-plaintext highlighter-rouge">urls.py</code>:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">urlpatterns</span> <span class="o">=</span> <span class="p">[</span>
<span class="p">...</span>
<span class="n">path</span><span class="p">(</span><span class="s">"message"</span><span class="p">,</span> <span class="n">views</span><span class="p">.</span><span class="n">message</span><span class="p">),</span>
<span class="p">]</span>
</code></pre></div></div>
<p>Refresh the page on your browser and click on the button.
The Console tab should not show any errors, and the Network tab should show the request returning <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204">status 204</a></p>
<h2 id="step-6-create-the-middleware">Step 6: Create the middleware</h2>
<p>Create <code class="language-plaintext highlighter-rouge">demoproject/middleware.py</code> with the following content:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">json</span>
<span class="kn">from</span> <span class="nn">django.utils.deprecation</span> <span class="kn">import</span> <span class="n">MiddlewareMixin</span>
<span class="kn">from</span> <span class="nn">django.contrib.messages</span> <span class="kn">import</span> <span class="n">get_messages</span>
<span class="k">class</span> <span class="nc">HtmxMessageMiddleware</span><span class="p">(</span><span class="n">MiddlewareMixin</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">process_response</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">request</span><span class="p">,</span> <span class="n">response</span><span class="p">):</span>
<span class="k">if</span> <span class="s">"HX-Request"</span> <span class="ow">in</span> <span class="n">request</span><span class="p">.</span><span class="n">headers</span><span class="p">:</span>
<span class="n">response</span><span class="p">.</span><span class="n">headers</span><span class="p">[</span><span class="s">"HX-Trigger"</span><span class="p">]</span> <span class="o">=</span> <span class="n">json</span><span class="p">.</span><span class="n">dumps</span><span class="p">({</span>
<span class="s">"messages"</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span><span class="s">"message"</span><span class="p">:</span> <span class="n">message</span><span class="p">.</span><span class="n">message</span><span class="p">,</span> <span class="s">"tags"</span><span class="p">:</span> <span class="n">message</span><span class="p">.</span><span class="n">tags</span><span class="p">}</span>
<span class="k">for</span> <span class="n">message</span> <span class="ow">in</span> <span class="n">get_messages</span><span class="p">(</span><span class="n">request</span><span class="p">)</span>
<span class="p">]</span>
<span class="p">})</span>
<span class="k">return</span> <span class="n">response</span>
</code></pre></div></div>
<p>⚠️ This is a simplified version. Use the <a href="https://github.com/bblanchon/django-htmx-messages-framework/blob/bootstrap5/htmx_messages/middleware.py">full version</a> in actual projects.</p>
<p>For each request made with HTMX (detected with the <a href="https://htmx.org/reference/#request_headers"><code class="language-plaintext highlighter-rouge">HX-Request</code> header</a>), this middleware puts all the messages into the <a href="https://htmx.org/headers/hx-trigger/"><code class="language-plaintext highlighter-rouge">HX-Trigger</code> header</a> of the response. In our case, this header will contain:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="nl">"messages"</span><span class="p">:[{</span><span class="nl">"message"</span><span class="p">:</span><span class="s2">"It works!"</span><span class="p">,</span><span class="nl">"tags"</span><span class="p">:</span><span class="s2">"success"</span><span class="p">}]}</span><span class="w">
</span></code></pre></div></div>
<p>When it sees this header in a response, HTMX raises a JavaScript event for each of the keys in the JSON object. In our case, it will raise a <a href="https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent"><code class="language-plaintext highlighter-rouge">CustomEvent</code></a> named <code class="language-plaintext highlighter-rouge">"messages"</code> carrying the following payload:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[{</span><span class="nl">"message"</span><span class="p">:</span><span class="s2">"It works!"</span><span class="p">,</span><span class="nl">"tags"</span><span class="p">:</span><span class="s2">"success"</span><span class="p">}]</span><span class="w">
</span></code></pre></div></div>
<p>We’ll come back to this event in a moment, but first, we must enable this middleware.</p>
<p>Open <code class="language-plaintext highlighter-rouge">settings.py</code> and append our middleware’s path to the <a href="https://docs.djangoproject.com/en/4.1/topics/http/middleware/#activating-middleware"><code class="language-plaintext highlighter-rouge">MIDDLEWARE</code> option</a>:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">MIDDLEWARE</span> <span class="o">=</span> <span class="p">[</span>
<span class="p">...</span>
<span class="s">'demoproject.middleware.HtmxMessageMiddleware'</span><span class="p">,</span>
<span class="p">]</span>
</code></pre></div></div>
<p>Go back to your browser and click the “Emit message” button. Look a the last request on the Network tab, and you should see a response like this:</p>
<div class="language-http highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">HTTP</span><span class="o">/</span><span class="m">1.1</span> <span class="m">204</span> <span class="ne">No Content</span>
<span class="na">Date</span><span class="p">:</span> <span class="s">Mon, 19 Sep 2022 18:29:02 GMT</span>
<span class="na">Server</span><span class="p">:</span> <span class="s">WSGIServer/0.2 CPython/3.9.13</span>
<span class="na">Content-Type</span><span class="p">:</span> <span class="s">text/html; charset=utf-8</span>
<span class="na">HX-Trigger</span><span class="p">:</span> <span class="s">{"messages": [{"message": "It works!", "tags": "success"}]}</span>
<span class="na">Content-Length</span><span class="p">:</span> <span class="s">0</span>
<span class="na">X-Content-Type-Options</span><span class="p">:</span> <span class="s">nosniff</span>
<span class="na">Referrer-Policy</span><span class="p">:</span> <span class="s">same-origin</span>
<span class="na">Cross-Origin-Opener-Policy</span><span class="p">:</span> <span class="s">same-origin</span>
</code></pre></div></div>
<p>Notice the <code class="language-plaintext highlighter-rouge">HX-Trigger</code> header containing our message.</p>
<h2 id="step-7-receive-the-messages-in-javascript">Step 7: Receive the messages in JavaScript</h2>
<p>Create <code class="language-plaintext highlighter-rouge">demoapp/static/toasts.js</code> with the following content:</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">htmx</span><span class="p">.</span><span class="n">on</span><span class="p">(</span><span class="s">"messages"</span><span class="p">,</span> <span class="p">(</span><span class="n">event</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="n">console</span><span class="p">.</span><span class="n">log</span><span class="p">(</span><span class="n">event</span><span class="p">.</span><span class="n">detail</span><span class="p">.</span><span class="n">value</span><span class="p">)</span>
<span class="p">})</span>
</code></pre></div></div>
<p><a href="https://htmx.org/api/#on"><code class="language-plaintext highlighter-rouge">htmx.on()</code></a> adds a listener for the specified event (you can see it as a shorthand for <code class="language-plaintext highlighter-rouge">document.addEventListener()</code>). In our case, we listen to the “messages” event to match the name we used in the <code class="language-plaintext highlighter-rouge">HX-Trigger</code> header. For now, we use a simple event handler that only prints the event’s payload.</p>
<p>Add the required <code class="language-plaintext highlighter-rouge"><script></code> before <code class="language-plaintext highlighter-rouge"></body></code>, like so:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{% load static %}
<span class="nt"><html</span> <span class="na">lang=</span><span class="s">"en"</span><span class="nt">></span>
<span class="nt"><body></span>
...
<span class="nt"><script </span><span class="na">src=</span><span class="s">"{% static 'toasts.js' %}"</span><span class="nt">></script></span>
<span class="nt"></body></span>
<span class="nt"></html></span>
</code></pre></div></div>
<p>Notice that I added <code class="language-plaintext highlighter-rouge">{% load static %}</code> at the top of the file so we can use <a href="https://docs.djangoproject.com/en/4.1/ref/templates/builtins/#std-templatetag-static">the <code class="language-plaintext highlighter-rouge">static</code> template tag</a>.</p>
<p>Restart the server so that Django sees this new <code class="language-plaintext highlighter-rouge">static/</code> folder. If you don’t restart the server, you’ll get a <code class="language-plaintext highlighter-rouge">404</code> on <code class="language-plaintext highlighter-rouge">toasts.js</code>.</p>
<p>Go back to your browser, refresh the page a click on the button. You should see something like this in the Console tab:</p>
<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[
{
"message": "It works!!",
"tags": "success"
}
]
</code></pre></div></div>
<h2 id="step-8-add-an-html-toast-template">Step 8: Add an HTML toast template</h2>
<p>Open <code class="language-plaintext highlighter-rouge">home.html</code> and add the following block before the <code class="language-plaintext highlighter-rouge"><script></code> tags:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><div</span> <span class="na">data-toast-container</span> <span class="na">class=</span><span class="s">"toast-container position-fixed top-0 end-0 p-3"</span><span class="nt">></span>
<span class="nt"><div</span> <span class="na">data-toast-template</span> <span class="na">class=</span><span class="s">"toast align-items-center border-0"</span> <span class="na">role=</span><span class="s">"alert"</span> <span class="na">aria-live=</span><span class="s">"assertive"</span> <span class="na">aria-atomic=</span><span class="s">"true"</span><span class="nt">></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"d-flex"</span><span class="nt">></span>
<span class="nt"><div</span> <span class="na">data-toast-body</span> <span class="na">class=</span><span class="s">"toast-body"</span><span class="nt">></div></span>
<span class="nt"><button</span> <span class="na">type=</span><span class="s">"button"</span> <span class="na">class=</span><span class="s">"btn-close btn-close-white me-2 m-auto"</span> <span class="na">data-bs-dismiss=</span><span class="s">"toast"</span> <span class="na">aria-label=</span><span class="s">"Close"</span><span class="nt">></button></span>
<span class="nt"></div></span>
<span class="nt"></div></span>
<span class="nt"></div></span>
</code></pre></div></div>
<p>This markup is inspired by <a href="https://getbootstrap.com/docs/5.2/components/toasts/#live-example">Bootstrap’s example for Toasts</a> and must be changed if you use a different CSS framework.</p>
<p>This toast is not visible. We’ll use it as a template to create more toasts. Notice the data attributes <code class="language-plaintext highlighter-rouge">data-toast-container</code>, <code class="language-plaintext highlighter-rouge">data-toast-template</code>, and <code class="language-plaintext highlighter-rouge">data-toast-body</code>, which we’ll use to find the elements.</p>
<h2 id="step-9-create-a-toast-for-each-message">Step 9: Create a toast for each message</h2>
<p>Add the following function in <code class="language-plaintext highlighter-rouge">toasts.js</code>:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">createToast</span><span class="p">(</span><span class="nx">message</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// Clone the template</span>
<span class="kd">const</span> <span class="nx">element</span> <span class="o">=</span> <span class="nx">htmx</span><span class="p">.</span><span class="nx">find</span><span class="p">(</span><span class="dl">"</span><span class="s2">[data-toast-template]</span><span class="dl">"</span><span class="p">).</span><span class="nx">cloneNode</span><span class="p">(</span><span class="kc">true</span><span class="p">)</span>
<span class="c1">// Remove the data-toast-template attribute</span>
<span class="k">delete</span> <span class="nx">element</span><span class="p">.</span><span class="nx">dataset</span><span class="p">.</span><span class="nx">toastTemplate</span>
<span class="c1">// Set the CSS class</span>
<span class="nx">element</span><span class="p">.</span><span class="nx">className</span> <span class="o">+=</span> <span class="dl">"</span><span class="s2"> </span><span class="dl">"</span> <span class="o">+</span> <span class="nx">message</span><span class="p">.</span><span class="nx">tags</span>
<span class="c1">// Set the text</span>
<span class="nx">htmx</span><span class="p">.</span><span class="nx">find</span><span class="p">(</span><span class="nx">element</span><span class="p">,</span> <span class="dl">"</span><span class="s2">[data-toast-body]</span><span class="dl">"</span><span class="p">).</span><span class="nx">innerText</span> <span class="o">=</span> <span class="nx">message</span><span class="p">.</span><span class="nx">message</span>
<span class="c1">// Add the new element to the container</span>
<span class="nx">htmx</span><span class="p">.</span><span class="nx">find</span><span class="p">(</span><span class="dl">"</span><span class="s2">[data-toast-container]</span><span class="dl">"</span><span class="p">).</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">element</span><span class="p">)</span>
<span class="c1">// Show the toast using Bootstrap's API</span>
<span class="kd">const</span> <span class="nx">toast</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">bootstrap</span><span class="p">.</span><span class="nx">Toast</span><span class="p">(</span><span class="nx">element</span><span class="p">,</span> <span class="p">{</span> <span class="na">delay</span><span class="p">:</span> <span class="mi">2000</span> <span class="p">})</span>
<span class="nx">toast</span><span class="p">.</span><span class="nx">show</span><span class="p">()</span>
<span class="p">}</span>
</code></pre></div></div>
<p>This function takes an object like <code class="language-plaintext highlighter-rouge">{message: "It works!", tags: "success"}</code> and creates a toast element in the DOM. This code is specific to Bootstrap 5 and must be adapted to your CSS framework.</p>
<p>Now, let’s call this function for each incoming message:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">htmx</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="dl">"</span><span class="s2">messages</span><span class="dl">"</span><span class="p">,</span> <span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">e</span><span class="p">.</span><span class="nx">detail</span><span class="p">.</span><span class="nx">value</span><span class="p">.</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">createToast</span><span class="p">)</span>
<span class="p">})</span>
</code></pre></div></div>
<p>Refresh the page in your browser and click on the button. Each click should create a white toast with the message “It works!”.</p>
<p>We can fix the color of this toast by configuring the tags in <code class="language-plaintext highlighter-rouge">settings.py</code>:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">django.contrib</span> <span class="kn">import</span> <span class="n">messages</span>
<span class="n">MESSAGE_TAGS</span> <span class="o">=</span> <span class="p">{</span>
<span class="n">messages</span><span class="p">.</span><span class="n">DEBUG</span><span class="p">:</span> <span class="s">"bg-light"</span><span class="p">,</span>
<span class="n">messages</span><span class="p">.</span><span class="n">INFO</span><span class="p">:</span> <span class="s">"text-white bg-primary"</span><span class="p">,</span>
<span class="n">messages</span><span class="p">.</span><span class="n">SUCCESS</span><span class="p">:</span> <span class="s">"text-white bg-success"</span><span class="p">,</span>
<span class="n">messages</span><span class="p">.</span><span class="n">WARNING</span><span class="p">:</span> <span class="s">"text-dark bg-warning"</span><span class="p">,</span>
<span class="n">messages</span><span class="p">.</span><span class="n">ERROR</span><span class="p">:</span> <span class="s">"text-white bg-danger"</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Go back to your browser and click on the button again. The new toast should now have the expected green background.</p>
<h2 id="conclusion">Conclusion</h2>
<p>I went as fast as possible to present the technique, so I had to cut some corners. I invite you to consult the <a href="https://github.com/bblanchon/django-htmx-messages-framework/">GitHub repository</a> for the complete source code.</p>
<p>Here is how the code in the repository differs from this article:</p>
<ol>
<li>It bundles message-related code in a <code class="language-plaintext highlighter-rouge">htmx_messages</code> app containing:
<ul>
<li><code class="language-plaintext highlighter-rouge">toasts.js</code></li>
<li><code class="language-plaintext highlighter-rouge">toasts.html</code></li>
<li><code class="language-plaintext highlighter-rouge">middleware.py</code></li>
</ul>
</li>
<li>It supports classic Django messages thanks to:
<ul>
<li><code class="language-plaintext highlighter-rouge">{% for message in messages %}</code> in <code class="language-plaintext highlighter-rouge">toasts.html</code></li>
<li>Code to show existing toasts in <code class="language-plaintext highlighter-rouge">toasts.js</code></li>
</ul>
</li>
<li>It supports views that set the <code class="language-plaintext highlighter-rouge">HX-Trigger</code> header:
<ul>
<li>The middleware reads and patches the header accordingly</li>
<li>It supports both the short and the extended syntaxes of <code class="language-plaintext highlighter-rouge">HX-Trigger</code></li>
</ul>
</li>
<li><code class="language-plaintext highlighter-rouge">toasts.js</code> uses an <a href="https://developer.mozilla.org/en-US/docs/Glossary/IIFE">IIFE</a> so that it can be concatenated with other JavaScript files.</li>
<li>The <code class="language-plaintext highlighter-rouge">message</code> view generates random toast messages with different levels.</li>
</ol>
<p>You can see all these differences explained in <a href="https://youtu.be/I5_g7XYyemQ">the video</a> as well.</p>
<p>If you want to see an alternative technique, check out <a href="/django-htmx-messages-framework-oob/">my next article</a>.</p>This article explains how to create a middleware that extracts the messages from Django’s messages framework to make them available to HTMX. It’s a condensed version of my 30-minute video: Django+HTMX: integration with the messages framework and a follow-up to my previous article on Django+HTMX, but you can read it independently.Toasts with Django+HTMX2022-03-02T00:00:00+00:002022-03-02T00:00:00+00:00https://blog.benoitblanchon.fr/django-htmx-toasts<p>This is a follow-up to my <a href="/django-htmx-modal-form/">previous article</a>, which showed how to implement a modal form with <a href="https://www.djangoproject.com/">Django</a> and <a href="https://htmx.org/">HTMX</a>. In this article, we’ll see how to add a user notification in the form of a toast.</p>
<p><img src="/images/2022/03/django-htmx-modal-form-toast-10-fps.gif" alt="Modal forms with Django+HTMX with toast" /></p>
<p>In the same spirit of my previous article, the solution I present here requires only a few lines of JavaScript and is reusable.</p>
<p>You can find the complete source code for this project on <a href="https://github.com/bblanchon/django-htmx-modal-form">GitHub</a>.
I uploaded two versions: one using <a href="https://github.com/bblanchon/django-htmx-modal-form/tree/bootstrap4">Bootstrap 4</a> and the other using <a href="https://github.com/bblanchon/django-htmx-modal-form/tree/bootstrap5">Bootstrap 5</a>.
You’ll find each version in the corresponding branch. The code snippets in this article use Bootstrap 5.</p>
<p><strong>This article is also available as a <a href="https://youtu.be/pAtrj8A-Kl4">YouTube video</a></strong></p>
<h2 id="step-1-create-the-toast-element">Step 1: create the toast element</h2>
<p>First, we need the HTML element for the toast. It will be initially hidden and empty.</p>
<p>Place the following lines at the end of <code class="language-plaintext highlighter-rouge"><body></code>:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><div</span> <span class="na">class=</span><span class="s">"position-fixed top-0 end-0 p-3"</span><span class="nt">></span>
<span class="nt"><div</span> <span class="na">id=</span><span class="s">"toast"</span> <span class="na">class=</span><span class="s">"toast align-items-center text-white bg-success border-0"</span> <span class="na">role=</span><span class="s">"alert"</span> <span class="na">aria-live=</span><span class="s">"assertive"</span> <span class="na">aria-atomic=</span><span class="s">"true"</span><span class="nt">></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"d-flex"</span><span class="nt">></span>
<span class="nt"><div</span> <span class="na">id=</span><span class="s">"toast-body"</span> <span class="na">class=</span><span class="s">"toast-body"</span><span class="nt">></div></span>
<span class="nt"><button</span> <span class="na">type=</span><span class="s">"button"</span> <span class="na">class=</span><span class="s">"btn-close btn-close-white me-2 m-auto"</span> <span class="na">data-bs-dismiss=</span><span class="s">"toast"</span> <span class="na">aria-label=</span><span class="s">"Close"</span><span class="nt">></button></span>
<span class="nt"></div></span>
<span class="nt"></div></span>
<span class="nt"></div></span>
</code></pre></div></div>
<p>This code is directly taken from <a href="https://getbootstrap.com/docs/5.1/components/modal/">Bootstrap’s documentation</a>, except that the toast body is empty.</p>
<p>Notice that I declared the ids <code class="language-plaintext highlighter-rouge">toast</code> and <code class="language-plaintext highlighter-rouge">toast-body</code>. We’ll use them from the JavaScript code.</p>
<h2 id="step-2-emit-a-second-event-from-the-view">Step 2: emit a second event from the view</h2>
<p>In the previous article, we used the <a href="https://htmx.org/headers/hx-trigger/"><code class="language-plaintext highlighter-rouge">HX-Trigger</code></a> header to raise a JavaScript event from the Django view:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">return</span> <span class="n">HttpResponse</span><span class="p">(</span><span class="n">status</span><span class="o">=</span><span class="mi">204</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="p">{</span><span class="s">'HX-Trigger'</span><span class="p">:</span> <span class="s">'movieListChanged'</span><span class="p">})</span>
</code></pre></div></div>
<p>Now, we’ll change this to raise a second event. HTMX lets us do that with an alternative syntax for the <a href="https://htmx.org/headers/hx-trigger/"><code class="language-plaintext highlighter-rouge">HX-Trigger</code></a> header. Instead of a simple string that names the event, we can provide a JSON string containing the names of the events associated with a payload. In our case, we need this JSON string to be:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"movieListChanged"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
</span><span class="nl">"showMessage"</span><span class="p">:</span><span class="w"> </span><span class="s2">"<name of the movie> added."</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>As you can see, the <code class="language-plaintext highlighter-rouge">movieListChanged</code> is still there but doesn’t carry any data.
I added the <code class="language-plaintext highlighter-rouge">showMessage</code> event with the associated message.</p>
<p>Here is the updated code for the view:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">return</span> <span class="n">HttpResponse</span><span class="p">(</span>
<span class="n">status</span><span class="o">=</span><span class="mi">204</span><span class="p">,</span>
<span class="n">headers</span><span class="o">=</span><span class="p">{</span>
<span class="s">'HX-Trigger'</span><span class="p">:</span> <span class="n">json</span><span class="p">.</span><span class="n">dumps</span><span class="p">({</span>
<span class="s">"movieListChanged"</span><span class="p">:</span> <span class="bp">None</span><span class="p">,</span>
<span class="s">"showMessage"</span><span class="p">:</span> <span class="sa">f</span><span class="s">"</span><span class="si">{</span><span class="n">movie</span><span class="p">.</span><span class="n">title</span><span class="si">}</span><span class="s"> added."</span>
<span class="p">})</span>
<span class="p">})</span>
</code></pre></div></div>
<h2 id="step-3-intercept-the-event">Step 3: intercept the event</h2>
<p>On the client-side, we must listen for this new <code class="language-plaintext highlighter-rouge">showMessage</code> event a display a toast when it occurs.</p>
<p>Here is the JavaScript code for that:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">toastElement</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">"</span><span class="s2">toast</span><span class="dl">"</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">toastBody</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">"</span><span class="s2">toast-body</span><span class="dl">"</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">toast</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">bootstrap</span><span class="p">.</span><span class="nx">Toast</span><span class="p">(</span><span class="nx">toastElement</span><span class="p">,</span> <span class="p">{</span> <span class="na">delay</span><span class="p">:</span> <span class="mi">2000</span> <span class="p">})</span>
<span class="nx">htmx</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="dl">"</span><span class="s2">showMessage</span><span class="dl">"</span><span class="p">,</span> <span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">toastBody</span><span class="p">.</span><span class="nx">innerText</span> <span class="o">=</span> <span class="nx">e</span><span class="p">.</span><span class="nx">detail</span><span class="p">.</span><span class="nx">value</span>
<span class="nx">toast</span><span class="p">.</span><span class="nx">show</span><span class="p">()</span>
<span class="p">})</span>
</code></pre></div></div>
<p>As announced in step 1, we use the ids <code class="language-plaintext highlighter-rouge">toast</code> and <code class="language-plaintext highlighter-rouge">toast-body</code> to locate the toast elements in the DOM.</p>
<p>The third line creates the <code class="language-plaintext highlighter-rouge">Toast</code> object. This is specific to Bootstrap 5; you’ll have to adapt this line to your CSS framework.</p>
<p>In the event handler, we set the toast message from the event’s payload, and finally, show the toast.</p>
<p>In the <a href="/django-htmx-messages-framework/">next article</a>, we’ll see how to do the same thing with Django’s messages framework.</p>This is a follow-up to my previous article, which showed how to implement a modal form with Django and HTMX. In this article, we’ll see how to add a user notification in the form of a toast.Modal forms with Django+HTMX2022-02-18T00:00:00+00:002022-02-18T00:00:00+00:00https://blog.benoitblanchon.fr/django-htmx-modal-form<p>This article describes the pattern I use to implement modal forms (i.e., forms in a modal dialog box) with <a href="https://www.djangoproject.com/">Django</a> and <a href="https://htmx.org/">HTMX</a>.</p>
<p><img src="/images/2022/01/django-htmx-modal-form-10-fps.gif" alt="Modal forms with Django+HTMX" /></p>
<p>The advantages of the solution presented in this article are:</p>
<ol>
<li>It doesn’t depend on <a href="https://htmx.org/docs/#hyperscript">Hyperscript</a> (unlike <a href="https://htmx.org/examples/modal-bootstrap/">HTMX’s example</a>).</li>
<li>It requires only a few lines of JavaScript.</li>
<li>It is reusable (no need to repeat the JavaScript code).</li>
<li>It supports server-side form validation.</li>
<li>It allows refreshing the main page on success.</li>
<li>It works with any CSS framework.</li>
<li>It can support progressive enhancement when JavaScript is unavailable (but I won’t show it in this article).</li>
</ol>
<h2 id="prerequisite">Prerequisite</h2>
<p>To read this article, you must be familiar with <a href="https://www.djangoproject.com/">Django</a>, know the basics of <a href="https://htmx.org/">HTMX</a> and a bit about <a href="https://getbootstrap.com/">Bootstrap</a>.
I won’t explain how to create a Django project or integrate HTMX in your application; I’m assuming that you already know how to do this.</p>
<p><strong>If this article goes too quickly, I recommend that you watch <a href="https://youtu.be/3dyQigrEj8A">this YouTube video</a> where I take the time to explain every step of the process.</strong></p>
<p>You can find the complete source code for this project on <a href="https://github.com/bblanchon/django-htmx-modal-form">GitHub</a>.
I uploaded two versions: one using <a href="https://github.com/bblanchon/django-htmx-modal-form/tree/bootstrap4">Bootstrap 4</a> and the other using <a href="https://github.com/bblanchon/django-htmx-modal-form/tree/bootstrap5">Bootstrap 5</a>.
You’ll find each version in the corresponding branch. The code snippets in this article use Bootstrap 5.</p>
<h2 id="step-1-create-a-placeholder">Step 1: create a placeholder</h2>
<p>First, we need a placeholder that will serve as our <a href="https://htmx.org/attributes/hx-target/"><code class="language-plaintext highlighter-rouge">hx-target</code></a> when displaying a modal dialog.</p>
<p>Place the following lines at the end of <code class="language-plaintext highlighter-rouge"><body></code>:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><div</span> <span class="na">id=</span><span class="s">"modal"</span> <span class="na">class=</span><span class="s">"modal fade"</span><span class="nt">></span>
<span class="nt"><div</span> <span class="na">id=</span><span class="s">"dialog"</span> <span class="na">class=</span><span class="s">"modal-dialog"</span> <span class="na">hx-target=</span><span class="s">"this"</span><span class="nt">></div></span>
<span class="nt"></div></span>
</code></pre></div></div>
<p>As you can see, this code is directly taken from <a href="https://getbootstrap.com/docs/5.1/components/modal/">Bootstrap’s documentation</a>, except that the dialog is empty.</p>
<p>Three things to notice:</p>
<ol>
<li>The modal’s id is <code class="language-plaintext highlighter-rouge">modal</code>. We’ll use this id later in the JavaScript code to control the visibility of the dialog.</li>
<li>The dialog’s id is <code class="language-plaintext highlighter-rouge">dialog</code>. We’ll use this id for <code class="language-plaintext highlighter-rouge">hx-target</code> to set the dialog’s content (see next step).</li>
<li>We set <code class="language-plaintext highlighter-rouge">hx-target</code> to <code class="language-plaintext highlighter-rouge">this</code> so that every HTMX request originating from the dialog (typically a form POST) remains in the dialog.</li>
</ol>
<h2 id="step-2-create-the-button-that-opens-the-dialog">Step 2: create the button that opens the dialog</h2>
<p>We’ll create a simple button that leverages HTMX attributes:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><button</span> <span class="na">hx-get=</span><span class="s">"{% url 'add_movie' %}"</span> <span class="na">hx-target=</span><span class="s">"#dialog"</span><span class="nt">></span>
Add a movie
<span class="nt"></button></span>
</code></pre></div></div>
<p>As promised, we used <code class="language-plaintext highlighter-rouge">#dialog</code> for <a href="https://htmx.org/attributes/hx-target/"><code class="language-plaintext highlighter-rouge">hx-target</code></a>, which means that the response’s HTML will be injected in the dialog.</p>
<p>For the <a href="https://htmx.org/attributes/hx-get/"><code class="language-plaintext highlighter-rouge">hx-get</code></a> attribute, we used a URL named <code class="language-plaintext highlighter-rouge">add_movie</code>; we’ll create this view in the next step.</p>
<h2 id="step-3-create-the-view-that-renders-the-form">Step 3: create the view that renders the form</h2>
<p>For now, we’ll only see the GET part in the view:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">add_movie</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
<span class="n">form</span> <span class="o">=</span> <span class="n">MovieForm</span><span class="p">()</span>
<span class="k">return</span> <span class="n">render</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="s">'movie_form.html'</span><span class="p">,</span> <span class="p">{</span>
<span class="s">'form'</span><span class="p">:</span> <span class="n">form</span><span class="p">,</span>
<span class="p">})</span>
</code></pre></div></div>
<p>Here is a simple version of <code class="language-plaintext highlighter-rouge">movie_form.html</code>:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><form</span> <span class="na">hx-post=</span><span class="s">"{{ request.path }}"</span> <span class="na">class=</span><span class="s">"modal-content"</span><span class="nt">></span>
{% csrf_token %}
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"modal-header"</span><span class="nt">></span>
<span class="nt"><h5</span> <span class="na">class=</span><span class="s">"modal-title"</span><span class="nt">></span>Edit Movie<span class="nt"></h5></span>
<span class="nt"></div></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"modal-body"</span><span class="nt">></span>
{{ form.as_p }}
<span class="nt"></div></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"modal-footer"</span><span class="nt">></span>
<span class="nt"><button</span> <span class="na">type=</span><span class="s">"button"</span> <span class="na">data-bs-dismiss=</span><span class="s">"modal"</span><span class="nt">></span>Cancel<span class="nt"></button></span>
<span class="nt"><button</span> <span class="na">type=</span><span class="s">"submit"</span><span class="nt">></span>Save<span class="nt"></button></span>
<span class="nt"></div></span>
<span class="nt"></form></span>
</code></pre></div></div>
<p>There is nothing fancy in this template, except maybe the value of <a href="https://htmx.org/attributes/hx-post/"><code class="language-plaintext highlighter-rouge">hx-post</code></a>.
<a href="https://docs.djangoproject.com/en/3.2/ref/request-response/#django.http.HttpRequest.path"><code class="language-plaintext highlighter-rouge">request.path</code></a> is the URL of the current request, which means that the POST request will use the same view as the GET request.
For now, our view only handles the GET method correctly; we’ll implement the POST method in a moment.</p>
<p>If you look at the code on GitHub, you’ll see that I use a slightly more complicated template to layout the form according to Bootstrap’s conventions.</p>
<h2 id="step-4-show-the-modal">Step 4: show the modal</h2>
<p>Thanks to the attribute <code class="language-plaintext highlighter-rouge">hx-target="#dialog"</code>, the form returned by the <code class="language-plaintext highlighter-rouge">add_movie</code> view is injected inside the dialog box.
At this point, the dialog box is still hidden; we need to write a small piece of JavaScript to show it.</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">modal</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">bootstrap</span><span class="p">.</span><span class="nx">Modal</span><span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">"</span><span class="s2">modal</span><span class="dl">"</span><span class="p">))</span>
<span class="nx">htmx</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="dl">"</span><span class="s2">htmx:afterSwap</span><span class="dl">"</span><span class="p">,</span> <span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="c1">// Response targeting #dialog => show the modal</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nx">detail</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">id</span> <span class="o">==</span> <span class="dl">"</span><span class="s2">dialog</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">modal</span><span class="p">.</span><span class="nx">show</span><span class="p">()</span>
<span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>
<p>Obviously, this code is specific to Bootstrap 5, so you’ll need to adapt it to your CSS framework.</p>
<p>What’s important here is that we listen to the <a href="https://htmx.org/events/#htmx:afterSwap"><code class="language-plaintext highlighter-rouge">htmx:afterSwap</code></a> event.
As the documentation says: “this event is triggered after new content has been swapped into the DOM.”</p>
<p>When this event occurs, we show the dialog.
Of course, we only do that if <code class="language-plaintext highlighter-rouge">#dialog</code> is the target, so it only affects dialog-related swaps.</p>
<h2 id="step-5-handle-the-form-post-in-the-view">Step 5: handle the form POST in the view</h2>
<p>Let’s edit the <code class="language-plaintext highlighter-rouge">add_movie</code> view to support the <code class="language-plaintext highlighter-rouge">POST</code> method:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">add_movie</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
<span class="k">if</span> <span class="n">request</span><span class="p">.</span><span class="n">method</span> <span class="o">==</span> <span class="s">"POST"</span><span class="p">:</span>
<span class="n">form</span> <span class="o">=</span> <span class="n">MovieForm</span><span class="p">(</span><span class="n">request</span><span class="p">.</span><span class="n">POST</span><span class="p">)</span>
<span class="k">if</span> <span class="n">form</span><span class="p">.</span><span class="n">is_valid</span><span class="p">():</span>
<span class="n">form</span><span class="p">.</span><span class="n">save</span><span class="p">()</span>
<span class="k">return</span> <span class="n">HttpResponse</span><span class="p">(</span><span class="n">status</span><span class="o">=</span><span class="mi">204</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">form</span> <span class="o">=</span> <span class="n">MovieForm</span><span class="p">()</span>
<span class="k">return</span> <span class="n">render</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="s">'movie_form.html'</span><span class="p">,</span> <span class="p">{</span>
<span class="s">'form'</span><span class="p">:</span> <span class="n">form</span><span class="p">,</span>
<span class="p">})</span>
</code></pre></div></div>
<p>As you can see, we use the classic pattern for function-based view with one twist: instead of returning a 302 to redirect the browser to a new page, we return an empty response with the appropriate status code (<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204"><code class="language-plaintext highlighter-rouge">204 No Content</code></a>).</p>
<p>Returning an empty response will be a signal for our JavaScript code to hide the dialog.
We’ll see that in the next step.</p>
<p>If the form is invalid, we render the template with the form errors.
Since we added <code class="language-plaintext highlighter-rouge">hx-target="this"</code> to the dialog’s <code class="language-plaintext highlighter-rouge"><div></code>, the response will replace the dialog’s content.
This is perfect because it will show the forms error in the dialog box.</p>
<h2 id="step-6-hide-the-dialog-box">Step 6: hide the dialog box</h2>
<p>Since we returned an empty response, you might think that HTMX will flush the dialog’s content, but that’s not the case: HTMX doesn’t swap the content for non-200 responses, and we used 204.</p>
<p>Right now, nothing changes on the screen after a successful POST, so we need to hide the dialog.
We’ll do that in the <a href="https://htmx.org/events/#htmx:beforeSwap"><code class="language-plaintext highlighter-rouge">htmx:beforeSwap</code></a> event:</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">htmx</span><span class="p">.</span><span class="n">on</span><span class="p">(</span><span class="s">"htmx:beforeSwap"</span><span class="p">,</span> <span class="p">(</span><span class="n">e</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="c1">// Empty response targeting #dialog => hide the modal</span>
<span class="k">if</span> <span class="p">(</span><span class="n">e</span><span class="p">.</span><span class="n">detail</span><span class="p">.</span><span class="n">target</span><span class="p">.</span><span class="n">id</span> <span class="o">==</span> <span class="s">"dialog"</span> <span class="o">&&</span> <span class="o">!</span><span class="n">e</span><span class="p">.</span><span class="n">detail</span><span class="p">.</span><span class="n">xhr</span><span class="p">.</span><span class="n">response</span><span class="p">)</span> <span class="p">{</span>
<span class="n">modal</span><span class="p">.</span><span class="n">hide</span><span class="p">()</span>
<span class="n">e</span><span class="p">.</span><span class="n">detail</span><span class="p">.</span><span class="n">shouldSwap</span> <span class="o">=</span> <span class="nb">false</span>
<span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>
<p>As you can see, we first check that the swap targets the dialog box and that the response is empty.
If that’s the case, the we hide the dialog box.</p>
<p>Notice that I didn’t check the status code of 204 because I want this code to support any empty response.
This is a safeguard in case you prefer to use 200 instead of 204.</p>
<p>Similarly, the event handler sets <a href="https://htmx.org/docs/#modifying_swapping_behavior_with_events"><code class="language-plaintext highlighter-rouge">shouldSwap</code></a> to <code class="language-plaintext highlighter-rouge">false</code> to prevent HTMX from clearing the content when the dialog fades away.
Again, this is a safeguard because the default is <code class="language-plaintext highlighter-rouge">false</code> for non-200 responses.</p>
<h2 id="step-7-empty-the-dialog">Step 7: empty the dialog</h2>
<p>Let’s summarise: the dialog box opens and hides as expected and even shows form errors. Great!
In fact, everything works fine except a tiny little detail.</p>
<p>Imagine this scenario:</p>
<ol>
<li>User opens the dialog</li>
<li>User submits the form with an error</li>
<li>Error gets rendered on the screen</li>
<li>User clicks “Cancel”</li>
<li>Dialog fades away</li>
</ol>
<p>At this point, the dialog is hidden but still contains the form with the errors.
If the user opens the dialog again, she might see the errors for a fraction of a second.</p>
<p>To avoid this, we must flush the content of the dialog after the fade transition.</p>
<p>With Bootstrap 5, we can do this from the <a href="https://getbootstrap.com/docs/5.1/components/modal/#events"><code class="language-plaintext highlighter-rouge">hidden.bs.modal</code></a> event.</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">htmx</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="dl">"</span><span class="s2">hidden.bs.modal</span><span class="dl">"</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">"</span><span class="s2">dialog</span><span class="dl">"</span><span class="p">).</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="dl">""</span>
<span class="p">})</span>
</code></pre></div></div>
<h2 id="step-8-refresh-page-content">Step 8: refresh page content</h2>
<p>Now the dialog box works perfectly, but we still have to find a way to refresh the page and reflect the changes introduced by the POST.
We returned an empty response, so how are we supposed to update the page?<br />
We’ll raise an event that triggers a refresh.</p>
<p>Let’s modify the <code class="language-plaintext highlighter-rouge">add_movie</code> view to include the <a href="https://htmx.org/headers/hx-trigger/"><code class="language-plaintext highlighter-rouge">HX-Trigger</code></a> header to the response, like so:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">return</span> <span class="n">HttpResponse</span><span class="p">(</span><span class="n">status</span><span class="o">=</span><span class="mi">204</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="p">{</span><span class="s">'HX-Trigger'</span><span class="p">:</span> <span class="s">'movieListChanged'</span><span class="p">})</span>
</code></pre></div></div>
<p>This header instructs HTMX to raise an event.
In this case, I decided to name the event <code class="language-plaintext highlighter-rouge">movieListChanged</code>.</p>
<p>Now, we need to listen to this event.
HTMX lets us do that with the attribute <a href="https://htmx.org/attributes/hx-trigger/"><code class="language-plaintext highlighter-rouge">hx-trigger</code></a><code class="language-plaintext highlighter-rouge">="movieListChanged from:body"</code>.</p>
<p>In my example, I also wanted to load the table on page load, so the actual code looks like this:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><tbody</span> <span class="na">hx-trigger=</span><span class="s">"load, movieListChanged from:body"</span> <span class="na">hx-get=</span><span class="s">"{% url 'movie_list' %}"</span> <span class="na">hx-target=</span><span class="s">"this"</span><span class="nt">></span>
<span class="nt"></tbody></span>
</code></pre></div></div>
<p>As you might expect, the <code class="language-plaintext highlighter-rouge">movie_list</code> view returns a bunch of rows tailored for this particular table.</p>
<p>That’s all! Now, the table updates itself when the form is submitted.</p>
<h2 id="conclusion">Conclusion</h2>
<p>There you have it: a reusable pattern to handle Django Form in modal dialogs.</p>
<p>I’ve been using it for a while now, and I can confirm that it is a reliable and scalable solution.</p>
<p>Should you have any questions about this article, please <a href="https://github.com/bblanchon/django-htmx-modal-form/issues">open an issue</a> in the GitHub project.</p>
<p><strong>2022/03/02</strong>: The <a href="/django-htmx-toasts/">next article</a> shows how to display a notification with this pattern.</p>This article describes the pattern I use to implement modal forms (i.e., forms in a modal dialog box) with Django and HTMX.GitHub Actions: How to run SSH commands (without third-party actions)2020-06-01T00:00:00+00:002020-06-01T00:00:00+00:00https://blog.benoitblanchon.fr/github-action-run-ssh-commands<p>Recently, I’ve been using GitHub Actions for my <a href="https://help.github.com/en/actions/getting-started-with-github-actions/core-concepts-for-github-actions#continuous-integration-ci">Continuous Integration</a> and <a href="https://help.github.com/en/actions/getting-started-with-github-actions/core-concepts-for-github-actions#continuous-deployment-cd">Continuous Delivery</a>.
Often, I use a simple deployment process that consists in executing remote commands on the target server.</p>
<p><img src="/images/2020/06/github-actions-ssh.png" alt="GitHub Actions SSH" /></p>
<p>I found two third-party actions in the marketplace:</p>
<ul>
<li><a href="https://github.com/marketplace/actions/ssh-remote-commands">appleboy/ssh-action</a></li>
<li><a href="https://github.com/marketplace/actions/run-ssh-command">garygrossgarten/github-action-ssh</a></li>
</ul>
<p>Here is what I dislike about them:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">appleboy/ssh-action</code> uses Docker, and therefore, is quite slow</li>
<li><code class="language-plaintext highlighter-rouge">garygrossgarten/github-action-ssh</code> fails to report errors</li>
<li>both make it difficult to split a <a href="https://help.github.com/en/actions/getting-started-with-github-actions/core-concepts-for-github-actions#job">job</a> into multiple <a href="https://help.github.com/en/actions/getting-started-with-github-actions/core-concepts-for-github-actions#step">steps</a></li>
</ul>
<p>Finally, I settled on the following solution: calling the <code class="language-plaintext highlighter-rouge">ssh</code> command directly from the <a href="https://help.github.com/en/actions/getting-started-with-github-actions/core-concepts-for-github-actions#workflow">workflow</a>.</p>
<p>However, before we can call <code class="language-plaintext highlighter-rouge">ssh</code>, we need to configure a few things:</p>
<ul>
<li>hostname</li>
<li>username</li>
<li>private key</li>
</ul>
<p>Of course, we’ll not store these values in the source code (at least, not the private key); instead, we’ll use the <a href="https://help.github.com/en/actions/configuring-and-managing-workflows/creating-and-storing-encrypted-secrets">repository’s secrets</a>.</p>
<p>To do that, we’ll’ begin the job with an additional step that creates the private key file on the <a href="https://help.github.com/en/actions/getting-started-with-github-actions/core-concepts-for-github-actions#runner">runner</a>, as well as an <a href="https://linux.die.net/man/5/ssh_config">SSH configuration file</a>.</p>
<p>To create the private key file, we simply need to echo the key’s content and make sure we set the right permissions:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span><span class="nb">mkdir</span> <span class="nt">-p</span> ~/.ssh/
<span class="gp">$</span><span class="w"> </span><span class="nb">echo</span> <span class="s2">"</span><span class="nv">$SSH_KEY</span><span class="s2">"</span> <span class="o">></span> ~/.ssh/staging.key
<span class="gp">$</span><span class="w"> </span><span class="nb">chmod </span>600 ~/.ssh/staging.key
</code></pre></div></div>
<p>To create the SSH configuration file, we could use <code class="language-plaintext highlighter-rouge">echo</code> as well, but I think it’s simpler with <code class="language-plaintext highlighter-rouge">cat</code>:</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span><span class="nb">cat</span> <span class="o">>></span>~/.ssh/config <span class="o"><<</span><span class="no">END</span><span class="sh">
</span><span class="go">Host staging
</span><span class="gp"> HostName $</span>SSH_HOST
<span class="gp"> User $</span>SSH_USER
<span class="go"> IdentityFile ~/.ssh/staging.key
StrictHostKeyChecking no
END
</span></code></pre></div></div>
<p>As you can see, this file defines the alias <code class="language-plaintext highlighter-rouge">staging</code> for the target machine (“staging” is just an example; you can use “deploy”, “production”, or anything else).
To this alias, it binds the hostname, the username, and the private key.</p>
<p>It also sets <code class="language-plaintext highlighter-rouge">StrictHostKeyChecking=no</code> to avoid the infamous error <code class="language-plaintext highlighter-rouge">Host key verification failed</code>. If you think this is not secure, you can set the target footprint in <code class="language-plaintext highlighter-rouge">~/.ssh/known_hosts</code> the same way we created the private key.</p>
<p>Here is the complete workflow:</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s">CI</span>
<span class="na">on</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">push</span><span class="pi">,</span> <span class="nv">pull_request</span><span class="pi">]</span>
<span class="na">jobs</span><span class="pi">:</span>
<span class="c1"># test:</span>
<span class="c1"># ...</span>
<span class="na">deploy</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Deploy</span><span class="nv"> </span><span class="s">to</span><span class="nv"> </span><span class="s">staging"</span>
<span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
<span class="na">if</span><span class="pi">:</span> <span class="s">github.event_name == 'push' && github.ref == 'refs/heads/master'</span>
<span class="c1"># needs: test</span>
<span class="na">steps</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Configure SSH</span>
<span class="na">run</span><span class="pi">:</span> <span class="pi">|</span>
<span class="s">mkdir -p ~/.ssh/</span>
<span class="s">echo "$SSH_KEY" > ~/.ssh/staging.key</span>
<span class="s">chmod 600 ~/.ssh/staging.key</span>
<span class="s">cat >>~/.ssh/config <<END</span>
<span class="s">Host staging</span>
<span class="s">HostName $SSH_HOST</span>
<span class="s">User $SSH_USER</span>
<span class="s">IdentityFile ~/.ssh/staging.key</span>
<span class="s">StrictHostKeyChecking no</span>
<span class="s">END</span>
<span class="na">env</span><span class="pi">:</span>
<span class="na">SSH_USER</span><span class="pi">:</span> <span class="s">${{ secrets.STAGING_SSH_USER }}</span>
<span class="na">SSH_KEY</span><span class="pi">:</span> <span class="s">${{ secrets.STAGING_SSH_KEY }}</span>
<span class="na">SSH_HOST</span><span class="pi">:</span> <span class="s">${{ secrets.STAGING_SSH_HOST }}</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Stop the server</span>
<span class="na">run</span><span class="pi">:</span> <span class="s">ssh staging 'sudo systemctl stop my-application'</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Check out the source</span>
<span class="na">run</span><span class="pi">:</span> <span class="s">ssh staging 'cd my-application && git fetch && git reset --hard origin/master'</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Start the server</span>
<span class="na">if</span><span class="pi">:</span> <span class="s">${{ always() }}</span>
<span class="na">run</span><span class="pi">:</span> <span class="s">ssh staging 'sudo systemctl start my-application'</span>
</code></pre></div></div>
<p>A few remarks on this workflow:</p>
<ul>
<li>I deploy only when all tests are passing, hence the commented <code class="language-plaintext highlighter-rouge">needs: test</code> to link the <code class="language-plaintext highlighter-rouge">deploy</code> job to the <code class="language-plaintext highlighter-rouge">test</code> job.</li>
<li>I deploy only on a push to the master branch; that’s the <code class="language-plaintext highlighter-rouge">if</code> statement on the job</li>
<li>I restart the application service even if the other steps fail, thanks to <code class="language-plaintext highlighter-rouge">if: ${{ always() }}</code> on the last step.</li>
<li>I used the <code class="language-plaintext highlighter-rouge">git</code> command on the remote host to check out the code, but instead, you could use the <code class="language-plaintext highlighter-rouge">scp</code> command to upload a build artifact.</li>
<li>To allow Git to check out the code, I installed a <a href="https://github.blog/2015-06-16-read-only-deploy-keys/">read-only deploy key</a> on the target machine</li>
</ul>
<p>As you can see, this simple technique allows us to copy files and execute commands on a remote host. Thanks to the alias defined in <code class="language-plaintext highlighter-rouge">.ssh/config</code>, we can split a job into several steps with minimal code duplication.</p>Recently, I’ve been using GitHub Actions for my Continuous Integration and Continuous Delivery. Often, I use a simple deployment process that consists in executing remote commands on the target server.Watch me on Maker Mind Meld Summit2019-12-04T00:00:00+00:002019-12-04T00:00:00+00:00https://blog.benoitblanchon.fr/maker-mind-meld<p>In December 2019, <a href="https://techexplorations.com/" rel="nofollow">TechExploration</a> is hosting the first “<a href="https://techexplorations.com/blog_an-online-conference-for-makers/" rel="nofollow">Maker Mind Meld</a>,” an online conference for makers and electronic hobbyists. <a href="https://techexplorations.com/st/aboutus/" rel="nofollow">Peter</a>’s brought together 22 amazing makers from around the world to show you how they create magic with software, electronics, plastic, and wood.</p>
<p><img src="/images/2019/12/maker-mind-meld-banner.png" alt="Maker Mind Melt" /></p>
<p><strong>I am one of the presenters</strong>, and I will be talking about serialization and JSON. I’ll demonstrate how you can implement serialization from scratch and when it makes sense to use a library. I’ll also compare various serialization formats so you can choose which one is the best match for your project.</p>
<p>Here’s just a small sample of what you’ll learn during the summit sessions:</p>
<ul>
<li>How to create graphics and animation using an Arduino and an LCD display</li>
<li>How to create almost anything with a laser cutter and 3D printer</li>
<li>How to re-shape society through Making</li>
<li>How to contribute to your favorite open-source project, even if you are not a programmer.</li>
<li>How to take your electronics prototype to market.</li>
<li>How to use the BBC Micro:bit in your next project.</li>
<li>How to design (and build) your own microprocessor.</li>
<li>How to design control algorithms using model-based design methodologies.</li>
<li>How to get started with embedded system design.</li>
<li>How to use JSON in your Arduino IoT projects</li>
<li>How to get on to IoT 2.0, a network dedicated to 20 billion devices.</li>
<li>How to build a reliable wired IoT system with noCAN.</li>
</ul>
<p>Plus much, much more!</p>
<p>The Maker Mind Meld Summit will take place between December 6 and December 13. My presentation will be about one hour long and will start <time datetime="2019-12-09 23:00:00.000Z">at 11:00 PM (GMT) on December 9</time> (depending on you timezone, it may be December 10).</p>
<style>
time {font-weight: bold;}
</style>
<div class="text-center">
<a href="https://techexplorations.com/st/summit/registration/" class="btn btn-warning btn-lg" rel="nofollow">Click here to get your free ticket! </a>
</div>
<p>See you at the Summit!</p>In December 2019, TechExploration is hosting the first “Maker Mind Meld,” an online conference for makers and electronic hobbyists. Peter’s brought together 22 amazing makers from around the world to show you how they create magic with software, electronics, plastic, and wood.New blog: C++ for Arduino2018-11-13T00:00:00+00:002018-11-13T00:00:00+00:00https://blog.benoitblanchon.fr/cpp-for-arduino<p>Since the publication of my book “<a href="https://arduinojson.org/book/">Mastering ArduinoJson</a>,” I received a lot of
positive feedback. One thing that surprised me, however, was that many readers
loved the second chapter, “The Missing C++ Course.” This chapter is a quick
refresher on C++; it explains some of the fundamental notions, like stack and
heap, but also gives tips for your everyday programming.</p>
<p>This success confirmed my intuition that many Arduino users are willing to learn
C++ but cannot find the right information. Indeed, many tutorials help you start
with Arduino but don’t explain anything about C++. Worse, they often show bad practices and anti-patterns.
On the other side of the spectrum, there are excellent resources for C++ experts. However, this content is inaccessible for a beginner and is often inapplicable in the context of an Arduino sketch.</p>
<p><img src="/images/2018/11/cpp_skill_scale.png" alt="Where do you go when you know the basics of Arduino, but are limited by your C++ skills?" /></p>
<p><strong>So, where do you go when you know the basics of Arduino, but are limited by your C++ skills?</strong> It seems that there is not enough content to help you grow from there.
That is why I started a new blog that intends to fill this gap.</p>
<p>In this blog, I’ll write articles that are accessible to beginners but still interesting for intermediate level. I’ll make sure that everything you learn is immediately applicable in Arduino and I’ll provide code samples so you can practice at home.</p>
<p>As many readers seemed to appreciate the writing style of “<a href="https://arduinojson.org/book/">Mastering ArduinoJson</a>,” I’ll stick to the same recipe:</p>
<ul>
<li>short paragraphs</li>
<li>short sentences</li>
<li>active voice</li>
<li>conversational style</li>
</ul>
<p>As a result, the blog should be as easy to read as the book, even for readers who don’t speak English as their first language.</p>
<p>However, I will not repeat in the blog what I already wrote in “The Missing C++ Chapter” because it would be unfair to people who purchased the book.</p>
<p>I want the blog to be an area for exchange and interaction, that’s why I added comments. So, don’t hesitate to ask a question if something isn’t explicit, or if you want me to cover a specific topic in a future article.</p>
<p>So, please visit this new blog on <a href="https://cpp4arduino.com/">cpp4arduino.com</a>.</p>
<p><a href="https://cpp4arduino.com/"><img src="/images/2018/11/cpp4arduino.png" alt="The logo of C++ for Arduino" /></a></p>
<p>Oh, I almost forgot! I also created a <a href="https://www.youtube.com/channel/UCUbizwLf1ES6eYYyrHaJGDQ">YouTube channel</a> featuring video versions of the articles. I know I suck big-time at shooting videos, but hopefully, it will improve over time.</p>
<p>See you there!</p>Since the publication of my book “Mastering ArduinoJson,” I received a lot of positive feedback. One thing that surprised me, however, was that many readers loved the second chapter, “The Missing C++ Course.” This chapter is a quick refresher on C++; it explains some of the fundamental notions, like stack and heap, but also gives tips for your everyday programming.[Arduino] How to debug any program with tracing2018-07-24T00:00:00+00:002018-07-24T00:00:00+00:00https://blog.benoitblanchon.fr/debug-arduino-with-tracing<p>Have you ever been faced with an Arduino board that crashes for no apparent reason?
Have you ever decode an ESP8266 (ESP32, etc.) exception stack trace and ended with more questions than answers?
If so, let me talk to you about a bulletproof technique: the good-old “tracing” technique.</p>
<h2 id="tracing">Tracing</h2>
<p>What I call “tracing” is logging every relevant step of the program, so you quickly understand what went wrong.</p>
<p>In a sense, it’s like using a debugger to execute the program step by step, except that you cannot interact with the program, you can only view what happened.</p>
<p>Everybody does that already: you add a little <code class="language-plaintext highlighter-rouge">Serial.print()</code> here… another there… until you find what line causes a crash.</p>
<p>Everybody does that but, unfortunately, there are is facility to help us, so everyone rolls her custom solution. That’s why I created <a href="https://github.com/bblanchon/ArduinoTrace">ArduinoTrace</a>, a small library to trace Arduino programs with minimal effort.</p>
<p>Here is how you use the library:</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include <ArduinoTrace.h>
</span>
<span class="kt">int</span> <span class="n">someValue</span> <span class="o">=</span> <span class="mi">42</span><span class="p">;</span>
<span class="kt">void</span> <span class="nf">setup</span><span class="p">()</span> <span class="p">{</span>
<span class="n">Serial</span><span class="p">.</span><span class="n">begin</span><span class="p">(</span><span class="mi">9600</span><span class="p">);</span>
<span class="n">DUMP</span><span class="p">(</span><span class="n">someValue</span><span class="p">);</span>
<span class="p">}</span>
<span class="kt">void</span> <span class="nf">loop</span><span class="p">()</span> <span class="p">{</span>
<span class="n">TRACE</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>
<p>The program above would print:</p>
<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>E:\MyProgram\MyProgram.ino:7: someValue = 42
E:\MyProgram\MyProgram.ino:11: void loop()
E:\MyProgram\MyProgram.ino:11: void loop()
E:\MyProgram\MyProgram.ino:11: void loop()
...
</code></pre></div></div>
<p>All you need to do is call <code class="language-plaintext highlighter-rouge">TRACE()</code> and <code class="language-plaintext highlighter-rouge">DUMP(variable)</code>.
Simple isn’t it?</p>
<h2 id="do-you-want-to-write-trace-yourself">Do you want to write TRACE() yourself?</h2>
<p>This library is very simple; in fact, you could even write the two macros yourself.</p>
<p>I made a video that shows how to write your own TRACE() macro, because I believe it’s important to look under the hood.</p>
<p>The video shows how I used tracing to fix the latest bug in <a href="https://arduinojson.org">ArduinoJson</a> and how it lead to the creation of <a href="https://github.com/bblanchon/ArduinoTrace">ArduinoTrace</a>.</p>
<div style="max-width: 500px; margin: 0 auto;">
<div class="video-container">
<iframe src="https://www.youtube.com/embed/JHMpszgzWSg" frameborder="0" allowfullscreen=""></iframe>
</div>
</div>
<h2 id="a-word-of-caution">A word of caution</h2>
<p>As I say in the video, tracing is not an alternative to logging.
Logging (most likely to an SD card) is something you do in production;
whereas tracing is something you do during very short periods, while looking for a bug.</p>
<p>Also, tracing is not an alternative to unit testing;
I only trace a program when I cannot reproduce the bug with a unit test,
like the Flash memory issue I show in the video.</p>
<p>Moreover, there is a severe performance hit when you add traces: the program gets fat and slow.
That’s another reason why you should always remove every traces before committing your source files.</p>
<hr />
<p>Please check out <a href="https://github.com/bblanchon/ArduinoTrace">ArduinoTrace</a>, and don’t forget to add a GitHub star to help me get the word out! Thanks ;-)</p>
<p>CAUTION: At the time I wrote this article, <a href="https://github.com/arduino/Arduino/issues/7799">the library was not yet available in the Arduino Library Manager</a>. If it’s still the case, clone the project in the library folder as for any other library.</p>Have you ever been faced with an Arduino board that crashes for no apparent reason? Have you ever decode an ESP8266 (ESP32, etc.) exception stack trace and ended with more questions than answers? If so, let me talk to you about a bulletproof technique: the good-old “tracing” technique.[Arduino] Making sense of compiler errors2018-04-17T00:00:00+00:002018-04-17T00:00:00+00:00https://blog.benoitblanchon.fr/arduino-compiler-errors<p><a href="https://arduinojson.org/?utm_source=blog&utm_medium=article">ArduinoJson</a> makes intensive use of templates and whenever there is an error, the compiler produces a long output that is very hard to digest.</p>
<p>In particular, it’s very difficult to see where the error is located. Is it in your code? Or is it in the library?
The compiler clearly shows that the error happened in the code of the library, but is this really a bug in ArduinoJson?</p>
<p>If you ever got in this situation, this article is for you!
We’ll see how to decipher the compiler output and get to the root of the error.</p>
<h2 id="our-example">Our example</h2>
<p>This blog post is inspired by an <a href="https://github.com/bblanchon/ArduinoJson/issues/591">issue submitted by John D. Allen</a>:</p>
<blockquote>
<p>Getting a number of errors when trying to use the library on a Adafruit Feather M0 (ATSAMD21G18 ARM Cortex M0 processor):</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>In file included from D:\libraries\ArduinoJson\src\ArduinoJson\Deserialization\..\JsonVariantBase.hpp:10:0,
from D:\libraries\ArduinoJson\src\ArduinoJson\Deserialization\..\JsonVariant.hpp:16,
from D:\libraries\ArduinoJson\src\ArduinoJson\Deserialization\..\JsonBuffer.hpp:15,
from D:\libraries\ArduinoJson\src\ArduinoJson\Deserialization\JsonParser.hpp:10,
from D:\libraries\ArduinoJson\src\ArduinoJson\JsonBufferBase.hpp:10,
from D:\libraries\ArduinoJson\src\ArduinoJson\DynamicJsonBuffer.hpp:10,
from D:\libraries\ArduinoJson\src\ArduinoJson.hpp:10,
from D:\libraries\ArduinoJson\src\ArduinoJson.h:10,
from D:\MyProgram\MyProgram.ino:7:
D:\libraries\ArduinoJson\src\ArduinoJson\Deserialization\..\JsonVariantCasts.hpp: In instantiation of 'ArduinoJson::JsonVariantCasts<TImpl>::operator T() const [with T = char*; TImpl = ArduinoJson::JsonObjectSubscript<const int&>]':
D:\MyProgram\MyProgram.ino:132:27: required from here
D:\libraries\ArduinoJson\src\ArduinoJson\Deserialization\..\JsonVariantCasts.hpp:52:35: error: invalid conversion from 'ArduinoJson::Internals::JsonVariantAs<char*>::type {aka const char*}' to 'char*' [-fpermissive]
return impl()->template as<T>();
^
In file included from D:\libraries\ArduinoJson\src\ArduinoJson.hpp:12:0,
from D:\libraries\ArduinoJson\src\ArduinoJson.h:10,
from D:\MyProgram\MyProgram.ino:7:
D:\libraries\ArduinoJson\src\ArduinoJson\JsonObject.hpp: In instantiation of 'ArduinoJson::Internals::List<ArduinoJson::JsonPair>::iterator ArduinoJson::JsonObject::findKey(TStringRef) [with TStringRef = const int&; ArduinoJson::Internals::List<ArduinoJson::JsonPair>::iterator = ArduinoJson::Internals::ListIterator<ArduinoJson::JsonPair>]':
D:\libraries\ArduinoJson\src\ArduinoJson\JsonObject.hpp:305:66: required from 'ArduinoJson::Internals::List<ArduinoJson::JsonPair>::const_iterator ArduinoJson::JsonObject::findKey(TStringRef) const [with TStringRef = const int&; ArduinoJson::Internals::List<ArduinoJson::JsonPair>::const_iterator = ArduinoJson::Internals::ListConstIterator<ArduinoJson::JsonPair>]'
D:\libraries\ArduinoJson\src\ArduinoJson\JsonObject.hpp:311:48: required from 'typename ArduinoJson::Internals::JsonVariantAs<TValue>::type ArduinoJson::JsonObject::get_impl(TStringRef) const [with TStringRef = const int&; TValue = char*; typename ArduinoJson::Internals::JsonVariantAs<TValue>::type = const char*]'
D:\libraries\ArduinoJson\src\ArduinoJson\JsonObject.hpp:172:48: required from 'typename ArduinoJson::TypeTraits::EnableIf<(! ArduinoJson::TypeTraits::IsArray<TString>::value), typename ArduinoJson::Internals::JsonVariantAs<T>::type>::type ArduinoJson::JsonObject::get(const TString&) const [with TValue = char*; TString = int; typename ArduinoJson::TypeTraits::EnableIf<(! ArduinoJson::TypeTraits::IsArray<TString>::value), typename ArduinoJson::Internals::JsonVariantAs<T>::type>::type = const char*]'
D:\libraries\ArduinoJson\src\ArduinoJson\JsonObjectSubscript.hpp:64:36: required from 'typename ArduinoJson::Internals::JsonVariantAs<T>::type ArduinoJson::JsonObjectSubscript<TKey>::as() const [with TValue = char*; TStringRef = const int&; typename ArduinoJson::Internals::JsonVariantAs<T>::type = const char*]'
D:\libraries\ArduinoJson\src\ArduinoJson\Deserialization\..\JsonVariantCasts.hpp:52:35: required from 'ArduinoJson::JsonVariantCasts<TImpl>::operator T() const [with T = char*; TImpl = ArduinoJson::JsonObjectSubscript<const int&>]'
D:\MyProgram\MyProgram.ino:132:27: required from here
D:\libraries\ArduinoJson\src\ArduinoJson\JsonObject.hpp:299:67: error: 'equals' is not a member of 'ArduinoJson::Internals::StringTraits<const int&, void>'
if (Internals::StringTraits<TStringRef>::equals(key, it->key)) break;
^
exit status 1
Error compiling for board Adafruit Feather M0.
</code></pre></div> </div>
<p>Since none of this actually references my code, I am assuming its an issue with the library and/or my environment.</p>
<p>This issue doesn’t seem to match any of the Known Issues in the FAQ from what I could see.</p>
</blockquote>
<p>That is clearly a good issue, we can definitely tell that John took time to understand what’s going on. He analyzed the compiler output and legitimately supposed that there was a problem with the library.</p>
<p>Now, let’s see how with can make sense of this compiler gibberish.</p>
<h3 id="how-gcc-presents-errors">How GCC presents errors</h3>
<p>Most of the time, the compiler will produce more than one error or warning; it’s essential to treat them one by one.</p>
<p>Start by finding the first line where the word <code class="language-plaintext highlighter-rouge">error:</code> or <code class="language-plaintext highlighter-rouge">warning:</code> appears, because it contains the location where the first problem was detected. If there are errors and warnings, choose the first of all because a warning often gives clues to understand an error.</p>
<p>Have a look at the picture below, where I highlighted the two errors from the listing above:</p>
<p><img src="/images/2018/04/compiler-errors.png" alt="Highlighted parts of compiler output" /></p>
<p>As you can see, each error contains many lines within three groups:</p>
<ol type="A">
<li>the "inclusion stack," which shows how the file was included,</li>
<li>the "instantiation stack," which shows how the type was instantiated,</li>
<li>the actual error message.</li>
</ol>
<p>You need to start your investigation from group C, as it contains the actual error message and shows where the error was detected. But this line shows the <em>effect</em>, not the <em>cause</em>, so we need to investigate further to find the cause.</p>
<p>The group B shows the steps that the compiler followed to build the erroneous line. It’s a stack of instantiation, and it’s very similar to the call stack that you see in a debugger. At the top of the stack is the most recent instantiation (the closest to the error), the second line is the instantiation that led to the first, etc. At the bottom of the stack is the original instantiation (the closest to the program), this is usually the root cause of the error.</p>
<p>Finally, group A shows how the erroneous file was included in the program. It shows which file included which, up to the root <code class="language-plaintext highlighter-rouge">.cpp</code> or <code class="language-plaintext highlighter-rouge">.ino</code>. It is rarely useful.</p>
<h3 id="the-first-error-in-our-example">The first error in our example</h3>
<p>When the output is complex, it can be useful to copy the content in a powerful text editor (I use Sublime Text). From there, we can remove the noise (long file path and namespace) and align the output to make it more readable.</p>
<p>Here is our first error (group C) after simplifying file path and namespaces:</p>
<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>JsonVariantCasts.hpp:52:35: error: invalid conversion from
'JsonVariantAs<char*>::type {aka const char*}' to 'char*' [-fpermissive]
</code></pre></div></div>
<p>This line tells that the compiler is unhappy to convert a <code class="language-plaintext highlighter-rouge">const char*</code> into a <code class="language-plaintext highlighter-rouge">char*</code>. Indeed this conversion is forbidden because it would lose the constness of the pointer. This error appears in ArduinoJson’s source code, but it’s the <em>effect</em>, not the <em>cause</em>, we need to look at the instantiation stack (group B) to find the cause:</p>
<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>JsonVariantCasts.hpp: In instantiation of
'JsonVariantCasts<TImpl>::operator T() const [with T = char*]':
MyProgram.ino:132:27: required from here
</code></pre></div></div>
<p>It’s usually easier to start at the bottom of the stack because it points to a line in the calling program. In this case, the line 132 of <code class="language-plaintext highlighter-rouge">MyProgram.ino</code> contains:</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">char</span><span class="o">*</span> <span class="n">value</span> <span class="o">=</span> <span class="n">obj</span><span class="p">[</span><span class="n">key</span><span class="p">];</span>
</code></pre></div></div>
<p>Indeed, this line contains an error: <code class="language-plaintext highlighter-rouge">JsonObject</code> returns strings as <code class="language-plaintext highlighter-rouge">const char*</code>, not <code class="language-plaintext highlighter-rouge">char*</code>, hence the invalid conversion. We can fix this error by adding <code class="language-plaintext highlighter-rouge">const</code> in front of <code class="language-plaintext highlighter-rouge">char*</code>:</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">value</span> <span class="o">=</span> <span class="n">obj</span><span class="p">[</span><span class="n">key</span><span class="p">];</span>
</code></pre></div></div>
<p>This error was easy to find, no need to unroll the instantiation stack. Let’s see the second.</p>
<h3 id="the-second-error-in-our-example">The second error in our example</h3>
<p>The second error message (group C) is:</p>
<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>JsonObject.hpp:299:error: 'equals' is not a member of 'StringTraits<const int&>'
if (StringTraits<TStringRef>::equals(key, it->key)) break;
</code></pre></div></div>
<p>This line says that compiler is looking for a member named <code class="language-plaintext highlighter-rouge">equals</code> in the type <code class="language-plaintext highlighter-rouge">StringTraits<const int&></code>, but this member doesn’t exist. As the author of the library, I know what it means, but it’s probably not your case. To understand what is happening, we need to look at the instantiation stack (group B):</p>
<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>JsonObject.hpp: In instantiation of 'ArduinoJson::Internals::List<Arduino...
JsonObject.hpp:305: required from 'ArduinoJson::Internals::List<JsonP...
JsonObject.hpp:311: required from 'typename ArduinoJson::Internals::J...
JsonObject.hpp:172: required from 'typename ArduinoJson::TypeTraits::...
JsonObjectSubscript.hpp:64: required from 'typename ArduinoJson::Internals::J...
JsonVariantCasts.hpp:52: required from 'ArduinoJson::JsonVariantCasts<TImp...
MyProgram.ino:132: required from here
</code></pre></div></div>
<p>The bottom of the stack is the same as for the first error, meaning that the same line generated two compiler errors. We already looked at this line, and it’s not apparent why it has any relation to <code class="language-plaintext highlighter-rouge">StringTraits<const int&></code>. To understand further, we need to unroll the stack: start from the bottom and climb up one line after the other.</p>
<p>The next instantiation (second from the bottom of group B) is:</p>
<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>JsonVariantCasts.hpp:52: required from
'ArduinoJson::JsonVariantCasts<TImpl>::operator T() const
[with T = char*;
TImpl = ArduinoJson::JsonObjectSubscript<const int&>]'
</code></pre></div></div>
<p>Again, the meaning is probably very obscure to you, so let’s move to the next instantiation (third from the bottom):</p>
<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>JsonObjectSubscript.hpp:64: required from
'typename ArduinoJson::Internals::JsonVariantAs<T>::type
ArduinoJson::JsonObjectSubscript<TKey>::as() const
[with TValue = char*;
TStringRef = const int&;
ArduinoJson::Internals::JsonVariantAs<T>::type = const char*]'
</code></pre></div></div>
<p>This line starts to make sense. First, we see the <code class="language-plaintext highlighter-rouge">char*</code> and <code class="language-plaintext highlighter-rouge">const char*</code> that were causing the first error. Then, we see something very suspicious: <code class="language-plaintext highlighter-rouge">TStringRef = const int&</code>, which means that a template type named <code class="language-plaintext highlighter-rouge">TStringRef</code> has been set to <code class="language-plaintext highlighter-rouge">const int&</code>. From the name <code class="language-plaintext highlighter-rouge">TStringRef</code>, we can infer that a string reference is expected, but it is currently set to an <code class="language-plaintext highlighter-rouge">int</code> reference.</p>
<p>Let’s look at the next instantiation (fourth from the bottom):</p>
<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>JsonObject.hpp:172: required from
'typename ArduinoJson::TypeTraits::EnableIf<
(! ArduinoJson::TypeTraits::IsArray<TString>::value),
typename ArduinoJson::Internals::JsonVariantAs<T>::type>::type
ArduinoJson::JsonObject::get(const TString&) const
[with TValue = char*;
TString = int;
ArduinoJson::Internals::JsonVariantAs<T>::type>::type = const char*]'
</code></pre></div></div>
<p>This line is very long, please focus on the middle part, where we see a call to <code class="language-plaintext highlighter-rouge">JsonObject::get()</code>. Let’s go back to our original program; here is line 132 of <code class="language-plaintext highlighter-rouge">MyProgram.ino</code>:</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">char</span><span class="o">*</span> <span class="n">value</span> <span class="o">=</span> <span class="n">obj</span><span class="p">[</span><span class="n">key</span><span class="p">];</span>
</code></pre></div></div>
<p>During the compilation phase, this statement was transformed into the following:</p>
<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">char</span><span class="o">*</span> <span class="n">value</span> <span class="o">=</span> <span class="n">obj</span><span class="p">.</span><span class="n">get</span><span class="o"><</span><span class="kt">char</span><span class="o">*></span><span class="p">(</span><span class="n">key</span><span class="p">);</span>
</code></pre></div></div>
<p>The transformation occurred in the two first instantiations: <code class="language-plaintext highlighter-rouge">JsonVariantCasts.hpp:52</code> and <code class="language-plaintext highlighter-rouge">JsonObjectSubscript.hpp:64</code>.</p>
<p>But this call to <code class="language-plaintext highlighter-rouge">JsonObject::get(const TString&)</code> is problematic. Indeed, the compiler says <code class="language-plaintext highlighter-rouge">TString = int</code>, which means the template type <code class="language-plaintext highlighter-rouge">TString</code> was deduced as <code class="language-plaintext highlighter-rouge">int</code>, whereas a string is expected.</p>
<p>The bug is now right before our eyes: the variable <code class="language-plaintext highlighter-rouge">key</code> is not a string, it’s an <code class="language-plaintext highlighter-rouge">int</code>!</p>
<p>Indeed a <code class="language-plaintext highlighter-rouge">JsonObject</code> is indexed by a string, and ArduinoJson doesn’t support accessing a key-value pair by its index. To fix this error, we must change the type of the variable <code class="language-plaintext highlighter-rouge">key</code> to one of the supported string types: <code class="language-plaintext highlighter-rouge">const char*</code>, <code class="language-plaintext highlighter-rouge">String</code>…</p>
<h2 id="conclusion">Conclusion</h2>
<p>In this article, we saw how GCC (the compiler used by Arduino) presents the errors: there is the error message, the instantiation stack, and the inclusion stack.</p>
<p>We saw that to understand what caused the error, we need to start with the error and unroll the instantiation stack until it makes sense.</p>
<p>For a seasoned C++ programmers, this was a piece of cake. But for a programmer that has never been in contact with templates, this can be a lot to swallow. Don’t worry, this will pass quickly, after one or two analysis of this kind, it’ll become a second nature :-)</p>
<h2 id="want-to-learn-more">Want to learn more?</h2>
<p>If you liked this article, you should check out my book <a href="https://arduinojson.org/book/?utm_source_=blog&utm_medium=article">Mastering ArduinoJson</a>; it explains everything there is to know about the library and a lot more.</p>
<p>In fact, this article is a section from the “Troubleshooting” chapter, but there are a lot of other C++ related stuff. In particular, the book begins with a quick C++ course; this chapter will refresh you with pointers, references, and memory management, which are very common sources of pain among Arduino developers.</p>ArduinoJson makes intensive use of templates and whenever there is an error, the compiler produces a long output that is very hard to digest.Dead simple breadcrumbs for GitHub Pages2018-02-14T07:59:57+00:002018-02-14T07:59:57+00:00https://blog.benoitblanchon.fr/dead-simple-breadcrumbs-for-github-pages<p>In a <a href="/semi-automatic-breadcrumbs-for-github-pages/">previous article</a>, I presented a simple technique to generate breadcrumbs on a Jekyll website; and, since it didn’t require any plugin, it can be used on GitHub Pages too. This technique used the URL of the page to generate the breadcrumbs; today, I’m presenting an even simpler solution base on the page’s categories.</p>
<p><img src="/images/2018/02/artur-rutkowski-112531.jpg" alt="Fresh Rustic Bread" /></p>
<p><em>Photo by <a href="https://unsplash.com/photos/VWQM9MbZDDw">Artur Rutkowski</a> on <a href="https://unsplash.com/search/photos/bread-sliced">Unsplash</a></em></p>
<p>Using the page’s category instead of using the page’s URL presents a huge benefit: it decouples the breadcrumbs from the URL. Now, you can change the navigation on the site without changing any URLs, so without breaking any links.</p>
<p>Depending on the configuration, Jekyll can also use the page category to determine its URL. In that case, the breadcrumbs are aligned with the URL of the page, so we get the same result as before, but with increased flexibility.</p>
<h2 id="listing-the-possible-paths">Listing the possible paths</h2>
<p>As before, we use a data file to store the breadcrumb information. In this case, we just want to associate the name of a category with a breadcrumb name and URL. Here is our <code class="language-plaintext highlighter-rouge">_data/breadcrumbs.yaml</code>:</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">doc</span><span class="pi">:</span>
<span class="na">title</span><span class="pi">:</span> <span class="s">Documentation</span>
<span class="na">url</span><span class="pi">:</span> <span class="s">/doc/</span>
<span class="na">api</span><span class="pi">:</span>
<span class="na">title</span><span class="pi">:</span> <span class="s">API</span>
<span class="na">url</span><span class="pi">:</span> <span class="s">/api/</span>
<span class="na">faq</span><span class="pi">:</span>
<span class="na">title</span><span class="pi">:</span> <span class="s">FAQ</span>
<span class="na">url</span><span class="pi">:</span> <span class="s">/faq/</span>
<span class="na">news</span><span class="pi">:</span>
<span class="na">title</span><span class="pi">:</span> <span class="s">News</span>
<span class="na">url</span><span class="pi">:</span> <span class="s">/news/</span>
</code></pre></div></div>
<p>For example, the category <code class="language-plaintext highlighter-rouge">doc</code> corresponds to the page <code class="language-plaintext highlighter-rouge">Documentation</code> located at <code class="language-plaintext highlighter-rouge">/doc/</code>.</p>
<h2 id="display-the-crumb">Display the crumb</h2>
<p>Now, let’s see how we can generate the HTML code. We just need to find the breadcrumb for each category of the page.</p>
<p>Here is the complete code in:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><ol</span> <span class="na">class=</span><span class="s">"breadcrumb"</span><span class="nt">></span>
<span class="nt"><li</span> <span class="na">class=</span><span class="s">"breadcrumb-item"</span><span class="nt">></span>
<span class="nt"><a</span> <span class="na">href=</span><span class="s">"{{ site.baseurl }}"</span><span class="nt">></span>Home<span class="nt"></a></span>
<span class="nt"></li></span>
{%- for category in page.categories -%}
{%- assign crumb = site.data.breadcrumbs[category] -%}
{%- if crumb -%}
<span class="nt"><li</span> <span class="na">class=</span><span class="s">"breadcrumb-item"</span><span class="nt">></span>
<span class="nt"><a</span> <span class="na">href=</span><span class="s">"{{ crumb.url | prepend: site.baseurl }}"</span><span class="nt">></span>
{{ crumb.title }}
<span class="nt"></a></span>
<span class="nt"></li></span>
{%- endif -%}
{%- endfor -%}
<span class="nt"><li</span> <span class="na">class=</span><span class="s">"breadcrumb-item active"</span><span class="nt">></span>
<span class="nt"><a</span> <span class="na">href=</span><span class="s">"{{ page.url | prepend: site.baseurl }}"</span><span class="nt">></span>
{{ page.title }}
<span class="nt"></a></span>
<span class="nt"></li></span>
<span class="nt"></ol></span>
</code></pre></div></div>
<h2 id="an-example">An example</h2>
<p>As an example, let’s see how the code works for the page <a href="https://arduinojson.org/api/jsonarray/?utm_source=blog&utm_medium=article">JsonArray</a>.</p>
<p>Here is the front matter:</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">title</span><span class="pi">:</span> <span class="s">JsonArray</span>
<span class="na">categories</span><span class="pi">:</span> <span class="s">doc api</span>
</code></pre></div></div>
<p>The beginning of the template produces the breadbrumb for the <code class="language-plaintext highlighter-rouge">Home</code>:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><ol</span> <span class="na">class=</span><span class="s">"breadcrumb"</span><span class="nt">></span>
<span class="nt"><li</span> <span class="na">class=</span><span class="s">"breadcrumb-item"</span><span class="nt">></span>
<span class="nt"><a</span> <span class="na">href=</span><span class="s">"/"</span><span class="nt">></span>Home<span class="nt"></a></span>
<span class="nt"></li></span>
</code></pre></div></div>
<p>Then, the <code class="language-plaintext highlighter-rouge">for</code> loop extracts the first category: <code class="language-plaintext highlighter-rouge">doc</code>. The <code class="language-plaintext highlighter-rouge">assign</code> statement retreives the matching breadcrumb from <code class="language-plaintext highlighter-rouge">site.data.breadcrumbs</code>. This breadcrumb is named <code class="language-plaintext highlighter-rouge">Documentation</code> and points to <code class="language-plaintext highlighter-rouge">/doc/</code>. So, the first iteration of the loop produces:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nt"><li</span> <span class="na">class=</span><span class="s">"breadcrumb-item"</span><span class="nt">></span>
<span class="nt"><a</span> <span class="na">href=</span><span class="s">"/doc/"</span><span class="nt">></span>
Documentation
<span class="nt"></a></span>
<span class="nt"></li></span>
</code></pre></div></div>
<p>The same happends for the second category: <code class="language-plaintext highlighter-rouge">api</code>. The second iteration produces:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nt"><li</span> <span class="na">class=</span><span class="s">"breadcrumb-item"</span><span class="nt">></span>
<span class="nt"><a</span> <span class="na">href=</span><span class="s">"/api/"</span><span class="nt">></span>
API
<span class="nt"></a></span>
<span class="nt"></li></span>
</code></pre></div></div>
<p>Finally, the end of the template produces the breadcrumb for the current page:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nt"><li</span> <span class="na">class=</span><span class="s">"breadcrumb-item active"</span><span class="nt">></span>
<span class="nt"><a</span> <span class="na">href=</span><span class="s">"/api/jsonarray/"</span><span class="nt">></span>
JsonArray
<span class="nt"></a></span>
<span class="nt"></li></span>
<span class="nt"></ol></span>
</code></pre></div></div>
<p>Here a screen capture of the result:</p>
<p><img src="/images/2018/02/jsonarray-breadcrumbs.png" alt="Screen capture" /></p>In a previous article, I presented a simple technique to generate breadcrumbs on a Jekyll website; and, since it didn’t require any plugin, it can be used on GitHub Pages too. This technique used the URL of the page to generate the breadcrumbs; today, I’m presenting an even simpler solution base on the page’s categories.