A Simple Slide Show Using Hotwire And Lucee CFML

Cyberdime
Published: January 31, 2023

Now that I have my ColdFusion and Hotwire playground up and running, I can start to explore the features of the Hotwire framework. And, one of the most attractive features is the ability to update a portion of the page using a full-page render. This works by scoping DOM (Document Object Model) changes to a given <turbo-frame> element. To see this in action, I wanted to create a simple slide show using Lucee CFML.

View this code in my ColdFusion + Hotwire Demos project on GitHub.

If you look at the header of my blog, I have a hero image with Prev/Next buttons. When you click one of those buttons, I make an API call back to the server that returns a JSON payload with information about the requested hero image. I then have to take that data and use it to update the hero image rendering.

Performing this update is actually quite a lot of work. I have to:

  • Make the API call.
  • Deserialize the JSON response.
  • Update the figure image being rendered.
  • Update the figure caption text (complete with links to hero participants).
  • Update the Prev/Next anchor links.

Updating [src] and [href] attributes is relatively easy; but, updating the figure caption text is a huge pain in the butt because I have to do it all in JavaScript with String concatenation. The advantage of using Hotwire’s Turbo Drive is that I can move all of that complexity out of the JavaScript context and back onto the ColdFusion server, where rendering dynamic HTML templates is much easier.

The key here is to wrap the slide show portion of the page in its own <turbo-frame> element (pseudo-code example):

<turbo-frame id="slide-show">
	<!--- Render slide show content --->
	<a href="?slideIndex=#( slideIndex - 1 )#">Prev</a>
	<a href="?slideIndex=#( slideIndex + 1 )#">Next</a>
</turbo-frame>

When the user clicks on one of the Prev/Next buttons, Hotwire Turbo Drive will intercept the click event, cancel the default navigation, and make a fetch call instead. Once Turbo Drive receives the HTML response – of the full page load – it will then query the response HTML for the id="slide-show" frame and use it to swap-out the contents of the already rendered frame (leaving the rest of the DOM tree unaffected).

This allows us to dynamically render the slide show without any application JavaScript! All of the rendering is pushed to the ColdFusion server.

Here’s the full demo from ColdFusion + Hotwire project. Instead of a Picture-based slide show, I’m creating a Quote-based slide show (to keep things simple). When the page first loads, a random quote is selected. However, if url.quoteIndex is provided, the specified quote is rendered:

<cfscript>
	param name="url.quoteIndex" type="numeric" default=0;
	// NOTE: In a production environment, this would be cached in the application scope.
	// But, to keep things simple, I'm just re-instantiating it on each request.
	quotes = new lib.Quotes();
	// Select the quote to render on the page.
	quote = ( url.quoteIndex )
		? quotes.getQuote( val( url.quoteIndex ) )
		: quotes.getRandomQuote()
	;
</cfscript>
<cfmodule template="./tags/page.cfm">
	<cfoutput>
		<h1>
			ColdFusion + Hotwire Slide Show Demo
		</h1>
		<!---
			While we want the slideshow to be in its own turbo-frame, so that it can
			operate without altering the rest of the page state, we still want the slide-
			show navigation buttons to change the URL so that a page refresh (or a copy-
			paste of the current URL) will result in the currently-rendered quote being
			re-rendered (as dictated by "url.quoteIndex"). As such, I'm including the
			[data-turbo-action] attribute value "advance". This will keep the DOM updates
			locked-down to the frame while replacing / updating the URL.
		--->
		<turbo-frame id="quote-frame" data-turbo-action="advance">
			<figure class="m1-quote">
				<blockquote class="m1-quote__quote">
					#encodeForHtml( quote.excerpt )#
				</blockquote>
				<figcaption class="m1-quote__caption">
					&mdash; #encodeForHtml( quote.author )#
				</figcaption>
			</figure>
			<!---
				Since these navigation links are inside a turbo-frame element, they will
				be used to replace the contents of THIS FRAME ONLY (id="quote-frame"),
				leaving the rest of the page content untouched.
				--
				NOTE: I originally tried to include the [data-turbo-preload] attribute to
				pre-fetch the Prev/Next pages; but, the preload feature does not work with
				turbo-frames at this time - https://github.com/hotwired/turbo/issues/857
			--->
			<div class="m2-controls">
				&larr;
				<a href="index.htm?quoteIndex=#encodeForUrl( quote.prevIndex )#">
					Prev quote
				</a>
				|
				<a href="index.htm?quoteIndex=#encodeForUrl( quote.nextIndex )#">
					Next quote
				</a>
				&rarr;
			</div>
		</turbo-frame>
		<p>
			<!--- This content will NOT BE ALTERED during slide show navigation. --->
			Page originally loaded with quote index (#encodeForHtml( url.quoteIndex )#).
		</p>
	</cfoutput>
</cfmodule>

As you can see, the <turbo-frame> contains the quote content as well as Prev/Next buttons. Outside of the frame, I have a paragraph that shows which quote index was initially provided on page load. As the user navigates between the quotes, what we should see is that content within the <turbo-frame> changes but everything else stays exactly the same:

A slide show of quotes that is updated using Next and Prev buttons.

How freaking cool is that! As you can see, nothing outside of the <turbo-frame> element has changed – only the contents within the frame get updated by Turbo Drive. And, to underscore the point, all of the rendering of the HTML is being performed by ColdFusion! No dealing with JSON, no concatenating strings and wiring up anchor tags – just full, server-side CFML template rendering.

ASIDE: Hotwire Turbo Drive uses the id attribute to match <turbo-frame> elements in the current DOM with <turbo-frame> elements provided in the fetch response.

You may also notice in the GIF that the URL is updating as I click on the Prev/Next buttons. By default, frame-based navigation events don’t update the URL. However, since I’m including the data-turbo-action="advance" attribute on the <turbo-frame> element, Turbo Drive will push state onto the history as part of the navigation. This way, the URL state is updated to reflect the state of the page. And, if I hit the Next button a few times followed by a hard-refresh of the page, you’ll see that the currently-selected quote is re-rendered:

Turbo Drive is updating the URL as the user clicks the Next button. This allows a hard-refresh of the page to re-render the currently-selected quote.

Seeing this in action – the moving of what would otherwise be complex JavaScript rendering back onto the server – this is really exciting. I still have trouble imagining a very complex application being built with Hotwire; but, this demo alone is enough to get me wanting to know more.

I’ve opened a GitHub Issue about this; so, perhaps it will be addressed in the future.

Check out the license.

Source: www.bennadel.com