Merge branch 'main' of github.com:bigskysoftware/building-hypermedia-systems

This commit is contained in:
Deniz Akşimşek 2023-03-01 19:28:50 +03:00
commit d7a766cb48
2 changed files with 149 additions and 150 deletions

View File

@ -18,7 +18,7 @@ slick features that most developers today would assume requires the use of a hea
In order to demonstrate how web 1.0 applications works, we need to pick a server-side language and a library for
handling HTTP requests. Colloquially, this is called our "`Server-Side`" or "`Web`" stack, and there are literally hundreds
of options to choose from, many with passionate followings. You probably have a web framework that you prefer and, while
we wish we could write this book for every possible stack out there, in the interest of simplicity (and sanity) we can only pick one.
we wish we could write this book for every possible stack out there, in the interest of simplicity (and sanity) we can only pick one.
For this book we are going to use the following stack:
@ -33,16 +33,18 @@ First, Python is the most popular programming language in the world, as of this
https://www.tiobe.com/tiobe-index/[TIOBE index], a respected measure of programming language popularity.
Perhaps more importantly, Python is very easy to read even if you aren't very familiar with it.
Flask was chosen as the web framework because it is very simple and does not impose a lot of structure on top of the
We chose the Flask web framework because it is very simple and does not impose a lot of structure on top of the
basics of HTTP request handling. This bare-bones approach isn't for everyone: in the Python community, for example, many people
prefer the "`Batteries Included`" nature of Django, which supplies much more functionality out of the box than Flask does.
For teaching purposes, and to minimize the conceptual burden for non-Python developers, we feel that an un-opinionated
and lighter-weight library will make it easier for readers to follow along. The focus of this book is on the _hypermedia
exchanges_, rather than deeper server-side functionality like working with a database, and by using Flask we are able
to keep the book focused on that aspect.
to keep the book focused on that aspect.
Jinja2 templates were picked because they are the default templating language for Flask. They are simple enough and
So, even if this isn't your preferred stack, keep going: you will gain from the patterns we introduce here.
We picked Jinja2 templates because they are the default templating language for Flask. They are simple enough and
similar enough to most other server-side templating languages that most people who are familiar with any server-side
(or client-side) templating library should be able to understand them quickly and easily.

View File

