Matt Swanson has done a great job describing the ins-and-outs of using hovercards via Stimulus JS . This blog post adds to what is there contained with another feature: (i) the ability to abort / cancel a hover card, if your mouse moves away. This entails hiding the hover card, and/or aborting a fetch request, if made.
In my experience, without the ability to cancel a hover card - the experience is very, very annoying to the user.
The key is to use an AbortController
.
The code will abundantly demonstrate the problem: I’ve provided comments so you can hopefully follow along:
import { Controller } from " stimulus " ;
export default class extends Controller {
static targets = [ " card " ];
static values = { url : String };
connect (){
this . abortController = new AbortController (); // set upt the abort conttoller and signal so we can cancel the fetch method, if required
this . signal = this . abortController . signal ;
}
show () {
// if the card already exists
// then we can simply hide it with a bootstrap
// class, instead of removing it from the DOM.
if ( this . hasCardTarget ) {
this . cardTarget . classList . remove ( " d-none " );
} else {
// if we've already aborted the fetch
// then we need to create a new abort controller
// to handle the situation if the user decides to
// hover over our target element again, and then decides against it, the abort process can be properly handled the SECOND time around.
if ( this . signal . aborted ) {
this . abortController = new AbortController ();
this . signal = this . abortController . signal ;
}
// this is the critical line: if we decide to abort the fetch, then the fetch method needs to be aware of it somehow: we pass in the signal into the fetch method.
fetch ( this . urlValue , { signal : this . signal })
. then (( r ) => r . text ())
. then (( html ) => {
let fragment = document
. createRange ()
. createContextualFragment ( html );
this . element . appendChild ( fragment );})
. catch ( function ( err ){
if ( err . name == ' AbortError ' ) { // handle abort() - this is critical. We need to catch the error thrown by the abort. In our case, we don't want to do anything if we abort.
} else {
throw err ;
}
});
}
}
hide () {
// if the user decides to move their mouse away
// then we immediately want to hide the hover card
// and this is the perfect time to mark something as having been aborted, if it was not already done so.
if ( this . signal . aborted ) {
}
else
{
this . abortController . abort ()
}
if ( this . hasCardTarget ) {
this . cardTarget . classList . add ( " d-none " )
// these are bootstrap classes which hide
// the card when required.
}
}
disconnect () {
if ( this . hasCardTarget ) {
this . cardTarget . remove ();
}
}
}
Perhaps I will provide a gif to demonstrate, but the above code served me very well.
I hope it helps you.