knowledge/technology/internet/HTMX.md

16 KiB
Raw Blame History

website obj
https://htmx.org/ concept

htmx

htmx is a library that allows you to access modern browser features directly from HTML, rather than using javascript.

htmx extends and generalizes the core idea of HTML as a hypertext, opening up many more possibilities directly within the language:

  • Now any element, not just anchors and forms, can issue an HTTP request
  • Now any event, not just clicks or form submissions, can trigger requests
  • Now any HTTP verb, not just GET and POST, can be used
  • Now any element, not just the entire window, can be the target for update by the request

Note: that when you are using htmx, on the server side you typically respond with HTML, not JSON. This keeps you firmly within the original web programming model, using Hypertext As The Engine Of Application State without even needing to really understand that concept.

Installing

Htmx is a dependency-free, browser-oriented javascript library. This means that using it is as simple as adding a <script> tag to your document head. No need for complicated build steps or systems.

Via CDN:

<script src="https://unpkg.com/htmx.org@1.9.5" integrity="sha384-xcuj3WpfgjlKF+FXhSQFQ0ZNr39ln+hwjN3npfM9VBnUskLolQAcN80McRIVOPuO" crossorigin="anonymous"></script>

Download a copy

The next easiest way to install htmx is to simply copy it into your project.

Download htmx.min.js from unpkg.com and add it to the appropriate directory in your project and include it where necessary with a <script> tag:

<script src="/path/to/htmx.min.js"></script>

You can also add extensions this way, by downloading them from the ext/ directory.

AJAX

The core of htmx is a set of attributes that allow you to issue AJAX requests directly from HTML:

Attribute Description
hx-get Issues a GET request to the given URL
hx-post Issues a POST request to the given URL
hx-put Issues a PUT request to the given URL
hx-patch Issues a PATCH request to the given URL
hx-delete Issues a DELETE request to the given URL

Each of these attributes takes a URL to issue an AJAX request to. The element will issue a request of the specified type to the given URL when the element is triggered:

<div hx-put="/messages">
    Put To Messages
</div>

This tells the browser:

When a user clicks on this div, issue a PUT request to the URL /messages and load the response into the div

Triggering Requests

By default, AJAX requests are triggered by the “natural” event of an element:

  • input, textarea & select are triggered on the change event
  • form is triggered on the submit event
  • everything else is triggered by the click event

If you want different behavior you can use the hx-trigger attribute to specify which event will cause the request.

Trigger Modifiers

A trigger can also have a few additional modifiers that change its behavior. For example, if you want a request to only happen once, you can use the once modifier for the trigger:

<div hx-post="/mouse_entered" hx-trigger="mouseenter once">
    [Here Mouse, Mouse!]
</div>

Other modifiers you can use for triggers are:

  • changed - only issue a request if the value of the element has changed
  • delay:<time interval> - wait the given amount of time (e.g. 1s) before issuing the request. If the event triggers again, the countdown is reset.
  • throttle:<time interval> - wait the given amount of time (e.g. 1s) before issuing the request. Unlike delay if a new event occurs before the time limit is hit the event will be discarded, so the request will trigger at the end of the time period.
  • from:<CSS Selector> - listen for the event on a different element. This can be used for things like keyboard shortcuts.

You can use these attributes to implement many common UX patterns, such as Active Search:

<input type="text" name="q"
    hx-get="/trigger_delay"
    hx-trigger="keyup changed delay:500ms"
    hx-target="#search-results"
    placeholder="Search..."
>
<div id="search-results"></div>

This input will issue a request 500 milliseconds after a key up event if the input has been changed and inserts the results into the div with the id search-results.

Multiple triggers can be specified in the hx-trigger attribute, separated by commas.

Special Events

htmx provides a few special events for use in hx-trigger:

  • load - fires once when the element is first loaded
  • revealed - fires once when an element first scrolls into the viewport
  • intersect - fires once when an element first intersects the viewport. This supports two additional options:
    • root:<selector> - a CSS selector of the root element for intersection
    • threshold:<float> - a floating point number between 0.0 and 1.0, indicating what amount of intersection to fire the event on

You can also use custom events to trigger requests if you have an advanced use case.

Polling

If you want an element to poll the given URL rather than wait for an event, you can use the every syntax with the hx-trigger attribute:

<div hx-get="/news" hx-trigger="every 2s"></div>

This tells htmx

Every 2 seconds, issue a GET to /news and load the response into the div

If you want to stop polling from a server response you can respond with the HTTP response code 286 and the element will cancel the polling.

Load Polling

Another technique that can be used to achieve polling in htmx is “load polling”, where an element specifies a load trigger along with a delay, and replaces itself with the response:

<div hx-get="/messages"
    hx-trigger="load delay:1s"
    hx-swap="outerHTML"
>
</div>

If the /messages end point keeps returning a div set up this way, it will keep “polling” back to the URL every second.

Load polling can be useful in situations where a poll has an end point at which point the polling terminates, such as when you are showing the user a progress bar.

Targets

If you want the response to be loaded into a different element other than the one that made the request, you can use the hx-target attribute, which takes a CSS selector. Looking back at our Live Search example:

<input type="text" name="q"
    hx-get="/trigger_delay"
    hx-trigger="keyup delay:500ms changed"
    hx-target="#search-results"
    placeholder="Search..."
>
<div id="search-results"></div>

You can see that the results from the search are going to be loaded into div#search-results, rather than into the input tag.

Extended CSS Selectors

