Skip to main content
Ben Nadel at cf.Objective() 2017 (Washington, D.C.) with: Zachary Brooks
Ben Nadel at cf.Objective() 2017 (Washington, D.C.) with: Zachary Brooks

Using A No-Content URL To Help Manage DOM Caching In HTMX And ColdFusion

By
Published in ,

When you hx-boost / AJAX'ify your ColdFusion application with HTMX, HTMX will cache the state of the DOM (Document Object Model) as you navigate from page to page. This way, when you hit the browser's back button, HTMX can restore the previous DOM state, pulling it out of the LocalStorage API. The caveat being, HTMX only does this in response to an AJAX request. Which means, if we want to cache the current state of the DOM, we must issue an AJAX request and we must change the URL.

Of course, we don't always want to navigate away from the current page to do this. Consider the example of closing a modal window. Since the vast majority of modal windows should be deep linkable within our applications, closing a modal window should update the URL to remove the modal window and expose the main page below. And then, hitting the browser's back button should re-render the modal window.

But, this back-button mechanic only works if HTMX took a snapshot of the DOM prior to closing the modal window. And, HTMX will only take a snapshot of the DOM after an AJAX request is issued. Which means, in order for this to work in the most natural way possible (from the user's perspective), closing the modal window has to be managed by an AJAX request/response life-cycle.

This sounds complicated; but, we can keep it relatively simple by creating a ColdFusion end-point that does nothing and can be cached by the browser. Here's my noContent.cfm CFML page. It does nothing and can be cached for a month (including a stale-while-revalidate grace period):

<cfscript>
HOUR_SECONDS = ( 60 * 60 );
DAY_SECONDS = ( HOUR_SECONDS * 24 );
WEEK_SECONDS = ( DAY_SECONDS * 7 );
MONTH_SECONDS = ( WEEK_SECONDS * 4 );
// Whatever unique URL we use for this end-point, it's going to be cached. As such,
// the first request to it may take some time; but, every subsequent request to the
// same URL signature will be instantaneously pulled from the browser cache.
header
name = "Cache-Control"
value = "max-age=#MONTH_SECONDS#, stale-while-revalidate=#MONTH_SECONDS#"
;
// ------------------------------------------------------------------------------- //
// ------------------------------------------------------------------------------- //
// These will likely be handled by the HX-attributes on the client; but, I'm allowing
// overrides to be passed-in on the URL just in case.
param name="url.reSwap" type="string" default="";
param name="url.reTarget" type="string" default="";
param name="url.reSelect" type="string" default="";
if ( url.reSwap.len() ) {
header
name = "HX-Reswap"
value = url.reSwap
;
}
if ( url.reTarget.len() ) {
header
name = "HX-Retarget"
value = url.reTarget
;
}
if ( url.reSelect.len() ) {
header
name = "HX-Reselect"
value = url.reSelect
;
}
// ... this page serves no content. ... //
</cfscript>
view raw noContent.cfm hosted with ❤ by GitHub

This ColdFusion page serves no content. It only exists to allow the URL to be updated via AJAX; and, when pulled from the cache, this update happens instantaneously.

This no-content end-point has value because HTMX allows us to override the experience of the interaction using hx-* attributes. So, while we might make the AJAX request to noContent.cfm behind the scenes, we can use the hx-push-url attribute in the HTML to alter the URL that goes into the history API.

Going back to our modal window scenario, we could make a request to the noContent.cfm page, but use the hx-push-url attribute to simply strip-out the deep-link, modal-window flag that's currently in the URL. I intent to cover that specific scenario in a future blog post; but for now, let's keep things simple.

To demonstrate the power of the noContent.cfm end-point, I've created a ColdFsion page that has a number of buttons. Each button:

  1. Triggers an AJAX request to the noContent.cfm page.

  2. Supplies a new, unique URL to be pushed into the browser history via an hx-push-url attribute.

  3. Uses an hx-swap="outerHTML" attribute to remove the button from the DOM.

This combination of actions means that every time we remove a button from the DOM, HTMX with snapshot the DOM into the LocalStorage API. And then, will restore said DOM snapshot state when we use the browser's pop-state operations (hitting either the Back or Forward buttons).

<cfoutput>
<body>
<div class="buttons">
<cfloop index="i" from="1" to="12">
<!---
Our HX-GET attribute points to our no-op end-point that is cached in the
browser. And, our HX-PUSH-URL attribute tells HTMX to push a URL into the
history API such that the current state of the DOM is cached before the
swap takes-place.
--->
<button
hx-get="noContent.cfm"
hx-push-url="#cgi.script_name#?removed=#i#"
hx-swap="outerHTML">
Remove (#i#)
</button>
</cfloop>
</div>
<script type="text/javascript">
// By default, the DOM snapshot history length is 10. For this demo, let's
// increase it since we have more buttons.
htmx.config.historyCacheSize = 20;
</script>
</body>
</cfoutput>
view raw index.cfm hosted with ❤ by GitHub

If we load this ColdFusion page and click to remove each button, we can see that the browser's back button will then restore each button in turn:

Screen recording of network activity showing 1ms response times from cached no-content end-point; followed by the use of the browser back button restore previous DOM state.

As you can see from the network activity, the browser's natural caching behaviors are in place; and each request to the noContent.cfm end-point is materialized in 1ms, being pulled from cache. This is, from the user's perspective, an instantaneous amount of time; but, it's enough to get HTMX to cache the DOM state. As such, when we click the browser's back button, HTMX will restore the previous DOM state, including the button that we just removed.

In this demo, we're not "deep linking" to the state of the page in which a given set of buttons have been removed. But, this hopefully illustrates how such a "no op" end-point could be used to surgically apply the current page state to the DOM snapshotting provided by HTMX. In a follow-up post, I'll look at this in a modal-window scenario that should make this mechanism even more clear.

Want to use code from this post? Check out the license.

Reader Comments

Post A Comment — I'd Love To Hear From You!

Markdown formatting: Basic formatting is supported: bold, italic, blockquotes, lists, fenced code-blocks. Read more about markdown syntax »
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.
Cancel
I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel