Sprig has
quickly become one of my favorite Craft CMS plugins. It reduces some of
the friction and overhead that adding interactivity to a site usually
brings. I'm currently using it in a client project that requires a fair
amount of interactivity and ran into an interesting challenge along the
way.
In this project, there is a table component similar to the Sprigboard demo that
reloads automatically every 5 seconds via HTMX' polling trigger. Within
each table row is another Sprig component - a button that, when
clicked, opens a modal dialog that in turn loads a user's data. The
trouble is that when the outer table component re-renders via polling,
the modal component also re-renders, and poof goes the modal. First I
tried using Sprig's s-preserve
attribute, but this caused
some other issues with my setup. I needed a way to programmatically tell
the outer component to stop polling when the modal was opened.
The Normal Way
The standard way to stop a component from polling is to have it
return a response code of 286. This is useful if the contents of the
polled element's response determines when polling should stop, say a
long-running API endpoint that should stop being polled when a specific
value is returned. But in our case, it's a user event (disconnected from
the polling element itself) that determines when the polling should
stop.
The Programmatic Way
Under the hood, HTMX tracks the state and settings of components using an object htmx-internal-data
function getInternalData(elt) {
var dataProp = 'htmx-internal-data';
var data = elt[dataProp];
if (!data) {
data = elt[dataProp] = {};
}
return data;
}
When a status code of 286 is returned, the following method is triggered:
function cancelPolling(elt) {
getInternalData(elt).cancelled = true;
}
Trouble is, neither of these methods are available on the global htmx
object. But now that we know how things work under the hood, we can achieve the same thing.
const pollingElement = document.querySelector('#sprig-component');
function disablePolling(el){
el['htmx-internal-data'].cancelled = true;
htmx.process(el);
}
disablePolling(pollingElement);
The last line htmx.process()
tells htmx to re-initialize
the element with the updated properties. Now polling can be cancelled
programmatically regardless of the component's response code.
To fire it back up, run the reverse code and refresh the component.
function enablePolling(el){
el['htmx-internal-data'].cancelled = false;
htmx.process(el);
htmx.trigger(el, 'refresh');
}