@ -11,10 +11,9 @@ was built using nothing but forms and anchor tags, the traditional hypermedia co
The application exchanges hypermedia (HTML) with the server over HTTP, issuing `GET` and `POST` HTTP requests and
receiving back full HTML documents in response.
It is a pretty basic web application, but it is also definitely a Hypermedia-Driven Application.
It is a basic web application, but it is also definitely a Hypermedia-Driven Application. It is robust, it leverages the web's native technologies, and it is simple to understand.
The application, as it stands, is robust, it leverages the web's native technologies and is simple to understand. So
what's not to like about the application, then?
So what's not to like about the application?
Unfortunately, our application has a few issues common to web 1.0 style applications:
@ -25,7 +24,7 @@ Unfortunately, our application has a few issues common to web 1.0 style applicat
* From a technical perspective, all the updates are done with the `POST` HTTP method. This, despite the fact that
more logical actions and HTTP request types like `PUT` and `DELETE` exist and would make more sense for some
of the operations we implemented. After all, if we wanted to delete a resource, wouldn't it make more sense to use an HTTP `DELETE` request
to do so? Somewhat ironically, since we are using pure HTML, we are unable to access the full expressive power
to do so? Somewhat ironically, since we have used pure HTML, we are unable to access the full expressive power
of HTTP, which was designed specifically _for_ HTML.
The first point, in particular, is noticeable in Web 1.0 style applications like ours and is what is responsible for giving
@ -54,18 +53,21 @@ In this style of application, communication with the server is typically done vi
with the application sacrificing the advantages of hypermedia in order to provide a better, smoother user experience.
Many web developers today would not even consider the hypermedia approach due to the perceived "`legacy`" feel of these
Web 1.0 style applications.
web 1.0 style applications.
The second, technical point may strike you as a bit pedantic, and we am the first to admit that conversations around
REST and which HTTP Action is right for a given operation can become very tedious. But still, it's mildly infuriating that,
The second, technical point may strike you as a bit pedantic, and we are the first to admit that conversations around
REST and which HTTP Action is right for a given operation can become very tedious. But still, it's odd that,
when using plain HTML, it is impossible to use HTTP fully.
== A Close Look At A Hyperlink
It turns out that we can actually boost the interactivity of our application without resorting to the SPA approach, by
using a library called https://htmx.org[htmx]. To understand how htmx allows us to improve the UX of our Web 1.0 style
It turns out that we can boost the interactivity of our application without resorting to the SPA approach, by using one of several available hypermedia-oriented JavaScript libraries.
We'll use the library that we've developed, https://htmx.org[htmx], not only because we know it inside and out, but because we built it purely to extend HTML as a hypermedia.
To understand how htmx allows us to improve the UX of our Web 1.0 style
application without abandoning hypermedia, let's revisit the hyperlink/anchor tag from Chapter 1. Recall, a hyperlink
is what is known as a _hypermedia control_, a mechanism
is what is known as a _hypermedia control_, a mechanism that describes some sort of interaction by encoding information about that interaction directly and completely within
itself.
Consider again this simple anchor tag which, when interpreted by a browser, creates a hyperlink to the website for
this book:
@ -78,7 +80,7 @@ this book:
</a>
----
Let's break down in painstaking detail exactly what happens with this link:
Let's break down exactly what happens with this link:
* The browser will render the text "`Hypermedia Systems`" to the screen, likely with a decoration indicating it is clickable.
* Then, when a user clicks on the text...
@ -92,25 +94,27 @@ Now, let's take a moment and think about how we can _generalize_ these last thre
=== Why Only Anchors & Forms?
An initial observation is: what makes anchor tags (and forms) so special?
Consider: what makes anchor tags (and forms) so special?
Shouldn't other elements be able to issue HTTP requests as well?
Why can't other elements issue HTTP requests as well?
For example, why shouldn't `button` elements be able to issue HTTP requests? It seemed kind of silly to have to wrap a
form tag around a button just to make deleting contacts work in our application. Maybe other elements should be able
For example, why shouldn't `button` elements be able to issue HTTP requests? It seems arbitrary to have to wrap a
form tag around a button just to make deleting contacts work in our application.
Maybe: other elements should be able
to issue HTTP requests as well, and act as hypermedia controls on their own.
This is our first opportunity to generalize HTML as a hypermedia:
This is our first opportunity to generalize HTML as a hypermedia.
[IMPORTANT]
.Opportunity 1
====
HTML could be extended to allow _any_ element to issue a request to the server and act as a hypermedia control
HTML could be extended to allow _any_ element to issue a request to the server and act as a hypermedia control.
====
=== Why Only Clicks & Submits?
For our next observation, let's consider the event that triggers the request to the server on our link: a click event.
Next, let's consider the event that triggers the request to the server on our link: a click event.
Well, what's so special about clicking (in the case of anchors) or submitting (in the case of forms)? Those are just two
of many, many events that are fired by the DOM, after all. Events like mouse down, or key up, or blur are all events
@ -123,28 +127,28 @@ This gives us our second opportunity to expand the expressiveness of HTML:
[IMPORTANT]
.Opportunity 2
====
HTML could be extended to allow _any_ event, not just a click, as in the case of our hyperlinks, to trigger HTTP requests
HTML could be extended to allow _any_ event, not just a click, as in the case of hyperlinks, to trigger HTTP requests.
====
=== Why Only `GET` & `POST`?
Getting a bit more technical in our thinking leads us to the problem we noted earlier: plain HTML only
give us access to the `GET` and `POST` actions of HTTP?
give us access to the `GET` and `POST` actions of HTTP.
HTTP _stands_ for Hypertext Transfer Protocol, and yet the format it was explicitly designed for, HTML, only supports
two of the five developer-facing request types! You _have_ to use JavaScript and issue an AJAX request to get at the
two of the five developer-facing request types. You _have_ to use JavaScript and issue an AJAX request to get at the
other three: `DELETE`, `PUT` and `PATCH`.
Let's recall what are all of these different HTTP request types designed to represent?
Let's recall what these different HTTP request types are designed to represent:
* `GET` corresponds with "`getting`" a representation for a resource from a URL: it is a pure read, with no mutation of
the resource
* `POST` submits an entity (or data) to the given resource, often creating or mutating the resource and causing a state change
* `PUT` submits an entity (or data) to the given resource for update or replacement, again likely causing a state change
* `PATCH` is similar to `PUT` but implies a partial update and state change rather than a complete replacement of the entity
* `DELETE` deletes the given resource
the resource.
* `POST` submits an entity (or data) to the given resource, often creating or mutating the resource and causing a state change.
* `PUT` submits an entity (or data) to the given resource for update or replacement, again likely causing a state change.
* `PATCH` is similar to `PUT` but implies a partial update and state change rather than a complete replacement of the entity.
* `DELETE` deletes the given resource.
These operations correspond closely to the CRUD operations we discussed in Chapter 2, and by only giving us access to two
These operations correspond closely to the CRUD operations we discussed in Chapter 2. By giving us access to only two
of the five, HTML hamstrings our ability to take full advantage of HTTP.
This gives us our third opportunity to expand the expressiveness of HTML:
@ -152,7 +156,7 @@ This gives us our third opportunity to expand the expressiveness of HTML:
[IMPORTANT]
.Opportunity 3
====
HTML could be extended so that it could access these missing three HTTP methods, `PUT`, `PATCH` and `DELETE`.
HTML could be extended so that it allows access to the missing three HTTP methods, `PUT`, `PATCH` and `DELETE`.
====
=== Why Only Replace The Entire Screen?
@ -161,7 +165,7 @@ As a final observation, consider the last aspect of a hyperlink: it replaces th
It turns out that this technical detail is the primary culprit for poor user experience in Web 1.0 Applications.
A full page refresh can cause a flash of unstyled content, it destroys the scroll state of the user by scrolling to the
top of the page no matter what, and so forth.
top of the page, and so forth.
But there is no rule saying that hypermedia exchanges _must_ replace the entire document.
@ -171,29 +175,29 @@ This gives us our fourth, final and perhaps most important opportunity to genera
.Opportunity 4
====
HTML could be extended to allow the responses to requests to replace elements _within_ the current document, rather than
requiring that they replace the _entire_ document
requiring that they replace the _entire_ document.
====
This is actually a very old concept in hypermedia. Ted Nelson, in his 1980 book "`Literary Machines`" coined the term
_transclusion_ to capture this idea: the inclusion of content into an existing document via a hypermedia reference.
If HTML supported this style of "`dynamic transclusion`", then Hypermedia Driven Applications could function much more like
If HTML supported this style of "`dynamic transclusion,`" then Hypermedia Driven Applications could function much more like
a Single Page Application, where only part of the DOM is updated by a given user interaction or network request.
== Extending HTML as a Hypermedia with htmx
These four opportunities present us a way to generalize HTML that would extend HTML well beyond its current abilities, but
These four opportunities present us a way to extend HTML well beyond its current abilities, but
in a way that is _entirely within_ the original hypermedia model of the web. The fundamentals of HTML, HTTP, the browser,
and so on, won't be changed dramatically. Rather, these generalizations of _existing functionality_ already found within
HTML would simply let us accomplish _more_ using HTML.
htmx is a JavaScript library that extends HTML in exactly this manner, and it will be the focus of the next few chapters
of this book. htmx is not the only JavaScript library that takes this hypermedia-oriented approach (other excellent
examples are https://unpoly.com[Unpoly] and https://hotwire.dev[Hotwire]), but htmx is the purest of these libraries in
Htmx is a JavaScript library that extends HTML in exactly this manner, and it will be the focus of the next few chapters
of this book. Again, htmx is not the only JavaScript library that takes this hypermedia-oriented approach (other excellent
examples are https://unpoly.com[Unpoly] and https://hotwire.dev[Hotwire]), but htmx is the purest in
its pursuit of extending HTML as a hypermedia.
=== Installing and Using htmx
From a practical "`getting started`" perspective, Htmx is a simple, dependency-free and stand-alone JavaScript library that
From a practical "`getting started`" perspective, htmx is a simple, dependency-free and stand-alone JavaScript library that
can be added to a web application by simply including it via a `script` tag in your `head` element.
Because of this simple installation model, you can take advantage of tools like public CDNs to install the library.
@ -217,13 +221,12 @@ We also mark the script as `crossorigin="anonymous"` so no credentials will be s
----
If you are used to modern JavaScript development, with complex build systems and large numbers of dependencies, it may
be a bit shocking to find that that's all it takes to install htmx!
be a pleasant surprise to find that that's all it takes to install htmx.
This is in the spirit of the early web, when you could simply include a script tag and things would "`just work`". To be honest,
this still feels a bit like magic, even today!
This is in the spirit of the early web, when you could simply include a script tag and things would "`just work.`"
Of course, you might not want to use a CDN. In that case you can download htmx to your local system and adjust the
script tag to point to wherever you keep your static assets. Or, you may have one of those more sophisticated build system
If you don't want to use a CDN, you can download htmx to your local system and adjust the
script tag to point to wherever you keep your static assets. Or, you may have a build system
that automatically installs dependencies. In this case you can use the Node Package Manager (npm) name for the library:
`htmx.org` and install it in the usual manner that your build system supports.
@ -231,10 +234,10 @@ Once htmx has been installed, you can begin using it immediately.
=== No JavaScript Required...
And here we get to the funny part of htmx: unlike the vast majority of JavaScript libraries, htmx does not require you,
And here we get to the fun part of htmx: htmx does not require you,
the user of htmx, to actually write any JavaScript.
Instead, you will use _attributes_ placed directly on elements in your HTML to drive more dynamic behavior. htmx extends
Instead, you will use _attributes_ placed directly on elements in your HTML to drive more dynamic behavior. Htmx extends
HTML as a hypermedia, and it wants that extension to be as natural and consistent as possible with existing
HTML concepts. Just as an anchor tag uses an `href` attribute to specify the URL to retrieve, and forms use an `action`
attribute to specify the URL to submit the form to, htmx uses HTML _attributes_ to specify the URL that an HTTP request
@ -246,19 +249,19 @@ Let's look at the first feature of htmx: the ability for any element in a web pa
core functionality provided by htmx, and it consists of five attributes that can be used to issue the five different
developer-facing types of HTTP requests:
* `hx-get` - issues an HTTP `GET` request
* `hx-post` - issues an HTTP `POST` request
* `hx-put` - issues an HTTP `PUT` request
* `hx-patch` - issues an HTTP `PATCH` request
* `hx-delete` - issues an HTTP `DELETE` request
* `hx-get` - issues an HTTP `GET` request.
* `hx-post` - issues an HTTP `POST` request.
* `hx-put` - issues an HTTP `PUT` request.
* `hx-patch` - issues an HTTP `PATCH` request.
* `hx-delete` - issues an HTTP `DELETE` request.
Each of these attributes, when placed on an element, tell the htmx library: "`When a user clicks (or whatever) this
Each of these attributes, when placed on an element, tells the htmx library: "`When a user clicks (or whatever) this
element, issue an HTTP request of the specified type`"
The values of these attributes are similar to the values of both `href` on anchors and `action` on forms: you specify the
URL you wish to issue the given HTTP request type to. Typically, this is done via a server-relative path.
As a first example, if we wanted a button to issue a `GET` request to `/contacts` then we would write the following
For example, if we wanted a button to issue a `GET` request to `/contacts` then we would write the following
HTML:
.A Simple htmx-Powered Button
@ -275,38 +278,35 @@ The htmx library will see the `hx-get` attribute on this button, and hook up som
Very easy to understand and very consistent with the rest of HTML.
=== It's All Just HTML!
=== It's All Just HTML
With this request being issued by the button above, we get to perhaps the most important thing to understand about htmx:
it expects the response to this AJAX request _to be HTML_! htmx is an extension of HTML. A native hypermedia control
With the request issued by the button above, we get to perhaps the most important thing to understand about htmx:
it expects the response to this AJAX request _to be HTML_. Htmx is an extension of HTML. A native hypermedia control
like an anchor tag will typically get an HTML response to a request it creates. Similarly, htmx expects the server to
respond to the requests that it makes with HTML.
This may come as a bit of a shock to web developers who are used to responding to an AJAX request with JSON,
This may surprise web developers who are used to responding to an AJAX request with JSON,
which is far and away the most common response format for such requests. But AJAX requests are just HTTP requests and
there is no rule saying they must use JSON! Recall again that AJAX stands for Asynchronous JavaScript & XML, so JSON
is already a step away from the format originally envisioned for this API: XML.
htmx simply goes another direction and expects HTML.
Htmx simply goes another direction and expects HTML.
.htmx vs. "`plain`" HTML responses
****
There is an important difference between the HTTP responses to "`normal`" anchor and form driven HTTP requests and to
htmx-powered requests like the one made by this button: in the case of htmx triggered requests, responses are often
only _partial_ bits of HTML.
htmx-powered requests like the one made by this button: in the case of htmx triggered requests, responses can be _partial_ bits of HTML.
In htmx-powered interactions, as you will see, we are often not replacing the entire document. Rather we are using
"`transclusion`" to include content _within_ an existing document. Because of this, it is often not necessary or desirable
to transfer an entire HTML document from the server to the browser.
This fact can be used to save bandwidth as well as resource loading time, since less overall content is transferred from
the server to the client and since it isn't necessary to reprocess a `head` tag with style sheets, script tags, and so forth.
This fact can be used to save bandwidth as well as resource loading time. Less overall content is transferred from
the server to the client, and it isn't necessary to reprocess a `head` tag with style sheets, script tags, and so forth.
****
Let's consider what a simple _partial_ HTML response to the "`Get Contacts`" button might be when it is clicked.
It might look something like this:
When the "`Get Contacts`" button is clicked, a _partial_ HTML response might look something like this:
[#listing-3-3, reftext={chapter}.{counter:listing}]
.A partial HTML Response to an htmx Request
@ -319,19 +319,19 @@ It might look something like this:
</ul>
----
This is just a simple unordered list of contacts with some clickable elements in it. Note that there is no opening
This is just an unordered list of contacts with some clickable elements in it. Note that there is no opening
`html` tag, no `head` tag, and so forth: it is a _raw_ HTML list, without any decoration around it. A response in a
real application might of course contain more sophisticated HTML than this simple list, but even if it were more complicated
it wouldn't need to be an entire page of HTML: it could be only the "`inner`" content of the HTML representation for
real application might contain more sophisticated HTML than this simple list, but even if it were more complicated
it wouldn't need to be an entire page of HTML: it could just be the "`inner`" content of the HTML representation for
this resource.
Now, this simple list response is perfect for htmx. htmx will simply take the returned content and then swap it in to
Now, this simple list response is perfect for htmx. Htmx will simply take the returned content and then swap it in to
the DOM in place of some element in the page. (More on exactly where it will be placed in the DOM in a moment.) Swapping
in HTML content in this manner is fast and efficient because it leverages the existing native HTML parser in the browser,
rather than requiring a significant amount of client-side JavaScript to be executed.
This small HTML response might not look like much, but it shows how htmx stays within the hypermedia
paradigm: just like in a "`normal`" hypermedia control in a "`normal`" web application, we see hypermedia being transferred
This small HTML response shows how htmx stays within the hypermedia
paradigm: just like a "`normal`" hypermedia control in a "`normal`" web application, we see hypermedia being transferred
to the client in a stateless and uniform manner.
This button just gives us a slightly more sophisticated mechanism for building a web application using hypermedia.
@ -343,7 +343,7 @@ content into the existing page (rather than replacing the entire page), the ques
content be placed?
It turns out that the default htmx behavior is to simply put the returned content inside the element that triggered the
request. That's obviously _not_ a good thing in this situation: we will end up with a list of contacts awkwardly embedded within
request. That's obviously _not_ a good thing in the case of our button: we will end up with a list of contacts awkwardly embedded within
the button element. That will look pretty silly and is obviously not what we want.
Fortunately htmx provides another attribute, `hx-target` which can be used to specify exactly where in the DOM the
@ -364,13 +364,13 @@ Let's add a `div` tag that encloses the button with the id `main`. We will then
</div>
----
<1> A `div` element that wraps the button
<2> The `hx-target` attribute that specifies the target of the response
<1> A `div` element that wraps the button.
<2> The `hx-target` attribute that specifies the target of the response.
We have added `hx-target="#main"` to our button, where `#main` is a CSS selector that says "`The thing with the ID '`main`'`".
By using CSS selectors, htmx is once again building on top of familiar and standard HTML concepts. This keeps the
additional conceptual load beyond HTML required for working with htmx to a minimum.
By using CSS selectors, htmx builds on top of familiar and standard HTML concepts. This keeps the
additional conceptual load for working with htmx to a minimum.
Given this new configuration, what would the HTML on the client look like after a user clicks on this button and a
response has been received and processed?
@ -391,24 +391,24 @@ It would look something like this:
----
The response HTML has been swapped into the `div`, replacing the button that triggered the request. Transclusion! And
this has happened "`in the background`" via AJAX, without a large, clunky page refresh.
this has happened "`in the background`" via AJAX, without a clunky page refresh.
== Swap Styles
Now, perhaps we don't want to simply load the content from the server response _into_ the div, as child elements. Perhaps,
Now, perhaps we don't want to load the content from the server response _into_ the div, as child elements. Perhaps,
for whatever reason, we wish to _replace_ the entire div with the response. To handle this, htmx provides another
attribute, `hx-swap`, that allows you to specify exactly _how_ the content should be swapped into the DOM.
The `hx-swap` attribute supports the following values:
* `innerHTML` - The default, replace the inner html of the target element
* `outerHTML` - Replace the entire target element with the response
* `beforebegin` - Insert the response before the target element
* `afterbegin` - Insert the response before the first child of the target element
* `beforeend` - Insert the response after the last child of the target element
* `afterend` - Insert the response after the target element
* `delete` - Deletes the target element regardless of the response
* `none` - No swap will be performed
* `innerHTML` - The default, replace the inner html of the target element.
* `outerHTML` - Replace the entire target element with the response.
* `beforebegin` - Insert the response before the target element.
* `afterbegin` - Insert the response before the first child of the target element.
* `beforeend` - Insert the response after the last child of the target element.
* `afterend` - Insert the response after the target element.
* `delete` - Deletes the target element regardless of the response.
* `none` - No swap will be performed.
The first two values, `innerHTML` and `outerHTML`, are taken from the standard DOM properties that allow you to replace content
within an element or in place of an entire element respectively.
@ -420,8 +420,8 @@ The last two values, `delete` and `none` are specific to htmx. The first option
DOM, while the second option will do nothing (you may want to only work with response headers, an advanced technique we
will look at later in the book.)
Again, you can see htmx stays as close as possible to the existing web standards in order to keep the conceptual load
necessary to use it to a minimum.
Again, you can see htmx stays as close as possible to existing web standards in order to minimize the conceptual load
necessary for its use.
So let's consider that case where, rather than replacing the `innerHTML` content of the main div above, we want to
replace the _entire div_ with the HTML response.
@ -440,7 +440,7 @@ To do so would require only a small change to our button, adding a new `hx-swap`
</div>
----
<1> The `hx-swap` attribute specifies how to swap new content in
<1> The `hx-swap` attribute specifies how to swap in new content.
Now, when a response is received, the _entire_ div will be replaced with the hypermedia content:
@ -464,19 +464,19 @@ contact management application.
Note that with the `hx-get`, `hx-post`, `hx-put`, `hx-patch` and `hx-delete` attributes, we have addressed two of the
four opportunities for improvement that we enumerated regarding plain HTML:
* Opportunity 1: We can now issue an HTTP request with _any_ element (in this case we are using a button)
* Opportunity 3: We can issue _any sort_ of HTTP request we want, `PUT`, `PATCH` and `DELETE`, in particular
* Opportunity 1: We can now issue an HTTP request with _any_ element (in this case we are using a button).
* Opportunity 3: We can issue _any sort_ of HTTP request we want, `PUT`, `PATCH` and `DELETE`, in particular.
And, with `hx-target` and `hx-swap` we have addressed a third opportunity:
the requirement that the entire page be replaced.
* Opportunity 4: We can now replace any element we want in our page via transclusion, and we can do so in any manner want
* Opportunity 4: We can now replace any element we want in our page via transclusion, and we can do so in any manner want.
So, with only seven relatively simple additional attributes, we have addressed most of the shortcomings of HTML as a
hypermedia that we identified earlier.
There was one remaining shortcoming of HTML that we noted: the fact that only a `click` event (on an anchor) or a `submit` event
(on a form) can trigger a HTTP request. Let's look at how we can address that concern next.
What's next? Recall the other shortcoming we noted: the fact that only a `click` event (on an anchor) or a `submit` event
(on a form) can trigger a HTTP request. Let's look at how we can address that limitation.
== Using Events
@ -492,14 +492,13 @@ However, htmx generalizes this notion of an event triggering a request by using,
trigger an HTTP request.
Often you don't need to use `hx-trigger` because the default triggering event will be what you want.
The default triggering event depends on the element type, but should be fairly intuitive to anyone
familiar with HTML:
The default triggering event depends on the element type, and should be fairly intuitive:
* 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
* 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.
In order to demonstrate how `hx-trigger` works, lets consider the following situation: we want to trigger the request
To demonstrate how `hx-trigger` works, consider the following situation: we want to trigger the request
on our button when the mouse enters it. Now, this is certainly not a _good_ UX pattern, but bear with us: we are just
using this an example.
@ -517,13 +516,13 @@ To respond to a mouse entering the button, we would add the following attribute
</div>
----
<1> Issue a request... on the `mouseenter` event?
<1> Issue a request... on the `mouseenter` event.
Now, with this `hx-trigger` attribute in place, whenever the mouse enters this button, a request will be triggered. Silly,
Now, with this `hx-trigger` attribute in place, whenever the mouse enters this button, a request will be triggered. Silly,
but it works.
Let's try something a bit more realistic and potentially useful: let's add some support for a keyboard shortcut for
loading the contacts, `Ctrl-L` (for "`Load`"). To do this we will need to take advantage of some additional syntax that
Let's try something a bit more realistic and potentially useful: let's add support for a keyboard shortcut for
loading the contacts, `Ctrl-L` (for "`Load`"). To do this we will need to take advantage of additional syntax that
the `hx-trigger` attribute supports: event filters and additional arguments.
Event filters are a mechanism for determining if a given event should trigger a request or not. They are applied to an
@ -552,8 +551,8 @@ one potential triggering event. We still want to respond to the `click` event a
to handling the `Ctrl-L` keyboard shortcut.
There are, unfortunately, two problems with our `keyup` addition: As it stands, it will trigger requests on _any_ keyup
event that occurs. And, worse, it will only trigger when a keyup occurs _within_ this button. This is highly unlikely (the
user would need to tab onto the button to make it active and then begin typing!)
event that occurs. And, worse, it will only trigger when a keyup occurs _within_ this button. This is highly unlikely; the
user would need to tab onto the button to make it active and then begin typing.
Let's fix these two issues. To fix the first one, we will use a trigger filter to test that Control key and the "`L`" key
are pressed together:
@ -570,7 +569,7 @@ are pressed together:
</div>
----
<1> `keyup` now has a filter, so the control key and L must be pressed
<1> `keyup` now has a filter, so the control key and L must be pressed.
The trigger filter in this case is `ctrlKey && key == 'l'`. This can be read as "`A key up event, where the ctrlKey property
is true and the key property is equal to l`". Note that the properties `ctrlKey` and `key` are resolved against the event
@ -605,16 +604,16 @@ Here is what our updated `hx-trigger` attribute looks like:
</div>
----
<1> Listen to the event on the `body` tag
<1> Listen to the 'keyup' event on the `body` tag.
Now, in addition to clicks, the button will listen for `keyup` events on the body of the page. So it will now issue a
request when it is clicked on and also whenever someone hits `Ctrl-L` within the body of the page.
A nice keyboard shortcut for our Hypermedia-Driven Application.
And now we have a nice keyboard shortcut for our Hypermedia-Driven Application.
The `hx-trigger` attribute supports many more modifiers, and it is more elaborate than other htmx attributes. This is because
events, in general, are complicated and require a lot of small details to get just right. The default trigger will often
suffice, however, and you shouldn't need to reach for complicated `hx-trigger` features too often when using htmx.
events, in general, are complicated and require a lot of details to get just right. The default trigger will often
suffice, however, and you typically don't need to reach for complicated `hx-trigger` features when using htmx.
Even with more sophisticated trigger specifications like the keyboard shortcut we just added, the overall feel of htmx is
_declarative_ rather than _imperative_. That keeps htmx-powered applications "`feeling like`" standard web 1.0 applications
@ -625,13 +624,13 @@ in a way that adding significant amounts of JavaScript does not.
And hey, check it out! With `hx-trigger` we have addressed the final opportunity for improvement of HTML that we
outlined at the start of this chapter:
* Opportunity 2: We can use _any_ event to trigger an HTTP request
* Opportunity 2: We can use _any_ event to trigger an HTTP request.
That's a grand total of eight, count 'em, _eight_ attributes that all fall squarely within the same conceptual model as
normal HTML and that, by extending HTML as a hypermedia, open up whole new world of user interaction possibilities
within HTML.
Here is a table summarizing those opportunities and which htmx attributes exactly address them:
Here is a table summarizing those opportunities and which htmx attributes address them:
.Opportunities For Improving HTML
Any element should be able to make HTTP requests::
@ -655,12 +654,12 @@ forms. Forms are used to pass additional information beyond just a URL up to th
This information is captured via input and input-like elements within the form via the various types of input tags
available in HTML.
htmx allows you include this additional information in a natural way that, as you should now expect, mirrors how HTML
Htmx allows you include this additional information in a way that mirrors how HTML
itself works.
=== Enclosing Forms
The simplest way to pass input values up with a request in htmx is to enclose the element making a request within a form
The simplest way to pass input values with a request in htmx is to enclose the element making a request within a form
tag.
Let's take our original button for retrieving contacts and repurpose it for searching contacts:
@ -681,12 +680,12 @@ Let's take our original button for retrieving contacts and repurpose it for sear
</div>
----
<1> With an enclosing form tag, all inputs values will be submitted
<2> A new input that users will be able to enter search text into
<3> Our button has been converted to an `hx-post`
<1> With an enclosing form tag, all input values will be submitted.
<2> A new input for user search text entry.
<3> Our button has been converted to an `hx-post`.
Here we have added a form tag surrounding the button along with a search input that can be used to enter a term to
search the contacts with.
search contacts.
Now, when a user clicks on the button, the value of the input with the id `search` will be included in the request. This
is by virtue of the fact that there is a form tag enclosing both the button and the input: when an htmx-driven request
@ -703,13 +702,13 @@ uses a `GET` by using the `hx-include` attribute, discussed next.
=== Including inputs
While enclosing all the inputs you want included in a request is the most common approach for including values from inputs
While enclosing all the inputs you want included in a request is the most common approach for inputs
in htmx requests, it isn't always possible or desirable: form tags can have layout consequences and simply cannot be
placed in some spots in HTML documents. A good example of the latter situation is in table row (`tr`) elements: the
`form` tag is not a valid child or parent of table rows, so you can't place a form within or around an entire
`form` tag is not a valid child or parent of table rows, so you can't place a form within or around a
row of data in a table.
To address this issue, htmx provides another mechanism for including input values in requests: the `hx-include` attribute.
To address this issue, htmx provides a mechanism for including input values in requests: the `hx-include` attribute.
The `hx-include` attribute allows you to select input values that you wish to include in a request via CSS selectors.
Here is the above example reworked to include the input, dropping the form:
@ -728,36 +727,35 @@ Here is the above example reworked to include the input, dropping the form:
</div>
----
<1> `hx-include` can be used to include values directly in a request
<1> `hx-include` can be used to include values directly in a request.
The `hx-include` attribute takes a CSS selector value and allows you to specify exactly which values to send along
with the request. This can be useful if it is difficult to colocate an element issuing a request with all the inputs
that need to be submitted with it.
with the request. This can be useful if it is difficult to colocate an element issuing a request with all the desired inputs.
It is also useful when you do, in fact, want to submit values with a `GET` request and overcome the default behavior of
htmx with respect to `GET` requests.
htmx.
==== Relative CSS Selectors
The `hx-include` attribute and, in fact, most attributes that take a CSS selector, also support _relative_ CSS selectors,
that allow you to specify a CSS selector _relative_ to the element it is declared on. Here are some examples:
The `hx-include` attribute and, in fact, most attributes that take a CSS selector, also support _relative_ CSS selectors.
These allow you to specify a CSS selector _relative_ to the element it is declared on. Here are some examples:
`closest`::
Find the closest parent element matching the given selector, e.g. `closest form`
Find the closest parent element matching the given selector, e.g. `closest form`.
`next`::
Find the next element (scanning forward) matching the given selector, e.g. `next input`
Find the next element (scanning forward) matching the given selector, e.g. `next input`.
`previous`::
Find the previous element (scanning backwards) matching the given selector, e.g. `previous input`
Find the previous element (scanning backwards) matching the given selector, e.g. `previous input`.
`find`::
Find the next element within this element matching the given selector, e.g. `find input`
Find the next element within this element matching the given selector, e.g. `find input`.
`this`::
the current element
The current element.
Using relative CSS selectors often allows you to avoid needing to generate ids for elements, since you can take advantage
Using relative CSS selectors often allows you to avoid generating ids for elements, since you can take advantage
of their local structural layout instead.
=== Inline Values
@ -769,14 +767,14 @@ standard mechanism for including additional, hidden information in HTML.)
Here is an example of `hx-vals`:
.A Simple htmx-Powered Button
.A simple htmx-powered button
[source,html]
----
<button hx-get="/contacts" hx-vals='{"state":"MT"}'> <1>
Get The Contacts In Montana
</button>
----
<1> `hx-vals`, a JSON value to include in the request
<1> `hx-vals`, a JSON value to include in the request.
The parameter `state` with the value `MT` will be included in the `GET` request, resulting in a path and parameters that
looks like this: `/contacts?state=MT`. One thing to note is that we switched the `hx-vals` attribute to use single quotes
@ -797,7 +795,7 @@ requests like so:
Get The Contacts In The Selected State
</button>
----
<1> With the `js:` prefix, this expression will evaluate at submit time
<1> With the `js:` prefix, this expression will evaluate at submit time.
These three mechanisms, using `form` tags, using the `hx-include` attribute and using the `hx-vals` attribute, allow you
to include values in your hypermedia requests with htmx in a manner that should feel very familiar and in keeping with
@ -805,14 +803,14 @@ the spirit of HTML, while also giving you the flexibility to achieve what you wa
== History Support
A final piece of functionality to discuss to close out our overview of htmx is browser history support. When you use normal
We have a final piece of functionality to close out our overview of htmx: browser history support. When you use normal
HTML links and forms, your browser will keep track of all the pages that you have visited. You can then use the back button
to navigate back to a previous page and, once you have done this, you can use a forward button to go forward to the
original page you were on.
This notion of history was one of the killer features of the early web. Unfortunately it turns out that history becomes
tricky when you move to the Single Page Application paradigm. An AJAX request does not, by itself, register a web
page in your browsers history, which is a good thing: an AJAX request may have nothing to do with the state of the
page in your browser's history, which is a good thing: an AJAX request may have nothing to do with the state of the
web page (perhaps it is just recording some activity in the browser), so it wouldn't be appropriate to create a new
history entry for the interaction.
@ -851,7 +849,7 @@ If we wanted it to create a history entry when this request happened, we would a
Get The Contacts
</button>
----
<1> `hx-push-url` will create an entry in history when the button is clicked
<1> `hx-push-url` will create an entry in history when the button is clicked.
Now, when the button is clicked, the `/contacts` path will be put into the browser's navigation bar and a history entry
will be created for it. Furthermore, if the user clicks the back button, the original content for the page will be
@ -862,7 +860,7 @@ Now, the name `hx-push-url` for this attribute might sound a little obscure, but
so you are "`pushing`" new entries onto the top of the stack of history entries.
With this relatively simple, declarative mechanism, htmx allows you to integrate with the back button in a way that mimics the
"`normal`" behavior of HTML. Not bad if you look at what other JavaScript libraries want you to do to make history work!
"`normal`" behavior of HTML.
Now, there is one additional thing we need to handle to get history "`just right`": we have "`pushed`" the `/contacts` path
into the browsers location bar successfully, and the back button works. But what if someone refreshes their browser while
@ -873,14 +871,13 @@ can do this using HTTP headers, a topic we will go into in detail later in the b
== Conclusion
So that's our whirlwind introduction to htmx. We've only seen about ten attributes from the library, but we think you
can probably see a hint of just how powerful these attributes can be: by adopting htmx you will be able to create a much
more sophisticated web application than is possible in plain HTML, but your conceptual load will not be nearly as high
as it is for most JavaScript-based approaches.
So that's our whirlwind introduction to htmx. We've only seen about ten attributes from the library, but you
can see a hint of just how powerful these attributes can be. Htmx enaables a much
more sophisticated web application than is possible in plain HTML, with minimal additional conceptual load compared to most JavaScript-based approaches.
htmx is a very pure extension to HTML, aiming to incrementally improve the language as a hypermedia in a manner that is
Htmx aims to incrementally improve HTML as a hypermedia in a manner that is
conceptually coherent with the underlying markup language. Like any technical choice, this is not without
trade-offs: by staying so close to HTML, htmx does not give developers a lot of infrastructure that many might feel
trade-offs: by staying so close to HTML, htmx does not give developers a lot of infrastructure that many might feel
should be there "`by default`".
A good example is the concept of modal dialogs. Many web applications today make heavy use of modal dialogs, effectively
@ -889,7 +886,7 @@ all just a web page: the web has no notion of "`modals`" in this regard.)
A web developer might expect htmx, as a front end library, to provide some sort of modal dialog component out of the box.
htmx, however, has no such notion of modals. That's not to say you can't use modals with htmx, and we will look at how you
Htmx, however, has no such notion of modals. That's not to say you can't use modals with htmx, and we will look at how you
can do so later. But htmx, like HTML itself, won't give you an API specifically for creating modals. You
would need to use a 3rd party library or roll your own modal implementation and then integrate htmx into it if you want
to use modals within an htmx-based application.