Skip to content

Instantly share code, notes, and snippets.

@1cg
Last active Feb 5, 2022
Embed
What would you like to do?

HTMX Request Synchronization Ideas

Overview

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.

Motivating Examples

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.

Potential Solutions

The hx-sync attribute

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 element
  • replace - the current element replaces an existing request that exists, aborting it

hx-boost syncing

With this feature added, hx-boost elements would, by default, sync on the body, to prevent issues like this:

bigskysoftware/htmx#686

The htmx:abort Event

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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment