hypermedia-systems/manuscript/CH11_gross_developing_with_htmx.adoc
2022-08-22 10:18:41 -06:00

792 lines
37 KiB
Plaintext

= Hypermedia In Action
:chapter: 11
:sectnums:
:figure-caption: Figure {chapter}.
:listing-caption: Listing {chapter}.
:table-caption: Table {chapter}.
:sectnumoffset: 10
// line above: :sectnumoffset: 5 (chapter# minus 1)
:leveloffset: 1
:sourcedir: ../code/src
:source-language:
= Developing With htmx
This chapter covers
* The details of htmx attributes
* Events & htmx
* HTTP Requests & htmx
* Updating Other Content
* Debugging htmx Applications
* Security Considerations
* Configuring htmx
[partintro]
== Getting Deeper Into htmx
In this chapter we are going to look more deeply into htmx. We've accomplished quite a bit with what we've learned
so far, but, when you are developing Hypermedia Driven Applications, there are likely to be situations that arise
that require additional functionality to address cleanly. We will go over some less commonly used attributes in htmx,
as well as expand on the details of some attributes we have already used.
Additionally, we will look at the 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.
== htmx Attributes
Thus far we have, we've used about fifteen different attributes from htmx in our application. The most important ones have been:
|===
|Attribute |Use
| `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
|===
Let's do a deep dive on two of these attributes, `hx-swap` and `hx-trigger`, because they support a large number of
options that might be useful when you are creating more advanced Hypermedia Driven Applications.
=== `hx-swap`
The `hx-swap` attribute is often not included on elements that issue htmx-driven requests. This is because, for many
cases, the default behavior, `innerHTML`, which swaps the inner HTML of the element, is fine. Of course, we have seen
cases where we wanted to override this behavior and use `outerHTML`, for example. And, in chapter 3, 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` also supports the following modifiers:
|===
|Modifier |Use
| `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.
| `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. (This defaults to
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 had completed, we would write the following HTML:
.Scrolling To The Top Of The Page
[source, html]
----
<button hx-get="/contacts" hx-target="#content-div"
hx-swap="innerHTML show:body:top"> <1>
Get Contacts
</button>
----
<1> This tells htmx to show the top of the body after the swap occurs
More details and examples can be found online at the documentation page for `hx-swap`: https://htmx.org/attributes/hx-swap/
=== `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 anyway. 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 was the active
search example we implemented in Contact.app:
.The Active Search Input
[source,html]
----
<input id="search" type="search" name="q" value="{{ request.args.get('q') or '' }}"
hx-get="/contacts"
hx-trigger="search, keyup delay:200ms changed"/> <1>
----
<1> An elaborate trigger specification
This example took advantage of two modifiers available for the `hx-trigger` attribute:
|===
|Modifier |Use
| `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 quite a few 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`:
|===
|Modifier |Use
| `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 parent elements, so a `click` event on a button will also trigger a `click` event on a parent
`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 parent 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.
|===
==== Filters
The `hx-trigger` attribute allows you to specify a _filter_ to 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, and you have
a JavaScript function, `contactRetrievalEnabled()` that returns a boolean, `true` if contacts can be retrieved and
`false` otherwise. You want to gate a button that issues a request to `/contacts` on this function. To do this using
an event filter in htmx, you would write the following HTML:
.The Active Search Input
[source,html]
----
<script>
function contactRetrievalEnabled() {
...
}
</script>
<button hx-get="/contacts" hx-trigger="click[contactRetrievalEnabled()]"> <1>
Get Contacts
</button>
----
<1> the event filter, calling `contactRetrievalEnabled()`
The button will not issue a request if `contactRetrievalEnabled()` returns false, allowing you to dynamically control
when the request will be made. Common situations that call for an event trigger are:
* Only issue a request when a certain element has focus
* Only issue a request when a given form is valid
* Only issue a request when 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 parent element.
This synthetic event uses the modern Intersection Observer API, which you can read more about
here: https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
Intersection gives you much finer grained control over exactly when a request should be triggered. For example, you can
specify a threshold and specify that the request should only be issued when an element is 50% visible.
The `hx-trigger` attribute certainly is the most complex on in htmx, and more details and examples can be found online its
documentation page: https://htmx.org/attributes/hx-trigger/
=== 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:
|====
|Attribute |Use
| 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 discuss this more below in the security section.
|====
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
[source, html]
----
<button hx-get="/contacts" hx-target="body"> <1>
Get Contacts
</button>
<button hx-get="/settings" hx-target="body"> <1>
Get Settings
</button>
----
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:
.Syncing Two Buttons
[source, html]
----
<div hx-target="body" <1>
hx-sync="this"> <2>
<button hx-get="/contacts"> <1>
Get Contacts
</button>
<button hx-get="/settings"> <1>
Get Settings
</button>
</div>
----
<1> Hoist the duplicate `hx-target` attributes to the parent `div`
<2> 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 documentation page for `hx-sync`: https://htmx.org/attributes/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 at https://htmx.org/reference/#attributes
== Events
We have been working with events in htmx primary via the `hx-trigger` attribute. This has proven to be a powerful
mechanism for driving our application using declarative, HTML-friendly syntax. However, there is more to events
and htmx than just `hx-trigger`.
=== htmx-generated Events
It turns out that, 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 in htmx:
|===
|Event |Description
|`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
|===
We have already seen how to use the `htmx:load` event, using the `htmx.onLoad()` API in the sortable.js example, which
is probably one of the most common uses of events.
=== Using The `htmx:configRequest` Event
Let's take a look at how you might use the `htmx:configRequest` event to configure an HTTP request. Consider the following
scenario: our server-side team has decided that they want you to include a token for extra validation on every request.
The token is going to be stored in `localStorage` in the browser, in the slot `special-token`. 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:
.Adding the `X-SPECIAL-TOKEN` Header
[source,js]
----
document.body.addEventListener("htmx:configRequest", function(configEvent){
configEvent.detail.headers['X-SPECIAL-TOKEN'] = localStorage['special-token']; <1>
})
----
<1> 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. After the event handler executes,
the `headers` property is read by htmx and used to construct the headers for an AJAX request. So, with this bit of
JavaScript code, we have added a new custom header to every AJAX request that htmx makes. Slick!
You can also update the `parameters` property to change the parameters submitted by the request, change the target
of the request, and so on. Full documentation for the `htmx:configRequest` event can be found here: https://htmx.org/events/#htmx:configRequest
=== Canceling a Request using `htmx:abort`
So we can listen for many useful events from htmx, and we can respond to 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:
.A Button With An Abort
[source, html]
----
<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>
----
<1> A normal htmx-driven `GET` request to `/contacts`
<2> JavaScript to look up the button and send it an `htxm: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
it isn't too bad to implement in hyperscript, so let's take a look at what that would look like:
.A hyperscript-Powered Button With An Abort
[source, html]
----
<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>
----
Now we have a "Cancel" button that is disabled 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. Not bad!
=== Server Generated Events
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.
It turns out that there is a _response_ header, also named `HX-Trigger` in that htmx supports. 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, lets 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 `/integrations/1` 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.
How could we implement this using the `HX-Trigger` response header? Well, we could conditionally add an `HX-Trigger`
response header with the value `contacts-updated`, which would trigger the `contacts-updated` event on the button that
made the AJAX request to `/integrations/1`. And we can then take advantage of the `from:` modifier of the `hx-trigger`
attribute to listen for that event! Now we can effectively trigger htmx request from the server side!
Here is what the client-side code might look like:
.The Contacts Table
[source, html]
----
<button hx-post="/integrations/1"> <1>
Pull Contacts From Integration
</button>
...
<table hx-get="/contacts/table" hx-trigger="contacts-updated from:body"> <2>
...
</table>
----
<1> The response to this request may conditionally trigger the `contacts-updated` event
<2> 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. Very nice!
Now, we are omitting the server side implementation of this feature in the interest of simplicity, but this gives you
and idea of how the `HX-Trigger` response header can be used to coordinate sophisticated interactions in the DOM.
== HTTP Requests & Responses
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 5 we discussed the
headers present in HTTP Requests. Here some of the more important headers you can use to change htmx behavior with
HTTP responses:
|===
|Response Header |Description
|`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 here: https://htmx.org/reference/#headers
=== HTTP Response Codes
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 4. 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:
.Showing a 404 Dialog
[source,js]
----
document.body.addEventListener('htmx:beforeSwap', function(evt) { <1>
if(evt.detail.xhr.status === 404){ <2>
showNotFoundError();
}
});
----
<1> hook into the `htmx:beforeSwap` event
<2> 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 here: https://htmx.org/events/#htmx:beforeSwap
== Updating Other Content
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:
=== Expanding Your Selection
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 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, I always recommend at least thinking about this approach first.
=== Out of Band Swaps
A second option, which is a bit more complex, is to take advantage of "Out Of Band" content support in htmx. When
htmx receives a response, it will look for top-level content in that response that includes the `hx-swap-oob` attribute
on it. 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 its id.
Let's look at an example of this approach. Let's consider the situation we had above, where a contacts table needs to be updated
conditionally, based on 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.
In this case, instead of using an event, let's take advantage of the `hx-swap-oob` attribute in the response to the
`POST` to `/integrations/1` to "piggy back" the new contacts table content on the response.
.The Updated Contacts Table
[source, html]
----
<button hx-post="/integrations/1"> <1>
Pull Contacts From Integration
</button>
...
<table id="contacts-table"> <2>
...
</table>
----
<1> the button still issues a `POST` to `/integrations/1`
<2> the table no longer listens for an event, but it now has an id
Now let's look at a potential response to the `POST` to `/integrations/1`. This response will include the "regular"
content that needs to be swapped into the button, per the usual htmx mechanism. But it will also include a new version,
updated version of the contacts table, which will be marked as `hx-swap-oob="true"`. This content will be removed from
the response so it is not inserted into the button, but will be instead swapped into the DOM in place of the existing
table since it has the same id value.
.A Response With Out of Band Content
[source]
----
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>
----
<1> this content will be placed in the button
<2> this content will be removed from the response and swapped by id
Using this technique, you are able to piggyback content updates of other elements on top of requests by other elements.
The `hx-swap-oob` attribute supports other additional features, all of which are documented here: https://htmx.org/attributes/hx-swap-oob/
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 more flexible content updates.
=== Events
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 lot 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
and we typically recommend this 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.
=== Being Pragmatic
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, 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 to complex a user interface, with too many inter-dependencies, 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 can gladly recommend that you consider a different technology: 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 are not hypermedia puritans and 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
hope is that, with htmx, hypermedia might be a tool you reach for more frequently!
== Debugging
We have been talking a lot about events in this chapter and 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 using 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.
=== Logging htmx Events
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 Logs
[source, text]
----
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">
----
Yikes! 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.
=== Monitoring Events in Chrome
The preceding technique is useful if the problem is occuring 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:
.htmx Logs
[source, javascript]
----
monitorEvents(document.getElementById("some-element"));
----
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.
== Security Considerations
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.
=== Content Security Policies & htmx
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 them for more information: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
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.
== Configuring
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: https://htmx.org/docs/#config
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:
.An htmx configuration via a `meta` tag
[source, javascript]
----
<meta name="htmx-config" content='{"defaultSwapStyle":"outerHTML"}'>
----
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.
== Summary
* In this chapter we looked at some details and tricks of htmx development
* We looked in detail at the options available for `hx-swap` and `hx-trigger`, including filters and scrolling
* We took a look at the events that htmx triggers and responds to
* We explored HTTP response headers and HTTP response codes in htmx, and how to modify how htmx handles them
* We looked at various techniques for updating content beyond the target of a request, including out of band swaps
* We saw how htmx applications can be debugged, secured and configured