A common problem in AJAX driven applications is synchronization: how to two elements on a page synchronize the requests that they need to make with one another. Currently htmx offers no mechanisms for synchronizing multiple element requests. This document will explore some ideas for doing so. The goal is to provide both declarative synchronization as well as scripting access to htmx-driven requests.
Consider this form:
<form hx-post="..." ...>
...
<input hx-get="..." ...>
</form>
Here we have an input issuing a GET
for something like server side validation, and an outer form that post back to the server when it is submitted.
How should these two requests interact? A reasonable solution would be "if a POST is issued while a GET is in flight, cancel/abort/ignore the GET". Right now htmx will just let whichever request comes back first win.
Consider a paged table with a "next" and "back" button:
<table id="results">
...
</table>
<a hx-get="/results&page=2" hx-target="#results">Previous</a>
<a hx-get="/results&page=4" hx-target="#results">Next</a>
What happens if the user clicks "Next" and then clicks "Previous"? Two requests will be in flight and the last one to respond will win. Not ideal.
A first idea is a new attribute, hx-sync
that allows elements to express synchronization in a declarative way.
Consider our first example:
<form hx-post="..." ...>
...
<input hx-get="..." hx-sync="closest form:abort">
</form>
Here the input says that it will synchronize its requests with the closest form and abort any requests in flight when the form makes a request.
Next let's consider the paging links:
<table id="results">
...
</table>
<a hx-get="/results&page=2" hx-target="#results" hx-sync="body:disable">Previous</a>
<a hx-get="/results&page=4" hx-target="#results" hx-sync="body:disable">Next</a>
Here the anchors sync on the body element and disable any other requests from being made by elements that also sync on the
body element. (As usual, hoisting the hx-sync
attribute up should be supported, so we don't have to repeat ourselves).
Another possible modifier for the hx-sync
attribute would be hx-sync="<selector>:queue"
which would use the queing behavior defined on the element for determining how events are queued with the two elements. This argues for elevating the queue:
modifer on hx-trigger
up to a top level element.
In general the syntax for hx-sync
would be hx-sync="<selector>:<sync strategy>"
where sync strategy could be:
drop
default - if the synced element has a request open when the current element tries to issue a requst, ignore the current element's request.abort
- if the synced element makes a request while this element has one open, abort this elements request. If this element tries to start a request while the other element has a request open, ignore it.queue
- enqueue the current request using the queue and queuing strategy specified on the synced elementreplace
- the current element replaces an existing request that exists, aborting it
With this feature added, hx-boost
elements would, by default, sync on the body, to prevent issues like this:
In order to facilitate scripting access to request management, we are also considering supporting an htmx:abort
event, which can be sent to an element with a request in process to abort the request:
<button id="expensive-btn" hx-get="/expensive">Get Something Expensive</button>
<button _="on click send htmx:abort to the previous <button/>">Abort</button>