CFWheels HTMX Plugin published

June 20, 2022

Posted in Plugin, Releases, Tutorials

Posted By: Peter Amiri

<p>A few weeks ago I published a <a href="/blog/todomvc-implementation-with-cfwheels-and-htmx/" rel="noopener noreferrer" target="_blank">Todo app</a> using CFWheels on the backend and HTMX to provide the interactivity on the front end to make the app look and feel like a full blown SPA app. As I was developing that app I ran into a few things that I wish we had to make development with HTMX a little easier. But I'm getting ahead of myself.</p><h3>What is HTMX</h3><p>Well, HTMX was released a couple of years ago and in that short time has just about exploded in the django community. So what is HTMX, HTMX tries to answer the following questions:</p><ol><li data-list="bullet"><span class="ql-ui" contenteditable="false"></span>Why should only&nbsp;<code><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a" rel="noopener noreferrer" target="_blank">&lt;a&gt;</a></code>&nbsp;and&nbsp;<code><a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form" rel="noopener noreferrer" target="_blank">&lt;form&gt;</a></code>&nbsp;be able to make HTTP requests?</li><li data-list="bullet"><span class="ql-ui" contenteditable="false"></span>Why should only&nbsp;<code><a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/click_event" rel="noopener noreferrer" target="_blank">click</a></code>&nbsp;&amp;&nbsp;<code><a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/submit_event" rel="noopener noreferrer" target="_blank">submit</a></code>&nbsp;events trigger them?</li><li data-list="bullet"><span class="ql-ui" contenteditable="false"></span>Why should only&nbsp;<code><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/GET" rel="noopener noreferrer" target="_blank">GET</a></code>&nbsp;&amp;&nbsp;<code><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST" rel="noopener noreferrer" target="_blank">POST</a></code>&nbsp;methods be&nbsp;<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods" rel="noopener noreferrer" target="_blank">available</a>?</li><li data-list="bullet"><span class="ql-ui" contenteditable="false"></span>Why should you only be able to replace the&nbsp;<strong>entire</strong>&nbsp;screen?</li></ol><p>By removing these arbitrary constraints, htmx completes HTML as a&nbsp;<a href="https://en.wikipedia.org/wiki/Hypertext" rel="noopener noreferrer" target="_blank">hypertext</a> medium. You may even start wondering why these features weren't in HTML in the first place. So let's look at an examples.</p><div class="ql-code-block-container" spellcheck="false"><select class="ql-ui" contenteditable="false"><option value="plain">Plain</option><option value="bash">Bash</option><option value="cpp">C++</option><option value="cs">C#</option><option value="css">CSS</option><option value="diff">Diff</option><option value="xml">HTML/XML</option><option value="java">Java</option><option value="javascript">JavaScript</option><option value="markdown">Markdown</option><option value="php">PHP</option><option value="python">Python</option><option value="ruby">Ruby</option><option value="sql">SQL</option></select><div class="ql-code-block" data-language="plain"> &lt;script src="https://unpkg.com/htmx.org@1.7.0"&gt;&lt;/script&gt;</div><div class="ql-code-block" data-language="plain"> &lt;!-- have a button POST a click via AJAX --&gt;</div><div class="ql-code-block" data-language="plain"> &lt;button hx-post="/clicked" hx-swap="outerHTML"&gt;</div><div class="ql-code-block" data-language="plain"> Click Me</div><div class="ql-code-block" data-language="plain"> &lt;/button&gt;</div></div><p>This block of code tells the browser:</p><blockquote>When a user clicks on this button, issue an AJAX request to /clicked, and replace the entire button with the HTML response.</blockquote><p>Let's look at a typical anchor tag:</p><div class="ql-code-block-container" spellcheck="false"><select class="ql-ui" contenteditable="false"><option value="plain">Plain</option><option value="bash">Bash</option><option value="cpp">C++</option><option value="cs">C#</option><option value="css">CSS</option><option value="diff">Diff</option><option value="xml">HTML/XML</option><option value="java">Java</option><option value="javascript">JavaScript</option><option value="markdown">Markdown</option><option value="php">PHP</option><option value="python">Python</option><option value="ruby">Ruby</option><option value="sql">SQL</option></select><div class="ql-code-block" data-language="plain">&lt;a href="/blog"&gt;Blog&lt;/a&gt;</div></div><p>This anchor tag tells the browser:</p><blockquote>When a user clicks on this link, issue an HTTP GET request to '/blog' and load the response content into the browser window.</blockquote><p>You can see how HTMX feels like a familiar extension to HTML. With this in mind lets look at a following block of HTML:</p><div class="ql-code-block-container" spellcheck="false"><select class="ql-ui" contenteditable="false"><option value="plain">Plain</option><option value="bash">Bash</option><option value="cpp">C++</option><option value="cs">C#</option><option value="css">CSS</option><option value="diff">Diff</option><option value="xml">HTML/XML</option><option value="java">Java</option><option value="javascript">JavaScript</option><option value="markdown">Markdown</option><option value="php">PHP</option><option value="python">Python</option><option value="ruby">Ruby</option><option value="sql">SQL</option></select><div class="ql-code-block" data-language="plain">&lt;button hx-post="/clicked"</div><div class="ql-code-block" data-language="plain"> hx-trigger="click"</div><div class="ql-code-block" data-language="plain"> hx-target="#parent-div"</div><div class="ql-code-block" data-language="plain"> hx-swap="outerHTML"</div><div class="ql-code-block" data-language="plain">&gt;</div><div class="ql-code-block" data-language="plain"> Click Me!</div><div class="ql-code-block" data-language="plain">&lt;/button&gt;</div></div><p>This tells HTMX:</p><blockquote>When a user clicks on this button, issue an HTTP POST request to '/clicked' and use the content from the response to replace the element with the id&nbsp;<code>parent-div</code>&nbsp;in the DOM</blockquote><p>So by using <code>hx-get</code>, <code>hx-post</code>, <code>hx-put</code>, <code>hx-patch</code>, or <code>hx-delete</code> we gain access to all the HTTP verbs. Imagine a delete button on a table row that actually issues a HTTP Delete to your backend.</p><p>The <code>hx-trigger</code> attribute gives us access to all the page events. HTML elements have sensible defaults, the <code>button</code> tag will get triggered by a click by default and an <code>input</code> tag will get triggered by a change event by default. But there are some special events as well, like the <code>load</code> event that will trigger the action when the page is initially loaded or the <code>revealed</code> event that will trigger the action, when the element scrolls into view. Think of an infinite scroll UX pattern where an element scrolls into view, which triggers a call to the backend to load more data that gets added to the bottom of the page.</p><p>The <code>hx-target</code> attribute lets you specify a different tag to target than the element that triggered the event. You have the typical CSS selectors but also some special syntax like <code>closest TR</code> to target the closest table row.</p><p>The last attribute shown in the example above is the <code>hx-swap</code> which specifies how to swap the response into the element. By default, the response replaces the <code>innerHTML</code> of the target element but you can just as easily replace the entire target element by using <code>outerHTML</code>. There are a few more designators that allow you to finely control placing the response before or after the target element in its parent element or at the begging of or end of a target's child elements.</p><p>This is just scratching the surface of what HTMX can do but you should be getting the picture. By sprinkling in a handful of HTML attributes into your markup you can gain interactivity that was the domain of full blown JavaScript frontend frameworks in the past.</p><h3>Why should we care as CFWheels developers</h3><p>By default HTMX is backend agnostic. It just deals with HTML and doesn't care what backend technology you use to generate it. This could just as easily be used in a plain vanilla CFML app or your framework of choice, hopefully it would be CFWheels since you are here reading this. Wheels has some built in features that make working with HTMX a breeze. We already have a templating system, we already have a router and controllers to intercept the HTTP request. We have a number of rendering methods that make responding to requests simple.</p><p>If the request is a for a full page, use the <code>renderView()</code> method or simply let the controller hand the request off to the view which in turn renders the view page. If the request is for a portion of the final page then use the <code>renderPartial()</code> method and return a snippet of code tucked away in a partial. The same partial could be used by your initial view page, keeping your code DRY. Sometimes, you just want to return a small bit of text or no text at all and it doesn't make sense to build out a view or partial for every instance of these scenarios, that's when the <code>renderText()</code> method comes in handy. Imagine a typical index page from a CRUD application that lists a bunch of rows of data and some action buttons on each row. Let's assume, one of these buttons is a delete button. Look at the following code:</p><div class="ql-code-block-container" spellcheck="false"><select class="ql-ui" contenteditable="false"><option value="plain">Plain</option><option value="bash">Bash</option><option value="cpp">C++</option><option value="cs">C#</option><option value="css">CSS</option><option value="diff">Diff</option><option value="xml">HTML/XML</option><option value="java">Java</option><option value="javascript">JavaScript</option><option value="markdown">Markdown</option><option value="php">PHP</option><option value="python">Python</option><option value="ruby">Ruby</option><option value="sql">SQL</option></select><div class="ql-code-block" data-language="plain">// image this button on a table row</div><div class="ql-code-block" data-language="plain">&lt;button hx-delete="/products/15" </div><div class="ql-code-block" data-language="plain"> hx-target="closest hr"</div><div class="ql-code-block" data-language="plain"> hx-swap="outerHTML"</div><div class="ql-code-block" data-language="plain"> hx-confirm="Are you sure?"&gt;</div><div class="ql-code-block" data-language="plain"> Delete</div><div class="ql-code-block" data-language="plain">&lt;/button&gt;</div><div class="ql-code-block" data-language="plain"><br></div><div class="ql-code-block" data-language="plain">// imagine this code in your action</div><div class="ql-code-block" data-language="plain">function delete() {</div><div class="ql-code-block" data-language="plain"> aProduct = model("product").findByKey(params.key);</div><div class="ql-code-block" data-language="plain"> aProduct.delete();</div><div class="ql-code-block" data-language="plain"> renderText("");</div><div class="ql-code-block" data-language="plain">}</div></div><p>So what does the above combination do:</p><blockquote>When the user clicks on the Delete button, prompt the user to make sure they are sure they wish to delete the record, if the user affirms the request, issue a DELETE request to the server. The server in turn deletes the record and sends back an empty text response to the client. When the response comes back to the frontend, find the closes table row, and remove it from the table.</blockquote><p>We just made an Ajax call to the server, removed the record from the database, and correspondingly updated the UI by just removing a single element from the DOM.</p><h3>What does this plugin do?</h3><p>By default, HTMX adds some request headers to the call sent to the backend which can be interrogated to see if the request is in fact an HTMX request. If the request is actually an HTMX request, some additional request headers are made available which can add more color to the call being processed. This plugin automatically adds these header elements to the <code>params</code> structure which makes them automatically available to your controller actions. This makes it easier to work with this data and incorporate it into your request processing logic. Take a look at the following example:</p><div class="ql-code-block-container" spellcheck="false"><select class="ql-ui" contenteditable="false"><option value="plain">Plain</option><option value="bash">Bash</option><option value="cpp">C++</option><option value="cs">C#</option><option value="css">CSS</option><option value="diff">Diff</option><option value="xml">HTML/XML</option><option value="java">Java</option><option value="javascript">JavaScript</option><option value="markdown">Markdown</option><option value="php">PHP</option><option value="python">Python</option><option value="ruby">Ruby</option><option value="sql">SQL</option></select><div class="ql-code-block" data-language="plain">function index() {</div><div class="ql-code-block" data-language="plain"> if (params.htmx.reqeust) {</div><div class="ql-code-block" data-language="plain"> renderPartial(partial="myPartial", layout="false");</div><div class="ql-code-block" data-language="plain"> }</div><div class="ql-code-block" data-language="plain">}</div></div><p>This code block says:</p><blockquote>When a request comes in to the index action of the controller, check to see if this is an HTMX request and if it is, respond with the <code>mypartial</code> partial and don't wrap it with the layout. Otherwise respond with the <code>index</code> view page of the current controller.</blockquote><p>Think of a paginated index page, where the first call to the index action sends the view with the first page of data and a button or element on the page triggers additional calls to the same action but this time only the next page of data is sent to the front end.</p><h3>Installing the Plugin</h3><p>To install this plugin, issue the following command from the root of your application in a CommandBox prompt:</p><div class="ql-code-block-container" spellcheck="false"><select class="ql-ui" contenteditable="false"><option value="plain">Plain</option><option value="bash">Bash</option><option value="cpp">C++</option><option value="cs">C#</option><option value="css">CSS</option><option value="diff">Diff</option><option value="xml">HTML/XML</option><option value="java">Java</option><option value="javascript">JavaScript</option><option value="markdown">Markdown</option><option value="php">PHP</option><option value="python">Python</option><option value="ruby">Ruby</option><option value="sql">SQL</option></select><div class="ql-code-block" data-language="plain">install cfwheels-htmx-plugin</div></div><p>Once installed, reload your application and you're off to the races.</p>

Latest Blog Posts