Tagged: HTMX
-
HTMX: Dynamic Url Parameters
Often, we need to send url parameters with our request. A straightforward way to do this with HTMX is to add the query params directly on the url. ```html <button hx-get="/records?active=true" hx-target="#table" hx-swap="outerHTML"> Get records </button> ``` The code above creates this request url: `{baseUrl}/records?active=true`. The `hx-target` attribute specifies the CSS selector of the DOM node that should be swapped with the incoming HTML. The `hx-swap` attribute defines the kind of swap. By default, HTMX uses `"innerHTML"` which will replace the contents of the `hx-target`. In our case, we want to replace the entire table, so `"outerHTML"` is specified. Another way to write this code is to use the `hx-vals` attribute. ```html <button hx-get="/records" hx-target="#table" hx-swap="outerHTML" hx-vals="{ active: true }" > Get records </button> ``` The code above produces the same url: `{baseUrl}/records?active=true`. But often a GET request with params is going to be more complex than this. What about a table that displays records, and the table has a sort button above each column? When you click the sort button it sorts that column `asc` first. If you click it again it sorts `desc`. And if you click it a third time, it removes the sort direction. With behavior like this, you need to keep track of the sort column and the sort direction. The column name can be hard coded in the `hx-get` or in `hx-vals`, but the sort direction is dynamic. In this case, a little scripting is in order. Let’s keep track of sort direction state in a name-spaced window object. ```javascript // add this script in the head of your html file like below // <script defer src="/path/to/tableData.js"></script> // * create namespace add data and methods/behavior if (!window.tableData) { window.tableData = { _sortDir: "", _sortField: "", get sortDir() { return this._sortDir; }, set sortDir(val) { this._sortDir = val; }, set sortField(field) { this._sortField = field; }, get sortField() { return this._sortField; }, toggleSort(field) { if (field !== this.sortField) { // if the sort field is not the same as the last time, then // reset the sort direction flow ("" => "asc" => "desc") this.sortDir = ""; this.sortField = field; } switch (this.sortDir) { case "asc": this.sortDir = "desc"; return this.sortDir; case "desc": this.sortDir = ""; return this.sortDir; case "": this.sortDir = "asc"; return this.sortDir; default: return this.sortDir; } }, }; } ``` Now, if we add `js:` before our `hx-vals` object, we can execute javascript in the params object. We are going to use the `toggleSort` method from `tableData` to determine the sort direction. Here is what the mark up would look like for the button above the column for `firstName`. ```html <button hx-get="/records" hx-target="#table" hx-swap="outerHTML" hx-vals="js:{ sortDir: tableData.toggleSort('firstName') sortField: 'firstName', }" > Sort First Name </button> ``` The code above produces this url: ```jsx {baseUrl}/records?sorDir={value}&sortField=firstName ``` where `{value}` is the sort direction returned by the `toggleSort()` method. To keep with the philosophy of HTMX, we want to keep scripting to a minimum — logic that can be executed on the server, should be executed on the server. But this is a case where a small piece of state and its corresponding behavioral logic makes sense. ## But… You could just as easily accomplish this entirely on the backend. Keeping with the `firstName` column, the server could prepare the url to already have the `asc` direction on load. When a user clicks that, the table head and table contents are replaced. This new code will return `desc` as the `firstName` sort direction. In other words, when the request url is ```jsx {baseUrl}/records?sorDir=asc&sortField=firstName ``` the HTML that comes as a response will replace the `firstName` sort button url with ```jsx {baseUrl}/records?sorDir=desc&sortField=firstName ``` Note `desc` in the url. If this new url is the request url, then the response will contain ```jsx {baseUrl}/records?sorDir=&sortField=firstName ``` as the url for the `firstName` sort button. Note there is no sort direction, meaning the response will use the default sorting that the backend provides. The function used by the server will use the same logic (switch statement) that our script uses. After sorting the `firstName` column, if a user clicks sort on, let’s say, the `lastName` column, the HTML from the server response will set this url on the `firstName` sort: ```jsx {baseUrl}/records?sorDir=asc&sortField=firstName ``` which is the original url for that sort button. In that same response, the HTML will have the following url in the `lastName` sort button: ```jsx {baseUrl}/records?sorDir=desc&sortField=lastName ``` as `desc` is the sort that the user calls when clicking the `lastName` sort button a second time (the first time, i.e. the original `lastName` sort url, the direction was `asc`). The lesson here is that you can use a little javascript to make request parameters dynamic, but there is often a way to accomplish this on the server. The more logic that is moved from the UI to the server, the easier it is to understand your UI code. Moving UI logic to the server also reduces the amount of code that has to be sent down the wire to users, thereby reducing load time and providing a smoother user experience.
-
HTMX: Execute Logic Before Swapping
When using a front end framework like React, you can make a request, wait for the response, and then apply logic on the returned data. If there is an error, you can run some error logic, if success then run success logic. HTMX works a little differently. If the request comes back with a status code outside of the 200 range, it doesn’t do anything with the response. The page does not change. HTMX does provide a way for you to run logic after a request finishes, and before there is any HTML swapping. The example here is detecting a successful response, and **closing a modal that contains a user form** only if the response has a status code of 200. ## The `afterRequest` event Often you will want to POST form data, and if validation errors are returned, you want to display the validation messages in the modal while it is still open. If the POST is successful and has no validation errors, then close the modal. With HTMX this can be achieved by listening for the HTMX event `afterRequest`. This event is fired when the response settles, and before HTMX does any DOM node swapping. In this ‘form inside a modal pattern’ example, we use the unassigned HTTP code `209` to mean the response contains error messages that need to be displayed. If the response has a status code of `209`, the modal should remain open to display validation messages. A successful operation returns a `200` status code. If a `200` response comes in, we want to close the modal. We do not want listen to just any successful response. We only care about the response triggered by the user form. HTMX uses headers that contain information regarding the source of the request. In our case, we can look at the `"HX-Trigger"` header to see the `id` of the DOM node that triggered the event. If the HTTP call was not triggered by the `#htmx-user-form`, we will ignore it. To code out this behavior, first, listen for events coming from the modal (`eventIsFromTheForm`). Then, detect if the request was successful (`eventWasSuccessful`). If the request is from the modal (`headers["HX-Trigger"] === "htmx-user-form"`) and is successful, then close the modal. If not, keep the modal open to display the validation messages. In order to not bury this logic in some place hard to find, name the script in a way that is easy to understand, like `userModalFormAfterRequestListener.js`, and put it in the head of your HTML document. ```jsx htmx.on("htmx:afterRequest", (e) => { // assign the data we need to descriptive variables const eventIsFromTheForm = e.detail.requestConfig.headers["HX-Trigger"] === "htmx-user-form"; const eventWasSuccessful = e.detail.xhr.status === 200; // we only care about events from the user form if (!eventIsFromTheUserForm) return; // only status code 200 close the modal. (our backend // gives a response with validation errors/messages a status of 209), so // keep the modal open in that case in order to display the messages if (!eventWasSuccessful) return; // if no validation errors come back, // reset the form and close the modal document.getElementById("htmx-user-form").reset(); // assuming the modal is a <dialog> element with an // id #userModal, `userModal.close()` will close it userModal.close(); }); ``` Although we are using the `"htmx:afterRequest"` event to determine whether or not to close a modal, listening for this event allows you to run any logic you need. You can grab status codes, the DOM node that issued the request, and anything else in the response in order to run your own logic based on response data. `"htmx:afterRequest"` is only one of many events that HTMX emits. For a full list, please see the HTMX docs at https://htmx.org/events/
Do you need a software agency?
We want to hear from you!