htmx on Sinatra

—interactive web pages with hypermedia

Part 2: The State of HTML

Let's go back to our HTML example from the previous episode.

In its purest form, hypermedia is composed of just markup (on the client-side); and server-side logic. So I've only applied minimal styling in this example: a larger-than-default font size, and the centering of the container within the viewport. There is zero JavaScript.

We see some links, and a form. The first link allows us to reply to the message. We click it and navigate to a new page. On it, there's a form that issues a POST request to the resource that is the collection of replies to this particular Message:

<li>
  <form action="/messages/<%= @message.id %>/replies" method="post">
    <fieldset>
      <!-- input fields omitted for brevity -->
    </fieldset>
  </form>
</li>

So this form will submit data to the following route:

post '/messages/:id/replies' do
  # logic for creating the reply
end

and will result in this HTTP request:

POST messages/42/replies

No surprises here. Now let's look at how Sinatra implements the functionality for updating this record. Suppose the user wants to set a different flag on the message:

<li>
  <form action="/messages/<%= @message.id %>" method="post">
    <input type="hidden" name="_method" value="PATCH">
    <fieldset>
      <!-- input fields omitted for brevity -->
    </fieldset>
  </form>
</li>

This form will submit data to the following route:

patch '/messages/:id' do
  # logic for updating the message
end

And the value of its method attribute states it wants to send a POST request to the resource's URL:

POST /messages/42

But right under the opening form tag, there is an input element with the type hidden, indicating that the method we actually want to use is PATCH!

The POST method is used to create a new entity on the server. That's not what we're doing here — we are definitely interested in PATCH, which updates some part of the model with the new data (whereas PUT replaces the entire model).

So Sinatra must rely on a special input field to make the server process this as a partial update to our Message record — and not create a new one!

Hypermedia is broken

The reason we must employ this workaround is that currently, only the first two out of the five core HTTP verbs are supported within the HTML form element:

  • GET: fetches a resource
  • POST: creates a new resource
  • PUT: replaces an existing resource
  • PATCH: updates some part of a resource
  • DELETE: deletes the resource.

So browsers only understand GET and POST verbs as values of the form element's method attribute. In order to use the remaining methods, you must call them from JavaScript running in the browser.

Imagine having a car with 4 wheels, and only two of them working — that's the state hypermedia today! (I've grouped PUT and PATCH together as an "update" operation for this analogy).

In addition to this, there is a limitation as to which elements are able to act as hypermedia controls — or to issue requests using the above set of verbs. HTML currently features two hypermedia (or action) controls:

  1. the link element (<a>) — used for GET requests
  2. the form element (<form>) — mostly used for POST requests.

If you think about it, these limitations make no sense.

  1. Any element should be able to request data from the server. There is no reason for this behavior to be exclusive to the link element — normally used for providing a navigation path between two resources. It's easy to imagine a situation where a div element, say, must make a GET request in order to to populate itself with the response.
  2. Using a form element to create a new resource makes sense when the form must submit data entered by the user into its nested input fields; this is the classical use case. However, what if the creation of a new resource doesn't require input from the user? Suddenly, an element that's able to issue a POST request on its own becomes useful.

Implications

In the original design, scripting (or "code-on-demand", as Roy Fielding calls it) is an optional part of hypermedia:

The final addition to our constraint set for REST comes from the code-on-demand style of Section 3.5.3 (Figure 5-8). REST allows client functionality to be extended by downloading and executing code in the form of applets or scripts. This simplifies clients by reducing the number of features required to be pre-implemented. Allowing features to be downloaded after deployment improves system extensibility. However, it also reduces visibility, and thus is only an optional constraint within REST.

Source: https://ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm.

Said another way, if you turned JavaScript off in your browser, your web application should still be 100% functional — powered entirely by HTML links and forms!

Hypermedia (or more narrowly, HTML) can be powered only by links and forms.

However, in its original design hypermedia has a more complete feature set to support user interactions and server-side change — all without resorting to scripting. It's the current implementation that's limited to links and forms.

To sum up: there are shortcomings in how both HTML and the browsers implement the hypermedia model. htmx aims to fill in these gaps — by making the browser behave more like a true hypermedia client.

It turns out that when we address these foundational design principles, adding interactivity to web pages becomes simple and straightforward. It all becomes an extension of how the web already works.