Hovercards Using Stimulus JS
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.
Written on October 1, 2020