Created
February 13, 2023 13:44
Using Stimulus To Preload Links On Hover In Hotwire And Lucee CFML
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<cfmodule template="./tags/page.cfm" section="home"> | |
<cfoutput> | |
<h2> | |
Welcome to Our Site! | |
</h2> | |
<p> | |
Copy, copy, copy.... | |
</p> | |
<h3> | |
Check Out Our Products | |
</h3> | |
<ul class="products"> | |
<cfloop index="productID" from="101" to="110"> | |
<li | |
data-controller="hover-preload" | |
data-hover-preload-delay-value="500" | |
class="products__item"> | |
<a | |
href="product.htm?id=#productID#" | |
data-hover-preload-target="link" | |
class="products__link"> | |
Product #ucase( productID )# | |
</a> | |
</li> | |
</cfloop> | |
</ul> | |
</cfoutput> | |
</cfmodule> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Import core modules. | |
import { Application } from "@hotwired/stimulus"; | |
import { Controller } from "@hotwired/stimulus"; | |
import * as Turbo from "@hotwired/turbo"; | |
// ----------------------------------------------------------------------------------- // | |
// ----------------------------------------------------------------------------------- // | |
class HoverPreloadController extends Controller { | |
// Every link target contained within the controller's scope will be preloaded when | |
// the hover timer has completed. | |
static targets = [ "link" ]; | |
// The hover threshold (ie, duration the user has to hover before the link preloading | |
// kicks-in) can be configured. | |
static values = { | |
delay: { | |
type: Number, | |
default: 300 | |
} | |
}; | |
// --- | |
// LIFE-CYCLE METHODS. | |
// --- | |
/** | |
* I run once when the controller has been instantiated, but before it has been | |
* connected to the host DOM element. A single instantiated controller may be connected | |
* to the host element multiples times during the life-time of a page. | |
*/ | |
initialize() { | |
this.hoverTimer = 0; | |
} | |
/** | |
* I run once after the component instance has been bound to the host DOM element. At | |
* this point, all of the classes, targets, and values have already been bound. | |
*/ | |
connect() { | |
if ( this.hasLinkTarget ) { | |
this.setupEvents(); | |
} | |
} | |
/** | |
* I get called once after the component instance has been unbound from the host DOM | |
* element. At this point, all of the targets have already been disconnected as well. | |
*/ | |
disconnect() { | |
this.teardownEvents(); | |
} | |
// --- | |
// PRIVATE METHODS. | |
// --- | |
/** | |
* I handle the mouseenter event on the host element. | |
*/ | |
handleMouseenter = ( event ) => { | |
this.hoverTimer = window.setTimeout( this.handleTimeout, this.delayValue ); | |
}; | |
/** | |
* I handle the mouseleave event on the host element. | |
*/ | |
handleMouseleave = ( event ) => { | |
window.clearTimeout( this.hoverTimer ); | |
this.hoverTimer = 0; | |
}; | |
/** | |
* I handle the timeout event triggered from the hover interactions. | |
*/ | |
handleTimeout = () => { | |
this.teardownEvents(); | |
this.preloadLinks(); | |
}; | |
/** | |
* I ask the Turbo session to preload all of the link targets embedded within this | |
* controller. | |
*/ | |
preloadLinks() { | |
for ( var target of this.linkTargets ) { | |
console.log( "Preloading links for:", target.href.split( "/" ).at( -1 ) ); | |
// CAUTION: The "preloader" is an UNDOCUMENTED PROPERTY of the session (at | |
// least as far as I can tell) - I only found it by looking at the source code | |
// for the Turbo project. As such, use this technique at your own risk! | |
Turbo.session.preloader.preloadURL( target ); | |
} | |
} | |
/** | |
* I setup the hover events on the host element. | |
*/ | |
setupEvents() { | |
this.element.addEventListener( "mouseenter", this.handleMouseenter ); | |
this.element.addEventListener( "mouseleave", this.handleMouseleave ); | |
} | |
/** | |
* I teardown the hover events on the host element. Any pending timer will be cleared. | |
*/ | |
teardownEvents() { | |
window.clearTimeout( this.hoverTimer ); | |
this.hoverTimer = 0; | |
this.element.removeEventListener( "mouseenter", this.handleMouseenter ); | |
this.element.removeEventListener( "mouseleave", this.handleMouseleave ); | |
} | |
} | |
// ----------------------------------------------------------------------------------- // | |
// ----------------------------------------------------------------------------------- // | |
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( "hover-preload", HoverPreloadController ); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<cfscript> | |
param name="url.id" type="string"; | |
// Adding a sleep to exaggerate the benefits of preloading the content. | |
sleep( 500 ); | |
</cfscript> | |
<cfmodule template="./tags/page.cfm"> | |
<cfoutput> | |
<h2> | |
Product #encodeForHtml( url.id )# | |
</h2> | |
<p> | |
Copy, copy, copy.... | |
</p> | |
<script type="text/javascript"> | |
// Logging the render time so that we can see CACHED vs LIVE rendering. | |
console.log( | |
"Product #encodeForJavaScript( url.id )# rendered at", | |
new Date().toLocaleTimeString() | |
); | |
</script> | |
</cfoutput> | |
</cfmodule> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div | |
data-controller="hover-preload" | |
data-hover-preload-delay-value="500"> | |
<a href="my-link.htm" data-hover-preload-target="link"> | |
My link.... | |
</a> | |
</li> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment