HTMX is a great little library that allows you to make HTTP requests (among other things) from any node in the DOM. There is a lot to say about HTMX and it’s philosophy. The main idea is that the server should respond with html, rather than json. To read more about the HTMX approach please refer to their docs at https://htmx.org/docs/ and their essays at https://htmx.org/essays/. If you have the time, I recommend reading their book HYPERMEDIA SYSTEMS. It outlines a new way to code websites and apps, using tried and true, but largely forgotten, web dev principles.
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.
<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.
<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.
// 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
.
<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:
{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
{baseUrl}/records?sorDir=asc&sortField=firstName
the HTML that comes as a response will replace the firstName
sort button url with
{baseUrl}/records?sorDir=desc&sortField=firstName
Note desc
in the url. If this new url is the request url, then the response will contain
{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:
{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:
{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.
Do you need a software agency?
We want to hear from you!