hx-target, and most attributes that take a CSS selector, support an “extended” CSS syntax:

  • You can use the this keyword, which indicates that the element that the hx-target attribute is on is the target
  • The closest <CSS selector> syntax will find the closest ancestor element or itself, that matches the given CSS selector. (e.g. closest tr will target the closest table row to the element)
  • The next <CSS selector> syntax will find the next element in the DOM matching the given CSS selector.
  • The previous <CSS selector> syntax will find the previous element in the DOM the given CSS selector.
  • find <CSS selector> which will find the first child descendant element that matches the given CSS selector. (e.g find tr would target the first child descendant row to the element)

Swapping

htmx offers a few different ways to swap the HTML returned into the DOM. By default, the content replaces the innerHTML of the target element. You can modify this by using the hx-swap attribute with any of the following values:

Name Description
innerHTML the default, puts the content inside the target element
outerHTML replaces the entire target element with the returned content
afterbegin prepends the content before the first child inside the target
beforebegin prepends the content before the target in the targets parent element
beforeend appends the content after the last child inside the target
afterend appends the content after the target in the targets parent element
delete deletes the target element regardless of the response
none does not append content from response (Out of Band Swaps and Response Headers will still be processed)

Parameters

By default, an element that causes a request will include its value if it has one. If the element is a form it will include the values of all inputs within it.

As with HTML forms, the name attribute of the input is used as the parameter name in the request that htmx sends.

Additionally, if the element causes a non-GET request, the values of all the inputs of the nearest enclosing form will be included.

If you wish to include the values of other elements, you can use the hx-include attribute with a CSS selector of all the elements whose values you want to include in the request.

If you wish to filter out some parameters you can use the hx-params attribute.

Finally, if you want to programmatically modify the parameters, you can use the htmx:configRequest event.

Extra Values

You can include extra values in a request using the hx-vals (name-expression pairs in JSON format) and hx-vars attributes (comma-separated name-expression pairs that are dynamically computed).

Attribute Inheritance

Most attributes in htmx are inherited: they apply to the element they are on as well as any children elements. This allows you to “hoist” attributes up the DOM to avoid code duplication. Consider the following htmx:

<button hx-delete="/account" hx-confirm="Are you sure?">
    Delete My Account
</button>
<button hx-put="/account" hx-confirm="Are you sure?">
    Update My Account
</button>

Here we have a duplicate hx-confirm attribute. We can hoist this attribute to a parent element:

<div hx-confirm="Are you sure?">
    <button hx-delete="/account">
        Delete My Account
    </button>
    <button hx-put="/account">
        Update My Account
    </button>
</div>

This hx-confirm attribute will now apply to all htmx-powered elements within it.

Sometimes you wish to undo this inheritance. Consider if we had a cancel button to this group, but didnt want it to be confirmed. We could add an unset directive on it like so:

<div hx-confirm="Are you sure?">
    <button hx-delete="/account">
        Delete My Account
    </button>
    <button hx-put="/account">
        Update My Account
    </button>
    <button hx-confirm="unset" hx-get="/">
        Cancel
    </button>
</div>

The top two buttons would then show a confirm dialog, but the bottom cancel button would not.

Automatic inheritance can be disabled using the hx-disinherit attribute.

Boosting

Htmx supports “boosting” regular HTML anchors and forms with the hx-boost attribute. This attribute will convert all anchor tags and forms into AJAX requests that, by default, target the body of the page.

Here is an example:

<div hx-boost="true">
    <a href="/blog">Blog</a>
</div>

The anchor tag in this div will issue an AJAX GET request to /blog and swap the response into the body tag.

History Support

Htmx provides a simple mechanism for interacting with the browser history API:

If you want a given element to push its request URL into the browser navigation bar and add the current state of the page to the browsers history, include the hx-push-url attribute:

<a hx-get="/blog" hx-push-url="true">Blog</a>

When a user clicks on this link, htmx will snapshot the current DOM and store it before it makes a request to /blog. It then does the swap and pushes a new location onto the history stack.

When a user hits the back button, htmx will retrieve the old content from storage and swap it back into the target, simulating “going back” to the previous state. If the location is not found in the cache, htmx will make an ajax request to the given URL, with the header HX-History-Restore-Request set to true, and expects back the HTML needed for the entire page. Alternatively, if the htmx.config.refreshOnHistoryMiss config variable is set to true, it will issue a hard browser refresh.

NOTE: If you push a URL into the history, you must be able to navigate to that URL and get a full page back! A user could copy and paste the URL into an email, or new tab. Additionally, htmx will need the entire page when restoring history if the page is not in the history cache.

Request & Responses

Request Headers

htmx includes a number of useful headers in requests:

Header Description
HX-Request will be set to “true”
HX-Trigger will be set to the id of the element that triggered the request
HX-Trigger-Name will be set to the name of the element that triggered the request
HX-Target will be set to the id of the target element
HX-Prompt will be set to the value entered by the user when prompted via hx-prompt

Response Headers

htmx supports some htmx-specific response headers:

  • HX-Push - pushes a new URL into the browsers address bar
  • HX-Redirect - triggers a client-side redirect to a new location
  • HX-Location - triggers a client-side redirect to a new location that acts as a swap
  • HX-Refresh - if set to “true” the client side will do a full refresh of the page
  • HX-Trigger - triggers client side events
  • HX-Trigger-After-Swap - triggers client side events after the swap step
  • HX-Trigger-After-Settle - triggers client side events after the settle step

For more on the HX-Trigger headers, see HX-Trigger Response Headers.

Submitting a form via htmx has the benefit of no longer needing the Post/Redirect/Get Pattern. After successfully processing a POST request on the server, you dont need to return a HTTP 302 (Redirect). You can directly return the new HTML fragment.