Skip to content

Instantly share code, notes, and snippets.

@bennadel
Last active April 23, 2023 12:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bennadel/49c6b97d6e05d9637b1cf188de9ed691 to your computer and use it in GitHub Desktop.
Save bennadel/49c6b97d6e05d9637b1cf188de9ed691 to your computer and use it in GitHub Desktop.
Incrementally Applying Hotwire To An Existing ColdFusion Application
{
"plugins": [
"@babel/plugin-proposal-optional-chaining"
]
}
// Import application modules.
import { ApiClient } from "./api-client.js";
// ----------------------------------------------------------------------------------- //
// ----------------------------------------------------------------------------------- //
export var apiClient = new ApiClient();
// Import core modules.
import * as Turbo from "@hotwired/turbo";
// ----------------------------------------------------------------------------------- //
// ----------------------------------------------------------------------------------- //
// By default, we don't want Turbo Drive to take over the navigation unless explicitly
// enabled within the ColdFusion markup. This way, we can baby-step our way towards a
// compatible Turbo Drive application.
Turbo.session.drive = false;
// Scroll down to the desired element (via ID selector).
document.documentElement.addEventListener(
"turbo:load",
function handleLoad( event ) {
var searchParams = new URLSearchParams( location.search );
var scrollTo = searchParams.get( "scrollTo" );
if ( ! scrollTo ) {
return;
}
document.querySelector( `#${ scrollTo }` )
?.scrollIntoView()
;
}
);
<!---
As various pages are updated to be Hotwire / Turbo Drive compatible, we
can add the "data-turbo" attribute to their nav links. This way, even if
most of the app is still powered by "old school" full page-refresh
navigation, Turbo Drive can still kick-in where it will be a value-add.
--
Note that only the ABOUT page here is Turbo-enabled.
--->
<nav>
<a href="index.htm">Home</a>
<a href="about.htm" data-turbo="true">About</a>
<a href="contact.htm">Contact</a>
</nav>
<cfscript>
param name="url.quoteIndex" type="numeric" default=0;
quote = application.quoteService.getQuote( url.quoteIndex );
</cfscript>
<cfmodule template="./tags/page.cfm" section="home">
<cfoutput>
<h2>
Welcome to My Site
</h2>
<figure>
<blockquote>
#encodeForHtml( quote.text )#
</blockquote>
<figcaption>
&larr;
<a href="index.htm?quoteIndex=#encodeForUrl( quote.prevID )#">Prev quote</a>
&mdash;
<a href="index.htm?quoteIndex=#encodeForUrl( quote.nextID )#">Next quote</a>
&rarr;
</figcaption>
</figure>
</cfoutput>
</cfmodule>
<!---
Even with Turbo Drive disabled by default, Turbo Frames still work. As such,
for navigation events that refresh the page, we can very easily wrap those
navigation elements in a Turbo Frame that advances the history.
--->
<turbo-frame id="quote-frame" data-turbo-action="advance">
<figure>
<blockquote>
#encodeForHtml( quote.text )#
</blockquote>
<figcaption>
&larr;
<a href="index.htm?quoteIndex=#encodeForUrl( quote.prevID )#">Prev quote</a>
&mdash;
<a href="index.htm?quoteIndex=#encodeForUrl( quote.nextID )#">Next quote</a>
&rarr;
</figcaption>
</figure>
</turbo-frame>
<!---
Even when Turbo Drive is disable by default, lazy-loaded Turbo Frames still
work. Which means, we can VERY EASILY lazy-load content into the current page.
--->
<turbo-frame id="lazy-frame" src="lazy.htm" loading="lazy">
<!--- Placeholder content (or if script hasn't loaded). --->
<a href="lazy.htm">Go to lazy content</a> &rarr;
</turbo-frame>
<!---
This represents a "pre-Stimulus" widget. It will be bootstrapped by a page-
level Stimulus controller until it can be migrated to use Stimulus on its own.
--->
<p class="old-school">
<button>
Clicked <span>0</span> Times
</button>
</p>
// Import core modules.
import { Application } from "@hotwired/stimulus";
import { Controller } from "@hotwired/stimulus";
// ----------------------------------------------------------------------------------- //
// ----------------------------------------------------------------------------------- //
class AppController extends Controller {
/**
* I get called whenever the controller instance is bound to a host element.
*/
connect() {
console.log( "App connected!" );
// Wire-up all the features that have not yet been ported to their own Stimulus
// controllers. Keep in mind that these methods will be called on every page load.
setupOldSchoolThing();
}
}
window.Stimulus = Application.start();
// When not using the Ruby On Rails asset pipeline / build system, Stimulus doesn't know
// how to map controller classes to data-controller attributes. As such, we have to
// explicitly register the Controllers on Stimulus startup.
Stimulus.register( "app", AppController );
// ----------------------------------------------------------------------------------- //
// ----------------------------------------------------------------------------------- //
function setupOldSchoolThing() {
// This setup method will be called on every page load. As such, it will only be
// applicable some of the time. If this page doesn't contain an old-school widget,
// exit out.
if ( ! document.querySelector( ".old-school" ) ) {
console.warn( "No old-school widget detected." );
return;
}
var dom = Object.create( null );
dom.container = document.querySelector( ".old-school" );
dom.button = dom.container.querySelector( "button" );
dom.counter = dom.button.querySelector( "span" );
// Parse initial counter from the DOM state.
var clickCount = +dom.counter.textContent;
dom.button.addEventListener(
"click",
function handleClick() {
dom.counter.textContent = ++clickCount;
}
);
}
// Import vendor modules.
import { Controller } from "@hotwired/stimulus";
// Import application modules.
import { apiClient } from "./injector.js";
// ----------------------------------------------------------------------------------- //
// ----------------------------------------------------------------------------------- //
export class CommentFormController extends Controller {
// ... truncated ...
apiClient.makeRequest();
// ... truncated ...
}
document.documentElement.addEventListener(
"turbo:click",
function handleClick( event ) {
// If the user is clicking through to an old demo, don't let Turbo Drive manage
// the application visit - the demo will NOT BE Hotwire-compatible. Instead, let's
// prevent the default Turbo Drive behavior, allowing the event to fall through to
// the browser as a normal link navigation.
if ( event.detail.url.includes( "/resources/" ) ) {
event.preventDefault();
}
}
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment