Create a series of questions, including code challenges that test a readers understanding of this material on HTMX:
Aim for 30 or so questions covering these topics. First, generate an outline of the specific topics you will create quiz questions about. The goal of these questions is to push the reader to achieve technical mastery of the subject.
Previous: A Dynamic Archive UI
Next: Client Side Scripting
# Tricks Of The Htmx Masters
Contents
* Htmx Attributes
* hx-swap
* hx-trigger
* Trigger filters
* Synthetic events
* Other Attributes
* Events
* Htmx-Generated Events
* Using the htmx:configRequest Event
* Canceling a Request Using htmx:abort
* Server Generated Events
* HTTP Requests & Responses
* HTTP Response Codes
* Updating Other Content
* Expanding Your Selection
* Out of Band Swaps
* Events
* Being Pragmatic
* Debugging
* Logging Htmx Events
* Monitoring Events in Chrome
* Security Considerations
* Content Security Policies & Htmx
* Configuring
* HTML Notes: Semantic HTML
In this chapter we are going to look deeper into the htmx toolkit. We’ve accomplished quite a bit with what we’ve learned so far. Still, when you are developing Hypermedia-Driven Applications, there will be times when you need to reach for additional options and techniques.
We will go over the more advanced attributes in htmx, as well as expand on the advanced details of attributes we have already used.
Additionally, we will look at functionality that htmx offers beyond simple HTML attributes: how htmx extends standard HTTP request and responses, how htmx works with (and produces) events, and how to approach situations where there isn’t a simple, single target on the page to be updated.
Finally, we will take a look at practical considerations when doing htmx development: how to debug htmx-based applications effectively, security considerations you will need to take into account when working with htmx, and how to configure the behavior of htmx.
With the features and techniques in this chapter, you will be able to pull off extremely sophisticated user interfaces using only htmx and perhaps a small bit of hypermedia-friendly client-side scripting.
## Htmx Attributes
Thus far we have used about fifteen different attributes from htmx in our application. The most important ones have been:
`hx-get`, `hx-post`, etc.
To specify the AJAX request an element should make
`hx-trigger`
To specify the event that triggers a request
`hx-swap`
To specify how to swap the returned HTML content into the DOM
`hx-target`
To specify where in the DOM to swap the returned HTML content
Two of these attributes, `hx-swap` and `hx-trigger`, support a number of useful options for creating more advanced Hypermedia-Driven Applications.
### hx-swap
We’ll start with the hx-swap attribute. This is often not included on elements that issue htmx-driven requests because its default behavior — `innerHTML`, which swaps the inner HTML of the element — tends to cover most use cases.
We earlier saw situations where we wanted to override the default behavior and use `outerHTML`, for example. And, in chapter 2, we discussed some other swap options beyond these two, `beforebegin`, `afterend`, etc.
In chapter 5, we also looked at the `swap` delay modifier for `hx-swap`, which allowed us to fade some content out before it was removed from the DOM.
In addition to these, `hx-swap` offers further control with the following modifiers:
`settle`
Like `swap`, this allows you to apply a specific delay between when the content has been swapped into the DOM and when its attributes are “settled”, that is, updated from their old values (if any) to their new values. This can give you fine-grained control over CSS transitions.
`show`
Allows you to specify an element that should be shown — that is, scrolled into the viewport of the browser if necessary — when a request is completed.
`scroll`
Allows you to specify a scrollable element (that is, an element with scrollbars), that should be scrolled to the top or bottom when a request is completed.
`focus-scroll`
Allows you to specify that htmx should scroll to the focused element when a request completes. The default for this modifier is “false.”
So, for example, if we had a button that issued a `GET` request, and we wished to scroll to the top of the `body` element when the request completed, we would write the following HTML:
<1> Get Contacts
Scrolling to the top of the page
1. This tells htmx to show the top of the body after the swap occurs.
More details and examples can be found online in the `hx-swap` documentation.
### hx-trigger
Like `hx-swap`, `hx-trigger` can often be omitted when you are using htmx, because the default behavior is typically what you want. Recall the default triggering events are determined by an element’s type:
* Requests on `input`, `textarea` & `select` elements are triggered by the `change` event.
* Requests on `form` elements are triggered on the `submit` event.
* Requests on all other elements are triggered by the `click` event.
There are times, however, when you want a more elaborate trigger specification. A classic example is the active search example we implemented in Contact.app:
<1>
The active search input
1. An elaborate trigger specification.
This example took advantage of two modifiers available for the `hx-trigger` attribute:
`delay`
Allows you to specify a delay to wait before a request is issued. If the event occurs again, the first event is discarded and the timer resets. This allows you to “debounce” requests.
`changed`
Allows you to specify that a request should only be issued when the `value` property of the given element has changed.
`hx-trigger` has several additional modifiers. This makes sense, because events are fairly complex and we want to be able to take advantage of all the power they offer. We will discuss events in more detail below.
Here are the other modifiers available on `hx-trigger`:
`once`
The given event will only trigger a request once.
`throttle`
Allows you to throttle events, only issuing them once every certain interval. This is different than `delay` in that the first event will trigger immediately, but any following events will not trigger until the throttle time period has elapsed.
`from`
A CSS selector that allows you to pick another element to listen for events on. We will see an example of this used later in the chapter.
`target`
A CSS selector that allows you to filter events to only those that occur directly on a given element. In the DOM, events “bubble” to their ancestor elements, so a `click` event on a button will also trigger a `click` event on an enclosing `div`, all the way up to the `body` element. Sometimes you want to specify an event directly on a given element, and this attribute allows you to do that.
`consume`
If this option is set to `true`, the triggering event will be cancelled and not propagate to ancestor elements.
`queue`
This option allows you to specify how events are queued in htmx. By default, when htmx receives a triggering event, it will issue a request and start an event queue. If the request is still in flight when another event is received, it will queue the event and, when the request finishes, trigger a new request. By default, it only keeps the last event it receives, but you can modify that behavior using this option: for example, you can set it to `none` and ignore all triggering events that occur during a request.
#### Trigger filters
The `hx-trigger` attribute also allows you to specify a _filter_ for events by using square brackets enclosing a JavaScript expression after the event name.
Let’s say you have a complex situation where contacts should only be retrievable in certain situations. You have a JavaScript function, `contactRetrievalEnabled()` that returns a boolean, `true` if contacts can be retrieved and `false` otherwise. How could you use this function to place a gate on a button that issues a request to `/contacts`?
To do this using an event filter in htmx, you would write the following HTML:
<1> Get Contacts
The active search input
1. A request is issued on click only when `contactRetrievalEnabled()` returns `true`.
The button will not issue a request if `contactRetrievalEnabled()` returns false, allowing you to dynamically control when the request will be made. There are common situations that call for an event trigger, when you only want to issue a request under specific circumstances:
* if a certain element has focus
* if a given form is valid
* if a set of inputs have specific values
Using event filters, you can use whatever logic you’d like to filter requests by htmx.
#### Synthetic events
In addition to these modifiers, `hx-trigger` offers a few “synthetic” events, that is events that are not part of the regular DOM API. We have already seen `load` and `revealed` in our lazy loading and infinite scroll examples, but htmx also gives you an `intersect` event that triggers when an element intersects its a viewport.
This synthetic event uses the modern Intersection Observer API, which you can read more about at MDN.
Intersection gives you fine-grained control over exactly when a request should be triggered. For example, you can set a threshold and specify that the request be issued only when an element is 50% visible.
The `hx-trigger` attribute certainly is the most complex in htmx. More details and examples can be found in its documentation.
### Other Attributes
Htmx offers many other less commonly used attributes for fine-tuning the behavior of your Hypermedia-Driven Application.
Here are some of the most useful ones:
hx-push-url
“Pushes” the request URL (or some other value) into the navigation bar.
hx-preserve
Preserves a bit of the DOM between requests; the original content will be kept, regardless of what is returned.
hx-sync
Synchronized requests between two or more elements.
hx-disable
Disables htmx behavior on this element and any children. We will come back to this when we discuss the topic of security.
Let’s take a look at `hx-sync`, which allows us to synchronize AJAX requests between two or more elements. Consider a simple case where we have two buttons that both target the same element on the screen:
Two competing buttons
This is fine and will work, but what if a user clicks the “Get Contacts” button and then the request takes a while to respond? And, in the meantime the user clicks the “Get Settings” button? In this case we would have two requests in flight at the same time.
If the /settings
request finished first and displayed the user’s setting information, they might be very surprised if they began making changes and then, suddenly, the /contacts
request finished and replaced the entire body with the contacts instead!
To deal with this situation, we might consider using an hx-indicator
to alert the user that something is going on, making it less likely that they click the second button. But if we really want to guarantee that there is only one request at a time issued between these two buttons, the right thing to do is to use the hx-sync
attribute. Let’s enclose both buttons in a div
and eliminate the redundant hx-target
specification by hoisting the attribute up to that div
. We can then use hx-sync
on that div to coordinate requests between the two buttons.
Here is our updated code:
<div hx-target="body" <1>
hx-sync="this"> <2>
<button hx-get="/contacts">
Get Contacts
</button>
<button hx-get="/settings">
Get Settings
</button>
</div>
Syncing two buttons
-
Hoist the duplicate
hx-target
attributes to the parentdiv
. -
Synchronize on the parent
div
.
By placing the hx-sync
attribute on the div
with the value this
, we are saying “Synchronize all htmx requests that occur within this div
element with one another.” This means that if one button already has a request in flight, other buttons within the div
will not issue requests until that has finished.
The hx-sync
attribute supports a few different strategies that allow you to, for example, replace an existing request in flight, or queue requests with a particular queuing strategy. You can find complete documentation, as well as examples, at the htmx.org page for hx-sync
.
As you can see, htmx offers a lot of attribute-driven functionality for more advanced Hypermedia-Driven Applications. A complete reference for all htmx attributes can be found on the htmx website.
Thus far we have worked with JavaScript events in htmx primarily via the hx-trigger
attribute. This attribute has proven to be a powerful mechanism for driving our application using a declarative, HTML-friendly syntax.
However, there is much more we can do with events. Events play a crucial role both in the extension of HTML as a hypermedia, and, as we’ll see, in hypermedia-friendly scripting. Events are the “glue” that brings the DOM, HTML, htmx and scripting together. You might think of the DOM as a sophisticated “event bus” for applications.
We can’t emphasize enough: to build advanced Hypermedia-Driven Applications, it is worth the effort to learn about events in depth.
In addition to making it easy to respond to events, htmx also emits many useful events. You can use these events to add more functionality to your application, either via htmx itself, or by way of scripting.
Here are some of the most commonly used events triggered by htmx:
htmx:load
Triggered when new content is loaded into the DOM by htmx.
htmx:configRequest
Triggered before a request is issued, allowing you to programmatically configure the request or cancel it entirely.
htmx:afterRequest
Triggered after a request has responded.
htmx:abort
A custom event that can be sent to an htmx-powered element to abort an open request.
Let’s look at an example of how to work with htmx-emitted events. We’ll use the htmx:configRequest
event to configure an HTTP request.
Consider the following scenario: your server-side team has decided that they want you to include a server-generated token for extra security on every request. The token is going to be stored in localStorage
in the browser, in the slot special-token
.
The token is being set via some JavaScript (don’t worry about the details yet) when the user first logs in:
let response = await fetch("/token"); <1>
localStorage['special-token'] = await response.text();
Getting The Token in JavaScript
- Get the value of the token then set it into localStorage
The server-side team wants you to include this special token on every request made by htmx, as the X-SPECIAL-TOKEN
header. How could you achieve this? One way would be to catch the htmx:configRequest
event and update the detail.headers
object with this token from localStorage
.
In VanillaJS, it would look something like this, placed in a <script>
tag in the <head>
of our HTML document:
document.body.addEventListener("htmx:configRequest", configEvent => {
configEvent.detail.headers['X-SPECIAL-TOKEN'] = <1>
localStorage['special-token'];
})
Adding the X-SPECIAL-TOKEN
header
- Retrieve the value from local storage and set it into a header.
As you can see, we add a new value to the headers
property of the event’s detail property. After the event handler executes, this headers
property is read by htmx and used to construct the request headers for the AJAX request it makes.
The detail
property of the htmx:configRequest
event contains a slew of useful properties that you can update to change the “shape” of the request, including:
detail.parameters
Allows you to add or remove request parameters
detail.target
Allows you to update the target of the request
detail.verb
Allows you to update HTTP “verb” of the request (e.g. GET
)
So, for example, if the server-side team decided they wanted the token included as a parameter, rather than as a request header, you could update your code to look like this:
document.body.addEventListener("htmx:configRequest", configEvent => {
configEvent.detail.parameters['token'] = <1>
localStorage['special-token'];
})
Adding the token
parameter
- Retrieve the value from local storage and set it into a parameter.
As you can see, this gives you a lot of flexibility in updating the AJAX request that htmx makes.
The full documentation for the htmx:configRequest
event (and other events you might be interested in) can be found on the htmx website.
We can listen for any of the many useful events from htmx, and we can respond to those events using hx-trigger
. What else can we do with events?
It turns out that htmx itself listens for one special event, htmx:abort
. When htmx receives this event on an element that has a request in flight, it will abort the request.
Consider a situation where we have a potentially long-running request to /contacts
, and we want to offer a way for the users to cancel the request. What we want is a button that issues the request, driven by htmx, of course, and then another button that will send an htmx:abort
event to the first one.
Here is what the code might look like:
<button id="contacts-btn" hx-get="/contacts" hx-target="body"> <1>
Get Contacts
</button>
<button
onclick="
document.getElementById('contacts-btn')
.dispatchEvent(new Event('htmx:abort')) <2>
">
Cancel
</button>
A button with an abort
-
A normal htmx-driven
GET
request to/contacts
-
JavaScript to look up the button and send it an
htmx:abort
event
So now, if a user clicks on the “Get Contacts” button and the request takes a while, they can click on the “Cancel” button and end the request. Of course, in a more sophisticated user interface, you may want to disable the “Cancel” button unless an HTTP request is in flight, but that would be a pain to implement in pure JavaScript.
Thankfully this isn’t too bad to implement in hyperscript, so let’s take a look at what that would look like:
<button id="contacts-btn" hx-get="/contacts" hx-target="body">
Get Contacts
</button>
<button
_="on click send htmx:abort to #contacts-btn
on htmx:beforeRequest from #contacts-btn remove @disabled from me
on htmx:afterRequest from #contacts-btn add @disabled to me">
Cancel
</button>
A hyperscript-Powered Button With An Abort
Now we have a “Cancel” button that is enabled only when a request from the contacts-btn
button is in flight. And we are taking advantage of htmx-generated and handled events, as well as the event-friendly syntax of hyperscript, to make it happen. Slick!
We are going to talk more about the various ways that htmx enhances regular HTTP requests and responses in the next section, but, since it involves events, we are going to discuss one HTTP Response header that htmx supports: HX-Trigger
. We have discussed before how HTTP requests and responses support headers , name-value pairs that contain metadata about a given request or response. We took advantage of the HX-Trigger
request header, which includes the id of the element that triggered a given request.
In addition to this request header , htmx also supports a response header also named HX-Trigger
. This response header allows you to trigger an event on the element that submitted an AJAX request. This turns out to be a powerful way to coordinate elements in the DOM in a decoupled manner.
To see how this might work, let’s consider the following situation: we have a button that grabs new contacts from some remote system on the server. We will ignore the details of the server-side implementation, but we know that if we issue a POST
to the /sync
path, it will trigger a synchronization with the system.
Now, this synchronization may or may not result in new contacts being created. In the case where new contacts are created, we want to refresh our contacts table. In the case where no contacts are created, we don’t want to refresh the table.
To implement this we could conditionally add an HX-Trigger
response header with the value contacts-updated
:
@app.route('/sync', methods=["POST"])
def sync_with_server():
contacts_updated = RemoteServer.sync() <1>
resp = make_response(render_template('sync.html'))
if contacts_updated <2>
resp.headers['HX-Trigger'] = 'contacts-updated'
return resp
Conditionally Triggering a contacts-updated
event
-
A call to the remote system that synchronized our contact database with it
-
If any contacts were updated we conditionally trigger the
contacts-updated
event on the client
This value would trigger the contacts-updated
event on the button that made the AJAX request to /sync
. We can then take advantage of the from:
modifier of the hx-trigger
attribute to listen for that event. With this pattern we can effectively trigger htmx requests from the server side.
Here is what the client-side code might look like:
<button hx-post="/integrations/1"> <1>
Pull Contacts From Integration
</button>
...
<table hx-get="/contacts/table"
hx-trigger="contacts-updated from:body"> <2>
...
</table>
The Contacts Table
-
The response to this request may conditionally trigger the
contacts-updated
event -
This table listens for the event and refreshes when it occurs
The table listens for the contacts-updated
event, and it does so on the body
element. It listens on the body
element since the event will bubble up from the button, and this allows us to not couple the button and table together: we can move the button and table around as we like and, via events, the behavior we want will continue to work fine. Additionally, we may want other elements or requests to trigger the contacts-updated
event, so this provides a general mechanism for refreshing the contacts table in our application.
We have just seen an advanced feature of HTTP responses supported by htmx, the HX-Trigger
response header, but htmx supports quite a few more headers for both requests and responses. In chapter 4 we discussed the headers present in HTTP Requests. Here are some of the more important headers you can use to change htmx behavior with HTTP responses:
HX-Location
Causes a client-side redirection to a new location
HX-Push-Url
Pushes a new URL into the location bar
HX-Refresh
Refreshes the current page
HX-Retarget
Allows you to specify a new target to swap the response content into on the client side
You can find a reference for all requests and response headers in the htmx documentation.
Even more important than response headers, in terms of information conveyed to the client, is the HTTP Response Code. We discussed HTTP Response Codes in Chapter 3. By and large htmx handles various response codes in the manner that you would expect: it swaps content for all 200-level response codes and does nothing for others. There are, however, two “special” 200-level response codes:
-
204 No Content
- When htmx receives this response code, it will not swap any content into the DOM (even if the response has a body) -
286
- When htmx receives this response code to a request that is polling, it will stop the polling
You can override the behavior of htmx with respect to response codes by, you guessed it, responding to an event! The htmx:beforeSwap
event allows you to change the behavior of htmx with respect to various status codes.
Let’s say that, rather than doing nothing when a 404
occurred, you wanted to alert the user that an error had occurred. To do so, you want to invoke a JavaScript method, showNotFoundError()
. Let’s add some code to use the htmx:beforeSwap
event to make this happen:
document.body.addEventListener('htmx:beforeSwap', evt => { <1>
if (evt.detail.xhr.status === 404) { <2>
showNotFoundError();
}
});
Showing a 404 dialog
-
Hook into the
htmx:beforeSwap
event. -
If the response code is a
404
, show the user a dialog.
You can also use the htmx:beforeSwap
event to configure if the response should be swapped into the DOM and what element the response should target. This gives you quite a bit of flexibility in choosing how you want to use HTTP Response codes in your application. Full documentation on the htmx:beforeSwap
event can be found at htmx.org.
Above we saw how to use a server-triggered event, via the HX-Trigger
HTTP response header, to update a piece of the DOM based on the response to another part of the DOM. This technique addresses the general problem that comes up in Hypermedia-Driven Applications: “How do I update other content?” After all, in normal HTTP requests, there is only one “target”, the entire screen, and, similarly, in htmx-based requests, there is only one target: either the explicit or implicit target of the element.
If you want to update other content in htmx, you have a few options:
The first option, and the simplest, is to “expand the target.” That is, rather than simply replacing a small part of the screen, expand the target of your htmx-driven request until it is large enough to enclose all the elements that need to be updated on a screen. This has the tremendous advantage of being simple and reliable. The downside is that it may not provide the user experience that you want, and it may not play well with a particular server-side template layout. Regardless, we always recommend at least thinking about this approach first.
A second option, a bit more complex, is to take advantage of “Out Of Band” content support in htmx. When htmx receives a response, it will inspect it for top-level content that includes the hx-swap-oob
attribute. That content will be removed from the response, so it will not be swapped into the DOM in the normal manner. Instead, it will be swapped in for the content that it matches by id.
Let’s look at an example. Consider the situation we had earlier, where a contacts table needs to be updated if an integration pulls down any new contacts. Previously we solved this by using events and a server-triggered event via the HX-Trigger
response header.
This time, we’ll use the hx-swap-oob
attribute in the response to the POST
to /integrations/1
. The new contacts table content will “piggyback” on the response.
<button hx-post="/integrations/1"> <1>
Pull Contacts From Integration
</button>
...
<table id="contacts-table"> <2>
...
</table>
The updated contacts table
-
The button still issues a
POST
to/integrations/1
. -
The table no longer listens for an event, but it now has an id.
Next, the response to the POST
to /integrations/1
will include the content that needs to be swapped into the button, per the usual htmx mechanism. But it will also include a new, updated version of the contacts table, which will be marked as hx-swap-oob="true"
. This content will be removed from the response so that it is not inserted into the button. Instead, it is swapped into the DOM in place of the existing table since it has a matching id.
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
...
Pull Contacts From Integration <1>
<table id="contacts-table" hx-swap-oob="true"> <2>
...
</table>
A response with out-of-band content
-
This content will be placed in the button.
-
This content will be removed from the response and swapped by id.
Using this piggybacking technique, you can update content wherever needed on a page. The hx-swap-oob
attribute supports other additional features, all of which are documented.
Depending on how exactly your server-side templating technology works, and what level of interactivity your application requires, out of band swapping can be a powerful mechanism for content updates.
Finally, the most complex mechanism for updating content is the one we saw back in the events section: using server-triggered events to update elements. This approach can be very clean, but also requires a deeper conceptual knowledge of HTML and events, and a commitment to the event-driven approach. While we like this style of development, it isn’t for everyone. We typically recommend this pattern only if the htmx philosophy of event-driven hypermedia really speaks to you.
If it does speak to you, however, we say: go for it. We’ve created some very complex and flexible user interfaces using this approach, and we are quite fond of it.
All of these approaches to the “Updating Other Content” problem will work, and will often work well. However, there may come a point where it would just be simpler to use a different approach for your UI, like the reactive one. As much as we like the hypermedia approach, the reality is that there are some UX patterns that simply cannot be implemented easily using it. The canonical example of this sort of pattern, which we have mentioned before, is something like a live online spreadsheet: it is simply too complex a user interface, with too many interdependencies, to be done well via exchanges of hypermedia with a server.
In cases like this, and any time you feel like an htmx-based solution is proving to be more complex than another approach might be, we recommend that you consider a different technology. Be pragmatic, and use the right tool for the job. You can always use htmx for the parts of your application that aren’t as complex and don’t need the full complexity of a reactive framework, and save that complexity budget for the parts that do.
We encourage you to learn many different web technologies, with an eye to the strengths and weaknesses of each one. This will give you a deep tool chest to reach into when problems present themselves. Our experience is that, with htmx, hypermedia is a tool you can reach for frequently.
We are not ashamed to admit: we are big fans of events. They are the underlying technology of almost any interesting user interface, and are particularly useful in the DOM once they have been unlocked for general use in HTML. They let you build nicely decoupled software while often preserving the locality of behavior we like so much.
However, events are not perfect. One area where events can be particularly tricky to deal with is debugging : you often want to know why an event isn’t happening. But where can you set a break point for something that isn’t happening? The answer, as of right now, is: you can’t.
There are two techniques that can help in this regard, one provided by htmx, the other provided by Chrome, the browser by Google.
The first technique, provided by htmx itself, is to call the htmx.logAll()
method. When you do this, htmx will log all the internal events that occur as it goes about its business, loading up content, responding to events and so forth.
This can be overwhelming, but with judicious filtering can help you zero in on a problem. Here are what (a bit of) the logs look like when clicking on the “docs” link on https://htmx.org, with logAll()
enabled:
htmx:configRequest
<a href="/docs/">
Object { parameters: {}, unfilteredParameters: {}, headers: {…}, target: body, verb: "get", errors: [], withCredentials: false, timeout: 0, path: "/docs/", triggeringEvent: a
, … }
htmx.js:439:29
htmx:beforeRequest
<a href="/docs/">
Object { xhr: XMLHttpRequest, target: body, requestConfig: {…}, etc: {}, pathInfo: {…}, elt: a
}
htmx.js:439:29
htmx:beforeSend
<a class="htmx-request" href="/docs/">
Object { xhr: XMLHttpRequest, target: body, requestConfig: {…}, etc: {}, pathInfo: {…}, elt: a.htmx-request
}
htmx.js:439:29
htmx:xhr:loadstart
<a class="htmx-request" href="/docs/">
Object { lengthComputable: false, loaded: 0, total: 0, elt: a.htmx-request
}
htmx.js:439:29
htmx:xhr:progress
<a class="htmx-request" href="/docs/">
Object { lengthComputable: true, loaded: 4096, total: 19915, elt: a.htmx-request
}
htmx.js:439:29
htmx:xhr:progress
<a class="htmx-request" href="/docs/">
Object { lengthComputable: true, loaded: 19915, total: 19915, elt: a.htmx-request
}
htmx.js:439:29
htmx:beforeOnLoad
<a class="htmx-request" href="/docs/">
Object { xhr: XMLHttpRequest, target: body, requestConfig: {…}, etc: {}, pathInfo: {…}, elt: a.htmx-request
}
htmx.js:439:29
htmx:beforeSwap
<body hx-ext="class-tools, preload">
Htmx logs
Not exactly easy on the eyes, is it?
But, if you take a deep breath and squint, you can see that it isn’t that bad: a series of htmx events, some of which we have seen before (there’s htmx:configRequest
!), get logged to the console, along with the element they are triggered on.
After a bit of reading and filtering, you will be able to make sense of the event stream, and it can help you debug htmx-related issues.
The preceding technique is useful if the problem is occurring somewhere within htmx, but what if htmx is never getting triggered at all? This comes up some times, like when, for example, you have accidentally typed an event name incorrectly somewhere.
In cases like this you will need recourse to a tool available in the browser itself. Fortunately, the Chrome browser by Google provides a very useful function, monitorEvents()
, that allows you to monitor all events that are triggered on an element.
This feature is available only in the console, so you can’t use it in code on your page. But, if you are working with htmx in Chrome, and are curious why an event isn’t triggering on an element, you can open the developers console and type the following:
monitorEvents(document.getElementById("some-element"));
Htmx logs
This will then print all the events that are triggered on the element with the id some-element
to the console. This can be very useful for understanding exactly which events you want to respond to with htmx, or troubleshooting why an expected event isn’t occurring.
Using these two techniques will help you as you (infrequently, we hope) troubleshoot event-related issues when developing with htmx.
In general, htmx and hypermedia tends to be more secure than JavaScript heavy approaches to building web applications. This is because, by moving much of the processing to the back end, the hypermedia approach tends not to expose as much surface area of your system to end users for manipulation and shenanigans.
However, even with hypermedia, there are still situations that require care when doing development. Of particular concern are situations where user-generated content is shown to other users: a clever user might try to insert htmx code that tricks the other users into clicking on content that triggers actions they don’t want to take.
In general, all user-generated content should be escaped on the server-side, and most server-side rendering frameworks provide functionality for handling this situation. But there is always a risk that something slips through the cracks.
In order to help you sleep better at night, htmx provides the hx-disable
attribute. When this attribute is placed on an element, all htmx attributes within that element will be ignored.
A Content Security Policy (CSP) is a browser technology that allows you to detect and prevent certain types of content injection-based attacks. A full discussion of CSPs is beyond the scope of this book, but we refer you to the Mozilla Developer Network article on the topic for more information.
A common feature to disable using a CSP is the eval()
feature of JavaScript, which allows you to evaluate arbitrary JavaScript code from a string. This has proven to be a security issue and many teams have decided that it is not worth the risk to keep it enabled in their web applications.
Htmx does not make heavy use of eval()
and, thus, a CSP with this restriction in place will be fine. The one feature that does rely on eval()
is event filters, discussed above. If you decide to disable eval()
for your web application, you will not be able to use the event filtering syntax.
There are a large number of configuration options available for htmx. Some examples of things you can configure are:
-
The default swap style
-
The default swap delay
-
The default timeout of AJAX requests
A full list of configuration options can be found in the config section of the main htmx documentation.
Htmx is typically configured via a meta
tag, found in the header of a page. The name of the meta tag should be htmx-config
, and the content attribute should contain the configuration overrides, formatted as JSON. Here is an example:
<meta name="htmx-config" content='{"defaultSwapStyle":"outerHTML"}'>
An htmx configuration via meta
tag
In this case, we are overriding the default swap style from the usual innerHTML
to outerHTML
. This might be useful if you find yourself using outerHTML
more frequently than innerHTML
and want to avoid having to explicitly set that swap value throughout your application.
Telling people to “use semantic HTML” instead of “read the spec” has led to a lot of people guessing at the meaning of tags — “looks pretty semantic to me!” — instead of engaging with the spec.
I think being asked to write meaningful HTML better lights the path to realizing that it isn’t about what the text means to humans—it’s about using tags for the purpose outlined in the specs to meet the needs of software like browsers, assistive technologies, and search engines.
https://t-ravis.com/post/doc/semantic_the_8_letter_s-word/
We recommend talking about, and writing, conformant HTML. (We can always bikeshed further). Use the elements to the full extent provided by the HTML specification, and let the software take from it whatever meaning they can.
Previous: A Dynamic Archive UI
Next: Client Side Scripting
Hypermedia Systems Contents
## 🤖 assistant _(Sep 28, 2024, 07:53 PM)_
Here's an outline of topics to create quiz questions about based on the HTMX material:
1. HTMX Attributes
- hx-swap and its modifiers
- hx-trigger and its modifiers
- Other important attributes (hx-get, hx-post, hx-target, etc.)
2. Events in HTMX
- HTMX-generated events
- Server-generated events
- Using htmx:configRequest
- Canceling requests with htmx:abort
3. HTTP Requests & Responses
- Custom headers in HTMX
- HTTP response codes and HTMX behavior
4. Updating Other Content
- Expanding selection
- Out of Band swaps
- Event-driven updates
5. Debugging HTMX
- Using htmx.logAll()
- Monitoring events in Chrome
6. Security Considerations
- Content Security Policies
- Using hx-disable
7. Configuring HTMX
- Setting config options via meta tags
8. Advanced HTMX Techniques
- Synchronizing requests with hx-sync
- Using event filters
- Working with synthetic events
Now I'll generate a series of questions and code challenges based on this outline to test the reader's understanding:
1. What is the purpose of the `hx-swap` attribute in HTMX? Provide an example of how to use it with the `show` modifier.
2. Explain the difference between the `delay` and `throttle` modifiers for the `hx-trigger` attribute.
3. Write an HTMX attribute that will trigger a GET request to "/data" when the element is clicked, but only once, and swap the result into the element with id "target".
4. How can you use the `htmx:configRequest` event to add a custom header to all HTMX requests? Provide a code example.
5. What is the purpose of the `HX-Trigger` response header? How would you use it to trigger a client-side event?
6. Write a button that, when clicked, will abort any ongoing HTMX request on an element with id "long-running-request".
7. How does HTMX handle a 204 No Content response? How about a 286 response?
8. Explain the concept of "Out of Band" swaps in HTMX. Provide an example of how to use the `hx-swap-oob` attribute.
9. What is the purpose of the `htmx.logAll()` function? When would you use it?
10. How can you use Chrome's `monitorEvents()` function to debug HTMX issues? Provide an example.
11. What is the purpose of the `hx-disable` attribute? In what scenarios would you use it?
12. How would you configure HTMX to use `outerHTML` as the default swap style for all requests? Provide the necessary HTML.
13. Explain how to use `hx-sync` to coordinate requests between multiple elements. Provide an example.
14. What is an event filter in HTMX? Provide an example of how to use one with the `hx-trigger` attribute.
15. How can you use the `intersect` synthetic event in HTMX? Provide an example.
16. Write an HTMX attribute that will load content from "/lazy-content" when the element becomes visible in the viewport.
17. How would you use the `htmx:beforeSwap` event to handle a 404 response by showing a custom error message?
18. Explain the purpose of the `hx-preserve` attribute. When might you use it?
19. How can you use the `hx-push-url` attribute to update the browser's address bar without a full page reload?
20. Write an HTMX attribute that will submit a form with id "myForm" every 5 seconds, but only if the form's data has changed.
21. How would you use the `from:` modifier in `hx-trigger` to listen for events on a different element?
22. Explain how to use the `queue` option in `hx-trigger`. What are some scenarios where this might be useful?
23. How can you use the `HX-Refresh` response header? What does it do?
24. Write an HTMX configuration that sets the default request timeout to 10 seconds and enables debugging.
25. How would you use the `hx-indicator` attribute to show a loading spinner during an HTMX request?
26. Explain how to use the `target` modifier in `hx-trigger` to filter events. Provide an example.
27. How can you use the `HX-Retarget` response header? What does it allow you to do?
28. Write an HTMX attribute that will load content from "/data" when the user has scrolled to within 200 pixels of the bottom of the page.
29. How would you use the `hx-boost` attribute to enhance regular links and forms with HTMX functionality?
30. Explain how to use the `HX-Location` response header. How does it differ from a regular HTTP redirect?
These questions cover a wide range of HTMX concepts and features, from basic usage to advanced techniques. They should help readers test and deepen their understanding of HTMX.
## 🧑 human _(Sep 28, 2024, 08:00 PM)_
Great! For these 30 questions, can you be more specific with code based quizes for each?
## 🤖 assistant _(Sep 28, 2024, 08:00 PM)_
Certainly! I'll provide more specific, code-based questions for each of the 30 topics. These questions will require the reader to write, modify, or analyze HTMX code to demonstrate their understanding.
1. Given the following HTML, modify the `hx-swap` attribute to fade out the old content over 300ms before swapping in the new content:
```html
<div hx-get="/data" hx-swap="innerHTML">Load Data</div>
- Complete the
hx-trigger
attribute to debounce keyup events by 500ms and only trigger if the input has changed:
<input type="text" name="search" hx-get="/search" hx-trigger="keyup [...]">
-
Write an HTMX attribute that will trigger a GET request to "/data" when the element is clicked, but only once, and swap the result into the element with id "target".
-
Complete the JavaScript to add a custom "X-CSRF-Token" header to all HTMX requests:
document.body.addEventListener('htmx:configRequest', (event) => {
// Add your code here
});
- Write the server-side code (in Python using Flask) to trigger a 'userUpdated' event on the client side when a user is updated:
@app.route('/update-user', methods=['POST'])
def update_user():
# Update user logic here
response = make_response("User updated")
# Add your code here to trigger the 'userUpdated' event
return response
-
Write the HTML for a button that, when clicked, will abort any ongoing HTMX request on an element with id "long-running-request".
-
Complete the server-side code (in Python using Flask) to respond with a 204 No Content status when a delete operation is successful:
@app.route('/delete-item/<int:id>', methods=['DELETE'])
def delete_item(id):
# Delete item logic here
# Add your code here to return a 204 No Content response
- Given the following HTML, add an out-of-band swap to update an element with id "notification" when the form is submitted:
<form hx-post="/submit">
<input type="text" name="data">
<button type="submit">Submit</button>
</form>
-
Write the JavaScript code to enable HTMX logging and filter the logs to only show 'htmx:afterRequest' events.
-
Write the JavaScript code to monitor all click events on an element with id "interactive-div" using Chrome's
monitorEvents()
function. -
Given the following HTML with user-generated content, add the appropriate attribute to disable HTMX processing within the div:
<div>
<h2>User Comment</h2>
<p>This is a user comment that might contain malicious HTMX attributes.</p>
</div>
-
Write the HTML
<meta>
tag to configure HTMX to useouterHTML
as the default swap style and set the default request timeout to 5 seconds. -
Complete the HTML to synchronize requests between two buttons so that only one request can be in flight at a time:
<div>
<button hx-get="/data1">Load Data 1</button>
<button hx-get="/data2">Load Data 2</button>
</div>
- Modify the
hx-trigger
attribute to only trigger the request when the input value is a valid email address:
<input type="email" name="email" hx-get="/validate-email" hx-trigger="change">
-
Write the HTML for a div that loads content from "/lazy-content" when it comes into view, with a 200px threshold.
-
Complete the JavaScript to show a custom error message when a 404 response is received:
document.body.addEventListener('htmx:beforeSwap', (event) => {
if (event.detail.xhr.status === 404) {
// Add your code here
}
});
- Given the following HTML, add the appropriate attribute to preserve the value of the input field during HTMX swaps:
<form hx-post="/submit" hx-target="#result">
<input type="text" name="important-data">
<button type="submit">Submit</button>
</form>
<div id="result"></div>
- Modify the following HTML to update the browser's address bar to "/new-page" when the button is clicked, without triggering a full page reload:
<button hx-get="/new-page-content" hx-target="body">Go to New Page</button>
- Complete the HTML to submit a form with id "myForm" every 5 seconds, but only if the form's data has changed:
<form id="myForm" hx-post="/auto-save" [...]>
<input type="text" name="data">
</form>
- Modify the HTML to listen for a 'userUpdated' event on the body element and trigger a refresh of the user info div:
<div id="user-info" hx-get="/user-info">
<!-- User info here -->
</div>
- Complete the
hx-trigger
attribute to queue up to 3 requests if they occur while a request is in flight:
<button hx-post="/increment" hx-trigger="click [...]">Increment</button>
- Write the server-side code (in Python using Flask) to force a refresh of the entire page after a successful form submission:
@app.route('/submit-form', methods=['POST'])
def submit_form():
# Form processing logic here
response = make_response("Form submitted successfully")
# Add your code here to force a page refresh
return response
- Modify the HTML to show a loading spinner (with class 'spinner') during an HTMX request:
<button hx-get="/slow-data">Load Slow Data</button>
<div class="spinner" style="display:none;">Loading...</div>
- Complete the
hx-trigger
attribute to only trigger the request when the click event occurs directly on the button, not on any child elements:
<button hx-post="/action" hx-trigger="click [...]">
<span>Perform Action</span>
</button>
- Write the server-side code (in Python using Flask) to retarget the swap to an element with id "notification" instead of the requesting element:
@app.route('/perform-action', methods=['POST'])
def perform_action():
# Action logic here
response = make_response("Action completed")
# Add your code here to retarget the swap
return response
- Complete the HTML to load more content when the user has scrolled to within 100 pixels of the bottom of the element with id "infinite-scroll":
<div id="infinite-scroll" hx-get="/more-content" [...]>
<!-- Existing content here -->
</div>
- Modify the following HTML to enhance the form with HTMX functionality, showing the result in the div with id "result":
<form action="/submit" method="post">
<input type="text" name="data">
<button type="submit">Submit</button>
</form>
<div id="result"></div>
- Write the server-side code (in Python using Flask) to redirect the user to "/dashboard" after a successful login, using HTMX:
@app.route('/login', methods=['POST'])
def login():
# Login logic here
if login_successful:
response = make_response("Login successful")
# Add your code here to redirect using HTMX
return response
else:
return "Login failed", 401
- Complete the HTML to load and append new items to the list, removing the clicked button:
<ul id="item-list">
<!-- Existing items here -->
</ul>
<button hx-get="/more-items" [...]>Load More</button>
- Modify the following HTML to swap in new content every 30 seconds, but stop after 5 swaps:
<div hx-get="/live-data" hx-trigger="every 30s">
<!-- Initial content here -->
</div>
These code-based questions should provide a more hands-on test of the reader's HTMX knowledge and skills.